core/state: seperate trie reader to mptReader and ubtReader

This commit is contained in:
Gary Rong 2026-04-20 09:42:03 +08:00
parent 53ff723cc7
commit cb15100422
3 changed files with 113 additions and 80 deletions

View file

@ -81,7 +81,7 @@ func (db *MPTDatabase) StateReader(stateRoot common.Hash) (StateReader, error) {
} }
// Configure the trie reader, which is expected to be available as the // Configure the trie reader, which is expected to be available as the
// gatekeeper unless the state is corrupted. // gatekeeper unless the state is corrupted.
tr, err := newTrieReader(stateRoot, db.triedb) tr, err := newMPTTrieReader(stateRoot, db.triedb)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -61,7 +61,7 @@ func (db *UBTDatabase) StateReader(stateRoot common.Hash) (StateReader, error) {
} }
// Configure the trie reader, which is expected to be available as the // Configure the trie reader, which is expected to be available as the
// gatekeeper unless the state is corrupted. // gatekeeper unless the state is corrupted.
tr, err := newTrieReader(stateRoot, db.triedb) tr, err := newUBTTrieReader(stateRoot, db.triedb)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -148,43 +148,124 @@ func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash,
return value, nil return value, nil
} }
// trieReader implements the StateReader interface, providing functions to access // mptTrieReader implements the StateReader interface, providing functions to
// state from the referenced trie. // access state from the referenced Merkle-Patricia-tree.
// //
// trieReader is safe for concurrent read. // mptTrieReader is safe for concurrent read.
type trieReader struct { type mptTrieReader struct {
root common.Hash // State root which uniquely represent a state root common.Hash // State root which uniquely represent a state
db *triedb.Database // Database for loading trie db *triedb.Database // Database for loading trie
// Main trie, resolved in constructor. Note either the Merkle-Patricia-tree mainTrie Trie // Main trie, resolved in constructor, not thread-safe
// or Verkle-tree is not safe for concurrent read.
mainTrie Trie
subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved
subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved
lock sync.Mutex // Lock for protecting concurrent read lock sync.Mutex // Lock for protecting concurrent read
} }
// newTrieReader constructs a trie reader of the specific state. An error will be // newMPTTrieReader constructs a Merkle-Patricia-tree reader of the specific state.
// returned if the associated trie specified by root is not existent. // An error will be returned if the associated trie specified by root is not existent.
func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) { func newMPTTrieReader(root common.Hash, db *triedb.Database) (*mptTrieReader, error) {
var ( tr, err := trie.NewStateTrie(trie.StateTrieID(root), db)
tr Trie if err != nil {
err error return nil, err
) }
if !db.IsUBT() { return &mptTrieReader{
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db) root: root,
db: db,
mainTrie: tr,
subRoots: make(map[common.Address]common.Hash),
subTries: make(map[common.Address]Trie),
}, nil
}
// account is the inner version of Account and assumes the r.lock is already held.
func (r *mptTrieReader) account(addr common.Address) (*types.StateAccount, error) {
account, err := r.mainTrie.GetAccount(addr)
if err != nil {
return nil, err
}
if account == nil {
r.subRoots[addr] = types.EmptyRootHash
} else { } else {
// When IsUBT() is true, create a BinaryTrie wrapped in TransitionTrie r.subRoots[addr] = account.Root
}
return account, nil
}
// 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.
func (r *mptTrieReader) Account(addr common.Address) (*types.StateAccount, error) {
r.lock.Lock()
defer r.lock.Unlock()
return r.account(addr)
}
// 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
// slot will be returned if it's not existent in the trie.
func (r *mptTrieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
r.lock.Lock()
defer r.lock.Unlock()
tr, found := r.subTries[addr]
if !found {
root, ok := r.subRoots[addr]
// The storage slot is accessed without account caching. It's unexpected
// behavior but try to resolve the account first anyway.
if !ok {
_, err := r.account(addr)
if err != nil {
return common.Hash{}, err
}
root = r.subRoots[addr]
}
var err error
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.Keccak256Hash(addr.Bytes()), root), r.db)
if err != nil {
return common.Hash{}, err
}
r.subTries[addr] = tr
}
ret, err := tr.GetStorage(addr, key.Bytes())
if err != nil {
return common.Hash{}, err
}
var value common.Hash
value.SetBytes(ret)
return value, nil
}
// ubtTrieReader implements the StateReader interface, providing functions to access
// state from the referenced Unified-binary-trie.
//
// ubtTrieReader is safe for concurrent read.
type ubtTrieReader struct {
root common.Hash // State root which uniquely represent a state
db *triedb.Database // Database for loading trie
tr Trie // Referenced unified binary trie
lock sync.Mutex // Lock for protecting concurrent read
}
// newUBTTrieReader constructs a Unified-binary-trie reader of the specific state.
// An error will be returned if the associated trie specified by root is not existent.
func newUBTTrieReader(root common.Hash, db *triedb.Database) (*ubtTrieReader, error) {
binTrie, binErr := bintrie.NewBinaryTrie(root, db) binTrie, binErr := bintrie.NewBinaryTrie(root, db)
if binErr != nil { if binErr != nil {
return nil, binErr return nil, binErr
} }
// Based on the transition status, determine if the overlay // Based on the transition status, determine if the overlay
// tree needs to be created, or if a single, target tree is // tree needs to be created, or if a single, target tree is
// to be picked. // to be picked.
ts := overlay.LoadTransitionState(db.Disk(), root, true) var (
tr Trie
ts = overlay.LoadTransitionState(db.Disk(), root, true)
)
if ts.InTransition() { if ts.InTransition() {
mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db) mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db)
if err != nil { if err != nil {
@ -207,42 +288,22 @@ func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
// by using TransitionTrie when there's no actual transition happening. // by using TransitionTrie when there's no actual transition happening.
tr = transitiontrie.NewTransitionTrie(nil, binTrie, false) tr = transitiontrie.NewTransitionTrie(nil, binTrie, false)
} }
} return &ubtTrieReader{
if err != nil {
return nil, err
}
return &trieReader{
root: root, root: root,
db: db, db: db,
mainTrie: tr, tr: tr,
subRoots: make(map[common.Address]common.Hash),
subTries: make(map[common.Address]Trie),
}, nil }, nil
} }
// account is the inner version of Account and assumes the r.lock is already held.
func (r *trieReader) account(addr common.Address) (*types.StateAccount, error) {
account, err := r.mainTrie.GetAccount(addr)
if err != nil {
return nil, err
}
if account == nil {
r.subRoots[addr] = types.EmptyRootHash
} else {
r.subRoots[addr] = account.Root
}
return account, nil
}
// Account implements StateReader, 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 // 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. // will be returned if it's not existent in the trie.
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) { func (r *ubtTrieReader) Account(addr common.Address) (*types.StateAccount, error) {
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
return r.account(addr) return r.tr.GetAccount(addr)
} }
// Storage implements StateReader, retrieving the storage slot specified by the // Storage implements StateReader, retrieving the storage slot specified by the
@ -250,43 +311,15 @@ func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
// //
// An error will be returned if the trie state is corrupted. An empty storage // An error will be returned if the trie state is corrupted. An empty storage
// slot will be returned if it's not existent in the trie. // slot will be returned if it's not existent in the trie.
func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) { func (r *ubtTrieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
var ( ret, err := r.tr.GetStorage(addr, key.Bytes())
tr Trie
found bool
value common.Hash
)
if r.db.IsUBT() {
tr = r.mainTrie
} else {
tr, found = r.subTries[addr]
if !found {
root, ok := r.subRoots[addr]
// The storage slot is accessed without account caching. It's unexpected
// behavior but try to resolve the account first anyway.
if !ok {
_, err := r.account(addr)
if err != nil {
return common.Hash{}, err
}
root = r.subRoots[addr]
}
var err error
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.Keccak256Hash(addr.Bytes()), root), r.db)
if err != nil {
return common.Hash{}, err
}
r.subTries[addr] = tr
}
}
ret, err := tr.GetStorage(addr, key.Bytes())
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
var value common.Hash
value.SetBytes(ret) value.SetBytes(ret)
return value, nil return value, nil
} }