Skip to content

Commit a562039

Browse files
rjl493456442blakehhuynh
authored andcommitted
core, trie: rework trie committer (ethereum#25320)
* all: rework trie and trie committer * all: get rid of internal cache in trie * all: fixes * trie: polish * core, trie: address comments * trie: fix imports * core/state: address comments * core/state/snapshot: polish * trie: remove unused code * trie: update tests * trie: don't set db as nil * trie: address comments * trie: unskip test
1 parent 896efd8 commit a562039

24 files changed

+587
-432
lines changed

core/blockchain.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,7 +1244,7 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
12441244

12451245
// writeBlockWithState writes block, metadata and corresponding state data to the
12461246
// database.
1247-
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error {
1247+
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error {
12481248
// Calculate the total difficulty of the block
12491249
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
12501250
if ptd == nil {
@@ -1339,7 +1339,7 @@ func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types
13391339
// writeBlockAndSetHead is the internal implementation of WriteBlockAndSetHead.
13401340
// This function expects the chain mutex to be held.
13411341
func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
1342-
if err := bc.writeBlockWithState(block, receipts, logs, state); err != nil {
1342+
if err := bc.writeBlockWithState(block, receipts, state); err != nil {
13431343
return NonStatTy, err
13441344
}
13451345
currentBlock := bc.CurrentBlock()
@@ -1703,7 +1703,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
17031703
var status WriteStatus
17041704
if !setHead {
17051705
// Don't set the head, only insert the block
1706-
err = bc.writeBlockWithState(block, receipts, logs, statedb)
1706+
err = bc.writeBlockWithState(block, receipts, statedb)
17071707
} else {
17081708
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
17091709
}

core/state/database.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,13 @@ type Trie interface {
8888
// can be used even if the trie doesn't have one.
8989
Hash() common.Hash
9090

91-
// Commit writes all nodes to the trie's memory database, tracking the internal
92-
// and external (for account tries) references.
93-
Commit(onleaf trie.LeafCallback) (common.Hash, int, error)
91+
// Commit collects all dirty nodes in the trie and replace them with the
92+
// corresponding node hash. All collected nodes(including dirty leaves if
93+
// collectLeaf is true) will be encapsulated into a nodeset for return.
94+
// The returned nodeset can be nil if the trie is clean(nothing to commit).
95+
// Once the trie is committed, it's not usable anymore. A new trie must
96+
// be created with new root and updated trie database for following usage
97+
Commit(collectLeaf bool) (common.Hash, *trie.NodeSet, error)
9498

9599
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
96100
// starts at the key after the given start key.

core/state/metrics.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ package state
1919
import "github.com/ethereum/go-ethereum/metrics"
2020

2121
var (
22-
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
23-
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
24-
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
25-
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
26-
accountCommittedMeter = metrics.NewRegisteredMeter("state/commit/account", nil)
27-
storageCommittedMeter = metrics.NewRegisteredMeter("state/commit/storage", nil)
22+
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
23+
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
24+
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
25+
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
26+
accountTrieCommittedMeter = metrics.NewRegisteredMeter("state/commit/accountnodes", nil)
27+
storageTriesCommittedMeter = metrics.NewRegisteredMeter("state/commit/storagenodes", nil)
2828
)

core/state/snapshot/generate.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,10 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, owner common.Hash, roo
367367
for i, key := range result.keys {
368368
snapTrie.Update(key, result.vals[i])
369369
}
370-
root, _, _ := snapTrie.Commit(nil)
370+
root, nodes, _ := snapTrie.Commit(false)
371+
if nodes != nil {
372+
snapTrieDb.Update(trie.NewWithNodeSet(nodes))
373+
}
371374
snapTrieDb.Commit(root, false, nil)
372375
}
373376
// Construct the trie for state iteration, reuse the trie

core/state/snapshot/generate_test.go

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ type testHelper struct {
143143
diskdb ethdb.Database
144144
triedb *trie.Database
145145
accTrie *trie.SecureTrie
146+
nodes *trie.MergedNodeSet
146147
}
147148

148149
func newHelper() *testHelper {
@@ -153,6 +154,7 @@ func newHelper() *testHelper {
153154
diskdb: diskdb,
154155
triedb: triedb,
155156
accTrie: accTrie,
157+
nodes: trie.NewMergedNodeSet(),
156158
}
157159
}
158160

@@ -184,17 +186,22 @@ func (t *testHelper) makeStorageTrie(stateRoot, owner common.Hash, keys []string
184186
for i, k := range keys {
185187
stTrie.Update([]byte(k), []byte(vals[i]))
186188
}
187-
var root common.Hash
188189
if !commit {
189-
root = stTrie.Hash()
190-
} else {
191-
root, _, _ = stTrie.Commit(nil)
190+
return stTrie.Hash().Bytes()
191+
}
192+
root, nodes, _ := stTrie.Commit(false)
193+
if nodes != nil {
194+
t.nodes.Merge(nodes)
192195
}
193196
return root.Bytes()
194197
}
195198

196199
func (t *testHelper) Commit() common.Hash {
197-
root, _, _ := t.accTrie.Commit(nil)
200+
root, nodes, _ := t.accTrie.Commit(true)
201+
if nodes != nil {
202+
t.nodes.Merge(nodes)
203+
}
204+
t.triedb.Update(t.nodes)
198205
t.triedb.Commit(root, false, nil)
199206
return root
200207
}
@@ -378,7 +385,7 @@ func TestGenerateCorruptAccountTrie(t *testing.T) {
378385
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
379386
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4
380387

381-
root, _, _ := helper.accTrie.Commit(nil) // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
388+
root := helper.Commit() // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
382389

383390
// Delete an account trie leaf and ensure the generator chokes
384391
helper.triedb.Commit(root, false, nil)
@@ -413,18 +420,8 @@ func TestGenerateMissingStorageTrie(t *testing.T) {
413420
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
414421
stRoot = helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
415422
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
416-
root, _, _ := helper.accTrie.Commit(nil)
417-
418-
// We can only corrupt the disk database, so flush the tries out
419-
helper.triedb.Reference(
420-
common.BytesToHash(stRoot),
421-
common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"),
422-
)
423-
helper.triedb.Reference(
424-
common.BytesToHash(stRoot),
425-
common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"),
426-
)
427-
helper.triedb.Commit(root, false, nil)
423+
424+
root := helper.Commit()
428425

429426
// Delete a storage trie root and ensure the generator chokes
430427
helper.diskdb.Delete(stRoot)
@@ -458,18 +455,7 @@ func TestGenerateCorruptStorageTrie(t *testing.T) {
458455
stRoot = helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
459456
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
460457

461-
root, _, _ := helper.accTrie.Commit(nil)
462-
463-
// We can only corrupt the disk database, so flush the tries out
464-
helper.triedb.Reference(
465-
common.BytesToHash(stRoot),
466-
common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"),
467-
)
468-
helper.triedb.Reference(
469-
common.BytesToHash(stRoot),
470-
common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"),
471-
)
472-
helper.triedb.Commit(root, false, nil)
458+
root := helper.Commit()
473459

474460
// Delete a storage trie leaf and ensure the generator chokes
475461
helper.diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes())
@@ -825,10 +811,12 @@ func populateDangling(disk ethdb.KeyValueStore) {
825811
// This test will populate some dangling storages to see if they can be cleaned up.
826812
func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
827813
var helper = newHelper()
828-
stRoot := helper.makeStorageTrie(common.Hash{}, common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
829814

815+
stRoot := helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
830816
helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
831817
helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
818+
819+
helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
832820
helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
833821

834822
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
@@ -858,10 +846,12 @@ func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
858846
// This test will populate some dangling storages to see if they can be cleaned up.
859847
func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) {
860848
var helper = newHelper()
861-
stRoot := helper.makeStorageTrie(common.Hash{}, common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
862849

850+
stRoot := helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
863851
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
864852
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
853+
854+
helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
865855
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()})
866856

867857
populateDangling(helper.diskdb)

core/state/state_object.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/ethereum/go-ethereum/crypto"
2929
"github.com/ethereum/go-ethereum/metrics"
3030
"github.com/ethereum/go-ethereum/rlp"
31+
"github.com/ethereum/go-ethereum/trie"
3132
)
3233

3334
var emptyCodeHash = crypto.Keccak256(nil)
@@ -375,23 +376,23 @@ func (s *stateObject) updateRoot(db Database) {
375376

376377
// CommitTrie the storage trie of the object to db.
377378
// This updates the trie root.
378-
func (s *stateObject) CommitTrie(db Database) (int, error) {
379+
func (s *stateObject) CommitTrie(db Database) (*trie.NodeSet, error) {
379380
// If nothing changed, don't bother with hashing anything
380381
if s.updateTrie(db) == nil {
381-
return 0, nil
382+
return nil, nil
382383
}
383384
if s.dbErr != nil {
384-
return 0, s.dbErr
385+
return nil, s.dbErr
385386
}
386387
// Track the amount of time wasted on committing the storage trie
387388
if metrics.EnabledExpensive {
388389
defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
389390
}
390-
root, committed, err := s.trie.Commit(nil)
391+
root, nodes, err := s.trie.Commit(false)
391392
if err == nil {
392393
s.data.Root = root
393394
}
394-
return committed, err
395+
return nodes, err
395396
}
396397

397398
// AddBalance adds amount to s's balance.

core/state/statedb.go

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ func (s *StateDB) GetRefund() uint64 {
774774
return s.refund
775775
}
776776

777-
// Finalise finalises the state by removing the s destructed objects and clears
777+
// Finalise finalises the state by removing the destructed objects and clears
778778
// the journal as well as the refunds. Finalise, however, will not push any updates
779779
// into the tries just yet. Only IntermediateRoot or Commit will do that.
780780
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
@@ -844,7 +844,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
844844
// Although naively it makes sense to retrieve the account trie and then do
845845
// the contract storage and account updates sequentially, that short circuits
846846
// the account prefetcher. Instead, let's process all the storage updates
847-
// first, giving the account prefeches just a few more milliseconds of time
847+
// first, giving the account prefetches just a few more milliseconds of time
848848
// to pull useful data from disk.
849849
for addr := range s.stateObjectsPending {
850850
if obj := s.stateObjects[addr]; !obj.deleted {
@@ -907,7 +907,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
907907
s.IntermediateRoot(deleteEmptyObjects)
908908

909909
// Commit objects to the trie, measuring the elapsed time
910-
var storageCommitted int
910+
var (
911+
accountTrieNodes int
912+
storageTrieNodes int
913+
nodes = trie.NewMergedNodeSet()
914+
)
911915
codeWriter := s.db.TrieDB().DiskDB().NewBatch()
912916
for addr := range s.stateObjectsDirty {
913917
if obj := s.stateObjects[addr]; !obj.deleted {
@@ -917,11 +921,17 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
917921
obj.dirtyCode = false
918922
}
919923
// Write any storage changes in the state object to its storage trie
920-
committed, err := obj.CommitTrie(s.db)
924+
set, err := obj.CommitTrie(s.db)
921925
if err != nil {
922926
return common.Hash{}, err
923927
}
924-
storageCommitted += committed
928+
// Merge the dirty nodes of storage trie into global set
929+
if set != nil {
930+
if err := nodes.Merge(set); err != nil {
931+
return common.Hash{}, err
932+
}
933+
storageTrieNodes += set.Len()
934+
}
925935
}
926936
}
927937
if len(s.stateObjectsDirty) > 0 {
@@ -937,30 +947,26 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
937947
if metrics.EnabledExpensive {
938948
start = time.Now()
939949
}
940-
// The onleaf func is called _serially_, so we can reuse the same account
941-
// for unmarshalling every time.
942-
var account types.StateAccount
943-
root, accountCommitted, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash, _ []byte) error {
944-
if err := rlp.DecodeBytes(leaf, &account); err != nil {
945-
return nil
946-
}
947-
if account.Root != emptyRoot {
948-
s.db.TrieDB().Reference(account.Root, parent)
949-
}
950-
return nil
951-
})
950+
root, set, err := s.trie.Commit(true)
952951
if err != nil {
953952
return common.Hash{}, err
954953
}
954+
// Merge the dirty nodes of account trie into global set
955+
if set != nil {
956+
if err := nodes.Merge(set); err != nil {
957+
return common.Hash{}, err
958+
}
959+
accountTrieNodes = set.Len()
960+
}
955961
if metrics.EnabledExpensive {
956962
s.AccountCommits += time.Since(start)
957963

958964
accountUpdatedMeter.Mark(int64(s.AccountUpdated))
959965
storageUpdatedMeter.Mark(int64(s.StorageUpdated))
960966
accountDeletedMeter.Mark(int64(s.AccountDeleted))
961967
storageDeletedMeter.Mark(int64(s.StorageDeleted))
962-
accountCommittedMeter.Mark(int64(accountCommitted))
963-
storageCommittedMeter.Mark(int64(storageCommitted))
968+
accountTrieCommittedMeter.Mark(int64(accountTrieNodes))
969+
storageTriesCommittedMeter.Mark(int64(storageTrieNodes))
964970
s.AccountUpdated, s.AccountDeleted = 0, 0
965971
s.StorageUpdated, s.StorageDeleted = 0, 0
966972
}
@@ -984,6 +990,9 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
984990
}
985991
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
986992
}
993+
if err := s.db.TrieDB().Update(nodes); err != nil {
994+
return common.Hash{}, err
995+
}
987996
s.originalRoot = root
988997
return root, err
989998
}

0 commit comments

Comments
 (0)