From d11ef8b62472edc6574a64798c849465a75cbe85 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 13:41:48 +0200 Subject: [PATCH 01/25] core,miner,params: implement EIP-7934 - RLP Execution Block Size Limit --- core/block_validator.go | 3 +++ core/error.go | 4 ++++ miner/worker.go | 12 ++++++++++++ params/protocol_params.go | 2 ++ 4 files changed, 21 insertions(+) diff --git a/core/block_validator.go b/core/block_validator.go index 591e472bc1b0..a25fb7e69e37 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -49,6 +49,9 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *Bloc // header's transaction and uncle roots. The headers are assumed to be already // validated at this point. func (v *BlockValidator) ValidateBody(block *types.Block) error { + if v.config.IsOsaka(block.Number(), block.Time()) && block.Size() > params.BlockRLPSizeCap { + return ErrBlockOversized + } // Check whether the block is already imported. if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) { return ErrKnownBlock diff --git a/core/error.go b/core/error.go index de95e6463620..e50780bf1b48 100644 --- a/core/error.go +++ b/core/error.go @@ -28,6 +28,10 @@ var ( // ErrNoGenesis is returned when there is no Genesis Block. ErrNoGenesis = errors.New("genesis not found in chain") + + // ErrBlockOversized is returned if the size of the RLP-encoded block + // exceeds the cap established by EIP 7934 + ErrBlockOversized = errors.New("block RLP-encoded size exceeds maximum") ) // List of evm-call-message pre-checking errors. All state transition messages will diff --git a/miner/worker.go b/miner/worker.go index 198745ad2785..09cd94aa4650 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -19,6 +19,7 @@ package miner import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/trie" "math/big" "sync/atomic" "time" @@ -94,6 +95,13 @@ type generateParams struct { noTxs bool // Flag whether an empty block without any transaction is expected } +func (env *environment) encodedSizeWithTxAndReceipt(tx *types.Transaction) uint64 { + body := types.Body{Transactions: append(env.txs, tx), Withdrawals: make([]*types.Withdrawal, 0)} + env.header.RequestsHash = &common.Hash{} + block := types.NewBlock(env.header, &body, env.receipts, trie.NewStackTrie(nil)) + return block.Size() +} + // generateWork generates a sealing block based on the given parameters. func (miner *Miner) generateWork(params *generateParams, witness bool) *newPayloadResult { work, err := miner.prepareWork(params, witness) @@ -391,6 +399,10 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran continue } + if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && env.encodedSizeWithTxAndReceipt(tx) > params.BlockRLPSizeCap { + break + } + // Make sure all transactions after osaka have cell proofs if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) { if sidecar := tx.BlobTxSidecar(); sidecar != nil { diff --git a/params/protocol_params.go b/params/protocol_params.go index 3676a7bdf106..029a52c5f36d 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -178,6 +178,8 @@ const ( BlobBaseCost = 1 << 13 // Base execution gas cost for a blob. HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. + + BlockRLPSizeCap = 9_961_472 // maximum size of an RLP-encoded block ) // Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation From bbb603a18960303cd2ffc86b6df5eee610efd9e3 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 13:47:58 +0200 Subject: [PATCH 02/25] rename func --- miner/worker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 09cd94aa4650..16a3b9e81a7e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -95,7 +95,7 @@ type generateParams struct { noTxs bool // Flag whether an empty block without any transaction is expected } -func (env *environment) encodedSizeWithTxAndReceipt(tx *types.Transaction) uint64 { +func (env *environment) encodedSizeWithTx(tx *types.Transaction) uint64 { body := types.Body{Transactions: append(env.txs, tx), Withdrawals: make([]*types.Withdrawal, 0)} env.header.RequestsHash = &common.Hash{} block := types.NewBlock(env.header, &body, env.receipts, trie.NewStackTrie(nil)) @@ -399,7 +399,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran continue } - if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && env.encodedSizeWithTxAndReceipt(tx) > params.BlockRLPSizeCap { + if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && env.encodedSizeWithTx(tx) > params.BlockRLPSizeCap { break } From 6403291ba4cdd3e6644242a9c7c45e6350919b4f Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 13:54:10 +0200 Subject: [PATCH 03/25] goimports --- miner/worker.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/miner/worker.go b/miner/worker.go index 16a3b9e81a7e..6e1a7fe3051f 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -19,11 +19,12 @@ package miner import ( "errors" "fmt" - "github.com/ethereum/go-ethereum/trie" "math/big" "sync/atomic" "time" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" From c14d3318a79d5eb0ffcbd75c9cc7ba4a58855e3f Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 15:31:15 +0200 Subject: [PATCH 04/25] simplification --- miner/worker.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/miner/worker.go b/miner/worker.go index 6e1a7fe3051f..87b6f9d734da 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -52,6 +52,7 @@ type environment struct { signer types.Signer state *state.StateDB // apply state changes here tcount int // tx count in cycle + size uint64 // size of the block we are building gasPool *core.GasPool // available gas used to pack transactions coinbase common.Address evm *vm.EVM @@ -265,6 +266,7 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase return &environment{ signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time), state: state, + size: uint64(header.Size()), coinbase: coinbase, header: header, witness: state.Witness(), @@ -282,6 +284,7 @@ func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) e } env.txs = append(env.txs, tx) env.receipts = append(env.receipts, receipt) + env.size += tx.Size() env.tcount++ return nil } @@ -400,6 +403,10 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran continue } + if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && env.size+tx.Size() > params.BlockRLPSizeCap { + break + } + if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && env.encodedSizeWithTx(tx) > params.BlockRLPSizeCap { break } From 84fdfe112f4182ccf14e6829ad8825b686aaa215 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 15:32:09 +0200 Subject: [PATCH 05/25] remove outdated change --- core/error.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/error.go b/core/error.go index e50780bf1b48..de95e6463620 100644 --- a/core/error.go +++ b/core/error.go @@ -28,10 +28,6 @@ var ( // ErrNoGenesis is returned when there is no Genesis Block. ErrNoGenesis = errors.New("genesis not found in chain") - - // ErrBlockOversized is returned if the size of the RLP-encoded block - // exceeds the cap established by EIP 7934 - ErrBlockOversized = errors.New("block RLP-encoded size exceeds maximum") ) // List of evm-call-message pre-checking errors. All state transition messages will From e38f7767c97359d31f63b76a311563299f4c896d Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 15:33:35 +0200 Subject: [PATCH 06/25] more updates --- miner/worker.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 87b6f9d734da..506e8960fd8b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -23,8 +23,6 @@ import ( "sync/atomic" "time" - "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" @@ -97,13 +95,6 @@ type generateParams struct { noTxs bool // Flag whether an empty block without any transaction is expected } -func (env *environment) encodedSizeWithTx(tx *types.Transaction) uint64 { - body := types.Body{Transactions: append(env.txs, tx), Withdrawals: make([]*types.Withdrawal, 0)} - env.header.RequestsHash = &common.Hash{} - block := types.NewBlock(env.header, &body, env.receipts, trie.NewStackTrie(nil)) - return block.Size() -} - // generateWork generates a sealing block based on the given parameters. func (miner *Miner) generateWork(params *generateParams, witness bool) *newPayloadResult { work, err := miner.prepareWork(params, witness) @@ -407,10 +398,6 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran break } - if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && env.encodedSizeWithTx(tx) > params.BlockRLPSizeCap { - break - } - // Make sure all transactions after osaka have cell proofs if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) { if sidecar := tx.BlobTxSidecar(); sidecar != nil { From 568b9ac62f7ddb22eee46b9f08b213e922c14b4c Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 15:38:32 +0200 Subject: [PATCH 07/25] add back some changes that were deleted --- core/error.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/error.go b/core/error.go index de95e6463620..e50780bf1b48 100644 --- a/core/error.go +++ b/core/error.go @@ -28,6 +28,10 @@ var ( // ErrNoGenesis is returned when there is no Genesis Block. ErrNoGenesis = errors.New("genesis not found in chain") + + // ErrBlockOversized is returned if the size of the RLP-encoded block + // exceeds the cap established by EIP 7934 + ErrBlockOversized = errors.New("block RLP-encoded size exceeds maximum") ) // List of evm-call-message pre-checking errors. All state transition messages will From b6e8755884841c68dc72895bb764bf394dea824e Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 18:21:28 +0200 Subject: [PATCH 08/25] fix withdrawal inclusion --- miner/worker.go | 30 ++++++++++++++++++++++++++---- params/protocol_params.go | 1 + 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 506e8960fd8b..118931135e2b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -96,12 +96,12 @@ type generateParams struct { } // generateWork generates a sealing block based on the given parameters. -func (miner *Miner) generateWork(params *generateParams, witness bool) *newPayloadResult { - work, err := miner.prepareWork(params, witness) +func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPayloadResult { + work, err := miner.prepareWork(genParam, witness) if err != nil { return &newPayloadResult{err: err} } - if !params.noTxs { + if !genParam.noTxs { interrupt := new(atomic.Int32) timer := time.AfterFunc(miner.config.Recommit, func() { interrupt.Store(commitInterruptTimeout) @@ -114,7 +114,29 @@ func (miner *Miner) generateWork(params *generateParams, witness bool) *newPaylo } } - body := types.Body{Transactions: work.txs, Withdrawals: params.withdrawals} + body := types.Body{Transactions: work.txs} + + var includedWithdrawals types.Withdrawals + if miner.chainConfig.IsOsaka(work.header.Number, work.header.Time) { + // cap the size of blocks we will produce below the cap allowed by + // EIP-7934. This gives us buffer room if the estimated size of the + // block we are building is somewhat off. + maxBlockSize := params.BlockRLPSizeCap - 1_000_000 + + for _, withdrawal := range genParam.withdrawals { + if int(work.size)+params.WithdrawalSize > maxBlockSize { + break + } + + includedWithdrawals = append(includedWithdrawals, withdrawal) + work.size += params.WithdrawalSize + } + + } else { + includedWithdrawals = genParam.withdrawals + } + body.Withdrawals = includedWithdrawals + allLogs := make([]*types.Log, 0) for _, r := range work.receipts { allLogs = append(allLogs, r.Logs...) diff --git a/params/protocol_params.go b/params/protocol_params.go index 029a52c5f36d..a11367a7ed8c 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -179,6 +179,7 @@ const ( HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. + WithdrawalSize = 23 // size of a withdrawal BlockRLPSizeCap = 9_961_472 // maximum size of an RLP-encoded block ) From 525f5607e894a8a85d0adc564d472b10ba12fcaf Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 18:25:09 +0200 Subject: [PATCH 09/25] fix lint --- miner/worker.go | 1 - 1 file changed, 1 deletion(-) diff --git a/miner/worker.go b/miner/worker.go index 118931135e2b..8f476f258f77 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -131,7 +131,6 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay includedWithdrawals = append(includedWithdrawals, withdrawal) work.size += params.WithdrawalSize } - } else { includedWithdrawals = genParam.withdrawals } From 799a5b1c7639908ece9053fe23113e5a60cb4702 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 19:52:04 +0200 Subject: [PATCH 10/25] add comment for size check in body validation --- core/block_validator.go | 1 + miner/worker.go | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/block_validator.go b/core/block_validator.go index a25fb7e69e37..d4b76e02f810 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -49,6 +49,7 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *Bloc // header's transaction and uncle roots. The headers are assumed to be already // validated at this point. func (v *BlockValidator) ValidateBody(block *types.Block) error { + // check EIP 7934 RLP-encoded block size cap if v.config.IsOsaka(block.Number(), block.Time()) && block.Size() > params.BlockRLPSizeCap { return ErrBlockOversized } diff --git a/miner/worker.go b/miner/worker.go index 8f476f258f77..86eec6310983 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -127,7 +127,6 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay if int(work.size)+params.WithdrawalSize > maxBlockSize { break } - includedWithdrawals = append(includedWithdrawals, withdrawal) work.size += params.WithdrawalSize } From 199faa76787ede2b2c9b11c8501fd6cf98c7dce5 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 9 Jun 2025 20:00:10 +0200 Subject: [PATCH 11/25] make sure that we respect the buffer when adding transactions. comments and constant deduplication. --- miner/worker.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 86eec6310983..1af174b465bf 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -69,6 +69,11 @@ const ( commitInterruptNewHead commitInterruptResubmit commitInterruptTimeout + + // cap the size of blocks we will produce below the max allowed by + // EIP-7934. This gives us buffer room if the estimated size of the + // block we are building is off from the actual encoded size. + blockRLPSizeCapBuffer = 1_000_000 ) // newPayloadResult is the result of payload generation. @@ -118,10 +123,7 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay var includedWithdrawals types.Withdrawals if miner.chainConfig.IsOsaka(work.header.Number, work.header.Time) { - // cap the size of blocks we will produce below the cap allowed by - // EIP-7934. This gives us buffer room if the estimated size of the - // block we are building is somewhat off. - maxBlockSize := params.BlockRLPSizeCap - 1_000_000 + maxBlockSize := params.BlockRLPSizeCap - blockRLPSizeCapBuffer for _, withdrawal := range genParam.withdrawals { if int(work.size)+params.WithdrawalSize > maxBlockSize { @@ -414,7 +416,9 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran continue } - if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && env.size+tx.Size() > params.BlockRLPSizeCap { + // if inclusion of the transaction would put the block size over the + // maximum we allow, don't add any more txs to the payload. + if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && env.size+tx.Size() > params.BlockRLPSizeCap-blockRLPSizeCapBuffer { break } From 0293afe1189c93e21b29c8be45f53734efeb0e84 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 10 Jun 2025 15:20:11 +0200 Subject: [PATCH 12/25] ensure that all requested withdrawals are included in the block even if we would hit the size cap when filling it with txs --- miner/worker.go | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 1af174b465bf..d945752f7466 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -106,22 +106,16 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay if err != nil { return &newPayloadResult{err: err} } - if !genParam.noTxs { - interrupt := new(atomic.Int32) - timer := time.AfterFunc(miner.config.Recommit, func() { - interrupt.Store(commitInterruptTimeout) - }) - defer timer.Stop() - - err := miner.fillTransactions(interrupt, work) - if errors.Is(err, errBlockInterruptedByTimeout) { - log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(miner.config.Recommit)) - } - } - - body := types.Body{Transactions: work.txs} - var includedWithdrawals types.Withdrawals + + // If we are post-osaka, incorporate the requested withdrawals into the + // block size up-front to ensure that all requested withdrawals can be + // included even if we hit the size cap when filling the block with txs. + // + // Also, ensure that including all requested withdrawals wouldn't bring us + // over the block size cap limit. The withdrawal cap ensures that this can't + // actually happen right now, but it doesn't hurt to make this code + // future-proof for a situation where the withdrawal cap is lifted. if miner.chainConfig.IsOsaka(work.header.Number, work.header.Time) { maxBlockSize := params.BlockRLPSizeCap - blockRLPSizeCapBuffer @@ -129,13 +123,26 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay if int(work.size)+params.WithdrawalSize > maxBlockSize { break } - includedWithdrawals = append(includedWithdrawals, withdrawal) work.size += params.WithdrawalSize + includedWithdrawals = append(includedWithdrawals, withdrawal) } } else { includedWithdrawals = genParam.withdrawals } - body.Withdrawals = includedWithdrawals + + if !genParam.noTxs { + interrupt := new(atomic.Int32) + timer := time.AfterFunc(miner.config.Recommit, func() { + interrupt.Store(commitInterruptTimeout) + }) + defer timer.Stop() + + err := miner.fillTransactions(interrupt, work) + if errors.Is(err, errBlockInterruptedByTimeout) { + log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(miner.config.Recommit)) + } + } + body := types.Body{Transactions: work.txs, Withdrawals: includedWithdrawals} allLogs := make([]*types.Log, 0) for _, r := range work.receipts { From 6caa1eccdcbcb594b6c05e93366f50d1b2b8a98a Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 10 Jun 2025 15:30:15 +0200 Subject: [PATCH 13/25] comment phrasing --- miner/worker.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index d945752f7466..0e0dcf0c8d71 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -109,8 +109,9 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay var includedWithdrawals types.Withdrawals // If we are post-osaka, incorporate the requested withdrawals into the - // block size up-front to ensure that all requested withdrawals can be - // included even if we hit the size cap when filling the block with txs. + // block size calculation up-front to ensure that all requested withdrawals + // can be included even if we hit the size cap when filling the block with + // txs. // // Also, ensure that including all requested withdrawals wouldn't bring us // over the block size cap limit. The withdrawal cap ensures that this can't From 7288192c88f554ea8d99dc077b7e1f8c260f3738 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 11 Jun 2025 18:06:16 +0200 Subject: [PATCH 14/25] consider tx without sidecar when accounting for the size it will contribute to the RLP-encoded block --- miner/worker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miner/worker.go b/miner/worker.go index 0e0dcf0c8d71..6b714364d6ba 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -305,7 +305,7 @@ func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) e } env.txs = append(env.txs, tx) env.receipts = append(env.receipts, receipt) - env.size += tx.Size() + env.size += tx.WithoutBlobTxSidecar().Size() env.tcount++ return nil } From 85290d0ca6d735dbb92fde97001320d60da94996 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 11 Jun 2025 18:12:41 +0200 Subject: [PATCH 15/25] don't calculate the current fork for every transaction that we attempt to add when creating a block --- miner/worker.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 6b714364d6ba..e691307d21b9 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -351,7 +351,11 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* } func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { - gasLimit := env.header.GasLimit + var ( + isOsaka = miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) + isCancun = miner.chainConfig.IsCancun(env.header.Number, env.header.Time) + gasLimit = env.header.GasLimit + ) if env.gasPool == nil { env.gasPool = new(core.GasPool).AddGas(gasLimit) } @@ -407,7 +411,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran // Most of the blob gas logic here is agnostic as to if the chain supports // blobs or not, however the max check panics when called on a chain without // a defined schedule, so we need to verify it's safe to call. - if miner.chainConfig.IsCancun(env.header.Number, env.header.Time) { + if isCancun { left := eip4844.MaxBlobsPerBlock(miner.chainConfig, env.header.Time) - env.blobs if left < int(ltx.BlobGas/params.BlobTxBlobGasPerBlob) { log.Trace("Not enough blob space left for transaction", "hash", ltx.Hash, "left", left, "needed", ltx.BlobGas/params.BlobTxBlobGasPerBlob) @@ -426,12 +430,12 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran // if inclusion of the transaction would put the block size over the // maximum we allow, don't add any more txs to the payload. - if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && env.size+tx.Size() > params.BlockRLPSizeCap-blockRLPSizeCapBuffer { + if isOsaka && env.size+tx.Size() > params.BlockRLPSizeCap-blockRLPSizeCapBuffer { break } // Make sure all transactions after osaka have cell proofs - if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) { + if isOsaka { if sidecar := tx.BlobTxSidecar(); sidecar != nil { if sidecar.Version == 0 { log.Info("Including blob tx with v0 sidecar, recomputing proofs", "hash", ltx.Hash) From 517f8fa6dbc2db403b2716757773a07fad7d8bdd Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Thu, 12 Jun 2025 13:18:52 +0200 Subject: [PATCH 16/25] correctly incorporate blob transaction size --- miner/worker.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/miner/worker.go b/miner/worker.go index e691307d21b9..780ad419bd9b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -305,7 +305,7 @@ func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) e } env.txs = append(env.txs, tx) env.receipts = append(env.receipts, receipt) - env.size += tx.WithoutBlobTxSidecar().Size() + env.size += tx.Size() env.tcount++ return nil } @@ -331,6 +331,7 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio env.receipts = append(env.receipts, receipt) env.sidecars = append(env.sidecars, sc) env.blobs += len(sc.Blobs) + env.size += tx.WithoutBlobTxSidecar().Size() *env.header.BlobGasUsed += receipt.BlobGasUsed env.tcount++ return nil From 7754e773a92865db818736a55479dc4fdf9250b6 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Mon, 23 Jun 2025 18:45:44 +0100 Subject: [PATCH 17/25] params: fix 7934 size limit. --- params/protocol_params.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/protocol_params.go b/params/protocol_params.go index a11367a7ed8c..2cfed776e473 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -180,7 +180,7 @@ const ( HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. WithdrawalSize = 23 // size of a withdrawal - BlockRLPSizeCap = 9_961_472 // maximum size of an RLP-encoded block + BlockRLPSizeCap = 8_388_608 // maximum size of an RLP-encoded block ) // Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation From 84609d8855a2e27715dae893ab8c78104acf704c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 8 Jul 2025 07:09:53 +0200 Subject: [PATCH 18/25] miner: return error when withdrawals overshoot max block size --- miner/worker.go | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 780ad419bd9b..0bd890b5d2b2 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -106,30 +106,16 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay if err != nil { return &newPayloadResult{err: err} } - var includedWithdrawals types.Withdrawals - - // If we are post-osaka, incorporate the requested withdrawals into the - // block size calculation up-front to ensure that all requested withdrawals - // can be included even if we hit the size cap when filling the block with - // txs. - // - // Also, ensure that including all requested withdrawals wouldn't bring us - // over the block size cap limit. The withdrawal cap ensures that this can't - // actually happen right now, but it doesn't hurt to make this code - // future-proof for a situation where the withdrawal cap is lifted. - if miner.chainConfig.IsOsaka(work.header.Number, work.header.Time) { - maxBlockSize := params.BlockRLPSizeCap - blockRLPSizeCapBuffer - - for _, withdrawal := range genParam.withdrawals { - if int(work.size)+params.WithdrawalSize > maxBlockSize { - break - } - work.size += params.WithdrawalSize - includedWithdrawals = append(includedWithdrawals, withdrawal) - } - } else { - includedWithdrawals = genParam.withdrawals + + // Check withdrawals fit max block size. + // Due to the cap on withdrawal count, this can actually never happen, but we still need to + // check to ensure the CL notices there's a problem if the withdrawal cap is ever lifted. + maxBlockSize := params.BlockRLPSizeCap - blockRLPSizeCapBuffer + if genParam.withdrawals.Size() > maxBlockSize { + return &newPayloadResult{err: errors.New("withdrawals exceed max block size")} } + // Also add size of withdrawals to work block size. + work.size += uint64(genParam.withdrawals.Size()) if !genParam.noTxs { interrupt := new(atomic.Int32) @@ -143,7 +129,7 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(miner.config.Recommit)) } } - body := types.Body{Transactions: work.txs, Withdrawals: includedWithdrawals} + body := types.Body{Transactions: work.txs, Withdrawals: genParam.withdrawals} allLogs := make([]*types.Log, 0) for _, r := range work.receipts { From 4402a89c426ad43c8e75d92ac3c823deab6e8ce8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 8 Jul 2025 07:10:26 +0200 Subject: [PATCH 19/25] miner: check tx fits block size unconditionally --- miner/worker.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/miner/worker.go b/miner/worker.go index 0bd890b5d2b2..421ea8465169 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -64,6 +64,11 @@ type environment struct { witness *stateless.Witness } +// txFits reports whether the transaction fits into the block size limit. +func (env *environment) txFitsSize(tx *types.Transaction) bool { + return env.size+tx.Size() < params.BlockRLPSizeCap-blockRLPSizeCapBuffer +} + const ( commitInterruptNone int32 = iota commitInterruptNewHead @@ -417,7 +422,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran // if inclusion of the transaction would put the block size over the // maximum we allow, don't add any more txs to the payload. - if isOsaka && env.size+tx.Size() > params.BlockRLPSizeCap-blockRLPSizeCapBuffer { + if !env.txFitsSize(tx) { break } From f40396d02555557197ddede5b6893d1ec952b73b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 8 Jul 2025 07:13:24 +0200 Subject: [PATCH 20/25] params: remove WithdrawalSize --- params/protocol_params.go | 1 - 1 file changed, 1 deletion(-) diff --git a/params/protocol_params.go b/params/protocol_params.go index 2cfed776e473..75e6f192c709 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -179,7 +179,6 @@ const ( HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. - WithdrawalSize = 23 // size of a withdrawal BlockRLPSizeCap = 8_388_608 // maximum size of an RLP-encoded block ) From b66ed1bc189234f2d48ab6248e0a58cf7e753b15 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 8 Jul 2025 14:44:07 +0800 Subject: [PATCH 21/25] core/types: add block encode test --- core/types/block_test.go | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/core/types/block_test.go b/core/types/block_test.go index 1f61be89a5d6..2130a2fcf3b2 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -24,11 +24,13 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" ) // from bcValidBlockTest.json, "SimpleTx" @@ -194,6 +196,60 @@ func TestEIP2718BlockEncoding(t *testing.T) { } } +func TestEIP4844BlockEncoding(t *testing.T) { + // https://github.com/ethereum/tests/blob/develop/BlockchainTests/ValidBlocks/bcEIP4844-blobtransactions/blockWithAllTransactionTypes.json + blockEnc := common.FromHex("0xf90417f90244a05eb7f6da0f3e237c62bcae48b7fb5f4506d392616b62890429c8b76b4a1d4104a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ba5e000000000000000000000000000000000000a011639dcca0b44f2acb5b630a82c8a69cb82742b3711383ec4e111a554d27aea5a05cb644f722e31f9792a8ef6e2a762334e1a862e8b40c1612e1e9507fd7121ef9a00c82719448356ba6807d6edfcd8e5aea575a5e97f36038ffb3e395749b26d41cb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a00008301482082079e42a00000000000000000000000000000000000000000000000000000000000020000880000000000000000820314a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218302000080a00000000000000000000000000000000000000000000000000000000000000000f901cbf864808203e885e8d4a5100094100000000000000000000000000000000000000a01801ca09de4adda6288582a6700dbcd8eb70c0a4a7fc9487d965f7bf22424e0bd121095a01cdb078764cc3770d5db847e99e10333aa7c356247baaf09b03eae04d64e7926b86901f86601018203e885e8d4a5100094100000000000000000000000000000000000000a0380c080a025090740da12684493e4fb466a3979e365b194e8cf462edf3c2c3be2f130bb2ea034fa18fb4c1bff4d957d72e28535d27f1352517a942aeaca0ed944085f0cd8bbb86a02f8670102018203e885e8d4a5100094100000000000000000000000000000000000000a0580c080a0352a7be5002ce111bc5167f3addf97a75e2e0b810d826af71d2caae18aed284ea065d38f8a5c8948ce706842e8861fb21020b93a4d5e489162a0e6d419a457b735b88c03f8890103018203e885e8d4a5100094100000000000000000000000000000000000000a0780c00ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8809f638144c46d5de7a9e630c0e7c5c63ae829ecfd8cc94715d9c29fe17c464de0a06c5fc54c3aa868ba35ef31a4e12431611631ab7bcdceb4214dd273d83f73b5e1c0c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + check := func(f string, got, want interface{}) { + if !reflect.DeepEqual(got, want) { + t.Errorf("%s mismatch: got %v, want %v", f, got, want) + } + } + check("Difficulty", block.Difficulty(), big.NewInt(0)) + check("GasLimit", block.GasLimit(), hexutil.MustDecodeUint64("0x16345785d8a0000")) + check("GasUsed", block.GasUsed(), hexutil.MustDecodeUint64("0x14820")) + check("Coinbase", block.Coinbase(), common.HexToAddress("0xba5e000000000000000000000000000000000000")) + check("MixDigest", block.MixDigest(), common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000020000")) + check("Root", block.Root(), common.HexToHash("0x11639dcca0b44f2acb5b630a82c8a69cb82742b3711383ec4e111a554d27aea5")) + check("WithdrawalRoot", *block.Header().WithdrawalsHash, common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) + check("Nonce", block.Nonce(), uint64(0)) + check("Time", block.Time(), hexutil.MustDecodeUint64("0x79e")) + check("Size", block.Size(), uint64(len(blockEnc))) + + // Create blob tx. + tx := NewTx(&BlobTx{ + ChainID: uint256.NewInt(1), + Nonce: 3, + To: common.HexToAddress("0x100000000000000000000000000000000000000a"), + Gas: hexutil.MustDecodeUint64("0xe8d4a51000"), + GasTipCap: uint256.MustFromHex("0x1"), + GasFeeCap: uint256.MustFromHex("0x3e8"), + BlobFeeCap: uint256.MustFromHex("0xa"), + BlobHashes: []common.Hash{ + common.BytesToHash(hexutil.MustDecode("0x01a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")), + }, + Value: uint256.MustFromHex("0x7"), + }) + sig := common.Hex2Bytes("00638144c46d5de7a9e630c0e7c5c63ae829ecfd8cc94715d9c29fe17c464de06c5fc54c3aa868ba35ef31a4e12431611631ab7bcdceb4214dd273d83f73b5e100") + tx, _ = tx.WithSignature(LatestSignerForChainID(big.NewInt(1)), sig) + + check("len(Transactions)", len(block.Transactions()), 4) + check("Transactions[3].Hash", block.Transactions()[3].Hash(), tx.Hash()) + check("Transactions[3].Type()", block.Transactions()[3].Type(), uint8(BlobTxType)) + + ourBlockEnc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(ourBlockEnc, blockEnc) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) + } +} + func TestUncleHash(t *testing.T) { uncles := make([]*Header, 0) h := CalcUncleHash(uncles) From 319fd95ab57cbdfc0dd6eca70f2fdef88feac66f Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 8 Jul 2025 14:50:02 +0800 Subject: [PATCH 22/25] miner: prevent double construction --- miner/worker.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 421ea8465169..88081d81aef4 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -318,11 +318,12 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio if err != nil { return err } - env.txs = append(env.txs, tx.WithoutBlobTxSidecar()) + txNoBlob := tx.WithoutBlobTxSidecar() + env.txs = append(env.txs, txNoBlob) env.receipts = append(env.receipts, receipt) env.sidecars = append(env.sidecars, sc) env.blobs += len(sc.Blobs) - env.size += tx.WithoutBlobTxSidecar().Size() + env.size += txNoBlob.Size() *env.header.BlobGasUsed += receipt.BlobGasUsed env.tcount++ return nil From 11f1b1f1da121b3d7eae40d39f829598a74c3f81 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 8 Jul 2025 11:21:38 +0200 Subject: [PATCH 23/25] params, miner: rename constants --- core/block_validator.go | 2 +- miner/worker.go | 14 +++++++------- params/protocol_params.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index d4b76e02f810..008444fbbc45 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -50,7 +50,7 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *Bloc // validated at this point. func (v *BlockValidator) ValidateBody(block *types.Block) error { // check EIP 7934 RLP-encoded block size cap - if v.config.IsOsaka(block.Number(), block.Time()) && block.Size() > params.BlockRLPSizeCap { + if v.config.IsOsaka(block.Number(), block.Time()) && block.Size() > params.MaxBlockSize { return ErrBlockOversized } // Check whether the block is already imported. diff --git a/miner/worker.go b/miner/worker.go index 88081d81aef4..45a4b55f55af 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -66,7 +66,7 @@ type environment struct { // txFits reports whether the transaction fits into the block size limit. func (env *environment) txFitsSize(tx *types.Transaction) bool { - return env.size+tx.Size() < params.BlockRLPSizeCap-blockRLPSizeCapBuffer + return env.size+tx.Size() < params.MaxBlockSize-maxBlockSizeBufferZone } const ( @@ -74,13 +74,13 @@ const ( commitInterruptNewHead commitInterruptResubmit commitInterruptTimeout - - // cap the size of blocks we will produce below the max allowed by - // EIP-7934. This gives us buffer room if the estimated size of the - // block we are building is off from the actual encoded size. - blockRLPSizeCapBuffer = 1_000_000 ) +// Block size is capped by the protocol at params.MaxBlockSize. When producing blocks, we +// try to say below the size including a buffer zone, this is to avoid going over the +// maximum size with auxilary data added into the block. +const maxBlockSizeBufferZone = 1_000_000 + // newPayloadResult is the result of payload generation. type newPayloadResult struct { err error @@ -115,7 +115,7 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay // Check withdrawals fit max block size. // Due to the cap on withdrawal count, this can actually never happen, but we still need to // check to ensure the CL notices there's a problem if the withdrawal cap is ever lifted. - maxBlockSize := params.BlockRLPSizeCap - blockRLPSizeCapBuffer + maxBlockSize := params.MaxBlockSize - maxBlockSizeBufferZone if genParam.withdrawals.Size() > maxBlockSize { return &newPayloadResult{err: errors.New("withdrawals exceed max block size")} } diff --git a/params/protocol_params.go b/params/protocol_params.go index 75e6f192c709..af3d4018d61a 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -179,7 +179,7 @@ const ( HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. - BlockRLPSizeCap = 8_388_608 // maximum size of an RLP-encoded block + MaxBlockSize = 8_388_608 // maximum size of an RLP-encoded block ) // Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation From 69fe54b2e50e406b2525857350e3de4761d9384e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 8 Jul 2025 14:04:25 +0200 Subject: [PATCH 24/25] core/types: maintain tx size cache in WithoutBlobTxSidecar --- core/types/transaction.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/types/transaction.go b/core/types/transaction.go index 934feb735316..733b6510f148 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -449,14 +449,18 @@ func (tx *Transaction) BlobGasFeeCapIntCmp(other *big.Int) int { // WithoutBlobTxSidecar returns a copy of tx with the blob sidecar removed. func (tx *Transaction) WithoutBlobTxSidecar() *Transaction { blobtx, ok := tx.inner.(*BlobTx) - if !ok { + if !ok || blobtx.Sidecar == nil { return tx } cpy := &Transaction{ inner: blobtx.withoutSidecar(), time: tx.time, } - // Note: tx.size cache not carried over because the sidecar is included in size! + if size := tx.size.Load(); size != 0 { + // The tx had a sidecar before, so we need to subtract it from the size. + scSize := rlp.ListSize(blobtx.Sidecar.encodedSize()) + cpy.size.Store(size - scSize) + } if h := tx.hash.Load(); h != nil { cpy.hash.Store(h) } From fd05c0a79bbbc3bed3eb58ac9db06bd12699f65c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 8 Jul 2025 16:33:50 +0200 Subject: [PATCH 25/25] miner: fix typo in comment --- miner/worker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miner/worker.go b/miner/worker.go index 45a4b55f55af..6738b2d49d01 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -78,7 +78,7 @@ const ( // Block size is capped by the protocol at params.MaxBlockSize. When producing blocks, we // try to say below the size including a buffer zone, this is to avoid going over the -// maximum size with auxilary data added into the block. +// maximum size with auxiliary data added into the block. const maxBlockSizeBufferZone = 1_000_000 // newPayloadResult is the result of payload generation.