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 { if err != nil {
return fmt.Errorf("code %x: %v", account.CodeHash, err) 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() it.accountHash = it.stateIt.Parent()
return nil return nil

View file

@ -35,12 +35,46 @@ import (
// CodeReader defines the interface for accessing contract code. // CodeReader defines the interface for accessing contract code.
type CodeReader interface { type CodeReader interface {
// ContractCode retrieves a particular contract's code. // 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) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error)
// ContractCodeSize retrieves a particular contracts code's size. // 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) 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 // cachingCodeReader implements CodeReader, accessing contract code either in
// local key-value store or the shared code cache. // local key-value store or the shared code cache.
type cachingCodeReader struct { type cachingCodeReader struct {
@ -62,6 +96,7 @@ func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstraine
} }
// ContractCode implements CodeReader, retrieving a particular contract's code. // 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) { func (r *cachingCodeReader) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) {
code, _ := r.codeCache.Get(codeHash) code, _ := r.codeCache.Get(codeHash)
if len(code) > 0 { if len(code) > 0 {
@ -71,37 +106,21 @@ func (r *cachingCodeReader) ContractCode(addr common.Address, codeHash common.Ha
if len(code) > 0 { if len(code) > 0 {
r.codeCache.Add(codeHash, code) r.codeCache.Add(codeHash, code)
r.codeSizeCache.Add(codeHash, len(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. // 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) { func (r *cachingCodeReader) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
if cached, ok := r.codeSizeCache.Get(codeHash); ok { if cached, ok := r.codeSizeCache.Get(codeHash); ok {
return cached, nil return cached, nil
} }
code, err := r.ContractCode(addr, codeHash) code, err := r.ContractCode(addr, codeHash)
return len(code), err if err != nil {
} return 0, err
}
// StateReader defines the interface for accessing accounts and storage slots return len(code), nil
// 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)
} }
// flatReader wraps a database state reader. // 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 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. // singleReader is the wrapper of CodeReader and StateReader interface.
type singleReader struct { type singleReader struct {
CodeReader CodeReader

View file

@ -514,6 +514,9 @@ func (s *stateObject) Code() []byte {
if err != nil { if err != nil {
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err)) 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 s.code = code
return code return code
} }
@ -532,6 +535,9 @@ func (s *stateObject) CodeSize() int {
if err != nil { if err != nil {
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err)) 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 return size
} }

View file

@ -18,6 +18,7 @@ package state
import ( import (
"bytes" "bytes"
"fmt"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "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 { for i, element := range codeElements {
data, err := cReader.ContractCode(common.Address{}, element.code) 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) t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code)
} }
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data} 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) codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1)
for i, element := range codeElements[:len(codeResults)] { for i, element := range codeElements[:len(codeResults)] {
data, err := cReader.ContractCode(common.Address{}, element.code) 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) t.Fatalf("failed to retrieve contract bytecode for %x", element.code)
} }
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data} 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)) results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue { for hash := range codeQueue {
data, err := cReader.ContractCode(common.Address{}, 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) t.Fatalf("failed to retrieve node data for %x", hash)
} }
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data}) results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@ -550,7 +551,7 @@ func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) {
delete(codeQueue, hash) delete(codeQueue, hash)
data, err := cReader.ContractCode(common.Address{}, 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) t.Fatalf("failed to retrieve node data for %x", hash)
} }
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data}) 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)) results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue { for hash := range codeQueue {
data, err := cReader.ContractCode(common.Address{}, 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) t.Fatalf("failed to retrieve node data for %x", hash)
} }
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data}) 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 // Sanity check that removing any node from the database is detected
for _, node := range addedCodes { for _, node := range addedCodes {
val := rawdb.ReadCode(dstDb, node) val := rawdb.ReadCode(dstDb, node)
if len(val) == 0 {
fmt.Println("node code")
} else {
fmt.Println("has code")
}
rawdb.DeleteCode(dstDb, node) rawdb.DeleteCode(dstDb, node)
if err := checkStateConsistency(dstDb, ndb.Scheme(), srcRoot); err == nil { if err := checkStateConsistency(dstDb, ndb.Scheme(), srcRoot); err == nil {
t.Errorf("trie inconsistency not caught, missing: %x", node) t.Errorf("trie inconsistency not caught, missing: %x", node)