Skip to content

Commit

Permalink
l2geth: new fee scheme
Browse files Browse the repository at this point in the history
Previously, the L1 gas price was being fetched from
a remote node and being held in memory. Now the L1
gas price is in a smart contract and the `SyncService`
will periodically update the L1 gas price in an in memory
cache to ensure that users that are sending transactions
are paying enough.

This also reads the overhead from the `OVM_GasPriceOracle`
and rejects transactions with too low of a fee.

The `tx.gasPrice` can now be variable, it no longer
will reject transactions if the gas price isn't exactly
the hardcoded number.

The cache is now updated when the sender of a transaction included
in the chain is from the current gas price oracle owner address.
This depends on the cache being initialized at startup.
  • Loading branch information
tynes authored and smartcontracts committed Oct 1, 2021
1 parent 67b6781 commit 19622d2
Show file tree
Hide file tree
Showing 15 changed files with 1,923 additions and 371 deletions.
5 changes: 5 additions & 0 deletions .changeset/honest-hats-live.md
@@ -0,0 +1,5 @@
---
'@eth-optimism/l2geth': patch
---

Use `OVM_GasPriceOracle` based L1 base fee instead of fetching it from remote
33 changes: 32 additions & 1 deletion l2geth/core/state_transition.go
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg"
)

Expand Down Expand Up @@ -61,6 +62,8 @@ type StateTransition struct {
data []byte
state vm.StateDB
evm *vm.EVM
// UsingOVM
l1Fee *big.Int
}

// Message represents a message sent to a contract.
Expand Down Expand Up @@ -122,6 +125,15 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo

// NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
l1Fee := new(big.Int)
if rcfg.UsingOVM {
if msg.GasPrice().Cmp(common.Big0) != 0 {
// Compute the L1 fee before the state transition
// so it only has to be read from state one time.
l1Fee, _ = fees.CalculateL1MsgFee(msg, evm.StateDB, nil)
}
}

return &StateTransition{
gp: gp,
evm: evm,
Expand All @@ -130,6 +142,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
value: msg.Value(),
data: msg.Data(),
state: evm.StateDB,
l1Fee: l1Fee,
}
}

Expand Down Expand Up @@ -163,6 +176,15 @@ func (st *StateTransition) useGas(amount uint64) error {

func (st *StateTransition) buyGas() error {
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
if rcfg.UsingOVM {
// Only charge the L1 fee for QueueOrigin sequencer transactions
if st.msg.QueueOrigin() == types.QueueOriginSequencer {
mgval = mgval.Add(mgval, st.l1Fee)
if st.msg.CheckNonce() {
log.Debug("Adding L1 fee", "l1-fee", st.l1Fee)
}
}
}
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
return errInsufficientBalanceForGas
}
Expand Down Expand Up @@ -243,7 +265,16 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
}
}
st.refundGas()
st.state.AddBalance(evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
if rcfg.UsingOVM {
// The L2 Fee is the same as the fee that is charged in the normal geth
// codepath. Add the L1 fee to the L2 fee for the total fee that is sent
// to the sequencer.
l2Fee := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)
fee := new(big.Int).Add(st.l1Fee, l2Fee)
st.state.AddBalance(evm.Coinbase, fee)
} else {
st.state.AddBalance(evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
}

return ret, st.gasUsed(), vmerr != nil, err
}
Expand Down
7 changes: 5 additions & 2 deletions l2geth/core/tx_pool.go
Expand Up @@ -565,8 +565,11 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
}
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
if !rcfg.UsingOVM {
// This check is done in SyncService.verifyFee
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
}
// Ensure the transaction has more gas than the basic tx fee.
intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul)
Expand Down
15 changes: 6 additions & 9 deletions l2geth/core/types/transaction.go
Expand Up @@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
)

//go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go
Expand Down Expand Up @@ -214,14 +213,12 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
return nil
}

func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) }
func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit }
func (tx *Transaction) L2Gas() uint64 { return fees.DecodeL2GasLimitU64(tx.data.GasLimit) }
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) }
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) }
func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce }
func (tx *Transaction) CheckNonce() bool { return true }

func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) }
func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit }
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) }
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) }
func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce }
func (tx *Transaction) CheckNonce() bool { return true }
func (tx *Transaction) SetNonce(nonce uint64) { tx.data.AccountNonce = nonce }

