Skip to content

Commit cafa5e6

Browse files
authored
core, consensus/beacon: defer trie resolution (#31725)
Previously, the account trie for a given state root was resolved immediately when the stateDB was created, implying that the trie was always required by the stateDB. However, this assumption no longer holds, especially for path archive nodes, where historical states can be accessed even if the corresponding trie data does not exist.
1 parent 6dd38d2 commit cafa5e6

File tree

9 files changed

+55
-36
lines changed

9 files changed

+55
-36
lines changed

consensus/beacon/consensus.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,12 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
391391
if err != nil {
392392
return nil, fmt.Errorf("error opening pre-state tree root: %w", err)
393393
}
394+
postTrie := state.GetTrie()
395+
if postTrie == nil {
396+
return nil, errors.New("post-state tree is not available")
397+
}
394398
vktPreTrie, okpre := preTrie.(*trie.VerkleTrie)
395-
vktPostTrie, okpost := state.GetTrie().(*trie.VerkleTrie)
399+
vktPostTrie, okpost := postTrie.(*trie.VerkleTrie)
396400

397401
// The witness is only attached iff both parent and current block are
398402
// using verkle tree.

core/chain_makers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
124124
}
125125
// Merge the tx-local access event into the "block-local" one, in order to collect
126126
// all values, so that the witness can be built.
127-
if b.statedb.GetTrie().IsVerkle() {
127+
if b.statedb.Database().TrieDB().IsVerkle() {
128128
b.statedb.AccessEvents().Merge(evm.AccessEvents)
129129
}
130130
b.txs = append(b.txs, tx)

core/state/dump.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ func (d iterativeDump) OnRoot(root common.Hash) {
112112

113113
// DumpToCollector iterates the state according to the given options and inserts
114114
// the items into a collector for aggregation or serialization.
115+
//
116+
// The state iterator is still trie-based and can be converted to snapshot-based
117+
// once the state snapshot is fully integrated into database. TODO(rjl493456442).
115118
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
116119
// Sanitize the input to allow nil configs
117120
if conf == nil {
@@ -123,15 +126,20 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
123126
start = time.Now()
124127
logged = time.Now()
125128
)
126-
log.Info("Trie dumping started", "root", s.trie.Hash())
127-
c.OnRoot(s.trie.Hash())
129+
log.Info("Trie dumping started", "root", s.originalRoot)
130+
c.OnRoot(s.originalRoot)
128131

129-
trieIt, err := s.trie.NodeIterator(conf.Start)
132+
tr, err := s.db.OpenTrie(s.originalRoot)
133+
if err != nil {
134+
return nil
135+
}
136+
trieIt, err := tr.NodeIterator(conf.Start)
130137
if err != nil {
131138
log.Error("Trie dumping error", "err", err)
132139
return nil
133140
}
134141
it := trie.NewIterator(trieIt)
142+
135143
for it.Next() {
136144
var data types.StateAccount
137145
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
@@ -147,7 +155,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
147155
}
148156
address *common.Address
149157
addr common.Address
150-
addrBytes = s.trie.GetKey(it.Key)
158+
addrBytes = tr.GetKey(it.Key)
151159
)
152160
if addrBytes == nil {
153161
missingPreimages++
@@ -165,12 +173,13 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
165173
}
166174
if !conf.SkipStorage {
167175
account.Storage = make(map[common.Hash]string)
168-
tr, err := obj.getTrie()
176+
177+
storageTr, err := s.db.OpenStorageTrie(s.originalRoot, addr, obj.Root(), tr)
169178
if err != nil {
170179
log.Error("Failed to load storage trie", "err", err)
171180
continue
172181
}
173-
trieIt, err := tr.NodeIterator(nil)
182+
trieIt, err := storageTr.NodeIterator(nil)
174183
if err != nil {
175184
log.Error("Failed to create trie iterator", "err", err)
176185
continue
@@ -182,7 +191,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
182191
log.Error("Failed to decode the value returned by iterator", "error", err)
183192
continue
184193
}
185-
key := s.trie.GetKey(storageIt.Key)
194+
key := storageTr.GetKey(storageIt.Key)
186195
if key == nil {
187196
continue
188197
}

core/state/iterator.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
// required in order to resolve the contract address.
3333
type nodeIterator struct {
3434
state *StateDB // State being iterated
35+
tr Trie // Primary account trie for traversal
3536

3637
stateIt trie.NodeIterator // Primary iterator for the global state trie
3738
dataIt trie.NodeIterator // Secondary iterator for the data trie of a contract
@@ -75,13 +76,20 @@ func (it *nodeIterator) step() error {
7576
if it.state == nil {
7677
return nil
7778
}
79+
if it.tr == nil {
80+
tr, err := it.state.db.OpenTrie(it.state.originalRoot)
81+
if err != nil {
82+
return err
83+
}
84+
it.tr = tr
85+
}
7886
// Initialize the iterator if we've just started
79-
var err error
8087
if it.stateIt == nil {
81-
it.stateIt, err = it.state.trie.NodeIterator(nil)
88+
stateIt, err := it.tr.NodeIterator(nil)
8289
if err != nil {
8390
return err
8491
}
92+
it.stateIt = stateIt
8593
}
8694
// If we had data nodes previously, we surely have at least state nodes
8795
if it.dataIt != nil {
@@ -116,14 +124,14 @@ func (it *nodeIterator) step() error {
116124
return err
117125
}
118126
// Lookup the preimage of account hash
119-
preimage := it.state.trie.GetKey(it.stateIt.LeafKey())
127+
preimage := it.tr.GetKey(it.stateIt.LeafKey())
120128
if preimage == nil {
121129
return errors.New("account address is not available")
122130
}
123131
address := common.BytesToAddress(preimage)
124132

125133
// Traverse the storage slots belong to the account
126-
dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root, it.state.trie)
134+
dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root, it.tr)
127135
if err != nil {
128136
return err
129137
}

core/state/state_object.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ func (s *stateObject) touch() {
124124
// subsequent reads to expand the same trie instead of reloading from disk.
125125
func (s *stateObject) getTrie() (Trie, error) {
126126
if s.trie == nil {
127+
// Assumes the primary account trie is already loaded
127128
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
128129
if err != nil {
129130
return nil, err

core/state/state_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ func TestDump(t *testing.T) {
5454
obj3.SetBalance(uint256.NewInt(44))
5555

5656
// write some of them to the trie
57-
s.state.updateStateObject(obj1)
58-
s.state.updateStateObject(obj2)
5957
root, _ := s.state.Commit(0, false, false)
6058

6159
// check that DumpToCollector contains the state objects that are in trie
@@ -114,8 +112,6 @@ func TestIterativeDump(t *testing.T) {
114112
obj4.AddBalance(uint256.NewInt(1337))
115113

116114
// write some of them to the trie
117-
s.state.updateStateObject(obj1)
118-
s.state.updateStateObject(obj2)
119115
root, _ := s.state.Commit(0, false, false)
120116
s.state, _ = New(root, tdb)
121117

core/state/statedb.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ func (m *mutation) isDelete() bool {
7979
type StateDB struct {
8080
db Database
8181
prefetcher *triePrefetcher
82-
trie Trie
8382
reader Reader
83+
trie Trie // it's resolved on first access
8484

8585
// originalRoot is the pre-state root, before any changes were made.
8686
// It will be updated when the Commit is called.
@@ -169,13 +169,8 @@ func New(root common.Hash, db Database) (*StateDB, error) {
169169
// NewWithReader creates a new state for the specified state root. Unlike New,
170170
// this function accepts an additional Reader which is bound to the given root.
171171
func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, error) {
172-
tr, err := db.OpenTrie(root)
173-
if err != nil {
174-
return nil, err
175-
}
176172
sdb := &StateDB{
177173
db: db,
178-
trie: tr,
179174
originalRoot: root,
180175
reader: reader,
181176
stateObjects: make(map[common.Address]*stateObject),
@@ -664,7 +659,6 @@ func (s *StateDB) Copy() *StateDB {
664659
// Copy all the basic fields, initialize the memory ones
665660
state := &StateDB{
666661
db: s.db,
667-
trie: mustCopyTrie(s.trie),
668662
reader: s.reader,
669663
originalRoot: s.originalRoot,
670664
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
@@ -688,6 +682,9 @@ func (s *StateDB) Copy() *StateDB {
688682
transientStorage: s.transientStorage.Copy(),
689683
journal: s.journal.copy(),
690684
}
685+
if s.trie != nil {
686+
state.trie = mustCopyTrie(s.trie)
687+
}
691688
if s.witness != nil {
692689
state.witness = s.witness.Copy()
693690
}
@@ -783,6 +780,20 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
783780
// Finalise all the dirty storage states and write them into the tries
784781
s.Finalise(deleteEmptyObjects)
785782

783+
// Initialize the trie if it's not constructed yet. If the prefetch
784+
// is enabled, the trie constructed below will be replaced by the
785+
// prefetched one.
786+
//
787+
// This operation must be done before state object storage hashing,
788+
// as it assumes the main trie is already loaded.
789+
if s.trie == nil {
790+
tr, err := s.db.OpenTrie(s.originalRoot)
791+
if err != nil {
792+
s.setError(err)
793+
return common.Hash{}
794+
}
795+
s.trie = tr
796+
}
786797
// If there was a trie prefetcher operating, terminate it async so that the
787798
// individual storage tries can be updated as soon as the disk load finishes.
788799
if s.prefetcher != nil {

core/state/statedb_test.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ func TestCopy(t *testing.T) {
171171
for i := byte(0); i < 255; i++ {
172172
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
173173
obj.AddBalance(uint256.NewInt(uint64(i)))
174-
orig.updateStateObject(obj)
175174
}
176175
orig.Finalise(false)
177176

@@ -190,10 +189,6 @@ func TestCopy(t *testing.T) {
190189
origObj.AddBalance(uint256.NewInt(2 * uint64(i)))
191190
copyObj.AddBalance(uint256.NewInt(3 * uint64(i)))
192191
ccopyObj.AddBalance(uint256.NewInt(4 * uint64(i)))
193-
194-
orig.updateStateObject(origObj)
195-
copy.updateStateObject(copyObj)
196-
ccopy.updateStateObject(copyObj)
197192
}
198193

199194
// Finalise the changes on all concurrently
@@ -238,7 +233,6 @@ func TestCopyWithDirtyJournal(t *testing.T) {
238233
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
239234
obj.AddBalance(uint256.NewInt(uint64(i)))
240235
obj.data.Root = common.HexToHash("0xdeadbeef")
241-
orig.updateStateObject(obj)
242236
}
243237
root, _ := orig.Commit(0, true, false)
244238
orig, _ = New(root, db)
@@ -248,8 +242,6 @@ func TestCopyWithDirtyJournal(t *testing.T) {
248242
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
249243
amount := uint256.NewInt(uint64(i))
250244
obj.SetBalance(new(uint256.Int).Sub(obj.Balance(), amount))
251-
252-
orig.updateStateObject(obj)
253245
}
254246
cpy := orig.Copy()
255247

@@ -284,7 +276,6 @@ func TestCopyObjectState(t *testing.T) {
284276
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
285277
obj.AddBalance(uint256.NewInt(uint64(i)))
286278
obj.data.Root = common.HexToHash("0xdeadbeef")
287-
orig.updateStateObject(obj)
288279
}
289280
orig.Finalise(true)
290281
cpy := orig.Copy()
@@ -573,7 +564,7 @@ func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.H
573564
)
574565

575566
for it.Next() {
576-
key := common.BytesToHash(s.trie.GetKey(it.Key))
567+
key := common.BytesToHash(tr.GetKey(it.Key))
577568
visited[key] = true
578569
if value, dirty := so.dirtyStorage[key]; dirty {
579570
if !cb(key, value) {

core/state_processor.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,9 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
161161

162162
// Merge the tx-local access event into the "block-local" one, in order to collect
163163
// all values, so that the witness can be built.
164-
if statedb.GetTrie().IsVerkle() {
164+
if statedb.Database().TrieDB().IsVerkle() {
165165
statedb.AccessEvents().Merge(evm.AccessEvents)
166166
}
167-
168167
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil
169168
}
170169

0 commit comments

Comments
 (0)