diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 8a44d0e936d6..ef65129479e2 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -715,7 +715,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM 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) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.blockchain.Config(), signer, stateDB, header.Number) if err != nil { return nil, err } diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 38b7d38bb2ac..bc4a1f68cc5e 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -186,6 +186,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { misc.ApplyDAOHardFork(statedb) } + // Apply Curie hard fork + if chainConfig.CurieBlock != nil && chainConfig.CurieBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { + misc.ApplyCurieHardFork(statedb) + } if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb) @@ -221,7 +225,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, ) evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, chainConfig, new(big.Int).SetUint64(pre.Env.Number)) 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()}) diff --git a/consensus/misc/curie.go b/consensus/misc/curie.go new file mode 100644 index 000000000000..b119181d7557 --- /dev/null +++ b/consensus/misc/curie.go @@ -0,0 +1,23 @@ +package misc + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rollup/rcfg" +) + +// ApplyCurieHardFork modifies the state database according to the Curie hard-fork rules, +// updating the bytecode and storage of the L1GasPriceOracle contract. +func ApplyCurieHardFork(statedb *state.StateDB) { + log.Info("Applying Curie hard fork") + + // update contract byte code + statedb.SetCode(rcfg.L1GasPriceOracleAddress, rcfg.CurieL1GasPriceOracleBytecode) + + // initialize new storage slots + statedb.SetState(rcfg.L1GasPriceOracleAddress, rcfg.IsCurieSlot, common.BytesToHash([]byte{1})) + statedb.SetState(rcfg.L1GasPriceOracleAddress, rcfg.L1BlobBaseFeeSlot, common.BytesToHash([]byte{1})) + statedb.SetState(rcfg.L1GasPriceOracleAddress, rcfg.CommitScalarSlot, common.BigToHash(rcfg.InitialCommitScalar)) + statedb.SetState(rcfg.L1GasPriceOracleAddress, rcfg.BlobScalarSlot, common.BigToHash(rcfg.InitialBlobScalar)) +} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index bc6f8112f015..60a559650b73 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -17,6 +17,7 @@ package core import ( + "encoding/json" "errors" "fmt" "math/big" @@ -39,7 +40,9 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/rcfg" "github.com/ethereum/go-ethereum/trie" + "github.com/stretchr/testify/assert" ) // So we can deterministically seed different blockchains @@ -4716,3 +4719,78 @@ func TestEIP3651(t *testing.T) { t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) } } + +func TestCurieTransition(t *testing.T) { + // Set fork blocks in config + // (we make a deep copy to avoid interference with other tests) + var config *params.ChainConfig + b, _ := json.Marshal(params.AllEthashProtocolChanges) + json.Unmarshal(b, &config) + config.CurieBlock = big.NewInt(2) + config.DescartesBlock = nil + + var ( + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{Config: config} + genesis = gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults)) + ) + + blockchain, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer blockchain.Stop() + blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, nil) + + if _, err := blockchain.InsertChain(blocks); err != nil { + t.Fatal(err) + } + + latestBlock := uint64(4) + assert.Equal(t, latestBlock, blockchain.CurrentHeader().Number.Uint64()) + + for ii := uint64(0); ii <= latestBlock; ii++ { + block := blockchain.GetBlockByNumber(ii) + + number := block.Number().Uint64() + baseFee := block.BaseFee() + + statedb, _ := state.New(block.Root(), state.NewDatabase(db), nil) + + code := statedb.GetCode(rcfg.L1GasPriceOracleAddress) + codeSize := statedb.GetCodeSize(rcfg.L1GasPriceOracleAddress) + keccakCodeHash := statedb.GetKeccakCodeHash(rcfg.L1GasPriceOracleAddress) + poseidonCodeHash := statedb.GetPoseidonCodeHash(rcfg.L1GasPriceOracleAddress) + + l1BlobBaseFee := statedb.GetState(rcfg.L1GasPriceOracleAddress, rcfg.L1BlobBaseFeeSlot) + commitScalar := statedb.GetState(rcfg.L1GasPriceOracleAddress, rcfg.CommitScalarSlot) + blobScalar := statedb.GetState(rcfg.L1GasPriceOracleAddress, rcfg.BlobScalarSlot) + isCurie := statedb.GetState(rcfg.L1GasPriceOracleAddress, rcfg.IsCurieSlot) + + if number < config.CurieBlock.Uint64() { + assert.Nil(t, baseFee, "Expected zero base fee before Curie") + + // we don't have predeploys configured in this test, + // so there is no gas oracle deployed before Curie + assert.Nil(t, code) + assert.Equal(t, uint64(0), codeSize) + assert.Equal(t, common.Hash{}, keccakCodeHash) + assert.Equal(t, common.Hash{}, poseidonCodeHash) + + assert.Equal(t, common.Hash{}, l1BlobBaseFee) + assert.Equal(t, common.Hash{}, commitScalar) + assert.Equal(t, common.Hash{}, blobScalar) + assert.Equal(t, common.Hash{}, isCurie) + } else { + assert.NotNil(t, baseFee, "Expected nonzero base fee after Curie") + + // all gas oracle entries updated + assert.NotNil(t, code) + assert.NotEqual(t, uint64(0), codeSize) + assert.NotEqual(t, common.Hash{}, keccakCodeHash) + assert.NotEqual(t, common.Hash{}, poseidonCodeHash) + + assert.NotEqual(t, common.Hash{}, l1BlobBaseFee) + assert.NotEqual(t, common.Hash{}, commitScalar) + assert.NotEqual(t, common.Hash{}, blobScalar) + assert.NotEqual(t, common.Hash{}, isCurie) + } + } +} diff --git a/core/chain_makers.go b/core/chain_makers.go index 39adb12ec06b..d24b3102729c 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -328,6 +328,9 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 { misc.ApplyDAOHardFork(statedb) } + if config.CurieBlock != nil && config.CurieBlock.Cmp(b.header.Number) == 0 { + misc.ApplyCurieHardFork(statedb) + } // Execute any user modifications to the block if gen != nil { gen(i, b) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index a7ee63eff1a7..4199169def6c 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -71,7 +71,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c } statedb.SetTxContext(tx.Hash(), i) - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, p.config, block.Number()) if err != nil { return } diff --git a/core/state_processor.go b/core/state_processor.go index a816192d42d1..de33b45728c9 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -72,6 +72,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) } + // Apply Curie hard fork + if p.config.CurieBlock != nil && p.config.CurieBlock.Cmp(block.Number()) == 0 { + misc.ApplyCurieHardFork(statedb) + } var ( context = NewEVMBlockContext(header, p.bc, p.config, nil) vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) @@ -110,7 +114,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, config, blockNumber) if err != nil { return nil, err } diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 22aa8540be62..8eb3ec212395 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1080,7 +1080,7 @@ func (p *BlobPool) validateTx(tx *types.Transaction) error { return nil }, } - if err := txpool.ValidateTransactionWithState(tx, p.signer, stateOpts); err != nil { + if err := txpool.ValidateTransactionWithState(tx, p.signer, stateOpts, p.chain.Config(), p.head.Number); err != nil { return err } // If the transaction replaces an existing one, ensure that price bumps are diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index e441d5887463..af04d016a49c 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -638,7 +638,7 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { return nil }, } - if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts); err != nil { + if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts, pool.chainconfig, pool.currentHead.Load().Number); err != nil { return err } return nil @@ -759,7 +759,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Try to replace an existing transaction in the pending pool if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { // Nonce already pending, check if required price bump is met - inserted, old := list.Add(tx, pool.config.PriceBump) + inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead.Load().Number) if !inserted { pendingDiscardMeter.Mark(1) return false, txpool.ErrReplaceUnderpriced @@ -833,7 +833,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local if pool.queue[from] == nil { pool.queue[from] = newList(false) } - inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) + inserted, old := pool.queue[from].Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead.Load().Number) if !inserted { // An older transaction was better, discard this queuedDiscardMeter.Mark(1) @@ -887,7 +887,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ } list := pool.pending[addr] - inserted, old := list.Add(tx, pool.config.PriceBump) + inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead.Load().Number) if !inserted { // An older transaction was better, discard this pool.all.Remove(hash) diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index 05ae0b58cd59..5b636545e462 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -26,7 +26,11 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/fees" ) // nonceHeap is a heap.Interface implementation over 64bit unsigned integers for @@ -298,7 +302,7 @@ func (l *list) Contains(nonce uint64) bool { // // If the new transaction is accepted into the list, the lists' cost and gas // thresholds are also potentially updated. -func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { +func (l *list) Add(tx *types.Transaction, state *state.StateDB, priceBump uint64, chainconfig *params.ChainConfig, blockNumber *big.Int) (bool, *types.Transaction) { // If there's an older better transaction, abort old := l.txs.Get(tx.Nonce()) if old != nil { @@ -322,13 +326,24 @@ func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transa return false, nil } // Old is being replaced, subtract old cost + // TODO: fix for L1DataFee l.subTotalCost([]*types.Transaction{old}) } + l1DataFee := big.NewInt(0) + if state != nil && chainconfig != nil { + var err error + l1DataFee, err = fees.CalculateL1DataFee(tx, state, chainconfig, blockNumber) + if err != nil { + log.Error("Failed to calculate L1 data fee", "err", err, "tx", tx) + return false, nil + } + } // Add new tx cost to totalcost + // TODO: fix totalcost for L1DataFee for both sub and add l.totalcost.Add(l.totalcost, tx.Cost()) // Otherwise overwrite the old transaction with the current one l.txs.Put(tx) - if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 { + if cost := new(big.Int).Add(tx.Cost(), l1DataFee); l.costcap.Cmp(cost) < 0 { l.costcap = cost } if gas := tx.Gas(); l.gascap < gas { @@ -454,6 +469,7 @@ func (l *list) LastElement() *types.Transaction { // subTotalCost subtracts the cost of the given transactions from the // total cost of all transactions. +// TODO: fix for L1DataFee func (l *list) subTotalCost(txs []*types.Transaction) { for _, tx := range txs { l.totalcost.Sub(l.totalcost, tx.Cost()) diff --git a/core/txpool/legacypool/list_test.go b/core/txpool/legacypool/list_test.go index b5cd34b23b62..6f9543c10b9c 100644 --- a/core/txpool/legacypool/list_test.go +++ b/core/txpool/legacypool/list_test.go @@ -21,13 +21,19 @@ import ( "math/rand" "testing" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" ) // Tests that transactions can be added to strict lists and list contents and // nonce boundaries are correctly maintained. func TestStrictListAdd(t *testing.T) { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) + // Generate a list of transactions to insert key, _ := crypto.GenerateKey() @@ -38,7 +44,7 @@ func TestStrictListAdd(t *testing.T) { // Insert the transactions in a random order list := newList(true) for _, v := range rand.Perm(len(txs)) { - list.Add(txs[v], DefaultConfig.PriceBump) + list.Add(txs[v], statedb, DefaultConfig.PriceBump, blockchain.Config(), blockchain.CurrentBlock().Number) } // Verify internal state if len(list.txs.items) != len(txs) { @@ -52,6 +58,9 @@ func TestStrictListAdd(t *testing.T) { } func BenchmarkListAdd(b *testing.B) { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) + // Generate a list of transactions to insert key, _ := crypto.GenerateKey() @@ -65,7 +74,7 @@ func BenchmarkListAdd(b *testing.B) { for i := 0; i < b.N; i++ { list := newList(true) for _, v := range rand.Perm(len(txs)) { - list.Add(txs[v], DefaultConfig.PriceBump) + list.Add(txs[v], statedb, DefaultConfig.PriceBump, blockchain.Config(), blockchain.CurrentBlock().Number) list.Filter(priceLimit, DefaultConfig.PriceBump) } } diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 6770483fe8d2..ae86e6176b29 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -197,7 +197,7 @@ type ValidationOptionsWithState struct { // // This check is public to allow different transaction pools to check the stateful // rules without duplicating code and running the risk of missed updates. -func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, opts *ValidationOptionsWithState) error { +func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, opts *ValidationOptionsWithState, chainConfig *params.ChainConfig, headNumber *big.Int) error { // Ensure the transaction adheres to nonce ordering from, err := signer.Sender(tx) // already validated (and cached), but cleaner to check if err != nil { @@ -227,7 +227,7 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op // 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) + l1DataFee, err := fees.CalculateL1DataFee(tx, opts.State, chainConfig, headNumber) if err != nil { return fmt.Errorf("failed to calculate L1 data fee, err: %w", err) } diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 9f1609adbe1a..3e98f741571b 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -249,7 +249,7 @@ 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) - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, eth.blockchain.Config(), block.Number()) if err != nil { return nil, vm.BlockContext{}, nil, nil, err } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index f8dacbe5ec8c..55726ed46b42 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -280,7 +280,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed TxIndex: i, TxHash: tx.Hash(), } - l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb, api.backend.ChainConfig(), task.block.Number()) 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()} @@ -546,7 +546,7 @@ 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) - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, chainConfig, block.Number()) 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 @@ -627,7 +627,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac TxIndex: i, TxHash: tx.Hash(), } - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number()) if err != nil { return nil, err } @@ -674,7 +674,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat TxIndex: task.index, TxHash: txs[task.index].Hash(), } - l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb) + l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb, api.backend.ChainConfig(), block.Number()) 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()} @@ -707,7 +707,7 @@ 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{}) - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number()) if err != nil { failed = err break txloop @@ -819,7 +819,7 @@ 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) - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, chainConfig, block.Number()) if err == nil { _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee) } @@ -891,7 +891,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * TxIndex: int(index), TxHash: hash, } - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number()) if err != nil { return nil, err } @@ -955,7 +955,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc traceConfig = &config.TraceConfig } signer := types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) - l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, block.BaseFee(), api.backend.ChainConfig().ChainID, signer, statedb) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, block.BaseFee(), api.backend.ChainConfig(), signer, statedb, block.Number()) if err != nil { return nil, err } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index a0159a57cacb..a956116389b4 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -177,7 +177,7 @@ 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{}) - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, b.chainConfig, block.Number()) if err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x CalculateL1DataFee failed: %v", tx.Hash(), err) } diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 35850272e3fb..e6297d79c1c2 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -151,7 +151,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, test.Genesis.Config, context.BlockNumber) if err != nil { t.Fatalf("failed to calculate l1DataFee: %v", err) } @@ -256,7 +256,7 @@ 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() - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, test.Genesis.Config, context.BlockNumber) if err != nil { b.Fatalf("failed to calculate l1DataFee: %v", err) } @@ -398,7 +398,7 @@ func TestInternals(t *testing.T) { SkipAccountChecks: false, } signer := types.MakeSigner(params.MainnetChainConfig, context.BlockNumber, context.Time) - l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, nil, params.MainnetChainConfig.ChainID, signer, statedb) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, nil, params.MainnetChainConfig, signer, statedb, context.BlockNumber) if err != nil { t.Fatalf("test %v: failed to estimate L1DataFee: %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 8a305e9bba50..2f5143411b3a 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -115,7 +115,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string if err != nil { return fmt.Errorf("failed to prepare transaction for tracing: %v", err) } - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, test.Genesis.Config, context.BlockNumber) if err != nil { return fmt.Errorf("failed to calculate L1DataFee: %v", err) } diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index 75b31e59e18d..3b2789e6a1ca 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -122,7 +122,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, test.Genesis.Config, context.BlockNumber) if err != nil { t.Fatalf("failed to calculate L1DataFee: %v", err) } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 70a441fe4bb7..d5d0d008a59f 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -100,7 +100,7 @@ func BenchmarkTransactionTrace(b *testing.B) { for i := 0; i < b.N; i++ { snap := statedb.Snapshot() - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, params.AllEthashProtocolChanges, context.BlockNumber) if err != nil { b.Fatal(err) } diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 005da99758c7..449a287db260 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/rcfg" "github.com/ethereum/go-ethereum/rpc" ) @@ -186,8 +187,22 @@ var ( ) var genesis = &core.Genesis{ - Config: params.AllEthashProtocolChanges, - Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, + Config: params.AllEthashProtocolChanges, + Alloc: core.GenesisAlloc{ + testAddr: {Balance: testBalance}, + rcfg.L1GasPriceOracleAddress: { + Balance: big.NewInt(0), + Storage: map[common.Hash]common.Hash{ + rcfg.L1BaseFeeSlot: common.BigToHash(big.NewInt(10000)), + rcfg.OverheadSlot: common.BigToHash(big.NewInt(10000)), + rcfg.ScalarSlot: common.BigToHash(big.NewInt(10000)), + rcfg.L1BlobBaseFeeSlot: common.BigToHash(big.NewInt(10000)), + rcfg.CommitScalarSlot: common.BigToHash(big.NewInt(10000)), + rcfg.BlobScalarSlot: common.BigToHash(big.NewInt(10000)), + rcfg.IsCurieSlot: common.BytesToHash([]byte{1}), + }, + }, + }, ExtraData: []byte("test genesis"), Timestamp: 9000, BaseFee: big.NewInt(params.InitialBaseFee), diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index 60ab43451302..46120f87db6d 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rollup/rcfg" "github.com/ethereum/go-ethereum/rpc" ) @@ -80,8 +81,22 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { func generateTestChain() (*core.Genesis, []*types.Block) { genesis := &core.Genesis{ Config: params.AllEthashProtocolChanges, - Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}}, - testContract: {Nonce: 1, Code: []byte{0x13, 0x37}}}, + Alloc: core.GenesisAlloc{ + testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}}, + testContract: {Nonce: 1, Code: []byte{0x13, 0x37}}, + rcfg.L1GasPriceOracleAddress: { + Balance: big.NewInt(0), + Storage: map[common.Hash]common.Hash{ + rcfg.L1BaseFeeSlot: common.BigToHash(big.NewInt(10000)), + rcfg.OverheadSlot: common.BigToHash(big.NewInt(10000)), + rcfg.ScalarSlot: common.BigToHash(big.NewInt(10000)), + rcfg.L1BlobBaseFeeSlot: common.BigToHash(big.NewInt(10000)), + rcfg.CommitScalarSlot: common.BigToHash(big.NewInt(10000)), + rcfg.BlobScalarSlot: common.BigToHash(big.NewInt(10000)), + rcfg.IsCurieSlot: common.BytesToHash([]byte{1}), + }, + }, + }, ExtraData: []byte("test genesis"), Timestamp: 9000, } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 439b8a6f2d99..c56a744ae817 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1234,7 +1234,7 @@ func EstimateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, bloc }() signer := types.MakeSigner(config, header.Number, header.Time) - return fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, evm.StateDB) + return fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, evm.StateDB, header.Number) } // executeEstimate is a helper that executes the transaction under a given gas limit and returns @@ -1729,7 +1729,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH config := vm.Config{Tracer: tracer, NoBaseFee: true} vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil) signer := types.MakeSigner(b.ChainConfig(), header.Number, header.Time) - l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig().ChainID, signer, statedb) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig(), signer, statedb, header.Number) if err != nil { return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) } diff --git a/les/state_accessor.go b/les/state_accessor.go index b966f5198ea5..58c851482520 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -70,7 +70,7 @@ 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{}) - l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, leth.blockchain.Config(), block.Number()) if err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } diff --git a/light/odr_test.go b/light/odr_test.go index 6596e16c95b0..383bb1481c2d 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -219,7 +219,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true}) gp := new(core.GasPool).AddGas(math.MaxUint64) signer := types.MakeSigner(config, header.Number, header.Time) - l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, st) + l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, st, header.Number) result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) res = append(res, result.Return()...) if st.Error() != nil { diff --git a/light/txpool.go b/light/txpool.go index 08e15ac5f8fa..d8ec442a5e71 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -72,6 +72,8 @@ type TxPool struct { istanbul bool // Fork indicator whether we are in the istanbul stage. eip2718 bool // Fork indicator whether we are in the eip2718 stage. shanghai bool // Fork indicator whether we are in the shanghai stage. + + currentHead *big.Int // Current blockchain head } // TxRelayBackend provides an interface to the mechanism that forwards transactions to the @@ -320,6 +322,8 @@ func (pool *TxPool) setNewHead(head *types.Header) { pool.istanbul = pool.config.IsIstanbul(next) pool.eip2718 = pool.config.IsBerlin(next) pool.shanghai = pool.config.IsShanghai(next, uint64(time.Now().Unix())) + + pool.currentHead = next } // Stop stops the light transaction pool @@ -389,7 +393,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error // 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) + l1DataFee, err := fees.CalculateL1DataFee(tx, currentState, pool.config, pool.currentHead) if err != nil { return fmt.Errorf("failed to calculate L1 data fee, err: %w", err) } diff --git a/miner/worker.go b/miner/worker.go index 0314216ff8d7..4e494b58a39c 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" @@ -1384,6 +1385,9 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { log.Error("Failed to create sealing context", "err", err) return nil, err } + if w.chainConfig.CurieBlock != nil && w.chainConfig.CurieBlock.Cmp(header.Number) == 0 { + misc.ApplyCurieHardFork(env.state) + } if header.ParentBeaconRoot != nil { context := core.NewEVMBlockContext(header, w.chain, w.chainConfig, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, w.chainConfig, vm.Config{}) @@ -1571,35 +1575,45 @@ func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) { if err != nil { return } - // Fill pending transactions from the txpool into the block. - err = w.fillTransactions(interrupt, work) - switch { - case err == nil: - // The entire block is filled, decrease resubmit interval in case - // of current interval is larger than the user-specified one. - w.resubmitAdjustCh <- &intervalAdjust{inc: false} - - case errors.Is(err, errBlockInterruptedByRecommit): - // Notify resubmit loop to increase resubmitting interval if the - // interruption is due to frequent commits. - gaslimit := work.header.GasLimit - ratio := float64(gaslimit-work.gasPool.Gas()) / float64(gaslimit) - if ratio < 0.1 { - ratio = 0.1 - } - w.resubmitAdjustCh <- &intervalAdjust{ - ratio: ratio, - inc: true, - } - case errors.Is(err, errBlockInterruptedByNewHead): - // If the block building is interrupted by newhead event, discard it - // totally. Committing the interrupted block introduces unnecessary - // delay, and possibly causes miner to mine on the previous head, - // which could result in higher uncle rate. - work.discard() - return + noTxs := false + // zkEVM requirement: Curie transition block has 0 transactions + if w.chainConfig.CurieBlock != nil && w.chainConfig.CurieBlock.Cmp(work.header.Number) == 0 { + noTxs = true } + + if !noTxs { + // Fill pending transactions from the txpool into the block. + err = w.fillTransactions(interrupt, work) + switch { + case err == nil: + // The entire block is filled, decrease resubmit interval in case + // of current interval is larger than the user-specified one. + w.resubmitAdjustCh <- &intervalAdjust{inc: false} + + case errors.Is(err, errBlockInterruptedByRecommit): + // Notify resubmit loop to increase resubmitting interval if the + // interruption is due to frequent commits. + gaslimit := work.header.GasLimit + ratio := float64(gaslimit-work.gasPool.Gas()) / float64(gaslimit) + if ratio < 0.1 { + ratio = 0.1 + } + w.resubmitAdjustCh <- &intervalAdjust{ + ratio: ratio, + inc: true, + } + + case errors.Is(err, errBlockInterruptedByNewHead): + // If the block building is interrupted by newhead event, discard it + // totally. Committing the interrupted block introduces unnecessary + // delay, and possibly causes miner to mine on the previous head, + // which could result in higher uncle rate. + work.discard() + return + } + } + // Submit the generated block for consensus sealing. w.commit(work.copy(), w.fullTaskHook, true, start) diff --git a/rollup/fees/rollup_fee.go b/rollup/fees/rollup_fee.go index 33efebc2b52b..8fece55dd405 100644 --- a/rollup/fees/rollup_fee.go +++ b/rollup/fees/rollup_fee.go @@ -2,6 +2,7 @@ package fees import ( "bytes" + "math" "math/big" "github.com/ethereum/go-ethereum/common" @@ -43,25 +44,42 @@ type StateDB interface { GetBalance(addr common.Address) *big.Int } -func EstimateL1DataFeeForMessage(msg Message, baseFee, chainID *big.Int, signer types.Signer, state StateDB) (*big.Int, error) { +type gpoState struct { + l1BaseFee *big.Int + overhead *big.Int + scalar *big.Int + l1BlobBaseFee *big.Int + commitScalar *big.Int + blobScalar *big.Int +} + +func EstimateL1DataFeeForMessage(msg Message, baseFee *big.Int, config *params.ChainConfig, signer types.Signer, state StateDB, blockNumber *big.Int) (*big.Int, error) { if msg.GetIsL1MessageTx() { return big.NewInt(0), nil } - unsigned := asUnsignedTx(msg, baseFee, chainID) + unsigned := asUnsignedTx(msg, baseFee, config.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) + raw, err := tx.MarshalBinary() if err != nil { return nil, err } - l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) - l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) + gpoState := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) + + var l1DataFee *big.Int + + if !config.IsCurie(blockNumber) { + l1DataFee = calculateEncodedL1DataFee(raw, gpoState.overhead, gpoState.l1BaseFee, gpoState.scalar) + } else { + l1DataFee = calculateEncodedL1DataFeeCurie(raw, gpoState.l1BaseFee, gpoState.l1BlobBaseFee, gpoState.commitScalar, gpoState.blobScalar) + } + return l1DataFee, nil } @@ -116,35 +134,46 @@ func asUnsignedDynamicTx(msg Message, chainID *big.Int) *types.Transaction { }) } -// rlpEncode RLP encodes the transaction into bytes -func rlpEncode(tx *types.Transaction) ([]byte, error) { - raw := new(bytes.Buffer) - if err := tx.EncodeRLP(raw); err != nil { - return nil, err - } - - return raw.Bytes(), nil -} - -func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int, *big.Int) { - l1BaseFee := state.GetState(addr, rcfg.L1BaseFeeSlot) - overhead := state.GetState(addr, rcfg.OverheadSlot) - scalar := state.GetState(addr, rcfg.ScalarSlot) - return l1BaseFee.Big(), overhead.Big(), scalar.Big() +func readGPOStorageSlots(addr common.Address, state StateDB) gpoState { + var gpoState gpoState + gpoState.l1BaseFee = state.GetState(addr, rcfg.L1BaseFeeSlot).Big() + gpoState.overhead = state.GetState(addr, rcfg.OverheadSlot).Big() + gpoState.scalar = state.GetState(addr, rcfg.ScalarSlot).Big() + gpoState.l1BlobBaseFee = state.GetState(addr, rcfg.L1BlobBaseFeeSlot).Big() + gpoState.commitScalar = state.GetState(addr, rcfg.CommitScalarSlot).Big() + gpoState.blobScalar = state.GetState(addr, rcfg.BlobScalarSlot).Big() + return gpoState } // 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) - l1DataFee := new(big.Int).Mul(l1GasUsed, l1GasPrice) +func calculateEncodedL1DataFee(data []byte, overhead, l1BaseFee *big.Int, scalar *big.Int) *big.Int { + l1GasUsed := calculateL1GasUsed(data, overhead) + l1DataFee := new(big.Int).Mul(l1GasUsed, l1BaseFee) return mulAndScale(l1DataFee, scalar, rcfg.Precision) } -// CalculateL1GasUsed computes the L1 gas used based on the calldata and +// calculateEncodedL1DataFeeCurie computes the L1 fee for an RLP-encoded tx, post Curie +func calculateEncodedL1DataFeeCurie(data []byte, l1BaseFee *big.Int, l1BlobBaseFee *big.Int, commitScalar *big.Int, blobScalar *big.Int) *big.Int { + // calldata component of commit fees (calldata gas + execution) + calldataGas := new(big.Int).Mul(commitScalar, l1BaseFee) + + // blob component of commit fees + blobGas := big.NewInt(int64(len(data))) + blobGas = new(big.Int).Mul(blobGas, l1BlobBaseFee) + blobGas = new(big.Int).Mul(blobGas, blobScalar) + + // combined + l1DataFee := new(big.Int).Add(calldataGas, blobGas) + l1DataFee = new(big.Int).Quo(l1DataFee, rcfg.Precision) + + return l1DataFee +} + +// calculateL1GasUsed computes the L1 gas used based on the calldata and // constant sized overhead. The overhead can be decreased as the cost of the // batch submission goes down via contract optimizations. This will not overflow // under standard network conditions. -func CalculateL1GasUsed(data []byte, overhead *big.Int) *big.Int { +func calculateL1GasUsed(data []byte, overhead *big.Int) *big.Int { zeroes, ones := zeroesAndOnes(data) zeroesGas := zeroes * params.TxDataZeroGas onesGas := (ones + txExtraDataBytes) * params.TxDataNonZeroGasEIP2028 @@ -173,18 +202,32 @@ func mulAndScale(x *big.Int, y *big.Int, precision *big.Int) *big.Int { return new(big.Int).Quo(z, precision) } -func CalculateL1DataFee(tx *types.Transaction, state StateDB) (*big.Int, error) { +func CalculateL1DataFee(tx *types.Transaction, state StateDB, config *params.ChainConfig, blockNumber *big.Int) (*big.Int, error) { if tx.IsL1MessageTx() { return big.NewInt(0), nil } - raw, err := rlpEncode(tx) + raw, err := tx.MarshalBinary() if err != nil { return nil, err } - l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) - l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) + gpoState := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) + + var l1DataFee *big.Int + + if !config.IsCurie(blockNumber) { + l1DataFee = calculateEncodedL1DataFee(raw, gpoState.overhead, gpoState.l1BaseFee, gpoState.scalar) + } else { + l1DataFee = calculateEncodedL1DataFeeCurie(raw, gpoState.l1BaseFee, gpoState.l1BlobBaseFee, gpoState.commitScalar, gpoState.blobScalar) + } + + // ensure l1DataFee fits into uint64 for circuit compatibility + // (note: in practice this value should never be this big) + if !l1DataFee.IsUint64() { + l1DataFee.SetUint64(math.MaxUint64) + } + return l1DataFee, nil } diff --git a/rollup/fees/rollup_fee_test.go b/rollup/fees/rollup_fee_test.go index 54b77eeb151c..b35d8cd9bf55 100644 --- a/rollup/fees/rollup_fee_test.go +++ b/rollup/fees/rollup_fee_test.go @@ -7,14 +7,27 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCalculateEncodedL1DataFee(t *testing.T) { +func TestL1DataFeeBeforeCurie(t *testing.T) { l1BaseFee := new(big.Int).SetUint64(15000000) - - data := []byte{0, 10, 1, 0} overhead := new(big.Int).SetUint64(100) scalar := new(big.Int).SetUint64(10) - expected := new(big.Int).SetUint64(184) // 184.2 + data := []byte{0, 10, 1, 0} + + expected := new(big.Int).SetUint64(30) // 30.6 actual := calculateEncodedL1DataFee(data, overhead, l1BaseFee, scalar) assert.Equal(t, expected, actual) } + +func TestL1DataFeeAfterCurie(t *testing.T) { + l1BaseFee := new(big.Int).SetUint64(1500000000) + l1BlobBaseFee := new(big.Int).SetUint64(150000000) + commitScalar := new(big.Int).SetUint64(10) + blobScalar := new(big.Int).SetUint64(10) + + data := []byte{0, 10, 1, 0} + + expected := new(big.Int).SetUint64(21) + actual := calculateEncodedL1DataFeeCurie(data, l1BaseFee, l1BlobBaseFee, commitScalar, blobScalar) + assert.Equal(t, expected, actual) +} diff --git a/rollup/rcfg/config.go b/rollup/rcfg/config.go index 5efdc64798d3..28be6b5bbdcc 100644 --- a/rollup/rcfg/config.go +++ b/rollup/rcfg/config.go @@ -34,4 +34,23 @@ var ( L1BaseFeeSlot = common.BigToHash(big.NewInt(1)) OverheadSlot = common.BigToHash(big.NewInt(2)) ScalarSlot = common.BigToHash(big.NewInt(3)) + + // New fields added in the Curie hard fork + L1BlobBaseFeeSlot = common.BigToHash(big.NewInt(5)) + CommitScalarSlot = common.BigToHash(big.NewInt(6)) + BlobScalarSlot = common.BigToHash(big.NewInt(7)) + IsCurieSlot = common.BigToHash(big.NewInt(8)) + + InitialCommitScalar = big.NewInt(230759955285) + InitialBlobScalar = big.NewInt(417565260) + + // CurieL1GasPriceOracleBytecode is the new (blob-enabled) gas price oracle after + // the Curie hard fork. Run these commands in the monorepo to verify this bytecode: + // + // git checkout f12e8e3bafc09bb1c38065dfb57ae1f0fbe24915 + // cd contracts + // yarn + // FOUNDRY_EVM_VERSION="cancun" forge build + // cat artifacts/src/L1GasPriceOracle.sol/L1GasPriceOracle.json | jq -r .deployedBytecode.object + CurieL1GasPriceOracleBytecode = common.Hex2Bytes("608060405234801561000f575f80fd5b5060043610610132575f3560e01c8063715018a6116100b4578063a911d77f11610079578063a911d77f1461024c578063bede39b514610254578063de26c4a114610267578063e88a60ad1461027a578063f2fde38b1461028d578063f45e65d8146102a0575f80fd5b8063715018a6146101eb57806384189161146101f35780638da5cb5b146101fc57806393e59dc114610226578063944b247f14610239575f80fd5b80633d0f963e116100fa5780633d0f963e146101a057806349948e0e146101b3578063519b4bd3146101c65780636a5e67e5146101cf57806370465597146101d8575f80fd5b80630c18c1621461013657806313dad5be1461015257806323e524ac1461016f5780633577afc51461017857806339455d3a1461018d575b5f80fd5b61013f60025481565b6040519081526020015b60405180910390f35b60085461015f9060ff1681565b6040519015158152602001610149565b61013f60065481565b61018b6101863660046109b3565b6102a9565b005b61018b61019b3660046109ca565b61033b565b61018b6101ae3660046109ea565b610438565b61013f6101c1366004610a2b565b6104bb565b61013f60015481565b61013f60075481565b61018b6101e63660046109b3565b6104e0565b61018b61056e565b61013f60055481565b5f5461020e906001600160a01b031681565b6040516001600160a01b039091168152602001610149565b60045461020e906001600160a01b031681565b61018b6102473660046109b3565b6105a2565b61018b61062e565b61018b6102623660046109b3565b61068a565b61013f610275366004610a2b565b610747565b61018b6102883660046109b3565b610764565b61018b61029b3660046109ea565b6107f0565b61013f60035481565b5f546001600160a01b031633146102db5760405162461bcd60e51b81526004016102d290610ad6565b60405180910390fd5b621c9c388111156102ff57604051635742c80560e11b815260040160405180910390fd5b60028190556040518181527f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4906020015b60405180910390a150565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa158015610382573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a69190610b0d565b6103c3576040516326b3506d60e11b815260040160405180910390fd5b600182905560058190556040518281527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c449060200160405180910390a16040518181527f9a14bfb5d18c4c3cf14cae19c23d7cf1bcede357ea40ca1f75cd49542c71c214906020015b60405180910390a15050565b5f546001600160a01b031633146104615760405162461bcd60e51b81526004016102d290610ad6565b600480546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f22d1c35fe072d2e42c3c8f9bd4a0d34aa84a0101d020a62517b33fdb3174e5f7910161042c565b6008545f9060ff16156104d7576104d18261087b565b92915050565b6104d1826108c1565b5f546001600160a01b031633146105095760405162461bcd60e51b81526004016102d290610ad6565b610519633b9aca006103e8610b40565b81111561053957604051631e44fdeb60e11b815260040160405180910390fd5b60038190556040518181527f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a90602001610330565b5f546001600160a01b031633146105975760405162461bcd60e51b81526004016102d290610ad6565b6105a05f610904565b565b5f546001600160a01b031633146105cb5760405162461bcd60e51b81526004016102d290610ad6565b6105d9633b9aca0080610b40565b8111156105f95760405163874f603160e01b815260040160405180910390fd5b60068190556040518181527f2ab3f5a4ebbcbf3c24f62f5454f52f10e1a8c9dcc5acac8f19199ce881a6a10890602001610330565b5f546001600160a01b031633146106575760405162461bcd60e51b81526004016102d290610ad6565b60085460ff161561067b576040516379f9c57560e01b815260040160405180910390fd5b6008805460ff19166001179055565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa1580156106d1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106f59190610b0d565b610712576040516326b3506d60e11b815260040160405180910390fd5b60018190556040518181527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c4490602001610330565b6008545f9060ff161561075b57505f919050565b6104d182610953565b5f546001600160a01b0316331461078d5760405162461bcd60e51b81526004016102d290610ad6565b61079b633b9aca0080610b40565b8111156107bb5760405163f37ec21560e01b815260040160405180910390fd5b60078190556040518181527f6b332a036d8c3ead57dcb06c87243bd7a2aed015ddf2d0528c2501dae56331aa90602001610330565b5f546001600160a01b031633146108195760405162461bcd60e51b81526004016102d290610ad6565b6001600160a01b03811661086f5760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f206164647265737300000060448201526064016102d2565b61087881610904565b50565b5f633b9aca0060055483516007546108939190610b40565b61089d9190610b40565b6001546006546108ad9190610b40565b6108b79190610b57565b6104d19190610b6a565b5f806108cc83610953565b90505f600154826108dd9190610b40565b9050633b9aca00600354826108f29190610b40565b6108fc9190610b6a565b949350505050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80515f908190815b818110156109a45784818151811061097557610975610b89565b01602001516001600160f81b0319165f036109955760048301925061099c565b6010830192505b60010161095b565b50506002540160400192915050565b5f602082840312156109c3575f80fd5b5035919050565b5f80604083850312156109db575f80fd5b50508035926020909101359150565b5f602082840312156109fa575f80fd5b81356001600160a01b0381168114610a10575f80fd5b9392505050565b634e487b7160e01b5f52604160045260245ffd5b5f60208284031215610a3b575f80fd5b813567ffffffffffffffff80821115610a52575f80fd5b818401915084601f830112610a65575f80fd5b813581811115610a7757610a77610a17565b604051601f8201601f19908116603f01168101908382118183101715610a9f57610a9f610a17565b81604052828152876020848701011115610ab7575f80fd5b826020860160208301375f928101602001929092525095945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b5f60208284031215610b1d575f80fd5b81518015158114610a10575f80fd5b634e487b7160e01b5f52601160045260245ffd5b80820281158282048414176104d1576104d1610b2c565b808201808211156104d1576104d1610b2c565b5f82610b8457634e487b7160e01b5f52601260045260245ffd5b500490565b634e487b7160e01b5f52603260045260245ffdfea26469706673582212200c2ac583f18be4f94ab169ae6f2ea3a708a7c0d4424746b120b177adb39e626064736f6c63430008180033") ) diff --git a/rollup/tracing/tracing.go b/rollup/tracing/tracing.go index 45c8b81db742..cab062310a49 100644 --- a/rollup/tracing/tracing.go +++ b/rollup/tracing/tracing.go @@ -220,7 +220,7 @@ func (env *TraceEnv) GetBlockTrace(block *types.Block) (*types.BlockTrace, error msg, _ := core.TransactionToMessage(tx, env.signer, block.BaseFee()) env.state.SetTxContext(tx.Hash(), i) vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{}) - l1DataFee, err := fees.CalculateL1DataFee(tx, env.state) + l1DataFee, err := fees.CalculateL1DataFee(tx, env.state, env.chainConfig, block.Number()) if err != nil { failed = err break @@ -330,7 +330,7 @@ func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.B state.SetTxContext(txctx.TxHash, txctx.TxIndex) // Computes the new state by applying the given message. - l1DataFee, err := fees.CalculateL1DataFee(tx, state) + l1DataFee, err := fees.CalculateL1DataFee(tx, state, env.chainConfig, block.Number()) if err != nil { return err } @@ -515,6 +515,10 @@ func (env *TraceEnv) fillBlockTrace(block *types.Block) (*types.BlockTrace, erro rcfg.L1BaseFeeSlot, rcfg.OverheadSlot, rcfg.ScalarSlot, + rcfg.L1BlobBaseFeeSlot, + rcfg.CommitScalarSlot, + rcfg.BlobScalarSlot, + rcfg.IsCurieSlot, }, } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 9556c628eb80..573fa028656a 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -290,7 +290,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh snapshot := statedb.Snapshot() gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) - l1DataFee, err := fees.CalculateL1DataFee(&ttx, statedb) + l1DataFee, err := fees.CalculateL1DataFee(&ttx, statedb, config, block.Number()) if err != nil { return nil, nil, nil, common.Hash{}, err }