From 26cf90abb38f4d3436dc080b3520fd1613404db9 Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Thu, 9 Oct 2025 20:50:16 +0200 Subject: [PATCH] all: Store DA footprint in blob gas used header field --- consensus/beacon/consensus.go | 10 ++++ consensus/misc/eip1559/eip1559.go | 26 +++++++--- consensus/misc/eip1559/eip1559_test.go | 69 ++++++++++++++++---------- consensus/misc/eip4844/eip4844.go | 16 +++--- core/block_validator.go | 19 ++++++- core/chain_makers.go | 8 --- core/state_processor.go | 8 --- core/types/block.go | 1 + core/types/rollup_cost.go | 21 ++++---- miner/miner_optimism_test.go | 52 ++++++++++++++----- miner/worker.go | 21 ++++---- 11 files changed, 158 insertions(+), 93 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index a2364eab16..366cce89fd 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -410,6 +410,16 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea } } + // Store DA footprint in BlobGasUsed header field if it hasn't already been set yet. + // Builder code may already calculate it during block building to avoid recalculating it here. + if chain.Config().IsDAFootprintBlockLimit(header.Time) && (header.BlobGasUsed == nil || *header.BlobGasUsed == 0) { + daFootprint, err := types.CalcDAFootprint(body.Transactions) + if err != nil { + return nil, fmt.Errorf("error calculating DA footprint: %w", err) + } + header.BlobGasUsed = &daFootprint + } + // Assemble the final block. block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil), chain.Config()) diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go index 373d6f83ef..eb5dfed657 100644 --- a/consensus/misc/eip1559/eip1559.go +++ b/consensus/misc/eip1559/eip1559.go @@ -36,7 +36,7 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade if !config.IsLondon(parent.Number) { parentGasLimit = parent.GasLimit * config.ElasticityMultiplier() } - if config.Optimism == nil { // gasLimit can adjust instantly in optimism + if !config.IsOptimism() { // OP Stack gasLimit can adjust instantly if err := misc.VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { return err } @@ -75,7 +75,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64) } // OPStack addition: calculate the base fee using the upstream code. - baseFee := calcBaseFeeInner(parent, elasticity, denominator) + baseFee := calcBaseFeeInner(config, parent, elasticity, denominator) // OPStack addition: enforce minimum base fee. // If the minimum base fee is 0, this has no effect. @@ -89,10 +89,20 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64) return baseFee } -func calcBaseFeeInner(parent *types.Header, elasticity uint64, denominator uint64) *big.Int { +func calcBaseFeeInner(config *params.ChainConfig, parent *types.Header, elasticity uint64, denominator uint64) *big.Int { parentGasTarget := parent.GasLimit / elasticity - // If the parent gasUsed is the same as the target, the baseFee remains unchanged. - if parent.GasUsed == parentGasTarget { + parentGasMetered := parent.GasUsed + if config.IsDAFootprintBlockLimit(parent.Time) { + if parent.BlobGasUsed == nil { + panic("Jovian parent block has nil BlobGasUsed") + } else if *parent.BlobGasUsed > parent.GasUsed { + // Jovian updates the base fee based on the maximum of total transactions gas used and total DA footprint (which is + // stored in the BlobGasUsed field of the header). + parentGasMetered = *parent.BlobGasUsed + } + } + // If the parent gasMetered is the same as the target, the baseFee remains unchanged. + if parentGasMetered == parentGasTarget { return new(big.Int).Set(parent.BaseFee) } @@ -101,10 +111,10 @@ func calcBaseFeeInner(parent *types.Header, elasticity uint64, denominator uint6 denom = new(big.Int) ) - if parent.GasUsed > parentGasTarget { + if parentGasMetered > parentGasTarget { // If the parent block used more gas than its target, the baseFee should increase. // max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) - num.SetUint64(parent.GasUsed - parentGasTarget) + num.SetUint64(parentGasMetered - parentGasTarget) num.Mul(num, parent.BaseFee) num.Div(num, denom.SetUint64(parentGasTarget)) num.Div(num, denom.SetUint64(denominator)) @@ -115,7 +125,7 @@ func calcBaseFeeInner(parent *types.Header, elasticity uint64, denominator uint6 } else { // Otherwise if the parent block used less gas than its target, the baseFee should decrease. // max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) - num.SetUint64(parentGasTarget - parent.GasUsed) + num.SetUint64(parentGasTarget - parentGasMetered) num.Mul(num, parent.BaseFee) num.Div(num, denom.SetUint64(parentGasTarget)) num.Div(num, denom.SetUint64(denominator)) diff --git a/consensus/misc/eip1559/eip1559_test.go b/consensus/misc/eip1559/eip1559_test.go index b8139f963b..9e86341099 100644 --- a/consensus/misc/eip1559/eip1559_test.go +++ b/consensus/misc/eip1559/eip1559_test.go @@ -57,17 +57,19 @@ func config() *params.ChainConfig { return config } -var TestCanyonTime = uint64(10) -var TestHoloceneTime = uint64(12) -var TestJovianTime = uint64(14) +var ( + testCanyonTime = uint64(10) + testHoloceneTime = uint64(12) + testJovianTime = uint64(14) +) func opConfig() *params.ChainConfig { config := copyConfig(params.TestChainConfig) config.LondonBlock = big.NewInt(5) eip1559DenominatorCanyon := uint64(250) - config.CanyonTime = &TestCanyonTime - config.HoloceneTime = &TestHoloceneTime - config.JovianTime = &TestJovianTime + config.CanyonTime = &testCanyonTime + config.HoloceneTime = &testHoloceneTime + config.JovianTime = &testJovianTime config.Optimism = ¶ms.OptimismConfig{ EIP1559Elasticity: 6, EIP1559Denominator: 50, @@ -227,59 +229,74 @@ func TestCalcBaseFeeOptimismHolocene(t *testing.T) { // TestCalcBaseFeeJovian tests that the minimum base fee is enforced // when the computed base fee is less than the minimum base fee, // if the feature is active and not enforced otherwise. +// It also tests that the base fee udpate will take the DA footprint as stored +// in the blob gas used field into account if it is larger than the gas used +// field. func TestCalcBaseFeeJovian(t *testing.T) { parentGasLimit := uint64(30_000_000) denom := uint64(50) elasticity := uint64(3) + parentGasTarget := parentGasLimit / elasticity + const zeroParentBlobGasUsed = 0 - preJovian := TestJovianTime - 1 - postJovian := TestJovianTime + preJovian := testJovianTime - 1 + postJovian := testJovianTime tests := []struct { - parentBaseFee int64 - parentGasUsed uint64 - parentTime uint64 - minBaseFee uint64 - expectedBaseFee uint64 + parentBaseFee int64 + parentGasUsed uint64 + parentBlobGasUsed uint64 + parentTime uint64 + minBaseFee uint64 + expectedBaseFee uint64 }{ // Test 0: gas used is below target, and the new calculated base fee is very low. // But since we are pre Jovian, we don't enforce the minBaseFee. - {1, parentGasLimit/elasticity - 1_000_000, preJovian, 1e9, 1}, + {1, parentGasTarget - 1_000_000, zeroParentBlobGasUsed, preJovian, 1e9, 1}, // Test 1: gas used is exactly the target gas, but the base fee is set too low so // the base fee is expected to be the minBaseFee - {1, parentGasLimit / elasticity, postJovian, 1e9, 1e9}, + {1, parentGasTarget, zeroParentBlobGasUsed, postJovian, 1e9, 1e9}, // Test 2: gas used exceeds gas target, but the new calculated base fee is still // too low so the base fee is expected to be the minBaseFee - {1, parentGasLimit/elasticity + 1_000_000, postJovian, 1e9, 1e9}, + {1, parentGasTarget + 1_000_000, zeroParentBlobGasUsed, postJovian, 1e9, 1e9}, // Test 3: gas used exceeds gas target, but the new calculated base fee is higher // than the minBaseFee, so don't enforce minBaseFee. See the calculation below: // gasUsedDelta = gasUsed - parentGasTarget = 20_000_000 - 30_000_000 / 3 = 10_000_000 // 2e9 * 10_000_000 / 10_000_000 / 50 = 40_000_000 // 2e9 + 40_000_000 = 2_040_000_000, which is greater than minBaseFee - {2e9, parentGasLimit/elasticity + 10_000_000, postJovian, 1e9, 2_040_000_000}, + {2e9, parentGasTarget + 10_000_000, zeroParentBlobGasUsed, postJovian, 1e9, 2_040_000_000}, // Test 4: gas used is below target, but the new calculated base fee is still // too low so the base fee is expected to be the minBaseFee - {1, parentGasLimit/elasticity - 1_000_000, postJovian, 1e9, 1e9}, + {1, parentGasTarget - 1_000_000, zeroParentBlobGasUsed, postJovian, 1e9, 1e9}, // Test 5: gas used is below target, and the new calculated base fee is higher // than the minBaseFee, so don't enforce minBaseFee. See the calculation below: // gasUsedDelta = gasUsed - parentGasTarget = 9_000_000 - 30_000_000 / 3 = -1_000_000 // 2_097_152 * -1_000_000 / 10_000_000 / 50 = -4194.304 // 2_097_152 - 4194.304 = 2_092_957.696, which is greater than minBaseFee - {2_097_152, parentGasLimit/elasticity - 1_000_000, postJovian, 2e6, 2_092_958}, + {2_097_152, parentGasTarget - 1_000_000, zeroParentBlobGasUsed, postJovian, 2e6, 2_092_958}, // Test 6: parent base fee already at minimum, below target => no change - {1e4, parentGasLimit/elasticity - 1, postJovian, 1e4, 1e4}, + {1e4, parentGasTarget - 1, zeroParentBlobGasUsed, postJovian, 1e4, 1e4}, // Test 7: parent base fee already at minimum, above target => small increase as usual - {1e4, parentGasLimit/elasticity + 1, postJovian, 1e4, 1e4 + 1}, + {1e4, parentGasTarget + 1, zeroParentBlobGasUsed, postJovian, 1e4, 1e4 + 1}, + + // Test 8: Pre-Jovian: parent base fee already at minimum, gas used at target, blob gas used at limit + // => no increase, minBaseFee ignored, high blob gas used ignored + {1e4, parentGasTarget, parentGasLimit, preJovian, 1e6, 1e4}, + // Test 9: parent base fee already at minimum, gas used at target, da footprint above target => small increase + {1e4, parentGasTarget, parentGasTarget + 1, postJovian, 1e4, 1e4 + 1}, + // Test 10: Test 3, but with high blob gas used instead of gas used + {2e9, parentGasTarget, parentGasTarget + 10_000_000, postJovian, 1e9, 2_040_000_000}, } for i, test := range tests { testName := fmt.Sprintf("test %d", i) t.Run(testName, func(t *testing.T) { parent := &types.Header{ - Number: common.Big32, - GasLimit: parentGasLimit, - GasUsed: test.parentGasUsed, - BaseFee: big.NewInt(test.parentBaseFee), - Time: test.parentTime, + Number: common.Big32, + GasLimit: parentGasLimit, + GasUsed: test.parentGasUsed, + BlobGasUsed: &test.parentBlobGasUsed, + BaseFee: big.NewInt(test.parentBaseFee), + Time: test.parentTime, } parent.Extra = EncodeOptimismExtraData(opConfig(), test.parentTime, denom, elasticity, &test.minBaseFee) have, want := CalcBaseFee(opConfig(), parent, parent.Time+2), big.NewInt(int64(test.expectedBaseFee)) diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index 0b0941390a..a7df6f2cbc 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -110,12 +110,16 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade return errors.New("header is missing blobGasUsed") } - // Verify that the blob gas used remains within reasonable limits. - if !config.IsOptimism() && *header.BlobGasUsed > bcfg.maxBlobGas() { - return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas()) - } - if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 { - return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob) + // OP Stack sets a zero blobGasUsed pre-Jovian. Post-Jovian, it stores the DA footprint, which is + // probably not a multiple of [params.BlobTxBlobGasPerBlob]. + if !config.IsOptimism() { + // Verify that the blob gas used remains within reasonable limits. + if *header.BlobGasUsed > bcfg.maxBlobGas() { + return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas()) + } + if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 { + return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob) + } } // Verify the excessBlobGas is correct based on the parent header diff --git a/core/block_validator.go b/core/block_validator.go index 33987fa369..05c03235ed 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -106,7 +106,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { } // Check blob gas usage. - if header.BlobGasUsed != nil { + if !v.config.IsOptimism() && header.BlobGasUsed != nil { if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated return fmt.Errorf("blob gas used mismatch (header %v, calculated %v)", *header.BlobGasUsed, blobs*params.BlobTxBlobGasPerBlob) } @@ -116,6 +116,23 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { } } + // OP Stack Jovian DA footprint block limit. + if v.config.IsDAFootprintBlockLimit(header.Time) { + if header.BlobGasUsed == nil { + return errors.New("nil blob gas used in post-Jovian block header, should store DA footprint") + } + blobGasUsed := *header.BlobGasUsed + daFootprint, err := types.CalcDAFootprint(block.Transactions()) + if err != nil { + return fmt.Errorf("failed to calculate DA footprint: %w", err) + } else if blobGasUsed != daFootprint { + return fmt.Errorf("invalid DA footprint in blobGasUsed field (remote: %d local: %d)", blobGasUsed, daFootprint) + } + if daFootprint > block.GasLimit() { + return fmt.Errorf("DA footprint %d exceeds block gas limit %d", daFootprint, block.GasLimit()) + } + } + // Ancestor block must be known. if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { diff --git a/core/chain_makers.go b/core/chain_makers.go index f4adb78504..4a64830ef8 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -421,14 +421,6 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse b.header.RequestsHash = &reqHash } - if config.IsDAFootprintBlockLimit(b.header.Time) { - gasUsed, err := types.CalcGasUsedJovian(b.txs, b.header.GasUsed) - if err != nil { - panic(err) - } - b.header.GasUsed = gasUsed - } - body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals} block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts) if err != nil { diff --git a/core/state_processor.go b/core/state_processor.go index 94124e845e..c29d0c24de 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -129,14 +129,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg requests = [][]byte{} } - if p.config.IsDAFootprintBlockLimit(block.Time()) { - gasUsed, err := types.CalcGasUsedJovian(block.Transactions(), *usedGas) - if err != nil { - return nil, fmt.Errorf("failed to calculate Jovian gas used: %w", err) - } - *usedGas = gasUsed - } - // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body()) diff --git a/core/types/block.go b/core/types/block.go index 31d7c6a9d8..1a3f0f1773 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -96,6 +96,7 @@ type Header struct { WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers. + // OP Stack stores the DA footprint in this field starting with the Jovian fork. BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. diff --git a/core/types/rollup_cost.go b/core/types/rollup_cost.go index 0c628d8ace..ffbdd2098f 100644 --- a/core/types/rollup_cost.go +++ b/core/types/rollup_cost.go @@ -556,10 +556,11 @@ func ExtractDAFootprintGasScalar(data []byte) (uint16, error) { return daFootprintGasScalar, nil } -// CalcGasUsedJovian calculates the gas used for an OP Stack chain. -// Jovian introduces a DA footprint block limit, which potentially increases the gasUsed. -// CalcGasUsedJovian must not be called for pre-Jovian blocks. -func CalcGasUsedJovian(txs []*Transaction, evmGasUsed uint64) (uint64, error) { +// CalcDAFootprint calculates the total DA footprint of a block for an OP Stack chain. +// Jovian introduces a DA footprint block limit which is stored in the BlobGasUsed header field and that is taken +// into account during base fee updates. +// CalcDAFootprint must not be called for pre-Jovian blocks. +func CalcDAFootprint(txs []*Transaction) (uint64, error) { if len(txs) == 0 || !txs[0].IsDepositTx() { return 0, errors.New("missing deposit transaction") } @@ -572,25 +573,21 @@ func CalcGasUsedJovian(txs []*Transaction, evmGasUsed uint64) (uint64, error) { // sufficient to check last transaction because deposits precede non-deposit txs return 0, errors.New("unexpected non-deposit transactions in Jovian activation block") } - return evmGasUsed, nil + return 0, nil } // ExtractDAFootprintGasScalar catches all invalid lengths daFootprintGasScalar, err := ExtractDAFootprintGasScalar(data) if err != nil { return 0, err } - var cumulativeDAFootprint uint64 + var daFootprint uint64 for _, tx := range txs { if tx.IsDepositTx() { continue } - cumulativeDAFootprint += tx.RollupCostData().EstimatedDASize().Uint64() + daFootprint += tx.RollupCostData().EstimatedDASize().Uint64() * uint64(daFootprintGasScalar) } - daFootprint := uint64(daFootprintGasScalar) * cumulativeDAFootprint - if evmGasUsed < daFootprint { - return daFootprint, nil - } - return evmGasUsed, nil + return daFootprint, nil } // L1Cost computes the the data availability fee for transactions in blocks prior to the Ecotone diff --git a/miner/miner_optimism_test.go b/miner/miner_optimism_test.go index ce1d59513b..4be405f159 100644 --- a/miner/miner_optimism_test.go +++ b/miner/miner_optimism_test.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -21,15 +22,16 @@ const testDAFootprintGasScalar = 400 // transactions and then imports the block into the chain, asserting that // execution succeeds. func TestDAFootprintMining(t *testing.T) { - requireTxGas := func(t *testing.T, block *types.Block, receipts []*types.Receipt) { + requirePreJovianBehavior := func(t *testing.T, block *types.Block, receipts []*types.Receipt) { var txGas uint64 for _, receipt := range receipts { txGas += receipt.GasUsed } require.Equal(t, txGas, block.GasUsed(), "total tx gas used should be equal to block gas used") + require.Zero(t, *block.Header().BlobGasUsed, "expected 0 blob gas used") } - requireDAFootprint := func(t *testing.T, block *types.Block, receipts []*types.Receipt) { + requireLargeDAFootprintBehavior := func(t *testing.T, block *types.Block, receipts []*types.Receipt) { var ( txGas uint64 daFootprint uint64 @@ -45,30 +47,56 @@ func TestDAFootprintMining(t *testing.T) { } daFootprint += txs[i].RollupCostData().EstimatedDASize().Uint64() * testDAFootprintGasScalar } - require.Less(t, txGas, block.GasUsed(), "total tx gas used must be smaller than block gas used") - require.Equal(t, daFootprint, block.GasUsed(), "total DA footprint used should be equal to block gas used") + require.Equal(t, txGas, block.GasUsed(), "total tx gas used should be equal to block gas used") + require.Greater(t, daFootprint, block.GasUsed(), "total DA footprint used should be greater than block gas used") + require.LessOrEqual(t, daFootprint, block.GasLimit(), "total DA footprint used should be less or equal block gas limit") } + t.Run("jovian-one-min-tx", func(t *testing.T) { + testMineAndExecute(t, 0, jovianConfig(), func(t *testing.T, _ *core.BlockChain, block *types.Block, receipts []*types.Receipt) { + require.Len(t, receipts, 2) // 1 test pending tx and 1 deposit tx + requireLargeDAFootprintBehavior(t, block, receipts) + + // Double-confirm DA footprint calculation manually in this simple transaction case. + daFootprint, err := types.CalcDAFootprint(block.Transactions()) + require.NoError(t, err, "failed to calculate DA footprint") + require.Equal(t, daFootprint, *block.Header().BlobGasUsed, + "header blob gas used should match calculated DA footprint") + require.Equal(t, testDAFootprintGasScalar*types.MinTransactionSize.Uint64(), daFootprint, + "simple pending transaction should lead to min DA footprint") + }) + }) t.Run("jovian-at-limit", func(t *testing.T) { - testMineAndExecute(t, 17, jovianConfig(), func(t *testing.T, block *types.Block, receipts []*types.Receipt) { + testMineAndExecute(t, 17, jovianConfig(), func(t *testing.T, _ *core.BlockChain, block *types.Block, receipts []*types.Receipt) { require.Len(t, receipts, 19) // including 1 test pending tx and 1 deposit tx - requireDAFootprint(t, block, receipts) + requireLargeDAFootprintBehavior(t, block, receipts) }) }) t.Run("jovian-above-limit", func(t *testing.T) { - testMineAndExecute(t, 18, jovianConfig(), func(t *testing.T, block *types.Block, receipts []*types.Receipt) { + testMineAndExecute(t, 18, jovianConfig(), func(t *testing.T, _ *core.BlockChain, block *types.Block, receipts []*types.Receipt) { require.Len(t, receipts, 19) // same as for 17, because 18th tx from pool shouldn't have been included - requireDAFootprint(t, block, receipts) + requireLargeDAFootprintBehavior(t, block, receipts) }) }) t.Run("isthmus", func(t *testing.T) { - testMineAndExecute(t, 39, isthmusConfig(), func(t *testing.T, block *types.Block, receipts []*types.Receipt) { + testMineAndExecute(t, 39, isthmusConfig(), func(t *testing.T, _ *core.BlockChain, block *types.Block, receipts []*types.Receipt) { require.Len(t, receipts, 41) // including 1 test pending tx and 1 deposit tx - requireTxGas(t, block, receipts) + requirePreJovianBehavior(t, block, receipts) + }) + }) + + t.Run("jovian-invalid-blobGasUsed", func(t *testing.T) { + testMineAndExecute(t, 0, jovianConfig(), func(t *testing.T, bc *core.BlockChain, block *types.Block, receipts []*types.Receipt) { + require.Len(t, receipts, 2) // 1 test pending tx and 1 deposit tx + header := block.Header() + *header.BlobGasUsed += 1 // invalidate blobGasUsed + invalidBlock := block.WithSeal(header) + _, err := bc.InsertChain(types.Blocks{invalidBlock}) + require.ErrorContains(t, err, "invalid DA footprint in blobGasUsed field (remote: 40001 local: 40000)") }) }) } -func testMineAndExecute(t *testing.T, numTxs uint64, cfg *params.ChainConfig, assertFn func(t *testing.T, block *types.Block, receipts []*types.Receipt)) { +func testMineAndExecute(t *testing.T, numTxs uint64, cfg *params.ChainConfig, assertFn func(*testing.T, *core.BlockChain, *types.Block, []*types.Receipt)) { db := rawdb.NewMemoryDatabase() w, b := newTestWorker(t, cfg, beacon.New(ethash.NewFaker()), db, 0) @@ -105,7 +133,7 @@ func testMineAndExecute(t *testing.T, numTxs uint64, cfg *params.ChainConfig, as require.NoError(t, r.err, "block generation failed") require.NotNil(t, r.block, "no block generated") - assertFn(t, r.block, r.receipts) + assertFn(t, b.chain, r.block, r.receipts) // Import the block into the chain, which executes it via StateProcessor. _, err := b.chain.InsertChain(types.Blocks{r.block}) diff --git a/miner/worker.go b/miner/worker.go index b8729588f7..2bab122a20 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -76,7 +76,6 @@ type environment struct { // OP-Stack addition: DA footprint block limit daFootprintGasScalar uint16 - daFootprint uint64 header *types.Header txs []*types.Transaction @@ -202,11 +201,6 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay } } - // OP-Stack addition: Jovian maxes the block.gasUsed with the calldata footprint - if miner.chainConfig.IsDAFootprintBlockLimit(work.header.Time) && work.daFootprint > work.header.GasUsed { - work.header.GasUsed = work.daFootprint - } - body := types.Body{Transactions: work.txs, Withdrawals: genParam.withdrawals} if intr := genParam.interrupt; intr != nil && genParam.isUpdate && intr.Load() != commitInterruptNone { @@ -528,11 +522,14 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran break } - daFootprintLeft := gasLimit - env.daFootprint - // If we don't have enough DA space for any further transactions then we're done. - if isJovian && daFootprintLeft < minTransactionDAFootprint { - log.Debug("Not enough DA space for further transactions", "have", daFootprintLeft, "want", minTransactionDAFootprint) - break + var daFootprintLeft uint64 + if isJovian { + daFootprintLeft = gasLimit - *env.header.BlobGasUsed + // If we don't have enough DA space for any further transactions then we're done. + if daFootprintLeft < minTransactionDAFootprint { + log.Debug("Not enough DA space for further transactions", "have", daFootprintLeft, "want", minTransactionDAFootprint) + break + } } // If we don't have enough blob space for any further blob transactions, @@ -670,7 +667,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran // Everything ok, collect the logs and shift in the next transaction from the same account blockDABytes = daBytesAfter if isJovian { - env.daFootprint += txDAFootprint + *env.header.BlobGasUsed += txDAFootprint } txs.Shift()