diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index c0ad623f98..fb385e241a 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -42,6 +42,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/rpc" ) @@ -704,8 +705,13 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM evmContext := core.NewEVMBlockContext(header, b.blockchain, nil) vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true}) gasPool := new(core.GasPool).AddGas(math.MaxUint64) + signer := types.MakeSigner(b.blockchain.Config(), header.Number, header.Time) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.blockchain.Config().ChainID, signer, stateDB) + if err != nil { + return nil, err + } - return core.ApplyMessage(vmEnv, msg, gasPool) + return core.ApplyMessage(vmEnv, msg, gasPool, l1DataFee) } // SendTransaction updates the pending block to include the given transaction. diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 312f427d4c..a17e01c6ac 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/trie" "golang.org/x/crypto/sha3" ) @@ -220,8 +221,16 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, ) evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + log.Info("rejected tx due to fees.CalculateL1DataFee", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) + rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) + gaspool.SetGas(prevGas) + continue + } + // (ret []byte, usedGas uint64, failed bool, err error) - msgResult, err := core.ApplyMessage(evm, msg, gaspool) + msgResult, err := core.ApplyMessage(evm, msg, gaspool, l1DataFee) if err != nil { statedb.RevertToSnapshot(snapshot) log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) diff --git a/core/error.go b/core/error.go index f0205fb590..d5542d83fb 100644 --- a/core/error.go +++ b/core/error.go @@ -74,6 +74,10 @@ var ( // is higher than the balance of the user's account. ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") + // ErrInsufficientFundsWithL1DataFee is returned if (the total cost of executing a transaction + L1DataFee) + // is higher than the balance of the user's account. + ErrInsufficientFundsWithL1DataFee = errors.New("insufficient funds for l1fee + gas * price + value") + // ErrGasUintOverflow is returned when calculating gas usage. ErrGasUintOverflow = errors.New("gas uint64 overflow") diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 97401d283c..f0d7bf9db2 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -688,6 +688,7 @@ type storedReceiptRLP struct { PostStateOrStatus []byte CumulativeGasUsed uint64 Logs []*types.Log + L1Fee *big.Int } // ReceiptLogs is a barebone version of ReceiptForStorage which only keeps diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index ff867309de..eaba606301 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -17,6 +17,7 @@ package core import ( + "math/big" "sync/atomic" "github.com/ethereum/go-ethereum/consensus" @@ -24,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/fees" ) // statePrefetcher is a basic Prefetcher, which blindly executes a block on top @@ -68,7 +70,13 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c return // Also invalid block, bail out } statedb.SetTxContext(tx.Hash(), i) - if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { + + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return + } + + if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm, l1DataFee); err != nil { return // Ugh, something went horribly wrong, bail out } // If we're pre-byzantium, pre-load trie nodes for the intermediate root @@ -85,10 +93,10 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. -func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error { +func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM, l1DataFee *big.Int) error { // Update the evm with the new transaction context. evm.Reset(NewEVMTxContext(msg), statedb) // Add addresses to access list if applicable - _, err := ApplyMessage(evm, msg, gaspool) + _, err := ApplyMessage(evm, msg, gaspool, l1DataFee) return err } diff --git a/core/state_processor.go b/core/state_processor.go index 7dd81487d5..0c7b0e1c11 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/fees" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -109,8 +110,13 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, err + } + // Apply the transaction to the current state (included in the env). - result, err := ApplyMessage(evm, msg, gp) + result, err := ApplyMessage(evm, msg, gp, l1DataFee) if err != nil { return nil, err } @@ -151,6 +157,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta receipt.BlockHash = blockHash receipt.BlockNumber = blockNumber receipt.TransactionIndex = uint(statedb.TxIndex()) + receipt.L1Fee = result.L1DataFee return receipt, err } diff --git a/core/state_transition.go b/core/state_transition.go index 6b79c1c137..df1867c7bf 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -26,6 +26,7 @@ import ( cmath "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" ) @@ -149,6 +150,18 @@ type Message struct { IsL1MessageTx bool } +func (m *Message) GetFrom() common.Address { return m.From } +func (m *Message) GetTo() *common.Address { return m.To } +func (m *Message) GetGasPrice() *big.Int { return m.GasPrice } +func (m *Message) GetGasLimit() uint64 { return m.GasLimit } +func (m *Message) GetGasFeeCap() *big.Int { return m.GasFeeCap } +func (m *Message) GetGasTipCap() *big.Int { return m.GasTipCap } +func (m *Message) GetValue() *big.Int { return m.Value } +func (m *Message) GetNonce() uint64 { return m.Nonce } +func (m *Message) GetData() []byte { return m.Data } +func (m *Message) GetAccessList() types.AccessList { return m.AccessList } +func (m *Message) GetIsL1MessageTx() bool { return m.IsL1MessageTx } + // TransactionToMessage converts a transaction into a Message. func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) { msg := &Message{ @@ -182,8 +195,8 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) { - return NewStateTransition(evm, msg, gp).TransitionDb() +func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool, l1DataFee *big.Int) (*ExecutionResult, error) { + return NewStateTransition(evm, msg, gp, l1DataFee).TransitionDb() } // StateTransition represents a state transition. @@ -215,15 +228,18 @@ type StateTransition struct { initialGas uint64 state vm.StateDB evm *vm.EVM + + l1DataFee *big.Int } // NewStateTransition initialises and returns a new state transition object. -func NewStateTransition(evm *vm.EVM, msg *Message, gp *GasPool) *StateTransition { +func NewStateTransition(evm *vm.EVM, msg *Message, gp *GasPool, l1DataFee *big.Int) *StateTransition { return &StateTransition{ - gp: gp, - evm: evm, - msg: msg, - state: evm.StateDB, + gp: gp, + evm: evm, + msg: msg, + state: evm.StateDB, + l1DataFee: l1DataFee, } } @@ -238,11 +254,28 @@ func (st *StateTransition) to() common.Address { func (st *StateTransition) buyGas() error { mgval := new(big.Int).SetUint64(st.msg.GasLimit) mgval = mgval.Mul(mgval, st.msg.GasPrice) + + if st.evm.ChainConfig().Scroll.FeeVaultEnabled() { + // should be fine to add st.l1DataFee even without `L1MessageTx` check, since L1MessageTx will come with 0 l1DataFee, + // but double check to make sure + if !st.msg.IsL1MessageTx { + log.Debug("Adding L1DataFee", "l1DataFee", st.l1DataFee) + mgval = mgval.Add(mgval, st.l1DataFee) + } + } + balanceCheck := new(big.Int).Set(mgval) if st.msg.GasFeeCap != nil { balanceCheck.SetUint64(st.msg.GasLimit) balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) balanceCheck.Add(balanceCheck, st.msg.Value) + if st.evm.ChainConfig().Scroll.FeeVaultEnabled() { + // should be fine to add st.l1DataFee even without `L1MessageTx` check, since L1MessageTx will come with 0 l1DataFee, + // but double check to make sure + if !st.msg.IsL1MessageTx { + balanceCheck.Add(balanceCheck, st.l1DataFee) + } + } } if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { if blobGas := st.blobGasUsed(); blobGas > 0 { @@ -455,12 +488,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // are 0. This avoids a negative effectiveTip being applied to // the coinbase when simulating calls. } else { + // The L2 Fee is the same as the fee that is charged in the normal geth + // codepath. Add the L1DataFee to the L2 fee for the total fee that is sent + // to the sequencer. fee := new(big.Int).SetUint64(st.gasUsed()) fee.Mul(fee, effectiveTip) - st.state.AddBalance(st.evm.Context.Coinbase, fee) + fee.Add(fee, st.l1DataFee) + st.state.AddBalance(st.evm.Context.Coinbase, fee) // TODO: change to `st.evm.FeeRecipient()` } return &ExecutionResult{ + L1DataFee: st.l1DataFee, UsedGas: st.gasUsed(), Err: vmerr, ReturnData: ret, diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 0df363d81d..4cee97b254 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/fees" ) // ValidationOptions define certain differences between transaction validation @@ -219,9 +220,23 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op balance = opts.State.GetBalance(from) cost = tx.Cost() ) + // 1. Check balance >= transaction cost (V + GP * GL) to maintain compatibility with the logic without considering L1 data fee. if balance.Cmp(cost) < 0 { return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, cost, new(big.Int).Sub(cost, balance)) } + // 2. Perform an additional check for L1 data fees. + // Always perform the check, because it's not easy to check FeeVault here + // Get L1 data fee in current state + l1DataFee, err := fees.CalculateL1DataFee(tx, opts.State) + if err != nil { + return fmt.Errorf("failed to calculate L1 data fee, err: %w", err) + } + // Transactor should have enough funds to cover the costs + // cost == L1 data fee + V + GP * GL + cost = new(big.Int).Add(tx.Cost(), l1DataFee) + if balance.Cmp(cost) < 0 { + return fmt.Errorf("invalid transaction: %w", core.ErrInsufficientFundsWithL1DataFee) + } // Ensure the transactor has enough funds to cover for replacements or nonce // expansions without overdrafts spent := opts.ExistingExpenditure(from) diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index 4c641a9727..73c46a4dd1 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -31,6 +31,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { BlockHash common.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex hexutil.Uint `json:"transactionIndex"` + L1Fee *hexutil.Big `json:"l1Fee,omitempty"` } var enc Receipt enc.Type = hexutil.Uint64(r.Type) @@ -48,6 +49,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { enc.BlockHash = r.BlockHash enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) + enc.L1Fee = (*hexutil.Big)(r.L1Fee) return json.Marshal(&enc) } @@ -69,6 +71,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { BlockHash *common.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex *hexutil.Uint `json:"transactionIndex"` + L1Fee *hexutil.Big `json:"l1Fee,omitempty"` } var dec Receipt if err := json.Unmarshal(input, &dec); err != nil { @@ -124,5 +127,8 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { if dec.TransactionIndex != nil { r.TransactionIndex = uint(*dec.TransactionIndex) } + if dec.L1Fee != nil { + r.L1Fee = (*big.Int)(dec.L1Fee) + } return nil } diff --git a/core/types/receipt.go b/core/types/receipt.go index 8a6c831d34..2470c479a9 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -71,6 +71,9 @@ type Receipt struct { BlockHash common.Hash `json:"blockHash,omitempty"` BlockNumber *big.Int `json:"blockNumber,omitempty"` TransactionIndex uint `json:"transactionIndex"` + + // Scroll rollup + L1Fee *big.Int `json:"l1Fee,omitempty"` } type receiptMarshaling struct { @@ -84,6 +87,7 @@ type receiptMarshaling struct { BlobGasPrice *hexutil.Big BlockNumber *hexutil.Big TransactionIndex hexutil.Uint + L1Fee *hexutil.Big } // receiptRLP is the consensus encoding of a receipt. @@ -99,6 +103,7 @@ type storedReceiptRLP struct { PostStateOrStatus []byte CumulativeGasUsed uint64 Logs []*Log + L1Fee *big.Int } // NewReceipt creates a barebone transaction receipt, copying the init fields. @@ -264,6 +269,10 @@ type ReceiptForStorage Receipt // EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt // into an RLP stream. func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { + if r.L1Fee == nil { + r.L1Fee = big.NewInt(0) + } + w := rlp.NewEncoderBuffer(_w) outerList := w.List() w.WriteBytes((*Receipt)(r).statusEncoding()) @@ -275,6 +284,7 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { } } w.ListEnd(logList) + w.WriteBigInt(r.L1Fee) w.ListEnd(outerList) return w.Flush() } @@ -292,6 +302,7 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { r.CumulativeGasUsed = stored.CumulativeGasUsed r.Logs = stored.Logs r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) + r.L1Fee = stored.L1Fee return nil } diff --git a/eth/api_backend.go b/eth/api_backend.go index 9c26430cbf..f1cfa5df59 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -304,6 +304,7 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri } func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { + // will `VerifyFee` & `validateTx` in txPool.Add return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0] } diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 24694df66c..f825ac3eb2 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/trie" ) @@ -248,7 +249,11 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) statedb.SetTxContext(tx.Hash(), idx) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, err + } + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 300d904a99..03cc1c6322 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "os" "runtime" "sync" @@ -41,6 +42,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/rpc" ) @@ -276,7 +278,14 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed TxIndex: i, TxHash: tx.Hash(), } - res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) + l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb) + if err != nil { + // though it's not a "tracing error", we still need to put it here + task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} + log.Warn("CalculateL1DataFee failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) + break + } + res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee) if err != nil { task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) @@ -535,7 +544,12 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) ) statedb.SetTxContext(tx.Hash(), i) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + log.Warn("Tracing intermediate roots did not complete due to fees.CalculateL1DataFee", "txindex", i, "txhash", tx.Hash(), "err", err) + return nil, err + } + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee); err != nil { log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err) // We intentionally don't return the error here: if we do, then the RPC server will not // return the roots. Most likely, the caller already knows that a certain transaction fails to @@ -611,7 +625,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac TxIndex: i, TxHash: tx.Hash(), } - res, err := api.traceTx(ctx, msg, txctx, blockCtx, statedb, config) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, err + } + res, err := api.traceTx(ctx, msg, txctx, blockCtx, statedb, config, l1DataFee) if err != nil { return nil, err } @@ -654,7 +672,13 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat TxIndex: task.index, TxHash: txs[task.index].Hash(), } - res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) + l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb) + if err != nil { + // though it's not a "tracing error", we still need to put it here + results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} + continue + } + res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee) if err != nil { results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} continue @@ -681,7 +705,12 @@ txloop: msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) statedb.SetTxContext(tx.Hash(), i) vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + failed = err + break txloop + } + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee); err != nil { failed = err break txloop } @@ -788,7 +817,10 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block // Execute the transaction and flush any traces to disk vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) statedb.SetTxContext(tx.Hash(), i) - _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err == nil { + _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee) + } if writer != nil { writer.Flush() } @@ -857,7 +889,11 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * TxIndex: int(index), TxHash: hash, } - return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, err + } + return api.traceTx(ctx, msg, txctx, vmctx, statedb, config, l1DataFee) } // TraceCall lets you trace a given eth_call. It collects the structured logs @@ -916,13 +952,18 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc if config != nil { traceConfig = &config.TraceConfig } - return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) + signer := types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, block.BaseFee(), api.backend.ChainConfig().ChainID, signer, statedb) + if err != nil { + return nil, err + } + return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig, l1DataFee) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig, l1DataFee *big.Int) (interface{}, error) { var ( tracer Tracer err error @@ -942,6 +983,11 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte } vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) + // If gasPrice is 0, make sure that the account has sufficient balance to cover `l1DataFee`. + if message.GasPrice.Cmp(big.NewInt(0)) == 0 { + statedb.AddBalance(message.From, l1DataFee) + } + // Define a meaningful timeout of a single transaction trace if config.Timeout != nil { if timeout, err = time.ParseDuration(*config.Timeout); err != nil { @@ -961,7 +1007,7 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte // Call Prepare to clear out the statedb access list statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) - if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit)); err != nil { + if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit), l1DataFee); err != nil { return nil, fmt.Errorf("tracing failed: %w", err) } return tracer.GetResult() diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 0f78af9a01..1824bb1fb4 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -42,6 +42,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/rpc" "golang.org/x/exp/slices" ) @@ -177,7 +178,11 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block return msg, context, statedb, release, nil } vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x CalculateL1DataFee failed: %v", tx.Hash(), err) + } + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 6df49a90c1..35850272e3 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/tests" ) @@ -150,7 +151,11 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + t.Fatalf("failed to calculate l1DataFee: %v", err) + } + vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) if err != nil { t.Fatalf("failed to execute transaction: %v", err) } @@ -251,7 +256,11 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { } evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) snap := statedb.Snapshot() - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + b.Fatalf("failed to calculate l1DataFee: %v", err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) if _, err = st.TransitionDb(); err != nil { b.Fatalf("failed to execute transaction: %v", err) } @@ -388,7 +397,12 @@ func TestInternals(t *testing.T) { GasTipCap: big.NewInt(0), SkipAccountChecks: false, } - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)) + signer := types.MakeSigner(params.MainnetChainConfig, context.BlockNumber, context.Time) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, nil, params.MainnetChainConfig.ChainID, signer, statedb) + if err != nil { + t.Fatalf("test %v: failed to estimate L1DataFee: %v", tc.name, err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee) if _, err := st.TransitionDb(); err != nil { t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err) } diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index 423167b13c..8a305e9bba 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/tests" // Force-load the native, to trigger registration @@ -114,7 +115,11 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string if err != nil { return fmt.Errorf("failed to prepare transaction for tracing: %v", err) } - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return fmt.Errorf("failed to calculate L1DataFee: %v", err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) if _, err = st.TransitionDb(); err != nil { return fmt.Errorf("failed to execute transaction: %v", err) diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index b4fa5b6272..75b31e59e1 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/tests" ) @@ -121,7 +122,11 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + t.Fatalf("failed to calculate L1DataFee: %v", err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) if _, err = st.TransitionDb(); err != nil { t.Fatalf("failed to execute transaction: %v", err) } diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 4c9b910a27..595795293b 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -252,6 +252,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) { Failed: failed, ReturnValue: returnVal, StructLogs: formatLogs(l.StructLogs()), + // L1DataFee: (*hexutil.Big)(result.L1DataFee), }) } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index b4989ec984..70a441fe4b 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/tests" ) @@ -99,7 +100,11 @@ func BenchmarkTransactionTrace(b *testing.B) { for i := 0; i < b.N; i++ { snap := statedb.Snapshot() - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + b.Fatal(err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) _, err = st.TransitionDb() if err != nil { b.Fatal(err) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index f1f9ce82f0..1deb29feb3 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -45,6 +45,7 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" "github.com/tyler-smith/go-bip39" @@ -1099,7 +1100,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S // Execute the message. gp := new(core.GasPool).AddGas(math.MaxUint64) - result, err := core.ApplyMessage(evm, msg, gp) + result, err := core.ApplyMessage(evm, msg, gp, common.Big0) if err := vmError(); err != nil { return nil, err } @@ -1177,6 +1178,48 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO return result.Return(), result.Err } +func EstimateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, config *params.ChainConfig) (*big.Int, error) { + if !config.Scroll.FeeVaultEnabled() { + return big.NewInt(0), nil + } + + state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + if err := overrides.Apply(state); err != nil { + return nil, err + } + // Setup context so it may be cancelled the call has completed + // or, in case of unmetered gas, setup a context with a timeout. + var cancel context.CancelFunc + if timeout > 0 { + ctx, cancel = context.WithTimeout(ctx, timeout) + } else { + ctx, cancel = context.WithCancel(ctx) + } + // Make sure the context is cancelled when the call has completed + // this makes sure resources are cleaned up. + defer cancel() + + // Get a new instance of the EVM. + msg, err := args.ToMessage(globalGasCap, header.BaseFee) + if err != nil { + return nil, err + } + blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) + evm, _ := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx) + // Wait for the context to be done and cancel the evm. Even if the + // EVM has finished, cancelling may be done (repeatedly) + go func() { + <-ctx.Done() + evm.Cancel() + }() + + signer := types.MakeSigner(config, header.Number, header.Time) + return fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, evm.StateDB) +} + // executeEstimate is a helper that executes the transaction under a given gas limit and returns // true if the transaction fails for a reason that might be related to not enough gas. A non-nil // error means execution failed due to reasons unrelated to the gas limit. @@ -1244,12 +1287,25 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if feeCap.BitLen() != 0 { balance := state.GetBalance(*args.From) // from can't be nil available := new(big.Int).Set(balance) + + // account for tx value if args.Value != nil { if args.Value.ToInt().Cmp(available) >= 0 { return 0, core.ErrInsufficientFundsForTransfer } available.Sub(available, args.Value.ToInt()) } + + // account for l1 fee + l1DataFee, err := EstimateL1MsgFee(ctx, b, args, blockNrOrHash, nil, 0, gasCap, b.ChainConfig()) + if err != nil { + return 0, err + } + if l1DataFee.Cmp(available) >= 0 { + return 0, errors.New("insufficient funds for l1 fee") + } + available.Sub(available, l1DataFee) + allowance := new(big.Int).Div(available, feeCap) // If the allowance is larger than maximum uint64, skip checking @@ -1655,7 +1711,12 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) config := vm.Config{Tracer: tracer, NoBaseFee: true} vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil) - res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) + signer := types.MakeSigner(b.ChainConfig(), header.Number, header.Time) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig().ChainID, signer, statedb) + if err != nil { + return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) + } + res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee) if err != nil { return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) } @@ -1834,6 +1895,7 @@ func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber u "logsBloom": receipt.Bloom, "type": hexutil.Uint(tx.Type()), "effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice), + "l1Fee": (*hexutil.Big)(receipt.L1Fee), } // Assign receipt status or post state. diff --git a/les/api_backend.go b/les/api_backend.go index 3e9dbadce8..6660c6c487 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -196,6 +196,7 @@ func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *st } func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { + // will `VerifyFee` & `validateTx` in txPool.Add return b.eth.txPool.Add(ctx, signedTx) } diff --git a/les/state_accessor.go b/les/state_accessor.go index 9a8214ac2f..d8ca6bee9a 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/rollup/fees" ) // noopReleaser is returned in case there is no operation expected @@ -69,7 +70,11 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types. } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/light/odr_test.go b/light/odr_test.go index c415d73e7e..25544e3ae2 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" ) @@ -217,7 +218,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain context := core.NewEVMBlockContext(header, chain, nil) vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true}) gp := new(core.GasPool).AddGas(math.MaxUint64) - result, _ := core.ApplyMessage(vmenv, msg, gp) + signer := types.MakeSigner(config, header.Number, header.Time) + l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, st) + result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) res = append(res, result.Return()...) if st.Error() != nil { return res, st.Error() diff --git a/light/txpool.go b/light/txpool.go index b792d70b14..08e15ac5f8 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/fees" ) const ( @@ -379,11 +380,25 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error return txpool.ErrNegativeValue } + // 1. Check balance >= transaction cost (V + GP * GL) to maintain compatibility with the logic without considering L1 data fee. // Transactor should have enough funds to cover the costs // cost == V + GP * GL if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 { return core.ErrInsufficientFunds } + // 2. If FeeVault is enabled, perform an additional check for L1 data fees. + if pool.config.Scroll.FeeVaultEnabled() { + // Get L1 data fee in current state + l1DataFee, err := fees.CalculateL1DataFee(tx, currentState) + if err != nil { + return fmt.Errorf("failed to calculate L1 data fee, err: %w", err) + } + // Transactor should have enough funds to cover the costs + // cost == L1 data fee + V + GP * GL + if b := currentState.GetBalance(from); b.Cmp(new(big.Int).Add(tx.Cost(), l1DataFee)) < 0 { + return fmt.Errorf("invalid transaction: %w", core.ErrInsufficientFundsWithL1DataFee) + } + } // Should supply enough intrinsic gas gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai) diff --git a/params/config.go b/params/config.go index 3beadfbe75..e85487bca6 100644 --- a/params/config.go +++ b/params/config.go @@ -345,6 +345,9 @@ type ScrollConfig struct { // Maximum tx payload size of blocks that we produce [optional] MaxTxPayloadBytesPerBlock *int `json:"maxTxPayloadBytesPerBlock,omitempty"` + // Transaction fee vault address [optional] + FeeVaultAddress *common.Address `json:"feeVaultAddress,omitempty"` + // L1 config L1Config *L1Config `json:"l1Config,omitempty"` } @@ -366,6 +369,10 @@ func (c *L1Config) String() string { c.L1ChainId, c.L1MessageQueueAddress.Hex(), c.NumL1MessagesPerBlock, c.ScrollChainAddress.Hex()) } +func (s ScrollConfig) FeeVaultEnabled() bool { + return s.FeeVaultAddress != nil +} + func (s ScrollConfig) ShouldIncludeL1Messages() bool { return s.L1Config != nil && s.L1Config.NumL1MessagesPerBlock > 0 } diff --git a/rollup/fees/rollup_fee.go b/rollup/fees/rollup_fee.go index 2008077efb..76755e98d8 100644 --- a/rollup/fees/rollup_fee.go +++ b/rollup/fees/rollup_fee.go @@ -2,8 +2,6 @@ package fees import ( "bytes" - "errors" - "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -25,17 +23,17 @@ var ( // It should be a subset of the methods found on // types.Message type Message interface { - From() common.Address - To() *common.Address - GasPrice() *big.Int - Gas() uint64 - GasFeeCap() *big.Int - GasTipCap() *big.Int - Value() *big.Int - Nonce() uint64 - Data() []byte - AccessList() types.AccessList - IsL1MessageTx() bool + GetFrom() common.Address + GetTo() *common.Address + GetGasPrice() *big.Int + GetGasLimit() uint64 + GetGasFeeCap() *big.Int + GetGasTipCap() *big.Int + GetValue() *big.Int + GetNonce() uint64 + GetData() []byte + GetAccessList() types.AccessList + GetIsL1MessageTx() bool } // StateDB represents the StateDB interface @@ -46,7 +44,7 @@ type StateDB interface { } func EstimateL1DataFeeForMessage(msg Message, baseFee, chainID *big.Int, signer types.Signer, state StateDB) (*big.Int, error) { - if msg.IsL1MessageTx() { + if msg.GetIsL1MessageTx() { return big.NewInt(0), nil } @@ -70,7 +68,7 @@ func EstimateL1DataFeeForMessage(msg Message, baseFee, chainID *big.Int, signer // asUnsignedTx turns a Message into a types.Transaction func asUnsignedTx(msg Message, baseFee, chainID *big.Int) *types.Transaction { if baseFee == nil { - if msg.AccessList() == nil { + if msg.GetAccessList() == nil { return asUnsignedLegacyTx(msg) } @@ -82,38 +80,38 @@ func asUnsignedTx(msg Message, baseFee, chainID *big.Int) *types.Transaction { func asUnsignedLegacyTx(msg Message) *types.Transaction { return types.NewTx(&types.LegacyTx{ - Nonce: msg.Nonce(), - To: msg.To(), - Value: msg.Value(), - Gas: msg.Gas(), - GasPrice: msg.GasPrice(), - Data: msg.Data(), + Nonce: msg.GetNonce(), + To: msg.GetTo(), + Value: msg.GetValue(), + Gas: msg.GetGasLimit(), + GasPrice: msg.GetGasPrice(), + Data: msg.GetData(), }) } func asUnsignedAccessListTx(msg Message, chainID *big.Int) *types.Transaction { return types.NewTx(&types.AccessListTx{ - Nonce: msg.Nonce(), - To: msg.To(), - Value: msg.Value(), - Gas: msg.Gas(), - GasPrice: msg.GasPrice(), - Data: msg.Data(), - AccessList: msg.AccessList(), + Nonce: msg.GetNonce(), + To: msg.GetTo(), + Value: msg.GetValue(), + Gas: msg.GetGasLimit(), + GasPrice: msg.GetGasPrice(), + Data: msg.GetData(), + AccessList: msg.GetAccessList(), ChainID: chainID, }) } func asUnsignedDynamicTx(msg Message, chainID *big.Int) *types.Transaction { return types.NewTx(&types.DynamicFeeTx{ - Nonce: msg.Nonce(), - To: msg.To(), - Value: msg.Value(), - Gas: msg.Gas(), - GasFeeCap: msg.GasFeeCap(), - GasTipCap: msg.GasTipCap(), - Data: msg.Data(), - AccessList: msg.AccessList(), + Nonce: msg.GetNonce(), + To: msg.GetTo(), + Value: msg.GetValue(), + Gas: msg.GetGasLimit(), + GasFeeCap: msg.GetGasFeeCap(), + GasTipCap: msg.GetGasTipCap(), + Data: msg.GetData(), + AccessList: msg.GetAccessList(), ChainID: chainID, }) } @@ -189,37 +187,3 @@ func CalculateL1DataFee(tx *types.Transaction, state StateDB) (*big.Int, error) l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) return l1DataFee, nil } - -func calculateL2Fee(tx *types.Transaction) *big.Int { - l2GasLimit := new(big.Int).SetUint64(tx.Gas()) - return new(big.Int).Mul(tx.GasPrice(), l2GasLimit) -} - -func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error { - from, err := types.Sender(signer, tx) - if err != nil { - return errors.New("invalid transaction: invalid sender") - } - - balance := state.GetBalance(from) - l2Fee := calculateL2Fee(tx) - l1DataFee, err := CalculateL1DataFee(tx, state) - if err != nil { - return fmt.Errorf("invalid transaction: %w", err) - } - - cost := tx.Value() - cost = cost.Add(cost, l2Fee) - if balance.Cmp(cost) < 0 { - return errors.New("invalid transaction: insufficient funds for gas * price + value") - } - - cost = cost.Add(cost, l1DataFee) - if balance.Cmp(cost) < 0 { - return errors.New("invalid transaction: insufficient funds for l1fee + gas * price + value") - } - - // TODO: check GasPrice is in an expected range - - return nil -} diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 745a3c6b28..0e3c54576b 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -38,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/triedb/hashdb" "github.com/ethereum/go-ethereum/trie/triedb/pathdb" @@ -254,9 +255,9 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh return nil, nil, nil, common.Hash{}, err } + var ttx types.Transaction // Try to recover tx with current signer if len(post.TxBytes) != 0 { - var ttx types.Transaction err := ttx.UnmarshalBinary(post.TxBytes) if err != nil { triedb.Close() @@ -289,7 +290,11 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh snapshot := statedb.Snapshot() gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) - _, err = core.ApplyMessage(evm, msg, gaspool) + l1DataFee, err := fees.CalculateL1DataFee(&ttx, statedb) + if err != nil { + return nil, nil, nil, common.Hash{}, err + } + _, err = core.ApplyMessage(evm, msg, gaspool, l1DataFee) if err != nil { statedb.RevertToSnapshot(snapshot) }