// To returns the recipient address of the transaction.
Expand Down
48 changes: 44 additions & 4 deletions l2geth/eth/gasprice/rollup_gasprice.go
Expand Up @@ -6,23 +6,28 @@ import (
"sync"

"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rollup/fees"
)

// RollupOracle holds the L1 and L2 gas prices for fee calculation
type RollupOracle struct {
l1GasPrice *big.Int
l2GasPrice *big.Int
overhead *big.Int
scalar *big.Float
l1GasPriceLock sync.RWMutex
l2GasPriceLock sync.RWMutex
overheadLock sync.RWMutex
scalarLock sync.RWMutex
}

// NewRollupOracle returns an initialized RollupOracle
func NewRollupOracle() *RollupOracle {
return &RollupOracle{
l1GasPrice: new(big.Int),
l2GasPrice: new(big.Int),
l1GasPriceLock: sync.RWMutex{},
l2GasPriceLock: sync.RWMutex{},
l1GasPrice: new(big.Int),
l2GasPrice: new(big.Int),
overhead: new(big.Int),
scalar: new(big.Float),
}
}

Expand Down Expand Up @@ -59,3 +64,38 @@ func (gpo *RollupOracle) SetL2GasPrice(gasPrice *big.Int) error {
log.Info("Set L2 Gas Price", "gasprice", gpo.l2GasPrice)
return nil
}

// SuggestOverhead returns the cached overhead value from the
// OVM_GasPriceOracle
func (gpo *RollupOracle) SuggestOverhead(ctx context.Context) (*big.Int, error) {
gpo.overheadLock.RLock()
defer gpo.overheadLock.RUnlock()
return gpo.overhead, nil
}

// SetOverhead caches the overhead value that is set in the
// OVM_GasPriceOracle
func (gpo *RollupOracle) SetOverhead(overhead *big.Int) error {
gpo.overheadLock.Lock()
defer gpo.overheadLock.Unlock()
gpo.overhead = overhead
log.Info("Set batch overhead", "overhead", overhead)
return nil
}

// SuggestScalar returns the cached scalar value
func (gpo *RollupOracle) SuggestScalar(ctx context.Context) (*big.Float, error) {
gpo.scalarLock.RLock()
defer gpo.scalarLock.RUnlock()
return gpo.scalar, nil
}

// SetScalar sets the scalar value held in the OVM_GasPriceOracle
func (gpo *RollupOracle) SetScalar(scalar *big.Int, decimals *big.Int) error {
gpo.scalarLock.Lock()
defer gpo.scalarLock.Unlock()
value := fees.ScaleDecimals(scalar, decimals)
gpo.scalar = value
log.Info("Set scalar", "scalar", gpo.scalar)
return nil
}
12 changes: 6 additions & 6 deletions l2geth/internal/ethapi/api.go
Expand Up @@ -51,10 +51,6 @@ import (

var errOVMUnsupported = errors.New("OVM: Unsupported RPC Method")

// TEMPORARY: Set the gas price to 0 until message passing and ETH value work again.
// Otherwise the integration tests won't pass because the accounts have no ETH.
var bigDefaultGasPrice = new(big.Int).SetUint64(0)

// PublicEthereumAPI provides an API to access Ethereum related information.
// It offers only methods that operate on public data that is freely available to anyone.
type PublicEthereumAPI struct {
Expand All @@ -66,9 +62,13 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
return &PublicEthereumAPI{b}
}

// GasPrice always returns 1 gwei. See `DoEstimateGas` below for context.
// GasPrice returns the L2 gas price in the OVM_GasPriceOracle
func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
return (*hexutil.Big)(bigDefaultGasPrice), nil
gasPrice, err := s.b.SuggestL2GasPrice(context.Background())
if err != nil {
return nil, err
}
return (*hexutil.Big)(gasPrice), nil
}

// ProtocolVersion returns the current Ethereum protocol version this node supports
Expand Down
2 changes: 1 addition & 1 deletion l2geth/miner/worker.go
Expand Up @@ -506,7 +506,7 @@ func (w *worker) mainLoop() {
}
w.pendingMu.Unlock()
} else {
log.Debug("Problem committing transaction: %w", err)
log.Debug("Problem committing transaction", "msg", err)
}

case ev := <-w.txsCh:
Expand Down

0 comments on commit 19622d2

Please sign in to comment.