Skip to content

Commit e6b9d0c

Browse files
jwasingerspencer-tbfjlrjl493456442
authored
core,miner: implement EIP-7934 - RLP Execution Block Size Limit (#31990)
This PR adds a block validation check for the maximum block size, as required by EIP-7934, and also applies a slightly lower size limit during block building. --------- Co-authored-by: spencer-tb <[email protected]> Co-authored-by: Felix Lange <[email protected]> Co-authored-by: Gary Rong <[email protected]>
1 parent efbba96 commit e6b9d0c

File tree

6 files changed

+116
-10
lines changed

6 files changed

+116
-10
lines changed

core/block_validator.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *Bloc
4949
// header's transaction and uncle roots. The headers are assumed to be already
5050
// validated at this point.
5151
func (v *BlockValidator) ValidateBody(block *types.Block) error {
52+
// check EIP 7934 RLP-encoded block size cap
53+
if v.config.IsOsaka(block.Number(), block.Time()) && block.Size() > params.MaxBlockSize {
54+
return ErrBlockOversized
55+
}
5256
// Check whether the block is already imported.
5357
if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
5458
return ErrKnownBlock

core/error.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ var (
2828

2929
// ErrNoGenesis is returned when there is no Genesis Block.
3030
ErrNoGenesis = errors.New("genesis not found in chain")
31+
32+
// ErrBlockOversized is returned if the size of the RLP-encoded block
33+
// exceeds the cap established by EIP 7934
34+
ErrBlockOversized = errors.New("block RLP-encoded size exceeds maximum")
3135
)
3236

3337
// List of evm-call-message pre-checking errors. All state transition messages will

core/types/block_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ import (
2424
"testing"
2525

2626
"github.com/ethereum/go-ethereum/common"
27+
"github.com/ethereum/go-ethereum/common/hexutil"
2728
"github.com/ethereum/go-ethereum/common/math"
2829
"github.com/ethereum/go-ethereum/crypto"
2930
"github.com/ethereum/go-ethereum/internal/blocktest"
3031
"github.com/ethereum/go-ethereum/params"
3132
"github.com/ethereum/go-ethereum/rlp"
33+
"github.com/holiman/uint256"
3234
)
3335

3436
// from bcValidBlockTest.json, "SimpleTx"
@@ -194,6 +196,60 @@ func TestEIP2718BlockEncoding(t *testing.T) {
194196
}
195197
}
196198

199+
func TestEIP4844BlockEncoding(t *testing.T) {
200+
// https://github.com/ethereum/tests/blob/develop/BlockchainTests/ValidBlocks/bcEIP4844-blobtransactions/blockWithAllTransactionTypes.json
201+
blockEnc := common.FromHex("0xf90417f90244a05eb7f6da0f3e237c62bcae48b7fb5f4506d392616b62890429c8b76b4a1d4104a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ba5e000000000000000000000000000000000000a011639dcca0b44f2acb5b630a82c8a69cb82742b3711383ec4e111a554d27aea5a05cb644f722e31f9792a8ef6e2a762334e1a862e8b40c1612e1e9507fd7121ef9a00c82719448356ba6807d6edfcd8e5aea575a5e97f36038ffb3e395749b26d41cb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a00008301482082079e42a00000000000000000000000000000000000000000000000000000000000020000880000000000000000820314a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218302000080a00000000000000000000000000000000000000000000000000000000000000000f901cbf864808203e885e8d4a5100094100000000000000000000000000000000000000a01801ca09de4adda6288582a6700dbcd8eb70c0a4a7fc9487d965f7bf22424e0bd121095a01cdb078764cc3770d5db847e99e10333aa7c356247baaf09b03eae04d64e7926b86901f86601018203e885e8d4a5100094100000000000000000000000000000000000000a0380c080a025090740da12684493e4fb466a3979e365b194e8cf462edf3c2c3be2f130bb2ea034fa18fb4c1bff4d957d72e28535d27f1352517a942aeaca0ed944085f0cd8bbb86a02f8670102018203e885e8d4a5100094100000000000000000000000000000000000000a0580c080a0352a7be5002ce111bc5167f3addf97a75e2e0b810d826af71d2caae18aed284ea065d38f8a5c8948ce706842e8861fb21020b93a4d5e489162a0e6d419a457b735b88c03f8890103018203e885e8d4a5100094100000000000000000000000000000000000000a0780c00ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8809f638144c46d5de7a9e630c0e7c5c63ae829ecfd8cc94715d9c29fe17c464de0a06c5fc54c3aa868ba35ef31a4e12431611631ab7bcdceb4214dd273d83f73b5e1c0c0")
202+
var block Block
203+
if err := rlp.DecodeBytes(blockEnc, &block); err != nil {
204+
t.Fatal("decode error: ", err)
205+
}
206+
207+
check := func(f string, got, want interface{}) {
208+
if !reflect.DeepEqual(got, want) {
209+
t.Errorf("%s mismatch: got %v, want %v", f, got, want)
210+
}
211+
}
212+
check("Difficulty", block.Difficulty(), big.NewInt(0))
213+
check("GasLimit", block.GasLimit(), hexutil.MustDecodeUint64("0x16345785d8a0000"))
214+
check("GasUsed", block.GasUsed(), hexutil.MustDecodeUint64("0x14820"))
215+
check("Coinbase", block.Coinbase(), common.HexToAddress("0xba5e000000000000000000000000000000000000"))
216+
check("MixDigest", block.MixDigest(), common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000020000"))
217+
check("Root", block.Root(), common.HexToHash("0x11639dcca0b44f2acb5b630a82c8a69cb82742b3711383ec4e111a554d27aea5"))
218+
check("WithdrawalRoot", *block.Header().WithdrawalsHash, common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"))
219+
check("Nonce", block.Nonce(), uint64(0))
220+
check("Time", block.Time(), hexutil.MustDecodeUint64("0x79e"))
221+
check("Size", block.Size(), uint64(len(blockEnc)))
222+
223+
// Create blob tx.
224+
tx := NewTx(&BlobTx{
225+
ChainID: uint256.NewInt(1),
226+
Nonce: 3,
227+
To: common.HexToAddress("0x100000000000000000000000000000000000000a"),
228+
Gas: hexutil.MustDecodeUint64("0xe8d4a51000"),
229+
GasTipCap: uint256.MustFromHex("0x1"),
230+
GasFeeCap: uint256.MustFromHex("0x3e8"),
231+
BlobFeeCap: uint256.MustFromHex("0xa"),
232+
BlobHashes: []common.Hash{
233+
common.BytesToHash(hexutil.MustDecode("0x01a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")),
234+
},
235+
Value: uint256.MustFromHex("0x7"),
236+
})
237+
sig := common.Hex2Bytes("00638144c46d5de7a9e630c0e7c5c63ae829ecfd8cc94715d9c29fe17c464de06c5fc54c3aa868ba35ef31a4e12431611631ab7bcdceb4214dd273d83f73b5e100")
238+
tx, _ = tx.WithSignature(LatestSignerForChainID(big.NewInt(1)), sig)
239+
240+
check("len(Transactions)", len(block.Transactions()), 4)
241+
check("Transactions[3].Hash", block.Transactions()[3].Hash(), tx.Hash())
242+
check("Transactions[3].Type()", block.Transactions()[3].Type(), uint8(BlobTxType))
243+
244+
ourBlockEnc, err := rlp.EncodeToBytes(&block)
245+
if err != nil {
246+
t.Fatal("encode error: ", err)
247+
}
248+
if !bytes.Equal(ourBlockEnc, blockEnc) {
249+
t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc)
250+
}
251+
}
252+
197253
func TestUncleHash(t *testing.T) {
198254
uncles := make([]*Header, 0)
199255
h := CalcUncleHash(uncles)

core/types/transaction.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,14 +449,18 @@ func (tx *Transaction) BlobGasFeeCapIntCmp(other *big.Int) int {
449449
// WithoutBlobTxSidecar returns a copy of tx with the blob sidecar removed.
450450
func (tx *Transaction) WithoutBlobTxSidecar() *Transaction {
451451
blobtx, ok := tx.inner.(*BlobTx)
452-
if !ok {
452+
if !ok || blobtx.Sidecar == nil {
453453
return tx
454454
}
455455
cpy := &Transaction{
456456
inner: blobtx.withoutSidecar(),
457457
time: tx.time,
458458
}
459-
// Note: tx.size cache not carried over because the sidecar is included in size!
459+
if size := tx.size.Load(); size != 0 {
460+
// The tx had a sidecar before, so we need to subtract it from the size.
461+
scSize := rlp.ListSize(blobtx.Sidecar.encodedSize())
462+
cpy.size.Store(size - scSize)
463+
}
460464
if h := tx.hash.Load(); h != nil {
461465
cpy.hash.Store(h)
462466
}

miner/worker.go

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type environment struct {
5050
signer types.Signer
5151
state *state.StateDB // apply state changes here
5252
tcount int // tx count in cycle
53+
size uint64 // size of the block we are building
5354
gasPool *core.GasPool // available gas used to pack transactions
5455
coinbase common.Address
5556
evm *vm.EVM
@@ -63,13 +64,23 @@ type environment struct {
6364
witness *stateless.Witness
6465
}
6566

67+
// txFits reports whether the transaction fits into the block size limit.
68+
func (env *environment) txFitsSize(tx *types.Transaction) bool {
69+
return env.size+tx.Size() < params.MaxBlockSize-maxBlockSizeBufferZone
70+
}
71+
6672
const (
6773
commitInterruptNone int32 = iota
6874
commitInterruptNewHead
6975
commitInterruptResubmit
7076
commitInterruptTimeout
7177
)
7278

79+
// Block size is capped by the protocol at params.MaxBlockSize. When producing blocks, we
80+
// try to say below the size including a buffer zone, this is to avoid going over the
81+
// maximum size with auxiliary data added into the block.
82+
const maxBlockSizeBufferZone = 1_000_000
83+
7384
// newPayloadResult is the result of payload generation.
7485
type newPayloadResult struct {
7586
err error
@@ -95,12 +106,23 @@ type generateParams struct {
95106
}
96107

97108
// generateWork generates a sealing block based on the given parameters.
98-
func (miner *Miner) generateWork(params *generateParams, witness bool) *newPayloadResult {
99-
work, err := miner.prepareWork(params, witness)
109+
func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPayloadResult {
110+
work, err := miner.prepareWork(genParam, witness)
100111
if err != nil {
101112
return &newPayloadResult{err: err}
102113
}
103-
if !params.noTxs {
114+
115+
// Check withdrawals fit max block size.
116+
// Due to the cap on withdrawal count, this can actually never happen, but we still need to
117+
// check to ensure the CL notices there's a problem if the withdrawal cap is ever lifted.
118+
maxBlockSize := params.MaxBlockSize - maxBlockSizeBufferZone
119+
if genParam.withdrawals.Size() > maxBlockSize {
120+
return &newPayloadResult{err: errors.New("withdrawals exceed max block size")}
121+
}
122+
// Also add size of withdrawals to work block size.
123+
work.size += uint64(genParam.withdrawals.Size())
124+
125+
if !genParam.noTxs {
104126
interrupt := new(atomic.Int32)
105127
timer := time.AfterFunc(miner.config.Recommit, func() {
106128
interrupt.Store(commitInterruptTimeout)
@@ -112,8 +134,8 @@ func (miner *Miner) generateWork(params *generateParams, witness bool) *newPaylo
112134
log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(miner.config.Recommit))
113135
}
114136
}
137+
body := types.Body{Transactions: work.txs, Withdrawals: genParam.withdrawals}
115138

116-
body := types.Body{Transactions: work.txs, Withdrawals: params.withdrawals}
117139
allLogs := make([]*types.Log, 0)
118140
for _, r := range work.receipts {
119141
allLogs = append(allLogs, r.Logs...)
@@ -256,6 +278,7 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase
256278
return &environment{
257279
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
258280
state: state,
281+
size: uint64(header.Size()),
259282
coinbase: coinbase,
260283
header: header,
261284
witness: state.Witness(),
@@ -273,6 +296,7 @@ func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) e
273296
}
274297
env.txs = append(env.txs, tx)
275298
env.receipts = append(env.receipts, receipt)
299+
env.size += tx.Size()
276300
env.tcount++
277301
return nil
278302
}
@@ -294,10 +318,12 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio
294318
if err != nil {
295319
return err
296320
}
297-
env.txs = append(env.txs, tx.WithoutBlobTxSidecar())
321+
txNoBlob := tx.WithoutBlobTxSidecar()
322+
env.txs = append(env.txs, txNoBlob)
298323
env.receipts = append(env.receipts, receipt)
299324
env.sidecars = append(env.sidecars, sc)
300325
env.blobs += len(sc.Blobs)
326+
env.size += txNoBlob.Size()
301327
*env.header.BlobGasUsed += receipt.BlobGasUsed
302328
env.tcount++
303329
return nil
@@ -318,7 +344,11 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*
318344
}
319345

320346
func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error {
321-
gasLimit := env.header.GasLimit
347+
var (
348+
isOsaka = miner.chainConfig.IsOsaka(env.header.Number, env.header.Time)
349+
isCancun = miner.chainConfig.IsCancun(env.header.Number, env.header.Time)
350+
gasLimit = env.header.GasLimit
351+
)
322352
if env.gasPool == nil {
323353
env.gasPool = new(core.GasPool).AddGas(gasLimit)
324354
}
@@ -374,7 +404,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
374404
// Most of the blob gas logic here is agnostic as to if the chain supports
375405
// blobs or not, however the max check panics when called on a chain without
376406
// a defined schedule, so we need to verify it's safe to call.
377-
if miner.chainConfig.IsCancun(env.header.Number, env.header.Time) {
407+
if isCancun {
378408
left := eip4844.MaxBlobsPerBlock(miner.chainConfig, env.header.Time) - env.blobs
379409
if left < int(ltx.BlobGas/params.BlobTxBlobGasPerBlob) {
380410
log.Trace("Not enough blob space left for transaction", "hash", ltx.Hash, "left", left, "needed", ltx.BlobGas/params.BlobTxBlobGasPerBlob)
@@ -391,8 +421,14 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
391421
continue
392422
}
393423

424+
// if inclusion of the transaction would put the block size over the
425+
// maximum we allow, don't add any more txs to the payload.
426+
if !env.txFitsSize(tx) {
427+
break
428+
}
429+
394430
// Make sure all transactions after osaka have cell proofs
395-
if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) {
431+
if isOsaka {
396432
if sidecar := tx.BlobTxSidecar(); sidecar != nil {
397433
if sidecar.Version == 0 {
398434
log.Info("Including blob tx with v0 sidecar, recomputing proofs", "hash", ltx.Hash)

params/protocol_params.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ const (
180180
BlobBaseCost = 1 << 13 // Base execution gas cost for a blob.
181181

182182
HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935.
183+
184+
MaxBlockSize = 8_388_608 // maximum size of an RLP-encoded block
183185
)
184186

185187
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation

0 commit comments

Comments
 (0)