mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
core, params: implement bal size constraint
This commit is contained in:
parent
0ac419900e
commit
4eabe32e26
4 changed files with 116 additions and 1 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue