mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 18:02:24 +00:00
core/state: define code reader interface
This commit is contained in:
parent
5f8322c964
commit
04bdd12ff7
5 changed files with 151 additions and 54 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue