Skip to content

Commit dc3794e

Browse files
jrheajwasingerrjl493456442
authored
core/rawdb: BAL storage layer (ethereum#34064)
Add persistent storage for Block Access Lists (BALs) in `core/rawdb/`. This provides read/write/delete accessors for BALs in the active key-value store. --------- Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com> Co-authored-by: Gary Rong <garyrong0905@gmail.com>
1 parent 965bd6b commit dc3794e

10 files changed

Lines changed: 269 additions & 68 deletions

File tree

consensus/beacon/consensus.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,22 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
275275
}
276276
}
277277

278+
// Verify the existence / non-existence of Amsterdam-specific header fields
278279
amsterdam := chain.Config().IsAmsterdam(header.Number, header.Time)
279-
if amsterdam && header.SlotNumber == nil {
280-
return errors.New("header is missing slotNumber")
281-
}
282-
if !amsterdam && header.SlotNumber != nil {
283-
return fmt.Errorf("invalid slotNumber: have %d, expected nil", *header.SlotNumber)
280+
if amsterdam {
281+
if header.BlockAccessListHash == nil {
282+
return errors.New("header is missing block access list hash")
283+
}
284+
if header.SlotNumber == nil {
285+
return errors.New("header is missing slotNumber")
286+
}
287+
} else {
288+
if header.BlockAccessListHash != nil {
289+
return fmt.Errorf("invalid block access list hash: have %x, expected nil", *header.BlockAccessListHash)
290+
}
291+
if header.SlotNumber != nil {
292+
return fmt.Errorf("invalid slotNumber: have %d, expected nil", *header.SlotNumber)
293+
}
284294
}
285295
return nil
286296
}

core/rawdb/accessors_chain.go

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/ethereum/go-ethereum/common"
2727
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
2828
"github.com/ethereum/go-ethereum/core/types"
29+
"github.com/ethereum/go-ethereum/core/types/bal"
2930
"github.com/ethereum/go-ethereum/crypto"
3031
"github.com/ethereum/go-ethereum/ethdb"
3132
"github.com/ethereum/go-ethereum/log"
@@ -605,6 +606,55 @@ func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
605606
}
606607
}
607608

609+
// HasAccessList verifies the existence of a block access list for a block.
610+
func HasAccessList(db ethdb.Reader, hash common.Hash, number uint64) bool {
611+
has, _ := db.Has(accessListKey(number, hash))
612+
return has
613+
}
614+
615+
// ReadAccessListRLP retrieves the RLP-encoded block access list for a block from KV.
616+
func ReadAccessListRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
617+
data, _ := db.Get(accessListKey(number, hash))
618+
return data
619+
}
620+
621+
// ReadAccessList retrieves and decodes the block access list for a block.
622+
func ReadAccessList(db ethdb.Reader, hash common.Hash, number uint64) *bal.BlockAccessList {
623+
data := ReadAccessListRLP(db, hash, number)
624+
if len(data) == 0 {
625+
return nil
626+
}
627+
b := new(bal.BlockAccessList)
628+
if err := rlp.DecodeBytes(data, b); err != nil {
629+
log.Error("Invalid BAL RLP", "hash", hash, "err", err)
630+
return nil
631+
}
632+
return b
633+
}
634+
635+
// WriteAccessList RLP-encodes and stores a block access list in the active KV store.
636+
func WriteAccessList(db ethdb.KeyValueWriter, hash common.Hash, number uint64, b *bal.BlockAccessList) {
637+
bytes, err := rlp.EncodeToBytes(b)
638+
if err != nil {
639+
log.Crit("Failed to encode BAL", "err", err)
640+
}
641+
WriteAccessListRLP(db, hash, number, bytes)
642+
}
643+
644+
// WriteAccessListRLP stores a pre-encoded block access list in the active KV store.
645+
func WriteAccessListRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, encoded rlp.RawValue) {
646+
if err := db.Put(accessListKey(number, hash), encoded); err != nil {
647+
log.Crit("Failed to store BAL", "err", err)
648+
}
649+
}
650+
651+
// DeleteAccessList removes a block access list from the active KV store.
652+
func DeleteAccessList(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
653+
if err := db.Delete(accessListKey(number, hash)); err != nil {
654+
log.Crit("Failed to delete BAL", "err", err)
655+
}
656+
}
657+
608658
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
609659
// the list of logs. When decoding a stored receipt into this object we
610660
// avoid creating the bloom filter.
@@ -659,13 +709,25 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
659709
if body == nil {
660710
return nil
661711
}
662-
return types.NewBlockWithHeader(header).WithBody(*body)
712+
block := types.NewBlockWithHeader(header).WithBody(*body)
713+
714+
// Best-effort assembly of the block access list from the database.
715+
if header.BlockAccessListHash != nil {
716+
al := ReadAccessList(db, hash, number)
717+
block = block.WithAccessListUnsafe(al)
718+
}
719+
return block
663720
}
664721

