core/state: rework the code reader

This commit is contained in:
Gary Rong 2024-11-28 11:11:53 +08:00
parent 04bdd12ff7
commit c3e12a3dfb
4 changed files with 61 additions and 34 deletions

View file

@ -140,6 +140,9 @@ func (it *nodeIterator) step() error {
if err != nil {
return fmt.Errorf("code %x: %v", account.CodeHash, err)
}
if len(it.code) == 0 {
return fmt.Errorf("code is not found: %x", account.CodeHash)
}
}
it.accountHash = it.stateIt.Parent()
return nil

View file

@ -35,12 +35,46 @@ import (
// CodeReader defines the interface for accessing contract code.
type CodeReader interface {
// ContractCode retrieves a particular contract's code.
//
// - Returns nil code along with nil error if the requested contract code
// doesn't exist
// - Returns an error only if an unexpected issue occurs
ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error)
// ContractCodeSize retrieves a particular contracts code's size.
//
// - Returns zero code size along with nil error if the requested contract code
// doesn't exist
// - Returns an error only if an unexpected issue occurs
ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error)
}
// StateReader defines the interface for accessing accounts and storage slots
// associated with a specific state.
type StateReader interface {
// Account retrieves the account associated with a particular address.
//
// - Returns a nil account if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned account is safe to modify after the call
Account(addr common.Address) (*types.StateAccount, error)
// Storage retrieves the storage slot associated with a particular account
// address and slot key.
//
// - Returns an empty slot if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned storage slot is safe to modify after the call
Storage(addr common.Address, slot common.Hash) (common.Hash, error)
}
// Reader defines the interface for accessing accounts, storage slots and contract
// code associated with a specific state.
type Reader interface {
CodeReader
StateReader
}
// cachingCodeReader implements CodeReader, accessing contract code either in
// local key-value store or the shared code cache.
type cachingCodeReader struct {
@ -62,6 +96,7 @@ func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstraine
}
// ContractCode implements CodeReader, retrieving a particular contract's code.
// If the contract code doesn't exist, no error will be returned.
func (r *cachingCodeReader) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) {
code, _ := r.codeCache.Get(codeHash)
if len(code) > 0 {
@ -71,37 +106,21 @@ func (r *cachingCodeReader) ContractCode(addr common.Address, codeHash common.Ha
if len(code) > 0 {
r.codeCache.Add(codeHash, code)
r.codeSizeCache.Add(codeHash, len(code))
return code, nil
}
return nil, errors.New("not found")
return code, nil
}
// ContractCodeSize implements CodeReader, retrieving a particular contracts code's size.
// If the contract code doesn't exist, no error will be returned.
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 StateReader interface {
// Account retrieves the account associated with a particular address.
//
// - Returns a nil account if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned account is safe to modify after the call
Account(addr common.Address) (*types.StateAccount, error)
// Storage retrieves the storage slot associated with a particular account
// address and slot key.
//
// - Returns an empty slot if it does not exist
// - Returns an error only if an unexpected issue occurs
// - The returned storage slot is safe to modify after the call
Storage(addr common.Address, slot common.Hash) (common.Hash, error)
if err != nil {
return 0, err
}
return len(code), nil
}
// flatReader wraps a database state reader.
@ -271,13 +290,6 @@ 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

View file

@ -514,6 +514,9 @@ func (s *stateObject) Code() []byte {
if err != nil {
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
}
if len(code) == 0 {
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
}
s.code = code
return code
}
@ -532,6 +535,9 @@ func (s *stateObject) CodeSize() int {
if err != nil {
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
}
if size == 0 {
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
}
return size
}

View file

@ -18,6 +18,7 @@ package state
import (
"bytes"
"fmt"
"testing"
"github.com/ethereum/go-ethereum/common"
@ -221,7 +222,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool, s
)
for i, element := range codeElements {
data, err := cReader.ContractCode(common.Address{}, element.code)
if err != nil {
if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code)
}
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
@ -345,7 +346,7 @@ func testIterativeDelayedStateSync(t *testing.T, scheme string) {
codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1)
for i, element := range codeElements[:len(codeResults)] {
data, err := cReader.ContractCode(common.Address{}, element.code)
if err != nil {
if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve contract bytecode for %x", element.code)
}
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
@ -451,7 +452,7 @@ func testIterativeRandomStateSync(t *testing.T, count int, scheme string) {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
data, err := cReader.ContractCode(common.Address{}, hash)
if err != nil {
if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@ -550,7 +551,7 @@ func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) {
delete(codeQueue, hash)
data, err := cReader.ContractCode(common.Address{}, hash)
if err != nil {
if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@ -670,7 +671,7 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
data, err := cReader.ContractCode(common.Address{}, hash)
if err != nil {
if err != nil || len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@ -733,6 +734,11 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
// Sanity check that removing any node from the database is detected
for _, node := range addedCodes {
val := rawdb.ReadCode(dstDb, node)
if len(val) == 0 {
fmt.Println("node code")
} else {
fmt.Println("has code")
}
rawdb.DeleteCode(dstDb, node)
if err := checkStateConsistency(dstDb, ndb.Scheme(), srcRoot); err == nil {
t.Errorf("trie inconsistency not caught, missing: %x", node)