Skip to content

Commit

Permalink
core/state: introduce code reader interface (ethereum#30816)
Browse files Browse the repository at this point in the history
This PR introduces a `ContractCodeReader` interface with functions defined:

type ContractCodeReader interface {
	Code(addr common.Address, codeHash common.Hash) ([]byte, error)
	CodeSize(addr common.Address, codeHash common.Hash) (int, error)
}

This interface can be implemented in various ways. Although the codebase
currently includes only one implementation, additional implementations
could be created for different purposes and scenarios, such as a code
reader designed for the Verkle tree approach or one that reads code from
the witness.

*Notably, this interface modifies the function’s semantics. If the
contract code is not found, no error will be returned. An error should
only be returned in the event of an unexpected issue, primarily for
future implementations.*

The original state.Reader interface is extended with ContractCodeReader
methods, it gives us more flexibility to manipulate the reader with additional
logic on top, e.g. Hooks.

type Reader interface {
	ContractCodeReader
	StateReader
}

---------

Co-authored-by: Felix Lange <[email protected]>
  • Loading branch information
rjl493456442 and fjl authored Nov 29, 2024
1 parent 05148d9 commit 03c37cd
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 120 deletions.
5 changes: 1 addition & 4 deletions core/blockchain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,7 @@ func (bc *BlockChain) stateRecoverable(root common.Hash) bool {

// ContractCodeWithPrefix retrieves a blob of data associated with a contract
// hash either from ephemeral in-memory cache, or from persistent storage.
//
// If the code doesn't exist in the in-memory cache, check the storage with
// new code scheme.
func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) {
func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte {
// TODO(rjl493456442) The associated account address is also required
// in Verkle scheme. Fix it once snap-sync is supported for Verkle.
return bc.statedb.ContractCodeWithPrefix(common.Address{}, hash)
Expand Down
48 changes: 10 additions & 38 deletions core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package state

import (
"errors"
"fmt"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -55,12 +54,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

Expand Down Expand Up @@ -180,15 +173,15 @@ func NewDatabaseForTesting() *CachingDB {

// Reader returns a state reader associated with the specified state root.
func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
var readers []Reader
var readers []StateReader

// 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, newFlatReader(snap))
}
}
// Set up the trie reader, which is expected to always be available
Expand All @@ -199,7 +192,11 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
}
readers = append(readers, tr)

return newMultiReader(readers...)
combined, err := newMultiStateReader(readers...)
if err != nil {
return nil, err
}
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
}

// OpenTrie opens the main account trie at a specific root hash.
Expand Down Expand Up @@ -229,45 +226,20 @@ 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.
func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error) {
func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) []byte {
code, _ := db.codeCache.Get(codeHash)
if len(code) > 0 {
return code, nil
return code
}
code = rawdb.ReadCodeWithPrefix(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")
}

// 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
return code
}

// TrieDB retrieves any intermediate trie-node caching layer.
Expand Down
5 changes: 4 additions & 1 deletion core/state/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,13 @@ 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.Code(address, common.BytesToHash(account.CodeHash))
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
Expand Down
Loading

0 comments on commit 03c37cd

Please sign in to comment.