665722
// WriteBlock serializes a block into the database, header and body separately.
666723
func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) {
667-
WriteBody(db, block.Hash(), block.NumberU64(), block.Body())
724+
hash, number := block.Hash(), block.NumberU64()
725+
WriteBody(db, hash, number, block.Body())
668726
WriteHeader(db, block.Header())
727+
728+
if accessList := block.AccessList(); accessList != nil {
729+
WriteAccessList(db, hash, number, accessList)
730+
}
669731
}
670732

671733
// WriteAncientBlocks writes entire block data into ancient store and returns the total written size.

core/rawdb/accessors_chain_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ import (
2727

2828
"github.com/ethereum/go-ethereum/common"
2929
"github.com/ethereum/go-ethereum/core/types"
30+
"github.com/ethereum/go-ethereum/core/types/bal"
3031
"github.com/ethereum/go-ethereum/crypto"
3132
"github.com/ethereum/go-ethereum/crypto/keccak"
3233
"github.com/ethereum/go-ethereum/params"
3334
"github.com/ethereum/go-ethereum/rlp"
35+
"github.com/holiman/uint256"
3436
)
3537

3638
// Tests block header storage and retrieval operations.
@@ -899,3 +901,78 @@ func TestHeadersRLPStorage(t *testing.T) {
899901
checkSequence(1, 1) // Only block 1
900902
checkSequence(1, 2) // Genesis + block 1
901903
}
904+
905+
func makeTestBAL(t *testing.T) (rlp.RawValue, *bal.BlockAccessList) {
906+
t.Helper()
907+
908+
cb := bal.NewConstructionBlockAccessList()
909+
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
910+
cb.AccountRead(addr)
911+
cb.StorageRead(addr, common.BytesToHash([]byte{0x01}))
912+
cb.StorageWrite(0, addr, common.BytesToHash([]byte{0x02}), common.BytesToHash([]byte{0xaa}))
913+
cb.BalanceChange(0, addr, uint256.NewInt(100))
914+
cb.NonceChange(addr, 0, 1)
915+
916+
var buf bytes.Buffer
917+
if err := cb.EncodeRLP(&buf); err != nil {
918+
t.Fatalf("failed to encode BAL: %v", err)
919+
}
920+
encoded := buf.Bytes()
921+
922+
var decoded bal.BlockAccessList
923+
if err := rlp.DecodeBytes(encoded, &decoded); err != nil {
924+
t.Fatalf("failed to decode BAL: %v", err)
925+
}
926+
return encoded, &decoded
927+
}
928+
929+
// TestBALStorage tests write/read/delete of BALs in the KV store.
930+
func TestBALStorage(t *testing.T) {
931+
db := NewMemoryDatabase()
932+
933+
hash := common.BytesToHash([]byte{0x03, 0x14})
934+
number := uint64(42)
935+
936+
// Check that no BAL exists in a new database.
937+
if HasAccessList(db, hash, number) {
938+
t.Fatal("BAL found in new database")
939+
}
940+
if b := ReadAccessList(db, hash, number); b != nil {
941+
t.Fatalf("non existent BAL returned: %v", b)
942+
}
943+
944+
// Write a BAL and verify it can be read back.
945+
encoded, testBAL := makeTestBAL(t)
946+
WriteAccessList(db, hash, number, testBAL)
947+
948+
if !HasAccessList(db, hash, number) {
949+
t.Fatal("HasAccessList returned false after write")
950+
}
951+
if blob := ReadAccessListRLP(db, hash, number); len(blob) == 0 {
952+
t.Fatal("ReadAccessListRLP returned empty after write")
953+
}
954+
if b := ReadAccessList(db, hash, number); b == nil {
955+
t.Fatal("ReadAccessList returned nil after write")
956+
} else if b.Hash() != testBAL.Hash() {
957+
t.Fatalf("BAL hash mismatch: got %x, want %x", b.Hash(), testBAL.Hash())
958+
}
959+
960+
// Also test WriteAccessListRLP with pre-encoded data.
961+
hash2 := common.BytesToHash([]byte{0x03, 0x15})
962+
WriteAccessListRLP(db, hash2, number, encoded)
963+
if b := ReadAccessList(db, hash2, number); b == nil {
964+
t.Fatal("ReadAccessList returned nil after WriteAccessListRLP")
965+
} else if b.Hash() != testBAL.Hash() {
966+
t.Fatalf("BAL hash mismatch after WriteAccessListRLP: got %x, want %x", b.Hash(), testBAL.Hash())
967+
}
968+
969+
// Delete the BAL and verify it's gone.
970+
DeleteAccessList(db, hash, number)
971+
972+
if HasAccessList(db, hash, number) {
973+
t.Fatal("HasAccessList returned true after delete")
974+
}
975+
if b := ReadAccessList(db, hash, number); b != nil {
976+
t.Fatalf("deleted BAL returned: %v", b)
977+
}
978+
}

