Skip to content

Add max size limit for block#1594

Merged
libotony merged 4 commits intoevm-upgradesfrom
paolo/evm-7934
Mar 31, 2026
Merged

Add max size limit for block#1594
libotony merged 4 commits intoevm-upgradesfrom
paolo/evm-7934

Conversation

@paologalligit
Copy link
Copy Markdown
Member

Description

Introduce 10MiB block size limit.
EIP: https://eips.ethereum.org/EIPS/eip-7934
GHIssue: https://github.com/vechain/protocol-board-repo/issues/1073

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • New and existing E2E tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules
  • I have not added any vulnerable dependencies to my code

@paologalligit paologalligit requested a review from a team as a code owner March 20, 2026 15:52
@libotony
Copy link
Copy Markdown
Member

I did a test for the max rlp encoded block size.

// TestMaxBlockHeaderRLPSize builds a header with every field at its theoretical
// maximum RLP-encoded value (no transactions) and reports the resulting encoded
// size. Use this as the upper-bound reference when sizing buffers or limits.
//
// Field-by-field RLP budget (all bytes):
//
//	ParentID      (bytes32)           0xa0 + 32        =  33
//	Timestamp     (uint64 max)        0x88 + 8         =   9
//	GasLimit      (uint64 max)        0x88 + 8         =   9
//	Beneficiary   (bytes20)           0x94 + 20        =  21
//	GasUsed       (uint64 max)        0x88 + 8         =   9
//	TotalScore    (uint64 max)        0x88 + 8         =   9
//	TxsRootFeatures list[bytes32,u32] 1 + 33 + 5       =  39
//	StateRoot     (bytes32)           0xa0 + 32        =  33
//	ReceiptsRoot  (bytes32)           0xa0 + 32        =  33
//	Signature     (146 bytes)         2    + 146       = 148
//	Extension list[alpha,com,baseFee] 2 + 33 + 1 + 33 =  69
//	                                              total = 412  (content)
//	Outer list prefix (412 >= 56)     0xf9 + 2 bytes   =   3
//	                                          TOTAL    = 415
func TestMaxBlockHeaderRLPSize(t *testing.T) {
	var maxBytes32 thor.Bytes32
	for i := range maxBytes32 {
		maxBytes32[i] = 0xff
	}

	var maxAddress thor.Address
	for i := range maxAddress {
		maxAddress[i] = 0xff
	}

	// Alpha is always 32 bytes in practice (chained from stateRoot or VRF beta).
	maxAlpha := make([]byte, 32)
	for i := range maxAlpha {
		maxAlpha[i] = 0xff
	}

	// Signature = ECDSA (65 bytes) + VRF proof (81 bytes) = ComplexSigSize.
	maxSig := make([]byte, block.ComplexSigSize)
	for i := range maxSig {
		maxSig[i] = 0xff
	}

	// BaseFee is a *big.Int; max sensible value is 2^256-1 (32 bytes, no leading zeros).
	maxBaseFee := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))

	// TxsRoot is derived from the (empty) tx list and is always 32 bytes;
	// its value does not affect the encoded size.
	// TransactionFeatures must be non-zero to trigger the list encoding of
	// TxsRootFeatures (vs. the shorter backward-compat bytes32 form).
	blk := new(block.Builder).
		ParentID(maxBytes32).
		Timestamp(math.MaxUint64).
		GasLimit(math.MaxUint64).
		Beneficiary(maxAddress).
		GasUsed(math.MaxUint64).
		TotalScore(math.MaxUint64).
		TransactionFeatures(tx.Features(math.MaxUint32)).
		StateRoot(maxBytes32).
		ReceiptsRoot(maxBytes32).
		Alpha(maxAlpha).
		COM().
		BaseFee(maxBaseFee).
		Build().
		WithSignature(maxSig)

	encoded, err := rlp.EncodeToBytes(blk.Header())
	require.NoError(t, err)

	t.Logf("theoretical max block header RLP size (no txs): %d bytes", len(encoded))
}

The result shows 415 bytes, so I would suggest set blockSizeBufferZone to 2_000.

@libotony
Copy link
Copy Markdown
Member

Acutually 8MiB according to the EIP, 2MiB was there for beacon block.

Comment thread packer/flow.go Outdated
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@paologalligit
Copy link
Copy Markdown
Member Author

I did a test for the max rlp encoded block size.

// TestMaxBlockHeaderRLPSize builds a header with every field at its theoretical
// maximum RLP-encoded value (no transactions) and reports the resulting encoded
// size. Use this as the upper-bound reference when sizing buffers or limits.
//
// Field-by-field RLP budget (all bytes):
//
//	ParentID      (bytes32)           0xa0 + 32        =  33
//	Timestamp     (uint64 max)        0x88 + 8         =   9
//	GasLimit      (uint64 max)        0x88 + 8         =   9
//	Beneficiary   (bytes20)           0x94 + 20        =  21
//	GasUsed       (uint64 max)        0x88 + 8         =   9
//	TotalScore    (uint64 max)        0x88 + 8         =   9
//	TxsRootFeatures list[bytes32,u32] 1 + 33 + 5       =  39
//	StateRoot     (bytes32)           0xa0 + 32        =  33
//	ReceiptsRoot  (bytes32)           0xa0 + 32        =  33
//	Signature     (146 bytes)         2    + 146       = 148
//	Extension list[alpha,com,baseFee] 2 + 33 + 1 + 33 =  69
//	                                              total = 412  (content)
//	Outer list prefix (412 >= 56)     0xf9 + 2 bytes   =   3
//	                                          TOTAL    = 415
func TestMaxBlockHeaderRLPSize(t *testing.T) {
	var maxBytes32 thor.Bytes32
	for i := range maxBytes32 {
		maxBytes32[i] = 0xff
	}

	var maxAddress thor.Address
	for i := range maxAddress {
		maxAddress[i] = 0xff
	}

	// Alpha is always 32 bytes in practice (chained from stateRoot or VRF beta).
	maxAlpha := make([]byte, 32)
	for i := range maxAlpha {
		maxAlpha[i] = 0xff
	}

	// Signature = ECDSA (65 bytes) + VRF proof (81 bytes) = ComplexSigSize.
	maxSig := make([]byte, block.ComplexSigSize)
	for i := range maxSig {
		maxSig[i] = 0xff
	}

	// BaseFee is a *big.Int; max sensible value is 2^256-1 (32 bytes, no leading zeros).
	maxBaseFee := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))

	// TxsRoot is derived from the (empty) tx list and is always 32 bytes;
	// its value does not affect the encoded size.
	// TransactionFeatures must be non-zero to trigger the list encoding of
	// TxsRootFeatures (vs. the shorter backward-compat bytes32 form).
	blk := new(block.Builder).
		ParentID(maxBytes32).
		Timestamp(math.MaxUint64).
		GasLimit(math.MaxUint64).
		Beneficiary(maxAddress).
		GasUsed(math.MaxUint64).
		TotalScore(math.MaxUint64).
		TransactionFeatures(tx.Features(math.MaxUint32)).
		StateRoot(maxBytes32).
		ReceiptsRoot(maxBytes32).
		Alpha(maxAlpha).
		COM().
		BaseFee(maxBaseFee).
		Build().
		WithSignature(maxSig)

	encoded, err := rlp.EncodeToBytes(blk.Header())
	require.NoError(t, err)

	t.Logf("theoretical max block header RLP size (no txs): %d bytes", len(encoded))
}

The result shows 415 bytes, so I would suggest set blockSizeBufferZone to 2_000.

Do you feel strongly about increasing it to 2M? We are currently mirroring ethereum with https://github.com/ethereum/go-ethereum/blob/master/miner/worker.go#L98 and since it's less then 500 bytes 1M looks more than enough. What do you think?

@libotony
Copy link
Copy Markdown
Member

Do you feel strongly about increasing it to 2M? We are currently mirroring ethereum with https://github.com/ethereum/go-ethereum/blob/master/miner/worker.go#L98 and since it's less then 500 bytes 1M looks more than enough. What do you think?

Actually I was suggesting set the buffer to 2K other than 1M. This is based on the test results, which show that the maximum header size with no transactions inside is 415 bytes for RLP encoding. 2K is approximately 4x ~ 5x, so I think it's enough, what do you think @paologalligit .

@paologalligit
Copy link
Copy Markdown
Member Author

Do you feel strongly about increasing it to 2M? We are currently mirroring ethereum with https://github.com/ethereum/go-ethereum/blob/master/miner/worker.go#L98 and since it's less then 500 bytes 1M looks more than enough. What do you think?

Actually I was suggesting set the buffer to 2K other than 1M. This is based on the test results, which show that the maximum header size with no transactions inside is 415 bytes for RLP encoding. 2K is approximately 4x ~ 5x, so I think it's enough, what do you think @paologalligit .

Ahhh, sorry, misunderstood the number. Yeah, 2K sounds reasonable to me as well.

@paologalligit paologalligit requested a review from libotony March 26, 2026 14:26
@libotony
Copy link
Copy Markdown
Member

@paologalligit Added ticket for testing https://github.com/vechain/protocol-board-repo/issues/1099 either unit test or customnet test would be fine, and please change the base branch since evm-upgrades was rebased with master.

@paologalligit paologalligit changed the base branch from evm-upgrades-bak-1 to evm-upgrades March 27, 2026 10:48
@libotony libotony merged commit 8d27f8a into evm-upgrades Mar 31, 2026
31 checks passed
@libotony libotony deleted the paolo/evm-7934 branch March 31, 2026 09:28
libotony added a commit that referenced this pull request Apr 16, 2026
Co-authored-by: Pedro Gomes <otherview@gmail.com>
Co-authored-by: libotony <tony.li@vechain.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants