From f055f50f9d5605df661714677813c8c4779868de Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:57:59 +0800 Subject: [PATCH] feat: update l1fee calculation (#351) --- accounts/abi/bind/backends/simulated.go | 8 +- cmd/evm/internal/t8ntool/execution.go | 10 +- core/state_prefetcher.go | 14 +- core/state_processor.go | 10 +- core/state_transition.go | 43 +++-- core/tx_pool.go | 2 +- core/tx_pool_test.go | 64 +++---- core/types/l2trace.go | 2 +- eth/state_accessor.go | 7 +- eth/tracers/api.go | 75 ++++++-- eth/tracers/api_blocktrace.go | 16 +- eth/tracers/api_blocktrace_test.go | 2 +- eth/tracers/api_test.go | 17 +- .../internal/tracetest/calltrace_test.go | 19 +- eth/tracers/tracers_test.go | 7 +- ethclient/gethclient/gethclient_test.go | 2 +- internal/ethapi/api.go | 33 ++-- les/odr_test.go | 9 +- les/state_accessor.go | 7 +- light/odr_test.go | 5 +- params/config.go | 2 +- params/version.go | 4 +- rollup/fees/rollup_fee.go | 166 +++++++++--------- rollup/fees/rollup_fee_test.go | 4 +- tests/state_test_util.go | 11 +- 25 files changed, 341 insertions(+), 198 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 9f8e483fab..2f4a9216ab 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -42,6 +42,7 @@ import ( "github.com/scroll-tech/go-ethereum/event" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/rpc" ) @@ -638,8 +639,13 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // about the transaction and calling mechanisms. 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(), head.Number) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, head.BaseFee, b.blockchain.Config().ChainID, signer, stateDB) + if err != nil { + return nil, err + } - return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb() + return core.NewStateTransition(vmEnv, msg, gasPool, l1DataFee).TransitionDb() } // 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 2f0204a2b6..e101b2c3bf 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -37,6 +37,7 @@ import ( "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/trie" ) @@ -166,8 +167,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, snapshot := statedb.Snapshot() 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()}) + 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/state_prefetcher.go b/core/state_prefetcher.go index 8ba89a9535..47bf31c766 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -17,6 +17,7 @@ package core import ( + "math/big" "sync/atomic" "github.com/scroll-tech/go-ethereum/consensus" @@ -24,6 +25,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/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.Prepare(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 types.Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error { +func precacheTransaction(msg types.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 e6d0a538e0..8011adf243 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -28,6 +28,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -97,8 +98,13 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon 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 } @@ -139,7 +145,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon receipt.BlockHash = blockHash receipt.BlockNumber = blockNumber receipt.TransactionIndex = uint(statedb.TxIndex()) - receipt.L1Fee = result.L1Fee + receipt.L1Fee = result.L1DataFee return receipt, err } diff --git a/core/state_transition.go b/core/state_transition.go index 6b13d65414..3aeb75c02e 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -28,7 +28,6 @@ import ( "github.com/scroll-tech/go-ethereum/crypto/codehash" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" - "github.com/scroll-tech/go-ethereum/rollup/fees" ) var emptyKeccakCodeHash = codehash.EmptyKeccakCodeHash @@ -63,8 +62,7 @@ type StateTransition struct { state vm.StateDB evm *vm.EVM - // l1 rollup fee - l1Fee *big.Int + l1DataFee *big.Int } // Message represents a message sent to a contract. @@ -88,7 +86,7 @@ type Message interface { // ExecutionResult includes all output after executing given evm // message no matter the execution itself is successful or not. type ExecutionResult struct { - L1Fee *big.Int + L1DataFee *big.Int UsedGas uint64 // Total used gas but include the refunded gas Err error // Any error encountered during the execution(listed in core/vm/errors.go) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) @@ -181,12 +179,7 @@ func toWordSize(size uint64) uint64 { } // NewStateTransition initialises and returns a new state transition object. -func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition { - l1Fee := new(big.Int) - if evm.ChainConfig().Scroll.FeeVaultEnabled() { - l1Fee, _ = fees.CalculateL1MsgFee(msg, evm.StateDB) - } - +func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool, l1DataFee *big.Int) *StateTransition { return &StateTransition{ gp: gp, evm: evm, @@ -197,7 +190,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition value: msg.Value(), data: msg.Data(), state: evm.StateDB, - l1Fee: l1Fee, + l1DataFee: l1DataFee, } } @@ -208,8 +201,8 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // 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() } // to returns the recipient of the message. @@ -225,9 +218,12 @@ func (st *StateTransition) buyGas() error { mgval = mgval.Mul(mgval, st.gasPrice) if st.evm.ChainConfig().Scroll.FeeVaultEnabled() { - // always add l1fee, because all tx are L2-to-L1 ATM - log.Debug("Adding L1 fee", "l1_fee", st.l1Fee) - mgval = mgval.Add(mgval, st.l1Fee) + // 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 := mgval @@ -236,8 +232,11 @@ func (st *StateTransition) buyGas() error { balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap) balanceCheck.Add(balanceCheck, st.value) if st.evm.ChainConfig().Scroll.FeeVaultEnabled() { - // always add l1fee, because all tx are L2-to-L1 ATM - balanceCheck.Add(balanceCheck, st.l1Fee) + // 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 have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 { @@ -391,7 +390,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // no refunds for l1 messages if st.msg.IsL1MessageTx() { return &ExecutionResult{ - L1Fee: big.NewInt(0), + L1DataFee: big.NewInt(0), UsedGas: st.gasUsed(), Err: vmerr, ReturnData: ret, @@ -416,17 +415,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.evm.ChainConfig().Scroll.FeeVaultEnabled() { // 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 + // codepath. Add the L1DataFee 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()), effectiveTip) - fee := new(big.Int).Add(st.l1Fee, l2Fee) + fee := new(big.Int).Add(st.l1DataFee, l2Fee) st.state.AddBalance(st.evm.FeeRecipient(), fee) } else { st.state.AddBalance(st.evm.FeeRecipient(), new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) } return &ExecutionResult{ - L1Fee: st.l1Fee, + L1DataFee: st.l1DataFee, UsedGas: st.gasUsed(), Err: vmerr, ReturnData: ret, diff --git a/core/tx_pool.go b/core/tx_pool.go index 810cb8edd4..abd45c796c 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -695,7 +695,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if pool.chainconfig.Scroll.FeeVaultEnabled() { if err := fees.VerifyFee(pool.signer, tx, pool.currentState); err != nil { - log.Trace("Discarding insufficient l1fee transaction", "hash", hash, "err", err) + log.Trace("Discarding insufficient l1DataFee transaction", "hash", hash, "err", err) invalidTxMeter.Mark(1) return false, err } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 3a701cce25..5ccc304cc1 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -43,32 +43,32 @@ var ( // sideeffects used during testing. testTxPoolConfig TxPoolConfig - // noL1feeConfig is a chain config without L1fee enabled. - noL1feeConfig *params.ChainConfig + // noL1DataFeeConfig is a chain config without L1DataFee enabled. + noL1DataFeeConfig *params.ChainConfig // eip1559Config is a chain config with EIP-1559 enabled at block 0. eip1559Config *params.ChainConfig - // eip1559NoL1feeConfig is a chain config with EIP-1559 enabled at block 0 but not enabling L1fee. - eip1559NoL1feeConfig *params.ChainConfig + // eip1559NoL1DataFeeConfig is a chain config with EIP-1559 enabled at block 0 but not enabling L1DataFee. + eip1559NoL1DataFeeConfig *params.ChainConfig ) func init() { testTxPoolConfig = DefaultTxPoolConfig testTxPoolConfig.Journal = "" - cpy0 := *params.TestNoL1feeChainConfig - noL1feeConfig = &cpy0 + cpy0 := *params.TestNoL1DataFeeChainConfig + noL1DataFeeConfig = &cpy0 cpy1 := *params.TestChainConfig eip1559Config = &cpy1 eip1559Config.BerlinBlock = common.Big0 eip1559Config.LondonBlock = common.Big0 - cpy2 := *params.TestNoL1feeChainConfig - eip1559NoL1feeConfig = &cpy2 - eip1559NoL1feeConfig.BerlinBlock = common.Big0 - eip1559NoL1feeConfig.LondonBlock = common.Big0 + cpy2 := *params.TestNoL1DataFeeChainConfig + eip1559NoL1DataFeeConfig = &cpy2 + eip1559NoL1DataFeeConfig.BerlinBlock = common.Big0 + eip1559NoL1DataFeeConfig.LondonBlock = common.Big0 } type testBlockChain struct { @@ -290,7 +290,7 @@ func testSetNonce(pool *TxPool, addr common.Address, nonce uint64) { func TestInvalidTransactions(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() tx := transaction(0, 100, key) @@ -327,7 +327,7 @@ func TestInvalidTransactions(t *testing.T) { func TestTransactionQueue(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() tx := transaction(0, 100, key) @@ -358,7 +358,7 @@ func TestTransactionQueue(t *testing.T) { func TestTransactionQueue2(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() tx1 := transaction(0, 100, key) @@ -384,7 +384,7 @@ func TestTransactionQueue2(t *testing.T) { func TestTransactionNegativeValue(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key) @@ -398,7 +398,7 @@ func TestTransactionNegativeValue(t *testing.T) { func TestTransactionTipAboveFeeCap(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, key := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key) @@ -411,7 +411,7 @@ func TestTransactionTipAboveFeeCap(t *testing.T) { func TestTransactionVeryHighValues(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, key := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() veryBigNumber := big.NewInt(1) @@ -431,7 +431,7 @@ func TestTransactionVeryHighValues(t *testing.T) { func TestTransactionChainFork(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -460,7 +460,7 @@ func TestTransactionChainFork(t *testing.T) { func TestTransactionDoubleNonce(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -511,7 +511,7 @@ func TestTransactionDoubleNonce(t *testing.T) { func TestTransactionMissingNonce(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -535,7 +535,7 @@ func TestTransactionNonceRecovery(t *testing.T) { t.Parallel() const n = 10 - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -561,7 +561,7 @@ func TestTransactionDropping(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -779,7 +779,7 @@ func TestTransactionGapFilling(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -833,7 +833,7 @@ func TestTransactionQueueAccountLimiting(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1114,7 +1114,7 @@ func TestTransactionPendingLimiting(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1203,7 +1203,7 @@ func TestTransactionAllowedTxSize(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1463,7 +1463,7 @@ func TestTransactionPoolRepricingDynamicFee(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - pool, _ := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, _ := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced @@ -1834,7 +1834,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) { func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) { t.Parallel() - pool, _ := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, _ := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() pool.config.GlobalSlots = 2 @@ -1941,7 +1941,7 @@ func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) { func TestDualHeapEviction(t *testing.T) { t.Parallel() - pool, _ := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, _ := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() pool.config.GlobalSlots = 10 @@ -2144,7 +2144,7 @@ func TestTransactionReplacementDynamicFee(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - pool, key := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, key := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) @@ -2443,7 +2443,7 @@ func BenchmarkPendingDemotion10000(b *testing.B) { benchmarkPendingDemotion(b, 1 func benchmarkPendingDemotion(b *testing.B, size int) { // Add a batch of transactions to a pool one by one - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -2468,7 +2468,7 @@ func BenchmarkFuturePromotion10000(b *testing.B) { benchmarkFuturePromotion(b, 1 func benchmarkFuturePromotion(b *testing.B, size int) { // Add a batch of transactions to a pool one by one - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -2496,7 +2496,7 @@ func BenchmarkPoolBatchLocalInsert10000(b *testing.B) { benchmarkPoolBatchInsert func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) { // Generate a batch of transactions to enqueue into the pool - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) diff --git a/core/types/l2trace.go b/core/types/l2trace.go index 88523aff35..42fd80df73 100644 --- a/core/types/l2trace.go +++ b/core/types/l2trace.go @@ -44,7 +44,7 @@ type StorageTrace struct { // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value type ExecutionResult struct { - L1Fee *hexutil.Big `json:"l1Fee,omitempty"` + L1DataFee *hexutil.Big `json:"l1DataFee,omitempty"` Gas uint64 `json:"gas"` Failed bool `json:"failed"` ReturnValue string `json:"returnValue"` diff --git a/eth/state_accessor.go b/eth/state_accessor.go index bac31fe7f2..6c2d2f9bfe 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -27,6 +27,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/trie" ) @@ -191,7 +192,11 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec // 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.Prepare(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, err + } + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil { return nil, vm.BlockContext{}, 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 a3705319ea..708b828132 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -283,7 +283,16 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config TxIndex: i, TxHash: tx.Hash(), } - res, err := api.traceTx(localctx, 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{Error: err.Error()} + log.Warn("CalculateL1DataFee failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) + break + } + + res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee) if err != nil { task.results[i] = &txTraceResult{Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) @@ -534,7 +543,14 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) ) statedb.Prepare(tx.Hash(), i) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); 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.Gas()), 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 @@ -608,7 +624,14 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac 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{Error: err.Error()} + continue + } + res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee) if err != nil { results[task.index] = &txTraceResult{Error: err.Error()} continue @@ -627,7 +650,12 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac msg, _ := tx.AsMessage(signer, block.BaseFee()) statedb.Prepare(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.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + failed = err + break + } + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee); err != nil { failed = err break } @@ -740,7 +768,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.Prepare(tx.Hash(), i) - _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err == nil { + _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee) + } if writer != nil { writer.Flush() } @@ -777,7 +808,7 @@ func containsTx(block *types.Block, hash common.Hash) bool { // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { - _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) + tx, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) if err != nil { return nil, err } @@ -802,7 +833,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 @@ -856,13 +891,20 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc Reexec: config.Reexec, } } - return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) + + signer := types.MakeSigner(api.backend.ChainConfig(), block.Number()) + 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) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.EVMLogger @@ -899,20 +941,15 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex // Run the transaction with tracing enabled. vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) - // If gasPrice is 0, make sure that the account has sufficient balance to cover `l1Fee`. - if api.backend.ChainConfig().Scroll.FeeVaultEnabled() && message.GasPrice().Cmp(big.NewInt(0)) == 0 { - l1Fee, err := fees.CalculateL1MsgFee(message, vmenv.StateDB) - if err != nil { - return nil, err - } - - statedb.AddBalance(message.From(), l1Fee) + // 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) } // Call Prepare to clear out the statedb access list statedb.Prepare(txctx.TxHash, txctx.TxIndex) - result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) + result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), l1DataFee) if err != nil { return nil, fmt.Errorf("tracing failed: %w", err) } @@ -930,7 +967,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex Failed: result.Failed(), ReturnValue: returnVal, StructLogs: vm.FormatLogs(tracer.StructLogs()), - L1Fee: (*hexutil.Big)(result.L1Fee), + L1DataFee: (*hexutil.Big)(result.L1DataFee), }, nil case Tracer: diff --git a/eth/tracers/api_blocktrace.go b/eth/tracers/api_blocktrace.go index 6b10688030..89cab46aec 100644 --- a/eth/tracers/api_blocktrace.go +++ b/eth/tracers/api_blocktrace.go @@ -16,6 +16,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/rollup/rcfg" "github.com/scroll-tech/go-ethereum/rollup/withdrawtrie" "github.com/scroll-tech/go-ethereum/rpc" @@ -182,7 +183,12 @@ func (api *API) getBlockTrace(block *types.Block, env *traceEnv) (*types.BlockTr msg, _ := tx.AsMessage(env.signer, block.BaseFee()) env.state.Prepare(tx.Hash(), i) vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, api.backend.ChainConfig(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, env.state) + if err != nil { + failed = err + break + } + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee); err != nil { failed = err break } @@ -258,7 +264,11 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc state.Prepare(txctx.TxHash, txctx.TxIndex) // Computes the new state by applying the given message. - result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, state) + if err != nil { + return fmt.Errorf("tracing failed: %w", err) + } + result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee) if err != nil { return fmt.Errorf("tracing failed: %w", err) } @@ -389,7 +399,7 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc To: receiver, AccountCreated: createdAcc, AccountsAfter: after, - L1Fee: (*hexutil.Big)(result.L1Fee), + L1DataFee: (*hexutil.Big)(result.L1DataFee), Gas: result.UsedGas, Failed: result.Failed(), ReturnValue: fmt.Sprintf("%x", returnVal), diff --git a/eth/tracers/api_blocktrace_test.go b/eth/tracers/api_blocktrace_test.go index d4d8f02a37..eea330ee22 100644 --- a/eth/tracers/api_blocktrace_test.go +++ b/eth/tracers/api_blocktrace_test.go @@ -164,7 +164,7 @@ func checkStructLogs(t *testing.T, expect []*txTraceResult, actual []*types.Exec assert.Equal(t, len(expect), len(actual)) for i, val := range expect { trace1, trace2 := val.Result.(*types.ExecutionResult), actual[i] - assert.Equal(t, trace1.L1Fee, trace2.L1Fee) + assert.Equal(t, trace1.L1DataFee, trace2.L1DataFee) assert.Equal(t, trace1.Failed, trace2.Failed) assert.Equal(t, trace1.Gas, trace2.Gas) assert.Equal(t, trace1.ReturnValue, trace2.ReturnValue) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index c36ecd441d..819914e8e7 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -42,6 +42,7 @@ import ( "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/internal/ethapi" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/rpc" ) @@ -172,7 +173,11 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block return msg, context, statedb, 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, err + } + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil { return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) @@ -218,7 +223,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -236,7 +241,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -266,7 +271,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -284,7 +289,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -338,7 +343,7 @@ func TestTraceTransaction(t *testing.T) { t.Errorf("Failed to trace transaction %v", err) } if !reflect.DeepEqual(result, &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 2be638874c..7fc405b750 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -37,6 +37,7 @@ import ( "github.com/scroll-tech/go-ethereum/eth/tracers" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/tests" // Force-load native and js pacakges, to trigger registration @@ -192,7 +193,11 @@ func testCallTracer(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) } @@ -303,7 +308,11 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { } evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, 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) } @@ -372,7 +381,11 @@ func TestZeroValueToNotExitCall(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/tracers_test.go b/eth/tracers/tracers_test.go index e8aece81c4..7b64f78eea 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -28,6 +28,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/tests" ) @@ -111,7 +112,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/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index 32a20597f7..04a498d0f9 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -334,7 +334,7 @@ func testCallContractNoGas(t *testing.T, client *rpc.Client) { } // this would fail with `insufficient funds for gas * price + value` - // before we started considering l1fee for 0 gas calls. + // before we started considering l1DataFee for 0 gas calls. if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), nil); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0f9fe50491..b277148a3c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -907,7 +907,7 @@ func newRPCBalance(balance *big.Int) **hexutil.Big { return &rpcBalance } -func CalculateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, config *params.ChainConfig) (*big.Int, error) { +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 } @@ -947,7 +947,8 @@ func CalculateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, blo evm.Cancel() }() - return fees.CalculateL1MsgFee(msg, evm.StateDB) + signer := types.MakeSigner(config, header.Number) + return fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, evm.StateDB) } func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { @@ -990,7 +991,14 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash // Execute the message. gp := new(core.GasPool).AddGas(math.MaxUint64) - result, err := core.ApplyMessage(evm, msg, gp) + + signer := types.MakeSigner(b.ChainConfig(), header.Number) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig().ChainID, signer, state) + if err != nil { + return nil, err + } + + result, err := core.ApplyMessage(evm, msg, gp, l1DataFee) if err := vmError(); err != nil { return nil, err } @@ -1043,7 +1051,7 @@ func (e *revertError) ErrorData() interface{} { // useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { // If gasPrice is 0 and no state override is set, make sure - // that the account has sufficient balance to cover `l1Fee`. + // that the account has sufficient balance to cover `l1DataFee`. isGasPriceZero := args.GasPrice == nil || args.GasPrice.ToInt().Cmp(big.NewInt(0)) == 0 if overrides == nil { @@ -1052,13 +1060,13 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, bl _, isOverrideSet := (*overrides)[args.from()] if isGasPriceZero && !isOverrideSet { - l1Fee, err := CalculateL1MsgFee(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), s.b.ChainConfig()) + l1DataFee, err := EstimateL1MsgFee(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), s.b.ChainConfig()) if err != nil { return nil, err } (*overrides)[args.from()] = OverrideAccount{ - BalanceAdd: newRPCBalance(l1Fee), + BalanceAdd: newRPCBalance(l1DataFee), } } @@ -1127,14 +1135,14 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr } // account for l1 fee - l1Fee, err := CalculateL1MsgFee(ctx, b, args, blockNrOrHash, nil, 0, gasCap, b.ChainConfig()) + l1DataFee, err := EstimateL1MsgFee(ctx, b, args, blockNrOrHash, nil, 0, gasCap, b.ChainConfig()) if err != nil { return 0, err } - if l1Fee.Cmp(available) >= 0 { + if l1DataFee.Cmp(available) >= 0 { return 0, errors.New("insufficient funds for l1 fee") } - available.Sub(available, l1Fee) + available.Sub(available, l1DataFee) allowance := new(big.Int).Div(available, feeCap) @@ -1501,7 +1509,12 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH if err != nil { return nil, 0, nil, err } - res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + signer := types.MakeSigner(b.ChainConfig(), header.Number) + 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.Gas()), l1DataFee) if err != nil { return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) } diff --git a/les/odr_test.go b/les/odr_test.go index dd749bac89..2f7af21d64 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -37,6 +37,7 @@ import ( "github.com/scroll-tech/go-ethereum/light" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte @@ -143,7 +144,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) - result, _ := core.ApplyMessage(vmenv, msg, gp) + signer := types.MakeSigner(config, header.Number) + l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, statedb) + result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) res = append(res, result.Return()...) } } else { @@ -155,7 +158,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai txContext := core.NewEVMTxContext(msg) vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true}) gp := new(core.GasPool).AddGas(math.MaxUint64) - result, _ := core.ApplyMessage(vmenv, msg, gp) + signer := types.MakeSigner(config, header.Number) + l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, state) + result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) if state.Error() == nil { res = append(res, result.Return()...) } diff --git a/les/state_accessor.go b/les/state_accessor.go index 8b2319a11e..93ed93e53a 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -26,6 +26,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/light" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) // stateAtBlock retrieves the state database associated with a certain block. @@ -64,7 +65,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, 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, 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 8145af607d..4d359312b6 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -36,6 +36,7 @@ import ( "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/trie" ) @@ -199,7 +200,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) + 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/params/config.go b/params/config.go index e84e7361fd..c0522c54f2 100644 --- a/params/config.go +++ b/params/config.go @@ -340,7 +340,7 @@ var ( }} TestRules = TestChainConfig.Rules(new(big.Int)) - TestNoL1feeChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil, + TestNoL1DataFeeChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil, ScrollConfig{ UseZktrie: false, FeeVaultAddress: nil, diff --git a/params/version.go b/params/version.go index cf6c36a0e3..8f6ff5ba46 100644 --- a/params/version.go +++ b/params/version.go @@ -23,8 +23,8 @@ import ( const ( VersionMajor = 4 // Major version component of the current release - VersionMinor = 1 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release + VersionMinor = 2 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string ) diff --git a/rollup/fees/rollup_fee.go b/rollup/fees/rollup_fee.go index dd2a726f05..81e427d631 100644 --- a/rollup/fees/rollup_fee.go +++ b/rollup/fees/rollup_fee.go @@ -8,24 +8,17 @@ import ( "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rollup/rcfg" ) var ( - // errTransactionSigned represents the error case of passing in a signed - // transaction to the L1 fee calculation routine. The signature is accounted - // for externally - errTransactionSigned = errors.New("transaction is signed") - // txExtraDataBytes is the number of bytes that we commit to L1 in addition - // to the RLP-encoded unsigned transaction. Note that these are all assumed + // to the RLP-encoded signed transaction. Note that these are all assumed // to be non-zero. // - tx length prefix: 4 bytes - // - sig.r: 32 bytes + 1 byte rlp prefix - // - sig.s: 32 bytes + 1 byte rlp prefix - // - sig.v: 3 bytes + 1 byte rlp prefix - txExtraDataBytes = uint64(74) + txExtraDataBytes = uint64(4) ) // Message represents the interface of a message. @@ -36,9 +29,13 @@ type Message interface { 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 } // StateDB represents the StateDB interface @@ -48,59 +45,87 @@ type StateDB interface { GetBalance(addr common.Address) *big.Int } -// CalculateL1MsgFee computes the L1 portion of the fee given -// a Message and a StateDB -// Reference: https://github.com/ethereum-optimism/optimism/blob/develop/l2geth/rollup/fees/rollup_fee.go -func CalculateL1MsgFee(msg Message, state StateDB) (*big.Int, error) { - tx := asTransaction(msg) +func EstimateL1DataFeeForMessage(msg Message, baseFee, chainID *big.Int, signer types.Signer, state StateDB) (*big.Int, error) { + if msg.IsL1MessageTx() { + return big.NewInt(0), nil + } + + unsigned := asUnsignedTx(msg, baseFee, chainID) + // with v=1 + tx, err := unsigned.WithSignature(signer, append(bytes.Repeat([]byte{0xff}, crypto.SignatureLength-1), 0x01)) + if err != nil { + return nil, err + } + raw, err := rlpEncode(tx) if err != nil { return nil, err } l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) - l1Fee := CalculateL1Fee(raw, overhead, l1BaseFee, scalar) - return l1Fee, nil + l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) + return l1DataFee, nil } -// asTransaction turns a Message into a types.Transaction -func asTransaction(msg Message) *types.Transaction { - if msg.To() == nil { - return types.NewContractCreation( - msg.Nonce(), - msg.Value(), - msg.Gas(), - msg.GasPrice(), - msg.Data(), - ) +// 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 { + return asUnsignedLegacyTx(msg) + } + + return asUnsignedAccessListTx(msg, chainID) } - return types.NewTransaction( - msg.Nonce(), - *msg.To(), - msg.Value(), - msg.Gas(), - msg.GasPrice(), - msg.Data(), - ) + + return asUnsignedDynamicTx(msg, chainID) +} + +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(), + }) +} + +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(), + 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(), + ChainID: chainID, + }) } // rlpEncode RLP encodes the transaction into bytes -// When a signature is not included, set pad to true to -// fill in a dummy signature full on non 0 bytes func rlpEncode(tx *types.Transaction) ([]byte, error) { raw := new(bytes.Buffer) if err := tx.EncodeRLP(raw); err != nil { return nil, err } - r, v, s := tx.RawSignatureValues() - if r.Cmp(common.Big0) != 0 || v.Cmp(common.Big0) != 0 || s.Cmp(common.Big0) != 0 { - return nil, errTransactionSigned - } - - // Slice off the 0 bytes representing the signature - b := raw.Bytes() - return b[:len(b)-3], nil + return raw.Bytes(), nil } func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int, *big.Int) { @@ -110,11 +135,11 @@ func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int return l1BaseFee.Big(), overhead.Big(), scalar.Big() } -// CalculateL1Fee computes the L1 fee -func CalculateL1Fee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Int) *big.Int { +// calculateEncodedL1DataFee computes the L1 fee for an RLP-encoded tx +func calculateEncodedL1DataFee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Int) *big.Int { l1GasUsed := CalculateL1GasUsed(data, overhead) - l1Fee := new(big.Int).Mul(l1GasUsed, l1GasPrice) - return mulAndScale(l1Fee, scalar, rcfg.Precision) + l1DataFee := new(big.Int).Mul(l1GasUsed, l1GasPrice) + return mulAndScale(l1DataFee, scalar, rcfg.Precision) } // CalculateL1GasUsed computes the L1 gas used based on the calldata and @@ -150,41 +175,24 @@ func mulAndScale(x *big.Int, y *big.Int, precision *big.Int) *big.Int { return new(big.Int).Quo(z, precision) } -// copyTransaction copies the transaction, removing the signature -func copyTransaction(tx *types.Transaction) *types.Transaction { - if tx.To() == nil { - return types.NewContractCreation( - tx.Nonce(), - tx.Value(), - tx.Gas(), - tx.GasPrice(), - tx.Data(), - ) +func CalculateL1DataFee(tx *types.Transaction, state StateDB) (*big.Int, error) { + if tx.IsL1MessageTx() { + return big.NewInt(0), nil } - return types.NewTransaction( - tx.Nonce(), - *tx.To(), - tx.Value(), - tx.Gas(), - tx.GasPrice(), - tx.Data(), - ) -} -func CalculateFees(tx *types.Transaction, state StateDB) (*big.Int, *big.Int, *big.Int, error) { - unsigned := copyTransaction(tx) - raw, err := rlpEncode(unsigned) + raw, err := rlpEncode(tx) if err != nil { - return nil, nil, nil, err + return nil, err } l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) - l1Fee := CalculateL1Fee(raw, overhead, l1BaseFee, scalar) + l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) + return l1DataFee, nil +} +func calculateL2Fee(tx *types.Transaction) *big.Int { l2GasLimit := new(big.Int).SetUint64(tx.Gas()) - l2Fee := new(big.Int).Mul(tx.GasPrice(), l2GasLimit) - fee := new(big.Int).Add(l1Fee, l2Fee) - return l1Fee, l2Fee, fee, nil + return new(big.Int).Mul(tx.GasPrice(), l2GasLimit) } func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error { @@ -194,8 +202,8 @@ func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error } balance := state.GetBalance(from) - - l1Fee, l2Fee, _, err := CalculateFees(tx, state) + l2Fee := calculateL2Fee(tx) + l1DataFee, err := CalculateL1DataFee(tx, state) if err != nil { return fmt.Errorf("invalid transaction: %w", err) } @@ -206,7 +214,7 @@ func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error return errors.New("invalid transaction: insufficient funds for gas * price + value") } - cost = cost.Add(cost, l1Fee) + cost = cost.Add(cost, l1DataFee) if balance.Cmp(cost) < 0 { return errors.New("invalid transaction: insufficient funds for l1fee + gas * price + value") } diff --git a/rollup/fees/rollup_fee_test.go b/rollup/fees/rollup_fee_test.go index d1bb2e9e74..54b77eeb15 100644 --- a/rollup/fees/rollup_fee_test.go +++ b/rollup/fees/rollup_fee_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCalculateL1Fee(t *testing.T) { +func TestCalculateEncodedL1DataFee(t *testing.T) { l1BaseFee := new(big.Int).SetUint64(15000000) data := []byte{0, 10, 1, 0} @@ -15,6 +15,6 @@ func TestCalculateL1Fee(t *testing.T) { scalar := new(big.Int).SetUint64(10) expected := new(big.Int).SetUint64(184) // 184.2 - actual := CalculateL1Fee(data, overhead, l1BaseFee, scalar) + actual := calculateEncodedL1DataFee(data, overhead, l1BaseFee, scalar) assert.Equal(t, expected, actual) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 1c8ab2c997..14457613f5 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -39,6 +39,7 @@ import ( "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) // StateTest checks transaction processing without block context. @@ -201,9 +202,9 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh return 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 { return nil, nil, common.Hash{}, err @@ -225,7 +226,13 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh snapshot := statedb.Snapshot() gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) - if _, err := core.ApplyMessage(evm, msg, gaspool); err != nil { + + l1DataFee, err := fees.CalculateL1DataFee(&ttx, statedb) + if err != nil { + return nil, nil, common.Hash{}, err + } + + if _, err = core.ApplyMessage(evm, msg, gaspool, l1DataFee); err != nil { statedb.RevertToSnapshot(snapshot) }