core/rawdb/database.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
413413
tds stat
414414
numHashPairings stat
415415
hashNumPairings stat
416+
blockAccessList stat
416417
legacyTries stat
417418
stateLookups stat
418419
accountTries stat
@@ -484,6 +485,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
484485
numHashPairings.add(size)
485486
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
486487
hashNumPairings.add(size)
488+
case bytes.HasPrefix(key, accessListPrefix) && len(key) == len(accessListPrefix)+8+common.HashLength:
489+
blockAccessList.add(size)
490+
487491
case IsLegacyTrieNode(key, it.Value()):
488492
legacyTries.add(size)
489493
case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength:
@@ -625,6 +629,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
625629
{"Key-Value store", "Difficulties (deprecated)", tds.sizeString(), tds.countString()},
626630
{"Key-Value store", "Block number->hash", numHashPairings.sizeString(), numHashPairings.countString()},
627631
{"Key-Value store", "Block hash->number", hashNumPairings.sizeString(), hashNumPairings.countString()},
632+
{"Key-Value store", "Block accessList", blockAccessList.sizeString(), blockAccessList.countString()},
628633
{"Key-Value store", "Transaction index", txLookups.sizeString(), txLookups.countString()},
629634
{"Key-Value store", "Log index filter-map rows", filterMapRows.sizeString(), filterMapRows.countString()},
630635
{"Key-Value store", "Log index last-block-of-map", filterMapLastBlock.sizeString(), filterMapLastBlock.countString()},

core/rawdb/schema.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ var (
112112

113113
blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body
114114
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
115+
accessListPrefix = []byte("j") // accessListPrefix + num (uint64 big endian) + hash -> block access list
115116

116117
txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata
117118
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
@@ -214,6 +215,11 @@ func blockReceiptsKey(number uint64, hash common.Hash) []byte {
214215
return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
215216
}
216217

218+
// accessListKey = accessListPrefix + num (uint64 big endian) + hash
219+
func accessListKey(number uint64, hash common.Hash) []byte {
220+
return append(append(accessListPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
221+
}
222+
217223
// txLookupKey = txLookupPrefix + hash
218224
func txLookupKey(hash common.Hash) []byte {
219225
return append(txLookupPrefix, hash.Bytes()...)

core/types/bal/bal_encoding.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,12 @@ func (e *BlockAccessList) PrettyPrint() string {
350350
}
351351

352352
// Copy returns a deep copy of the access list
353-
func (e *BlockAccessList) Copy() (res BlockAccessList) {
353+
func (e *BlockAccessList) Copy() *BlockAccessList {
354+
cpy := &BlockAccessList{
355+
Accesses: make([]AccountAccess, 0, len(e.Accesses)),
356+
}
354357
for _, accountAccess := range e.Accesses {
355-
res.Accesses = append(res.Accesses, accountAccess.Copy())
358+
cpy.Accesses = append(cpy.Accesses, accountAccess.Copy())
356359
}
357-
return
360+
return cpy
358361
}

core/types/bal/bal_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ func makeTestAccountAccess(sort bool) AccountAccess {
190190
}
191191
}
192192

193-
func makeTestBAL(sort bool) BlockAccessList {
194-
list := BlockAccessList{}
193+
func makeTestBAL(sort bool) *BlockAccessList {
194+
list := &BlockAccessList{}
195195
for i := 0; i < 5; i++ {
196196
list.Accesses = append(list.Accesses, makeTestAccountAccess(sort))
197197
}

core/types/block.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/ethereum/go-ethereum/common"
3232
"github.com/ethereum/go-ethereum/common/hexutil"
33+
"github.com/ethereum/go-ethereum/core/types/bal"
3334
"github.com/ethereum/go-ethereum/rlp"
3435
)
3536

@@ -99,6 +100,9 @@ type Header struct {
99100
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
100101
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
101102

103+
// BlockAccessListHash was added by EIP-7928 and is ignored in legacy headers.
104+
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
105+
102106
// SlotNumber was added by EIP-7843 and is ignored in legacy headers.
103107
SlotNumber *uint64 `json:"slotNumber" rlp:"optional"`
104108
}
@@ -204,6 +208,7 @@ type Block struct {
204208
uncles []*Header
205209
transactions Transactions
206210
withdrawals Withdrawals
211+
accessList *bal.BlockAccessList
207212

208213
// caches
209214
hash atomic.Pointer[common.Hash]
@@ -358,9 +363,10 @@ func (b *Block) Body() *Body {
358363
// Accessors for body data. These do not return a copy because the content
359364
// of the body slices does not affect the cached hash/size in block.
360365

361-
func (b *Block) Uncles() []*Header { return b.uncles }
362-
func (b *Block) Transactions() Transactions { return b.transactions }
363-
func (b *Block) Withdrawals() Withdrawals { return b.withdrawals }
366+
func (b *Block) Uncles() []*Header { return b.uncles }
367+
func (b *Block) Transactions() Transactions { return b.transactions }
368+
func (b *Block) Withdrawals() Withdrawals { return b.withdrawals }
369+
func (b *Block) AccessList() *bal.BlockAccessList { return b.accessList }
364370

365371
func (b *Block) Transaction(hash common.Hash) *Transaction {
366372
for _, transaction := range b.transactions {
@@ -513,6 +519,24 @@ func (b *Block) WithBody(body Body) *Block {
513519
return block
514520
}
515521

522+
// WithAccessList returns a copy of the block with the given access list embedded.
523+
func (b *Block) WithAccessList(accessList *bal.BlockAccessList) *Block {
524+
return b.WithAccessListUnsafe(accessList.Copy())
525+
}
526+
527+
// WithAccessListUnsafe returns a copy of the block with the given access list
528+
// embedded. Note that the access list is not deep-copied; use WithAccessList
529+
// if the provided list may be modified by other actors.
530+
func (b *Block) WithAccessListUnsafe(accessList *bal.BlockAccessList) *Block {
531+
return &Block{
532+
header: b.header,
533+
transactions: b.transactions,
534+
uncles: b.uncles,
535+
withdrawals: b.withdrawals,
536+
accessList: accessList,
537+
}
538+
}
539+
516540
// Hash returns the keccak256 hash of b's header.
517541
// The hash is computed on the first call and cached thereafter.
518542
func (b *Block) Hash() common.Hash {

0 commit comments

Comments
 (0)