core, params: implement bal size constraint

This commit is contained in:
Gary Rong 2026-05-13 22:34:40 +08:00
parent 0ac419900e
commit 4eabe32e26
4 changed files with 116 additions and 1 deletions

View file

@ -126,6 +126,8 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
return fmt.Errorf("access list hash mismatch, computed: %x, remote: %x", computed, *block.Header().BlockAccessListHash)
} else if err := block.AccessList().Validate(); err != nil {
return fmt.Errorf("invalid block access list: %v", err)
} else if err := block.AccessList().ValidateSize(block.GasLimit()); err != nil {
return fmt.Errorf("invalid block access list: %v", err)
}
}
} else if block.Header().BlockAccessListHash != nil || block.AccessList() != nil {
@ -183,10 +185,14 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
}
// Verify Block-level accessList once Amsterdam is enabled
if v.config.IsAmsterdam(block.Number(), block.Time()) {
local, remote := res.Bal.ToEncodingObj().Hash(), *block.Header().BlockAccessListHash
enc := res.Bal.ToEncodingObj()
local, remote := enc.Hash(), *block.Header().BlockAccessListHash
if local != remote {
return fmt.Errorf("access list hash mismatch, local: %x, remote: %x", local, remote)
}
if err := enc.ValidateSize(block.GasLimit()); err != nil {
return fmt.Errorf("invalid block access list: %v", err)
}
}
// Validate the state root against the received state root and throw
// an error if they don't match.

View file

@ -92,6 +92,32 @@ func (e *BlockAccessList) Validate() error {
return nil
}
// ItemCount returns the number of items in the BAL for EIP-7928 size-constraint
// purposes: the count of distinct addresses plus every storage key (writes +
// reads) carried by those accounts. A storage slot is counted once regardless
// of how many transactions wrote to it.
func (e *BlockAccessList) ItemCount() uint64 {
count := uint64(len(*e)) // distinct addresses
for i := range *e {
count += uint64(len((*e)[i].StorageWrites)) + uint64(len((*e)[i].StorageReads))
}
return count
}
// ValidateSize returns an error if the BAL violates the EIP-7928 size
// constraint for the given block gas limit:
//
// ItemCount() <= blockGasLimit / params.BALItemCost
func (e *BlockAccessList) ValidateSize(blockGasLimit uint64) error {
items := e.ItemCount()
limit := blockGasLimit / params.BALItemCost
if items > limit {
return fmt.Errorf("block access list exceeds size constraint: items=%d, limit=%d (block gas limit %d / %d)",
items, limit, blockGasLimit, params.BALItemCost)
}
return nil
}
// Hash computes the keccak256 hash of the access list
func (e *BlockAccessList) Hash() common.Hash {
var enc bytes.Buffer

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
)
@ -281,6 +282,78 @@ func TestBlockAccessListCopy(t *testing.T) {
}
}
func TestBlockAccessListItemCount(t *testing.T) {
empty := &BlockAccessList{}
if got := empty.ItemCount(); got != 0 {
t.Fatalf("empty BAL item count: got %d, want 0", got)
}
addr1 := [20]byte(testrand.Bytes(20))
addr2 := [20]byte(testrand.Bytes(20))
one := func() *uint256.Int { return new(uint256.Int).SetBytes(testrand.Bytes(32)) }
bal := &BlockAccessList{
AccountAccess{
Address: addr1,
StorageWrites: []encodingSlotWrites{
{Slot: one(), Accesses: []encodingStorageWrite{{TxIdx: 0, ValueAfter: one()}, {TxIdx: 1, ValueAfter: one()}}},
{Slot: one()},
},
StorageReads: []*uint256.Int{one()},
},
AccountAccess{Address: addr2}, // address-only, no slots
}
// 2 addresses + 2 write-slots + 1 read-slot = 5 items.
// (Multiple TxIdx writes to the same slot count as ONE item.)
if got := bal.ItemCount(); got != 5 {
t.Fatalf("item count: got %d, want 5", got)
}
}
func TestBlockAccessListValidateSize(t *testing.T) {
// Build a BAL with exactly 30 items: 3 addresses, each with 9 storage
// slots (some writes, some reads). 3 + 9*3 = 30.
one := func() *uint256.Int { return new(uint256.Int).SetBytes(testrand.Bytes(32)) }
bal := make(BlockAccessList, 3)
for i := range bal {
bal[i].Address = [20]byte(testrand.Bytes(20))
for j := 0; j < 5; j++ {
bal[i].StorageWrites = append(bal[i].StorageWrites, encodingSlotWrites{
Slot: one(), Accesses: []encodingStorageWrite{{TxIdx: 0, ValueAfter: one()}},
})
}
for j := 0; j < 4; j++ {
bal[i].StorageReads = append(bal[i].StorageReads, one())
}
}
if got := bal.ItemCount(); got != 30 {
t.Fatalf("setup: item count = %d, want 30", got)
}
// limit = blockGasLimit / BALItemCost.
// 30 items requires limit >= 30, i.e. gasLimit >= 30 * 2000 = 60_000.
tests := []struct {
name string
gasLimit uint64
expectError bool
}{
{"exactly at limit", 30 * params.BALItemCost, false},
{"well above limit", 60_000_000, false},
{"one below limit", 30*params.BALItemCost - 1, true},
{"zero gas limit", 0, true},
}
for _, tc := range tests {
err := bal.ValidateSize(tc.gasLimit)
if (err != nil) != tc.expectError {
t.Errorf("%s: got err=%v, expectError=%v", tc.name, err, tc.expectError)
}
}
// Empty BAL is always valid (even with 0 gas limit).
if err := (&BlockAccessList{}).ValidateSize(0); err != nil {
t.Fatalf("empty BAL must pass any limit: %v", err)
}
}
func TestBlockAccessListValidation(t *testing.T) {
// Validate the block access list after RLP decoding
enc := makeTestBAL(true)

View file

@ -186,6 +186,16 @@ const (
HistoryServeWindow = 8191 // Number of blocks to serve historical block hashes for, EIP-2935.
MaxBlockSize = 8_388_608 // maximum size of an RLP-encoded block
// BALItemCost is the gas-cost divisor for the EIP-7928 block access list
// size constraint: bal_items <= block_gas_limit / BALItemCost, where
// bal_items counts every distinct address in the BAL plus every storage
// key (writes + reads) carried by those accounts.
//
// The value (2000) is set deliberately below COLD_SLOAD_COST (2100) so
// the bound has a small safety margin for system-contract accesses that
// don't consume block gas.
BALItemCost uint64 = 2000
)
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation