core/state: define code reader interface

This commit is contained in:
Gary Rong 2024-11-27 16:55:17 +08:00
parent 5f8322c964
commit 04bdd12ff7
5 changed files with 151 additions and 54 deletions

View file

@ -55,12 +55,6 @@ type Database interface {
// OpenStorageTrie opens the storage trie of an account.
OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error)
// ContractCode retrieves a particular contract's code.
ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error)
// ContractCodeSize retrieves a particular contracts code's size.
ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error)
// PointCache returns the cache holding points used in verkle tree key computation
PointCache() *utils.PointCache
@ -182,13 +176,17 @@ func NewDatabaseForTesting() *CachingDB {
func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
var readers []Reader
// Construct the code reader with retained caches. These caches are
// thread safe.
cReader := newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache)
// Set up the state snapshot reader if available. This feature
// is optional and may be partially useful if it's not fully
// generated.
if db.snap != nil {
snap := db.snap.Snapshot(stateRoot)
if snap != nil {
readers = append(readers, newStateReader(snap)) // snap reader is optional
readers = append(readers, newSingleReader(cReader, newFlatReader(snap)))
}
}
// Set up the trie reader, which is expected to always be available
@ -197,7 +195,7 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
if err != nil {
return nil, err
}
readers = append(readers, tr)
readers = append(readers, newSingleReader(cReader, tr))
return newMultiReader(readers...)
}
@ -229,21 +227,6 @@ func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre
return tr, nil
}
// ContractCode retrieves a particular contract's code.
func (db *CachingDB) ContractCode(address common.Address, codeHash common.Hash) ([]byte, error) {
code, _ := db.codeCache.Get(codeHash)
if len(code) > 0 {
return code, nil
}
code = rawdb.ReadCode(db.disk, codeHash)
if len(code) > 0 {
db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code))
return code, nil
}
return nil, errors.New("not found")
}
// ContractCodeWithPrefix retrieves a particular contract's code. If the
// code can't be found in the cache, then check the existence with **new**
// db scheme.
@ -261,15 +244,6 @@ func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash com
return nil, errors.New("not found")
}
// ContractCodeSize retrieves a particular contracts code's size.
func (db *CachingDB) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
if cached, ok := db.codeSizeCache.Get(codeHash); ok {
return cached, nil
}
code, err := db.ContractCode(addr, codeHash)
return len(code), err
}
// TrieDB retrieves any intermediate trie-node caching layer.
func (db *CachingDB) TrieDB() *triedb.Database {
return db.triedb

View file

@ -136,7 +136,7 @@ func (it *nodeIterator) step() error {
}
if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
it.codeHash = common.BytesToHash(account.CodeHash)
it.code, err = it.state.db.ContractCode(address, common.BytesToHash(account.CodeHash))
it.code, err = it.state.reader.ContractCode(address, common.BytesToHash(account.CodeHash))
if err != nil {
return fmt.Errorf("code %x: %v", account.CodeHash, err)
}

View file

@ -20,8 +20,11 @@ import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils"
@ -29,9 +32,62 @@ import (
"github.com/ethereum/go-ethereum/triedb/database"
)
// Reader defines the interface for accessing accounts and storage slots
// CodeReader defines the interface for accessing contract code.
type CodeReader interface {
// ContractCode retrieves a particular contract's code.
ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error)
// ContractCodeSize retrieves a particular contracts code's size.
ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error)
}
// cachingCodeReader implements CodeReader, accessing contract code either in
// local key-value store or the shared code cache.
type cachingCodeReader struct {
db ethdb.KeyValueReader
// These caches could be shared by multiple code reader instances,
// they are natively thread-safe.
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
}
// newCachingCodeReader constructs the code reader.
func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstrainedCache[common.Hash, []byte], codeSizeCache *lru.Cache[common.Hash, int]) *cachingCodeReader {
return &cachingCodeReader{
db: db,
codeCache: codeCache,
codeSizeCache: codeSizeCache,
}
}
// ContractCode implements CodeReader, retrieving a particular contract's code.
func (r *cachingCodeReader) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) {
code, _ := r.codeCache.Get(codeHash)
if len(code) > 0 {
return code, nil
}
code = rawdb.ReadCode(r.db, codeHash)
if len(code) > 0 {
r.codeCache.Add(codeHash, code)
r.codeSizeCache.Add(codeHash, len(code))
return code, nil
}
return nil, errors.New("not found")
}
// ContractCodeSize implements CodeReader, retrieving a particular contracts code's size.
func (r *cachingCodeReader) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
if cached, ok := r.codeSizeCache.Get(codeHash); ok {
return cached, nil
}
code, err := r.ContractCode(addr, codeHash)
return len(code), err
}
// StateReader defines the interface for accessing accounts and storage slots
// associated with a specific state.
type Reader interface {
type StateReader interface {
// Account retrieves the account associated with a particular address.
//
// - Returns a nil account if it does not exist
@ -48,27 +104,27 @@ type Reader interface {
Storage(addr common.Address, slot common.Hash) (common.Hash, error)
}
// stateReader wraps a database state reader.
type stateReader struct {
// flatReader wraps a database state reader.
type flatReader struct {
reader database.StateReader
buff crypto.KeccakState
}
// newStateReader constructs a state reader with on the given state root.
func newStateReader(reader database.StateReader) *stateReader {
return &stateReader{
// newFlatReader constructs a state reader with on the given state root.
func newFlatReader(reader database.StateReader) *flatReader {
return &flatReader{
reader: reader,
buff: crypto.NewKeccakState(),
}
}
// Account implements Reader, retrieving the account specified by the address.
// Account implements StateReader, retrieving the account specified by the address.
//
// An error will be returned if the associated snapshot is already stale or
// the requested account is not yet covered by the snapshot.
//
// The returned account might be nil if it's not existent.
func (r *stateReader) Account(addr common.Address) (*types.StateAccount, error) {
func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
account, err := r.reader.Account(crypto.HashData(r.buff, addr.Bytes()))
if err != nil {
return nil, err
@ -91,14 +147,14 @@ func (r *stateReader) Account(addr common.Address) (*types.StateAccount, error)
return acct, nil
}
// Storage implements Reader, retrieving the storage slot specified by the
// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key.
//
// An error will be returned if the associated snapshot is already stale or
// the requested storage slot is not yet covered by the snapshot.
//
// The returned storage slot might be empty if it's not existent.
func (r *stateReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
addrHash := crypto.HashData(r.buff, addr.Bytes())
slotHash := crypto.HashData(r.buff, key.Bytes())
ret, err := r.reader.Storage(addrHash, slotHash)
@ -119,7 +175,7 @@ func (r *stateReader) Storage(addr common.Address, key common.Hash) (common.Hash
return value, nil
}
// trieReader implements the Reader interface, providing functions to access
// trieReader implements the StateReader interface, providing functions to access
// state from the referenced trie.
type trieReader struct {
root common.Hash // State root which uniquely represent a state
@ -155,7 +211,7 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
}, nil
}
// Account implements Reader, retrieving the account specified by the address.
// Account implements StateReader, retrieving the account specified by the address.
//
// An error will be returned if the trie state is corrupted. An nil account
// will be returned if it's not existent in the trie.
@ -172,7 +228,7 @@ func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
return account, nil
}
// Storage implements Reader, retrieving the storage slot specified by the
// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key.
//
// An error will be returned if the trie state is corrupted. An empty storage
@ -215,6 +271,27 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
return value, nil
}
// Reader defines the interface for accessing accounts, storage slots and contract
// code associated with a specific state.
type Reader interface {
CodeReader
StateReader
}
// singleReader is the wrapper of CodeReader and StateReader interface.
type singleReader struct {
CodeReader
StateReader
}
// newSingleReader constructs a reader with the supplied code reader and state reader.
func newSingleReader(codeReader CodeReader, stateReader StateReader) *singleReader {
return &singleReader{
CodeReader: codeReader,
StateReader: stateReader,
}
}
// multiReader is the aggregation of a list of Reader interface, providing state
// access by leveraging all readers. The checking priority is determined by the
// position in the reader list.
@ -269,3 +346,29 @@ func (r *multiReader) Storage(addr common.Address, slot common.Hash) (common.Has
}
return common.Hash{}, errors.Join(errs...)
}
// ContractCode implements Reader, retrieving a particular contract's code.
func (r *multiReader) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) {
var errs []error
for _, reader := range r.readers {
code, err := reader.ContractCode(addr, codeHash)
if err == nil {
return code, nil
}
errs = append(errs, err)
}
return nil, errors.Join(errs...)
}
// ContractCodeSize implements Reader, retrieving a particular contracts code's size.
func (r *multiReader) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
var errs []error
for _, reader := range r.readers {
size, err := reader.ContractCodeSize(addr, codeHash)
if err == nil {
return size, nil
}
errs = append(errs, err)
}
return 0, errors.Join(errs...)
}

View file

@ -510,7 +510,7 @@ func (s *stateObject) Code() []byte {
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return nil
}
code, err := s.db.db.ContractCode(s.address, common.BytesToHash(s.CodeHash()))
code, err := s.db.reader.ContractCode(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
}
@ -528,7 +528,7 @@ func (s *stateObject) CodeSize() int {
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return 0
}
size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash()))
size, err := s.db.reader.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
}

View file

@ -210,13 +210,17 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool, s
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
cReader, err := srcDb.Reader(srcRoot)
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
for len(nodeElements)+len(codeElements) > 0 {
var (
nodeResults = make([]trie.NodeSyncResult, len(nodeElements))
codeResults = make([]trie.CodeSyncResult, len(codeElements))
)
for i, element := range codeElements {
data, err := srcDb.ContractCode(common.Address{}, element.code)
data, err := cReader.ContractCode(common.Address{}, element.code)
if err != nil {
t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code)
}
@ -329,6 +333,10 @@ func testIterativeDelayedStateSync(t *testing.T, scheme string) {
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
cReader, err := srcDb.Reader(srcRoot)
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
for len(nodeElements)+len(codeElements) > 0 {
// Sync only half of the scheduled nodes
var nodeProcessed int
@ -336,7 +344,7 @@ func testIterativeDelayedStateSync(t *testing.T, scheme string) {
if len(codeElements) > 0 {
codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1)
for i, element := range codeElements[:len(codeResults)] {
data, err := srcDb.ContractCode(common.Address{}, element.code)
data, err := cReader.ContractCode(common.Address{}, element.code)
if err != nil {
t.Fatalf("failed to retrieve contract bytecode for %x", element.code)
}
@ -433,12 +441,16 @@ func testIterativeRandomStateSync(t *testing.T, count int, scheme string) {
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
cReader, err := srcDb.Reader(srcRoot)
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
for len(nodeQueue)+len(codeQueue) > 0 {
// Fetch all the queued nodes in a random order
if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
data, err := srcDb.ContractCode(common.Address{}, hash)
data, err := cReader.ContractCode(common.Address{}, hash)
if err != nil {
t.Fatalf("failed to retrieve node data for %x", hash)
}
@ -526,6 +538,10 @@ func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) {
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
cReader, err := srcDb.Reader(srcRoot)
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
for len(nodeQueue)+len(codeQueue) > 0 {
// Sync only half of the scheduled nodes, even those in random order
if len(codeQueue) > 0 {
@ -533,7 +549,7 @@ func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) {
for hash := range codeQueue {
delete(codeQueue, hash)
data, err := srcDb.ContractCode(common.Address{}, hash)
data, err := cReader.ContractCode(common.Address{}, hash)
if err != nil {
t.Fatalf("failed to retrieve node data for %x", hash)
}
@ -631,6 +647,10 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
if err != nil {
t.Fatalf("state is not available %x", srcRoot)
}
cReader, err := srcDb.Reader(srcRoot)
if err != nil {
t.Fatalf("state is not existent, %#x", srcRoot)
}
nodeQueue := make(map[string]stateElement)
codeQueue := make(map[common.Hash]struct{})
paths, nodes, codes := sched.Missing(1)
@ -649,7 +669,7 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
data, err := srcDb.ContractCode(common.Address{}, hash)
data, err := cReader.ContractCode(common.Address{}, hash)
if err != nil {
t.Fatalf("failed to retrieve node data for %x", hash)
}