diff --git a/cmd/ronin/dbcmd.go b/cmd/ronin/dbcmd.go index c4793b25e..dc8ce6174 100644 --- a/cmd/ronin/dbcmd.go +++ b/cmd/ronin/dbcmd.go @@ -180,7 +180,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, Action: dbDumpTrie, Name: "dumptrie", Usage: "Show the storage key/values of a given storage trie", - ArgsUsage: " ", + ArgsUsage: " ", Flags: []cli.Flag{ utils.DataDirFlag, utils.DBEngineFlag, @@ -468,7 +468,7 @@ func dbPut(ctx *cli.Context) error { // dbDumpTrie shows the key-value slots of a given storage trie func dbDumpTrie(ctx *cli.Context) error { - if ctx.NArg() < 1 { + if ctx.NArg() < 3 { return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) } stack, _ := makeConfigNode(ctx) @@ -477,29 +477,39 @@ func dbDumpTrie(ctx *cli.Context) error { db := utils.MakeChainDatabase(ctx, stack, true) defer db.Close() var ( - root []byte - start []byte - max = int64(-1) - err error + state []byte + storage []byte + account []byte + start []byte + max = int64(-1) + err error ) - if root, err = hexutil.Decode(ctx.Args().Get(0)); err != nil { - log.Info("Could not decode the root", "error", err) + if state, err = hexutil.Decode(ctx.Args().Get(0)); err != nil { + log.Info("Could not decode the state", "error", err) return err } - stRoot := common.BytesToHash(root) - if ctx.NArg() >= 2 { - if start, err = hexutil.Decode(ctx.Args().Get(1)); err != nil { + if account, err = hexutil.Decode(ctx.Args().Get(1)); err != nil { + log.Info("Could not decode the account hash", "error", err) + return err + } + if storage, err = hexutil.Decode(ctx.Args().Get(2)); err != nil { + log.Info("Could not decode the storage trie root", "error", err) + return err + } + if ctx.NArg() > 3 { + if start, err = hexutil.Decode(ctx.Args().Get(3)); err != nil { log.Info("Could not decode the seek position", "error", err) return err } } - if ctx.NArg() >= 3 { - if max, err = strconv.ParseInt(ctx.Args().Get(2), 10, 64); err != nil { + if ctx.NArg() > 4 { + if max, err = strconv.ParseInt(ctx.Args().Get(4), 10, 64); err != nil { log.Info("Could not decode the max count", "error", err) return err } } - theTrie, err := trie.New(common.Hash{}, stRoot, trie.NewDatabase(db)) + id := trie.StorageTrieID(common.BytesToHash(state), common.BytesToHash(account), common.BytesToHash(storage)) + theTrie, err := trie.New(id, trie.NewDatabase(db)) if err != nil { return err } diff --git a/cmd/ronin/snapshot.go b/cmd/ronin/snapshot.go index 78b398a21..86bde30e0 100644 --- a/cmd/ronin/snapshot.go +++ b/cmd/ronin/snapshot.go @@ -283,7 +283,7 @@ func traverseState(ctx *cli.Context) error { log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } triedb := trie.NewDatabase(chaindb) - t, err := trie.NewSecure(common.Hash{}, root, triedb) + t, err := trie.NewSecure(trie.StateTrieID(root), triedb) if err != nil { log.Error("Failed to open trie", "root", root, "err", err) return err @@ -304,7 +304,7 @@ func traverseState(ctx *cli.Context) error { return err } if acc.Root != emptyRoot { - storageTrie, err := trie.NewSecure(common.BytesToHash(accIter.Key), acc.Root, triedb) + storageTrie, err := trie.NewSecure(trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root), triedb) if err != nil { log.Error("Failed to open storage trie", "root", acc.Root, "err", err) return err @@ -373,7 +373,7 @@ func traverseRawState(ctx *cli.Context) error { log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } triedb := trie.NewDatabase(chaindb) - t, err := trie.NewSecure(common.Hash{}, root, triedb) + t, err := trie.NewSecure(trie.StateTrieID(root), triedb) if err != nil { log.Error("Failed to open trie", "root", root, "err", err) return err @@ -410,7 +410,7 @@ func traverseRawState(ctx *cli.Context) error { return errors.New("invalid account") } if acc.Root != emptyRoot { - storageTrie, err := trie.NewSecure(common.BytesToHash(accIter.LeafKey()), acc.Root, triedb) + storageTrie, err := trie.NewSecure(trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root), triedb) if err != nil { log.Error("Failed to open storage trie", "root", acc.Root, "err", err) return errors.New("missing storage trie") diff --git a/core/blockchain.go b/core/blockchain.go index 06f7cee92..9aef138f3 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -68,6 +68,8 @@ var ( snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storage/reads", nil) snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil) + triedbCommitTimer = metrics.NewRegisteredTimer("chain/triedb/commits", nil) + blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil) blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil) @@ -808,10 +810,10 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { if block == nil { return fmt.Errorf("non existent block [%x..]", hash[:4]) } - if _, err := trie.NewSecure(common.Hash{}, block.Root(), bc.triedb); err != nil { - return err + root := block.Root() + if !bc.HasState(root) { + return fmt.Errorf("non existent state [%x..]", root[:4]) } - // If all checks out, manually set the head block. if !bc.chainmu.TryLock() { return errChainStopped @@ -823,7 +825,7 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { // Destroy any existing state snapshot and regenerate it in the background, // also resuming the normal maintenance of any previously paused snapshot. if bc.snaps != nil { - bc.snaps.Rebuild(block.Root()) + bc.snaps.Rebuild(root) } log.Info("Committed new head block", "number", block.Number(), "hash", hash) return nil @@ -2019,8 +2021,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool, sidecars accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them + triedbCommitTimer.Update(statedb.TrieDBCommits) // Triedb commits are complete, we can mark them - blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits) + blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) blockInsertTimer.UpdateSince(start) blockTxsGauge.Update(int64(len(block.Transactions()))) blockGasUsedGauge.Update(int64(block.GasUsed())) diff --git a/core/state/database.go b/core/state/database.go index 02f5c4ea5..d2837c83f 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -43,7 +43,7 @@ type Database interface { OpenTrie(root common.Hash) (Trie, error) // OpenStorageTrie opens the storage trie of an account. - OpenStorageTrie(addrHash, root common.Hash) (Trie, error) + OpenStorageTrie(stateRoot, addrHash, root common.Hash) (Trie, error) // CopyTrie returns an independent copy of the given trie. CopyTrie(Trie) Trie @@ -145,7 +145,7 @@ type cachingDB struct { // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { - tr, err := trie.NewSecure(common.Hash{}, root, db.triedb) + tr, err := trie.NewSecure(trie.StateTrieID(root), db.triedb) if err != nil { return nil, err } @@ -153,8 +153,8 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { } // OpenStorageTrie opens the storage trie of an account. -func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) { - tr, err := trie.NewSecure(addrHash, root, db.triedb) +func (db *cachingDB) OpenStorageTrie(stateRoot, addrHash, root common.Hash) (Trie, error) { + tr, err := trie.NewSecure(trie.StorageTrieID(stateRoot, addrHash, root), db.triedb) if err != nil { return nil, err } diff --git a/core/state/iterator.go b/core/state/iterator.go index 611df5243..ba7efd465 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -109,7 +109,7 @@ func (it *NodeIterator) step() error { if err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob()), &account); err != nil { return err } - dataTrie, err := it.state.db.OpenStorageTrie(common.BytesToHash(it.stateIt.LeafKey()), account.Root) + dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, common.BytesToHash(it.stateIt.LeafKey()), account.Root) if err != nil { return err } diff --git a/core/state/metrics.go b/core/state/metrics.go index 35d2df92d..e702ef3a8 100644 --- a/core/state/metrics.go +++ b/core/state/metrics.go @@ -19,10 +19,12 @@ package state import "github.com/ethereum/go-ethereum/metrics" var ( - accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil) - storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil) - accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil) - storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil) - accountTrieCommittedMeter = metrics.NewRegisteredMeter("state/commit/accountnodes", nil) - storageTriesCommittedMeter = metrics.NewRegisteredMeter("state/commit/storagenodes", nil) + accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil) + storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil) + accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil) + storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil) + accountTrieUpdatedMeter = metrics.NewRegisteredMeter("state/update/accountnodes", nil) + storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil) + accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil) + storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil) ) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 559507d7f..7b01a74a2 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -410,7 +410,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { if genesis == nil { return errors.New("missing genesis block") } - t, err := trie.NewSecure(common.Hash{}, genesis.Root(), trie.NewDatabase(db)) + t, err := trie.NewSecure(trie.StateTrieID(genesis.Root()), trie.NewDatabase(db)) if err != nil { return err } @@ -430,7 +430,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { return err } if acc.Root != emptyRoot { - storageTrie, err := trie.NewSecure(common.BytesToHash(accIter.LeafKey()), acc.Root, trie.NewDatabase(db)) + storageTrie, err := trie.NewSecure(trie.StorageTrieID(genesis.Root(), common.BytesToHash(accIter.LeafKey()), acc.Root), trie.NewDatabase(db)) if err != nil { return err } diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index a18ecf22e..af4279da6 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -247,7 +247,7 @@ func (result *proofResult) forEach(callback func(key []byte, val []byte) error) // // The proof result will be returned if the range proving is finished, otherwise // the error will be returned to abort the entire procedure. -func (dl *diskLayer) proveRange(stats *generatorStats, owner common.Hash, root common.Hash, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) { +func (dl *diskLayer) proveRange(stats *generatorStats, trieID *trie.ID, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) { var ( keys [][]byte vals [][]byte @@ -304,8 +304,9 @@ func (dl *diskLayer) proveRange(stats *generatorStats, owner common.Hash, root c }(time.Now()) // The snap state is exhausted, pass the entire key/val set for verification + root := trieID.Root if origin == nil && !diskMore { - stackTr := trie.NewStackTrieWithOwner(nil, owner) + stackTr := trie.NewStackTrie(nil) for i, key := range keys { stackTr.TryUpdate(key, vals[i]) } @@ -319,7 +320,7 @@ func (dl *diskLayer) proveRange(stats *generatorStats, owner common.Hash, root c return &proofResult{keys: keys, vals: vals}, nil } // Snap state is chunked, generate edge proofs for verification. - tr, err := trie.New(owner, root, dl.triedb) + tr, err := trie.New(trieID, dl.triedb) if err != nil { stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) return nil, errMissingTrie @@ -380,9 +381,9 @@ type onStateCallback func(key []byte, val []byte, write bool, delete bool) error // generateRange generates the state segment with particular prefix. Generation can // either verify the correctness of existing state through rangeproof and skip // generation, or iterate trie to regenerate state on demand. -func (dl *diskLayer) generateRange(owner common.Hash, root common.Hash, prefix []byte, kind string, origin []byte, max int, stats *generatorStats, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) { +func (dl *diskLayer) generateRange(trieID *trie.ID, prefix []byte, kind string, origin []byte, max int, stats *generatorStats, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) { // Use range prover to check the validity of the flat state in the range - result, err := dl.proveRange(stats, owner, root, prefix, kind, origin, max, valueConvertFn) + result, err := dl.proveRange(stats, trieID, prefix, kind, origin, max, valueConvertFn) if err != nil { return false, nil, err } @@ -431,7 +432,7 @@ func (dl *diskLayer) generateRange(owner common.Hash, root common.Hash, prefix [ if len(result.keys) > 0 { snapNodeCache = rawdb.NewMemoryDatabase() snapTrieDb := trie.NewDatabase(snapNodeCache) - snapTrie, _ := trie.New(owner, common.Hash{}, snapTrieDb) + snapTrie := trie.NewEmpty(snapTrieDb) for i, key := range result.keys { snapTrie.Update(key, result.vals[i]) } @@ -443,7 +444,7 @@ func (dl *diskLayer) generateRange(owner common.Hash, root common.Hash, prefix [ } tr := result.tr if tr == nil { - tr, err = trie.New(owner, root, dl.triedb) + tr, err = trie.New(trieID, dl.triedb) if err != nil { stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) return false, nil, errMissingTrie @@ -526,7 +527,7 @@ func (dl *diskLayer) generateRange(owner common.Hash, root common.Hash, prefix [ } else { snapAccountTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds()) } - logger.Debug("Regenerated state range", "root", root, "last", hexutil.Encode(last), + logger.Debug("Regenerated state range", "root", trieID.Root, "last", hexutil.Encode(last), "count", count, "created", created, "updated", updated, "untouched", untouched, "deleted", deleted) // If there are either more trie items, or there are more snap items @@ -700,7 +701,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { } var storeOrigin = common.CopyBytes(storeMarker) for { - exhausted, last, err := dl.generateRange(common.Hash{}, acc.Root, append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...), "storage", storeOrigin, storageCheckRange, stats, onStorage, nil) + exhausted, last, err := dl.generateRange(trie.StateTrieID(acc.Root), append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...), "storage", storeOrigin, storageCheckRange, stats, onStorage, nil) if err != nil { return err } @@ -719,7 +720,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { // Global loop for regerating the entire state trie + all layered storage tries. for { - exhausted, last, err := dl.generateRange(common.Hash{}, dl.root, rawdb.SnapshotAccountPrefix, "account", accOrigin, accountRange, stats, onAccount, FullAccountRLP) + exhausted, last, err := dl.generateRange(trie.StateTrieID(dl.root), rawdb.SnapshotAccountPrefix, "account", accOrigin, accountRange, stats, onAccount, FullAccountRLP) // The procedure it aborted, either by external signal or internal error if err != nil { if abort == nil { // aborted by internal error, wait the signal diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 3d59590c8..ce1e358a3 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -144,7 +144,7 @@ type testHelper struct { func newHelper() *testHelper { diskdb := rawdb.NewMemoryDatabase() triedb := trie.NewDatabase(diskdb) - accTrie, _ := trie.NewSecure(common.Hash{}, common.Hash{}, triedb) + accTrie, _ := trie.NewSecure(trie.StateTrieID(common.Hash{}), triedb) return &testHelper{ diskdb: diskdb, triedb: triedb, @@ -177,7 +177,7 @@ func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string) } func (t *testHelper) makeStorageTrie(stateRoot, owner common.Hash, keys []string, vals []string, commit bool) []byte { - stTrie, _ := trie.NewSecure(owner, common.Hash{}, t.triedb) + stTrie, _ := trie.NewSecure(trie.StorageTrieID(stateRoot, owner, common.Hash{}), t.triedb) for i, k := range keys { stTrie.Update([]byte(k), []byte(vals[i])) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 91d3b71c4..bb9e3d678 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -162,9 +162,9 @@ func (s *stateObject) getTrie(db Database) Trie { } if s.trie == nil { var err error - s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root) + s.trie, err = db.OpenStorageTrie(s.db.originalRoot, s.addrHash, s.data.Root) if err != nil { - s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{}) + s.trie, _ = db.OpenStorageTrie(s.db.originalRoot, s.addrHash, common.Hash{}) s.setError(fmt.Errorf("can't create storage trie: %v", err)) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index e78968e2e..898e65597 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -124,6 +124,7 @@ type StateDB struct { SnapshotAccountReads time.Duration SnapshotStorageReads time.Duration SnapshotCommits time.Duration + TrieDBCommits time.Duration AccountUpdated int StorageUpdated int @@ -973,9 +974,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { // Commit objects to the trie, measuring the elapsed time var ( - accountTrieNodes int - storageTrieNodes int - nodes = trie.NewMergedNodeSet() + accountTrieNodesUpdated int + accountTrieNodesDeleted int + storageTrieNodesUpdated int + storageTrieNodesDeleted int + nodes = trie.NewMergedNodeSet() ) codeWriter := s.db.TrieDB().DiskDB().NewBatch() for addr := range s.stateObjectsDirty { @@ -996,7 +999,9 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if err := nodes.Merge(nodeSet); err != nil { return common.Hash{}, err } - storageTrieNodes += nodeSet.Len() + updated, deleted := nodeSet.Size() + storageTrieNodesUpdated += updated + storageTrieNodesDeleted += deleted } } } @@ -1029,7 +1034,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if err := nodes.Merge(nodeSet); err != nil { return common.Hash{}, err } - accountTrieNodes = nodeSet.Len() + accountTrieNodesUpdated, accountTrieNodesDeleted = nodeSet.Size() } if metrics.EnabledExpensive { s.AccountCommits += time.Since(start) @@ -1038,16 +1043,16 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { storageUpdatedMeter.Mark(int64(s.StorageUpdated)) accountDeletedMeter.Mark(int64(s.AccountDeleted)) storageDeletedMeter.Mark(int64(s.StorageDeleted)) - accountTrieCommittedMeter.Mark(int64(accountTrieNodes)) - storageTriesCommittedMeter.Mark(int64(storageTrieNodes)) + accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated)) + accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted)) + storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated)) + storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted)) s.AccountUpdated, s.AccountDeleted = 0, 0 s.StorageUpdated, s.StorageDeleted = 0, 0 } // If snapshotting is enabled, update the snapshot tree with this new version if s.snap != nil { - if metrics.EnabledExpensive { - defer func(start time.Time) { s.SnapshotCommits += time.Since(start) }(time.Now()) - } + start := time.Now() // Only update if there's a state transition (skip empty Clique blocks) if parent := s.snap.Root(); parent != root { if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); err != nil { @@ -1062,14 +1067,30 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { } } s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil + if metrics.EnabledExpensive { + s.SnapshotCommits += time.Since(start) + } } // Update Trie MergeNodeSets. - if err := s.db.TrieDB().Update(nodes); err != nil { - return common.Hash{}, err + if root == (common.Hash{}) { + root = emptyRoot + } + origin := s.originalRoot + if origin == (common.Hash{}) { + origin = emptyRoot + } + if root != origin { + start := time.Now() + if err := s.db.TrieDB().Update(nodes); err != nil { + return common.Hash{}, err + } + s.originalRoot = root + if metrics.EnabledExpensive { + s.TrieDBCommits += time.Since(start) + } } - s.originalRoot = root - return root, err + return root, nil } // ResetAccessList sets access list to empty diff --git a/core/state/sync_test.go b/core/state/sync_test.go index b35830d1a..58329481d 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -106,7 +106,7 @@ func checkTrieConsistency(db ethdb.Database, root common.Hash) error { if v, _ := db.Get(root[:]); v == nil { return nil // Consider a non existent state consistent. } - trie, err := trie.New(common.Hash{}, root, trie.NewDatabase(db)) + trie, err := trie.New(trie.StateTrieID(root), trie.NewDatabase(db)) if err != nil { return err } @@ -177,7 +177,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { if commit { srcDb.TrieDB().Commit(srcRoot, false, nil) } - srcTrie, _ := trie.New(common.Hash{}, srcRoot, srcDb.TrieDB()) + srcTrie, _ := trie.New(trie.StateTrieID(srcRoot), srcDb.TrieDB()) // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() @@ -225,7 +225,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { if err := rlp.DecodeBytes(srcTrie.Get(node.syncPath[0]), &acc); err != nil { t.Fatalf("failed to decode account on path %x: %v", node.syncPath[0], err) } - stTrie, err := trie.New(common.BytesToHash(node.syncPath[0]), acc.Root, srcDb.TrieDB()) + stTrie, err := trie.New(trie.StorageTrieID(srcRoot, common.BytesToHash(node.syncPath[0]), acc.Root), srcDb.TrieDB()) if err != nil { t.Fatalf("failed to retriev storage trie for path %x: %v", node.syncPath[1], err) } diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index a81872cd3..5c85e5adc 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -150,7 +150,7 @@ func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, keys [][] id := p.trieID(owner, root) fetcher := p.fetchers[id] if fetcher == nil { - fetcher = newSubfetcher(p.db, owner, root) + fetcher = newSubfetcher(p.db, p.root, owner, root) p.fetchers[id] = fetcher } fetcher.schedule(keys) @@ -206,6 +206,7 @@ func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string { // the trie being worked on is retrieved from the prefetcher. type subfetcher struct { db Database // Database to load trie nodes through + state common.Hash // Root hash of the state to prefetch owner common.Hash // Owner of the trie, usually account hash root common.Hash // Root hash of the trie to prefetch trie Trie // Trie being populated with nodes @@ -225,9 +226,10 @@ type subfetcher struct { // newSubfetcher creates a goroutine to prefetch state items belonging to a // particular root hash. -func newSubfetcher(db Database, owner common.Hash, root common.Hash) *subfetcher { +func newSubfetcher(db Database, state common.Hash, owner common.Hash, root common.Hash) *subfetcher { sf := &subfetcher{ db: db, + state: state, owner: owner, root: root, wake: make(chan struct{}, 1), @@ -298,7 +300,7 @@ func (sf *subfetcher) loop() { } sf.trie = trie } else { - trie, err := sf.db.OpenStorageTrie(sf.owner, sf.root) + trie, err := sf.db.OpenStorageTrie(sf.state, sf.owner, sf.root) if err != nil { log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) return diff --git a/eth/api.go b/eth/api.go index ec92272b2..d6333770f 100644 --- a/eth/api.go +++ b/eth/api.go @@ -551,11 +551,11 @@ func (api *PrivateDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Bloc } triedb := api.eth.BlockChain().StateCache().TrieDB() - oldTrie, err := trie.NewSecure(common.Hash{}, startBlock.Root(), triedb) + oldTrie, err := trie.NewSecure(trie.StateTrieID(startBlock.Root()), triedb) if err != nil { return nil, err } - newTrie, err := trie.NewSecure(common.Hash{}, endBlock.Root(), triedb) + newTrie, err := trie.NewSecure(trie.StateTrieID(endBlock.Root()), triedb) if err != nil { return nil, err } diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 018f03e38..49804de0f 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -234,7 +234,7 @@ func (dl *downloadTester) CurrentFastBlock() *types.Block { func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error { // For now only check that the state trie is correct if block := dl.GetBlockByHash(hash); block != nil { - _, err := trie.NewSecure(common.Hash{}, block.Root(), trie.NewDatabase(dl.stateDb)) + _, err := trie.NewSecure(trie.StateTrieID(block.Root()), trie.NewDatabase(dl.stateDb)) return err } return fmt.Errorf("non existent block: %x", hash[:4]) diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index f2bd4eae9..235340fa9 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -165,7 +165,7 @@ func handleMessage(backend Backend, peer *Peer) error { req.Bytes = softResponseLimit } // Retrieve the requested state and bail out if non existent - tr, err := trie.New(common.Hash{}, req.Root, backend.Chain().StateCache().TrieDB()) + tr, err := trie.New(trie.StateTrieID(req.Root), backend.Chain().StateCache().TrieDB()) if err != nil { return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID}) } @@ -315,7 +315,7 @@ func handleMessage(backend Backend, peer *Peer) error { if origin != (common.Hash{}) || abort { // Request started at a non-zero hash or was capped prematurely, add // the endpoint Merkle proofs - accTrie, err := trie.New(common.Hash{}, req.Root, backend.Chain().StateCache().TrieDB()) + accTrie, err := trie.New(trie.StateTrieID(req.Root), backend.Chain().StateCache().TrieDB()) if err != nil { return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) } @@ -323,7 +323,7 @@ func handleMessage(backend Backend, peer *Peer) error { if err := rlp.DecodeBytes(accTrie.Get(account[:]), &acc); err != nil { return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) } - stTrie, err := trie.New(account, acc.Root, backend.Chain().StateCache().TrieDB()) + stTrie, err := trie.New(trie.StorageTrieID(req.Root, account, acc.Root), backend.Chain().StateCache().TrieDB()) if err != nil { return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) } @@ -430,7 +430,7 @@ func handleMessage(backend Backend, peer *Peer) error { // Make sure we have the state associated with the request triedb := backend.Chain().StateCache().TrieDB() - accTrie, err := trie.NewSecure(common.Hash{}, req.Root, triedb) + accTrie, err := trie.NewSecure(trie.StateTrieID(req.Root), triedb) if err != nil { // We don't have the requested state available, bail out return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID}) @@ -472,7 +472,7 @@ func handleMessage(backend Backend, peer *Peer) error { if err != nil || account == nil { break } - stTrie, err := trie.NewSecure(common.BytesToHash(pathset[0]), common.BytesToHash(account.Root), triedb) + stTrie, err := trie.NewSecure(trie.StorageTrieID(req.Root, common.BytesToHash(pathset[0]), common.BytesToHash(account.Root)), triedb) loads++ // always account database reads, even for failures if err != nil { break diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 940357f41..59fb52f9f 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -1383,7 +1383,7 @@ func makeAccountTrieNoStorage(n int) (trie.NodeScheme, *trie.Trie, entrySlice) { root, nodes, _ := accTrie.Commit(false) db.Update(trie.NewWithNodeSet(nodes)) - accTrie, _ = trie.New(common.Hash{}, root, db) + accTrie, _ = trie.New(trie.StateTrieID(root), db) return db.Scheme(), accTrie, entries } @@ -1444,7 +1444,7 @@ func makeBoundaryAccountTrie(n int) (trie.NodeScheme, *trie.Trie, entrySlice) { root, nodes, _ := accTrie.Commit(false) db.Update(trie.NewWithNodeSet(nodes)) - accTrie, _ = trie.New(common.Hash{}, root, db) + accTrie, _ = trie.New(trie.StateTrieID(root), db) return db.Scheme(), accTrie, entries } @@ -1493,10 +1493,10 @@ func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) db.Update(nodes) // Re-create tries with new root - accTrie, _ = trie.New(common.Hash{}, root, db) + accTrie, _ = trie.New(trie.StateTrieID(root), db) for i := uint64(1); i <= uint64(accounts); i++ { key := key32(i) - trie, _ := trie.New(common.BytesToHash(key), storageRoots[common.BytesToHash(key)], db) + trie, _ := trie.New(trie.StorageTrieID(root, common.BytesToHash(key), storageRoots[common.BytesToHash(key)]), db) storageTries[common.BytesToHash(key)] = trie } return db.Scheme(), accTrie, entries, storageTries, storageEntries @@ -1555,13 +1555,13 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (trie. db.Update(nodes) // Re-create tries with new root - accTrie, err := trie.New(common.Hash{}, root, db) + accTrie, err := trie.New(trie.StateTrieID(root), db) if err != nil { panic(err) } for i := uint64(1); i <= uint64(accounts); i++ { key := key32(i) - trie, err := trie.New(common.BytesToHash(key), storageRoots[common.BytesToHash(key)], db) + trie, err := trie.New(trie.StorageTrieID(root, common.BytesToHash(key), storageRoots[common.BytesToHash(key)]), db) if err != nil { panic(err) } @@ -1574,7 +1574,7 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (trie. // not-yet-committed trie and the sorted entries. The seeds can be used to ensure // that tries are unique. func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *trie.Database) (common.Hash, *trie.NodeSet, entrySlice) { - trie, _ := trie.New(owner, common.Hash{}, db) + trie, _ := trie.New(trie.StorageTrieID(common.Hash{}, owner, common.Hash{}), db) var entries entrySlice for i := uint64(1); i <= n; i++ { // store 'x' at slot 'x' @@ -1600,7 +1600,7 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (commo var ( entries entrySlice boundaries []common.Hash - trie, _ = trie.New(owner, common.Hash{}, db) + trie, _ = trie.New(trie.StorageTrieID(common.Hash{}, owner, common.Hash{}), db) ) // Initialize boundaries var next common.Hash @@ -1647,7 +1647,7 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (commo func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { t.Helper() triedb := trie.NewDatabase(rawdb.NewDatabase(db)) - accTrie, err := trie.New(common.Hash{}, root, triedb) + accTrie, err := trie.New(trie.StateTrieID(root), triedb) if err != nil { t.Fatal(err) } @@ -1665,7 +1665,7 @@ func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { } accounts++ if acc.Root != emptyRoot { - storeTrie, err := trie.NewSecure(common.BytesToHash(accIt.Key), acc.Root, triedb) + storeTrie, err := trie.NewSecure(trie.StorageTrieID(root, common.BytesToHash(accIt.Key), acc.Root), triedb) if err != nil { t.Fatal(err) } diff --git a/les/downloader/downloader_test.go b/les/downloader/downloader_test.go index 70f76956f..963d4d903 100644 --- a/les/downloader/downloader_test.go +++ b/les/downloader/downloader_test.go @@ -229,7 +229,7 @@ func (dl *downloadTester) CurrentFastBlock() *types.Block { func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error { // For now only check that the state trie is correct if block := dl.GetBlockByHash(hash); block != nil { - _, err := trie.NewSecure(common.Hash{}, block.Root(), trie.NewDatabase(dl.stateDb)) + _, err := trie.NewSecure(trie.StateTrieID(block.Root()), trie.NewDatabase(dl.stateDb)) return err } return fmt.Errorf("non existent block: %x", hash[:4]) diff --git a/les/handler_test.go b/les/handler_test.go index 0ccc71973..4fa19e991 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -406,7 +406,7 @@ func testGetProofs(t *testing.T, protocol int) { accounts := []common.Address{bankAddr, userAddr1, userAddr2, signerAddr, {}} for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ { header := bc.GetHeaderByNumber(i) - trie, _ := trie.New(common.Hash{}, header.Root, trie.NewDatabase(server.db)) + trie, _ := trie.New(trie.StateTrieID(header.Root), trie.NewDatabase(server.db)) for _, acc := range accounts { req := ProofReq{ @@ -457,7 +457,7 @@ func testGetStaleProof(t *testing.T, protocol int) { var expected []rlp.RawValue if wantOK { proofsV2 := light.NewNodeSet() - t, _ := trie.New(common.Hash{}, header.Root, trie.NewDatabase(server.db)) + t, _ := trie.New(trie.StateTrieID(header.Root), trie.NewDatabase(server.db)) t.Prove(account, 0, proofsV2) expected = proofsV2.NodeList() } @@ -513,7 +513,7 @@ func testGetCHTProofs(t *testing.T, protocol int) { AuxData: [][]byte{rlp}, } root := light.GetChtRoot(server.db, 0, bc.GetHeaderByNumber(config.ChtSize-1).Hash()) - trie, _ := trie.New(common.Hash{}, root, trie.NewDatabase(rawdb.NewTable(server.db, light.ChtTablePrefix))) + trie, _ := trie.New(trie.StateTrieID(root), trie.NewDatabase(rawdb.NewTable(server.db, light.ChtTablePrefix))) trie.Prove(key, 0, &proofsV2.Proofs) // Assemble the requests for the different protocols requestsV2 := []HelperTrieReq{{ @@ -578,7 +578,7 @@ func testGetBloombitsProofs(t *testing.T, protocol int) { var proofs HelperTrieResps root := light.GetBloomTrieRoot(server.db, 0, bc.GetHeaderByNumber(config.BloomTrieSize-1).Hash()) - trie, _ := trie.New(common.Hash{}, root, trie.NewDatabase(rawdb.NewTable(server.db, light.BloomTrieTablePrefix))) + trie, _ := trie.New(trie.StateTrieID(root), trie.NewDatabase(rawdb.NewTable(server.db, light.BloomTrieTablePrefix))) trie.Prove(key, 0, &proofs.Proofs) // Send the proof request and verify the response diff --git a/les/server_handler.go b/les/server_handler.go index 9cda4368e..9029034a5 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -360,7 +360,7 @@ func (h *serverHandler) AddTxsSync() bool { // getAccount retrieves an account from the state based on root. func getAccount(triedb *trie.Database, root, hash common.Hash) (types.StateAccount, error) { - trie, err := trie.New(common.Hash{}, root, triedb) + trie, err := trie.New(trie.StateTrieID(root), triedb) if err != nil { return types.StateAccount{}, err } @@ -392,7 +392,7 @@ func (h *serverHandler) GetHelperTrie(typ uint, index uint64) *trie.Trie { if root == (common.Hash{}) { return nil } - trie, _ := trie.New(common.Hash{}, root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) + trie, _ := trie.New(trie.StateTrieID(root), trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) return trie } diff --git a/les/server_requests.go b/les/server_requests.go index b0b675b65..5b3511579 100644 --- a/les/server_requests.go +++ b/les/server_requests.go @@ -429,7 +429,7 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { p.bumpInvalid() continue } - trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root) + trie, err = statedb.OpenStorageTrie(root, common.BytesToHash(request.AccKey), account.Root) if trie == nil || err != nil { p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err) continue diff --git a/light/odr.go b/light/odr.go index 493f6fd7f..f998dbe58 100644 --- a/light/odr.go +++ b/light/odr.go @@ -54,9 +54,11 @@ type OdrRequest interface { // TrieID identifies a state or account storage trie type TrieID struct { - BlockHash, Root common.Hash - BlockNumber uint64 - AccKey []byte + BlockHash common.Hash + BlockNumber uint64 + StateRoot common.Hash + Root common.Hash + AccKey []byte } // StateTrieID returns a TrieID for a state trie belonging to a certain block @@ -65,8 +67,9 @@ func StateTrieID(header *types.Header) *TrieID { return &TrieID{ BlockHash: header.Hash(), BlockNumber: header.Number.Uint64(), - AccKey: nil, + StateRoot: header.Root, Root: header.Root, + AccKey: nil, } } @@ -77,6 +80,7 @@ func StorageTrieID(state *TrieID, addrHash, root common.Hash) *TrieID { return &TrieID{ BlockHash: state.BlockHash, BlockNumber: state.BlockNumber, + StateRoot: state.StateRoot, AccKey: addrHash[:], Root: root, } diff --git a/light/odr_test.go b/light/odr_test.go index 9b9ab7e1c..1cadad5ec 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -82,7 +82,7 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { req.Receipts = rawdb.ReadRawReceipts(odr.sdb, req.Hash, *number) } case *TrieRequest: - t, _ := trie.New(common.BytesToHash(req.Id.AccKey), req.Id.Root, trie.NewDatabase(odr.sdb)) + t, _ := trie.New(trie.StorageTrieID(req.Id.StateRoot, common.BytesToHash(req.Id.AccKey), req.Id.Root), trie.NewDatabase(odr.sdb)) nodes := NewNodeSet() t.Prove(req.Key, 0, nodes) req.Proof = nodes diff --git a/light/postprocess.go b/light/postprocess.go index 7d4583939..d6ba1089a 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -187,12 +187,12 @@ func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSecti root = GetChtRoot(c.diskdb, section-1, lastSectionHead) } var err error - c.trie, err = trie.New(common.Hash{}, root, c.triedb) + c.trie, err = trie.New(trie.StateTrieID(root), c.triedb) if err != nil && c.odr != nil { err = c.fetchMissingNodes(ctx, section, root) if err == nil { - c.trie, err = trie.New(common.Hash{}, root, c.triedb) + c.trie, err = trie.New(trie.StateTrieID(root), c.triedb) } } c.section = section @@ -228,7 +228,7 @@ func (c *ChtIndexerBackend) Commit() error { } } // Re-create trie with nelwy generated root and updated database. - c.trie, err = trie.New(common.Hash{}, root, c.triedb) + c.trie, err = trie.New(trie.StateTrieID(root), c.triedb) if err != nil { return err } @@ -414,11 +414,11 @@ func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, las root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead) } var err error - b.trie, err = trie.New(common.Hash{}, root, b.triedb) + b.trie, err = trie.New(trie.StateTrieID(root), b.triedb) if err != nil && b.odr != nil { err = b.fetchMissingNodes(ctx, section, root) if err == nil { - b.trie, err = trie.New(common.Hash{}, root, b.triedb) + b.trie, err = trie.New(trie.StateTrieID(root), b.triedb) } } b.section = section @@ -476,7 +476,7 @@ func (b *BloomTrieIndexerBackend) Commit() error { } // Re-create trie with nelwy generated root and updated database. - b.trie, err = trie.New(common.Hash{}, root, b.triedb) + b.trie, err = trie.New(trie.StateTrieID(root), b.triedb) if err != nil { return err } diff --git a/light/trie.go b/light/trie.go index a2ef8ebff..e60ad49c9 100644 --- a/light/trie.go +++ b/light/trie.go @@ -54,7 +54,7 @@ func (db *odrDatabase) OpenTrie(root common.Hash) (state.Trie, error) { return &odrTrie{db: db, id: db.id}, nil } -func (db *odrDatabase) OpenStorageTrie(addrHash, root common.Hash) (state.Trie, error) { +func (db *odrDatabase) OpenStorageTrie(stateRoot, addrHash, root common.Hash) (state.Trie, error) { return &odrTrie{db: db, id: StorageTrieID(db.id, addrHash, root)}, nil } @@ -63,8 +63,7 @@ func (db *odrDatabase) CopyTrie(t state.Trie) state.Trie { case *odrTrie: cpy := &odrTrie{db: t.db, id: t.id} if t.trie != nil { - cpytrie := *t.trie - cpy.trie = &cpytrie + cpy.trie = t.trie.Copy() } return cpy default: @@ -169,11 +168,13 @@ func (t *odrTrie) do(key []byte, fn func() error) error { for { var err error if t.trie == nil { - var owner common.Hash + var id *trie.ID if len(t.id.AccKey) > 0 { - owner = common.BytesToHash(t.id.AccKey) + id = trie.StorageTrieID(t.id.StateRoot, common.BytesToHash(t.id.AccKey), t.id.Root) + } else { + id = trie.StateTrieID(t.id.StateRoot) } - t.trie, err = trie.New(owner, t.id.Root, trie.NewDatabase(t.db.backend.Database())) + t.trie, err = trie.New(id, trie.NewDatabase(t.db.backend.Database())) } if err == nil { err = fn() @@ -199,11 +200,13 @@ func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator { // Open the actual non-ODR trie if that hasn't happened yet. if t.trie == nil { it.do(func() error { - var owner common.Hash + var id *trie.ID if len(t.id.AccKey) > 0 { - owner = common.BytesToHash(t.id.AccKey) + id = trie.StorageTrieID(t.id.StateRoot, common.BytesToHash(t.id.AccKey), t.id.Root) + } else { + id = trie.StateTrieID(t.id.StateRoot) } - t, err := trie.New(owner, t.id.Root, trie.NewDatabase(t.db.backend.Database())) + t, err := trie.New(id, trie.NewDatabase(t.db.backend.Database())) if err == nil { it.t.trie = t } diff --git a/tests/fuzzers/trie/trie-fuzzer.go b/tests/fuzzers/trie/trie-fuzzer.go index 4237abfc9..4be8ebb9e 100644 --- a/tests/fuzzers/trie/trie-fuzzer.go +++ b/tests/fuzzers/trie/trie-fuzzer.go @@ -21,7 +21,6 @@ import ( "encoding/binary" "fmt" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/trie" ) @@ -174,7 +173,7 @@ func runRandTest(rt randTest) error { return err } } - newtr, err := trie.New(common.Hash{}, hash, triedb) + newtr, err := trie.New(trie.TrieID(hash), triedb) if err != nil { return err } diff --git a/trie/committer.go b/trie/committer.go index 495da8a1f..cf43e12fe 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -43,8 +43,9 @@ type committer struct { tmp sliceBuffer sha crypto.KeccakState - owner common.Hash + owner common.Hash // TODO: same as nodes.owner, consider removing nodes *NodeSet + tracer *tracer collectLeaf bool } @@ -59,9 +60,10 @@ var committerPool = sync.Pool{ } // newCommitter creates a new committer or picks one from the pool. -func newCommitter(owner common.Hash, collectLeaf bool) *committer { +func newCommitter(owner common.Hash, tracer *tracer, collectLeaf bool) *committer { return &committer{ nodes: NewNodeSet(owner), + tracer: tracer, collectLeaf: collectLeaf, } } @@ -72,6 +74,20 @@ func (c *committer) Commit(n node) (hashNode, *NodeSet, error) { if err != nil { return nil, nil, err } + // Some nodes can be deleted from trie which can't be captured by committer + // itself. Iterate all deleted nodes tracked by tracer and marked them as + // deleted only if they are present in database previously. + for _, path := range c.tracer.deleteList() { + // There are a few possibilities for this scenario(the node is deleted + // but not present in database previously), for example the node was + // embedded in the parent and now deleted from the trie. In this case + // it's noop from database's perspective. + val := c.tracer.getPrev(path) + if len(val) == 0 { + continue + } + c.nodes.markDeleted(path, val) + } return h.(hashNode), c.nodes, nil } @@ -103,6 +119,12 @@ func (c *committer) commit(path []byte, n node) (node, error) { if hn, ok := hashedNode.(hashNode); ok { return hn, nil } + // The short node now is embedded in its parent. Mark the node as + // deleted if it's present in database previously. It's equivalent + // as deletion from database's perspective. + if prev := c.tracer.getPrev(path); len(prev) != 0 { + c.nodes.markDeleted(path, prev) + } return collapsed, nil case *fullNode: hashedKids, err := c.commitChildren(path, cn) @@ -116,6 +138,12 @@ func (c *committer) commit(path []byte, n node) (node, error) { if hn, ok := hashedNode.(hashNode); ok { return hn, nil } + // The short node now is embedded in its parent. Mark the node as + // deleted if it's present in database previously. It's equivalent + // as deletion from database's perspective. + if prev := c.tracer.getPrev(path); len(prev) != 0 { + c.nodes.markDeleted(path, prev) + } return collapsed, nil case hashNode: return cn, nil @@ -183,7 +211,7 @@ func (c *committer) store(path []byte, n node) node { ) // Collect the dirty node to nodeset for return. - c.nodes.add(string(path), mnode) + c.nodes.markUpdated(path, mnode, c.tracer.getPrev(path)) // Collect the corresponding leaf node if it's required. We don't check // full node since it's impossible to store value in fullNode. The key // length of leaves should be exactly same. diff --git a/trie/database.go b/trie/database.go index c3add0a8a..28fadba10 100644 --- a/trie/database.go +++ b/trie/database.go @@ -785,8 +785,8 @@ func (db *Database) Update(nodes *MergedNodeSet) error { // can be linked with their parent correctly. The order of writing between // different tries(account trie, storage tries) is not required. for owner, subset := range nodes.sets { - for _, path := range subset.paths { - n, ok := subset.nodes[path] + for _, path := range subset.updates.order { + n, ok := subset.updates.nodes[path] if !ok { return fmt.Errorf("missing node %x %v", owner, path) } @@ -826,6 +826,34 @@ func (db *Database) Size() (common.StorageSize, common.StorageSize) { return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, preimageSize } +// GetReader retrieves a node reader belonging to the given state root. +func (db *Database) GetReader(root common.Hash) Reader { + return newHashReader(db) +} + +// hashReader is reader of hashDatabase which implements the Reader interface. +type hashReader struct { + db *Database +} + +// newHashReader initializes the hash reader. +func newHashReader(db *Database) *hashReader { + return &hashReader{db: db} +} + +// Node retrieves the trie node with the given node hash. +// No error will be returned if the node is not found. +func (reader *hashReader) Node(_ common.Hash, _ []byte, hash common.Hash) (node, error) { + return reader.db.node(hash), nil +} + +// NodeBlob retrieves the RLP-encoded trie node blob with the given node hash. +// No error will be returned if the node is not found. +func (reader *hashReader) NodeBlob(_ common.Hash, _ []byte, hash common.Hash) ([]byte, error) { + blob, _ := reader.db.Node(hash) + return blob, nil +} + // saveCache saves clean state cache to given directory path // using specified CPU cores. func (db *Database) saveCache(dir string, threads int) error { diff --git a/trie/iterator.go b/trie/iterator.go index d37cccd60..39a9ebcef 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -361,7 +361,12 @@ func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) { } } } - return it.trie.resolveHash(hash, path) + // Retrieve the specified node from the underlying node reader. + // it.trie.resolveAndTrack is not used since in that function the + // loaded blob will be tracked, while it's not required here since + // all loaded nodes won't be linked to trie at all and track nodes + // may lead to out-of-memory issue. + return it.trie.reader.node(path, common.BytesToHash(hash)) } func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error { diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 32d2bfae3..d0e9b7f12 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -65,7 +65,7 @@ func TestIterator(t *testing.T) { t.Fatalf("Failed to commit trie %v", err) } db.Update(NewWithNodeSet(nodes)) - trie, _ = New(common.Hash{}, root, db) + trie, _ = New(TrieID(root), db) found := make(map[string]string) it := NewIterator(trie.NodeIterator(nil)) for it.Next() { @@ -226,7 +226,7 @@ func TestDifferenceIterator(t *testing.T) { } rootA, nodesA, _ := triea.Commit(false) dba.Update(NewWithNodeSet(nodesA)) - triea, _ = New(common.Hash{}, rootA, dba) + triea, _ = New(TrieID(rootA), dba) dbb := NewDatabase(rawdb.NewMemoryDatabase()) trieb := NewEmpty(dbb) @@ -235,7 +235,7 @@ func TestDifferenceIterator(t *testing.T) { } rootB, nodesB, _ := trieb.Commit(false) dbb.Update(NewWithNodeSet(nodesB)) - trieb, _ = New(common.Hash{}, rootB, dbb) + trieb, _ = New(TrieID(rootB), dbb) found := make(map[string]string) di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil)) @@ -268,7 +268,7 @@ func TestUnionIterator(t *testing.T) { } rootA, nodesA, _ := triea.Commit(false) dba.Update(NewWithNodeSet(nodesA)) - triea, _ = New(common.Hash{}, rootA, dba) + triea, _ = New(TrieID(rootA), dba) dbb := NewDatabase(rawdb.NewMemoryDatabase()) trieb := NewEmpty(dbb) @@ -277,7 +277,7 @@ func TestUnionIterator(t *testing.T) { } rootB, nodesB, _ := trieb.Commit(false) dbb.Update(NewWithNodeSet(nodesB)) - trieb, _ = New(common.Hash{}, rootB, dbb) + trieb, _ = New(TrieID(rootB), dbb) di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)}) it := NewIterator(di) @@ -355,7 +355,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { } for i := 0; i < 20; i++ { // Create trie that will load all nodes from DB. - tr, _ := New(common.Hash{}, tr.Hash(), triedb) + tr, _ := New(TrieID(tr.Hash()), triedb) // Remove a random node from the database. It can't be the root node // because that one is already loaded. @@ -444,7 +444,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { } // Create a new iterator that seeks to "bars". Seeking can't proceed because // the node is missing. - tr, _ := New(common.Hash{}, root, triedb) + tr, _ := New(TrieID(root), triedb) it := tr.NodeIterator([]byte("bars")) missing, ok := it.Error().(*MissingNodeError) if !ok { @@ -532,7 +532,7 @@ func makeLargeTestTrie() (*Database, *SecureTrie, *loggingDb) { // Create an empty trie logDb := &loggingDb{0, memorydb.New()} triedb := NewDatabase(rawdb.NewDatabase(logDb)) - trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb) + trie, _ := NewSecure(TrieID(common.Hash{}), triedb) // Fill it with some arbitrary data for i := 0; i < 10000; i++ { diff --git a/trie/nodeset.go b/trie/nodeset.go index 4825ecaeb..a94535069 100644 --- a/trie/nodeset.go +++ b/trie/nodeset.go @@ -18,52 +18,171 @@ package trie import ( "fmt" + "reflect" + "strings" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" ) // memoryNode is all the information we know about a single cached trie node // in the memory. type memoryNode struct { - hash common.Hash // Node hash, computed by hashing rlp value - size uint16 // Byte size of the useful cached data - node node // Cached collapsed trie node, or raw rlp data + hash common.Hash // Node hash, computed by hashing rlp value, empty for deleted nodes + size uint16 // Byte size of the useful cached data, 0 for deleted nodes + node node // Cached collapsed trie node, or raw rlp data, nil for deleted nodes +} + +// memoryNodeSize is the raw size of a memoryNode data structure without any +// node data included. It's an approximate size, but should be a lot better +// than not counting them. +// nolint:unused +var memoryNodeSize = int(reflect.TypeOf(memoryNode{}).Size()) + +// memorySize returns the total memory size used by this node. +// nolint:unused +func (n *memoryNode) memorySize(key int) int { + return int(n.size) + memoryNodeSize + key +} + +// rlp returns the raw rlp encoded blob of the cached trie node, either directly +// from the cache, or by regenerating it from the collapsed node. +// nolint:unused +func (n *memoryNode) rlp() []byte { + if node, ok := n.node.(rawNode); ok { + return node + } + enc, err := rlp.EncodeToBytes(n.node) + if err != nil { + log.Error("Failed to encode trie node", "err", err) + } + return enc +} + +// obj returns the decoded and expanded trie node, either directly from the cache, +// or by regenerating it from the rlp encoded blob. +// nolint:unused +func (n *memoryNode) obj() node { + if node, ok := n.node.(rawNode); ok { + return mustDecodeNode(n.hash[:], node) + } + return expandNode(n.hash[:], n.node) +} + +// nodeWithPrev wraps the memoryNode with the previous node value. +type nodeWithPrev struct { + *memoryNode + prev []byte // RLP-encoded previous value, nil means it's non-existent +} + +// unwrap returns the internal memoryNode object. +// nolint:unused +func (n *nodeWithPrev) unwrap() *memoryNode { + return n.memoryNode +} + +// memorySize returns the total memory size used by this node. It overloads +// the function in memoryNode by counting the size of previous value as well. +// nolint: unused +func (n *nodeWithPrev) memorySize(key int) int { + return n.memoryNode.memorySize(key) + len(n.prev) +} + +// nodesWithOrder represents a collection of dirty nodes which includes +// newly-inserted and updated nodes. The modification order of all nodes +// is represented by order list. +type nodesWithOrder struct { + order []string // the path list of dirty nodes, sort by insertion order + nodes map[string]*nodeWithPrev // the map of dirty nodes, keyed by node path } // NodeSet contains all dirty nodes collected during the commit operation // Each node is keyed by path. It's not the thread-safe to use. type NodeSet struct { - owner common.Hash // the identifier of the trie - paths []string // the path of dirty nodes, sort by insertion order - nodes map[string]*memoryNode // the map of dirty nodes, keyed by node path - leaves []*leaf // the list of dirty leaves + owner common.Hash // the identifier of the trie + updates *nodesWithOrder // the set of updated nodes(newly inserted, updated) + deletes map[string][]byte // the map of deleted nodes, keyed by node + leaves []*leaf // the list of dirty leaves } // NewNodeSet initializes an empty node set to be used for tracking dirty nodes // from a specific account or storage trie. The owner is zero for the account // trie and the owning account address hash for storage tries. - func NewNodeSet(owner common.Hash) *NodeSet { return &NodeSet{ owner: owner, - nodes: make(map[string]*memoryNode), + updates: &nodesWithOrder{ + nodes: make(map[string]*nodeWithPrev), + }, + deletes: make(map[string][]byte), + } +} + +// NewNodeSetWithDeletion initializes the nodeset with provided deletion set. +func NewNodeSetWithDeletion(owner common.Hash, paths [][]byte, prev [][]byte) *NodeSet { + set := NewNodeSet(owner) + for i, path := range paths { + set.markDeleted(path, prev[i]) + } + return set +} + +// markUpdated marks the node as dirty(newly-inserted or updated) with provided +// node path, node object along with its previous value. +func (set *NodeSet) markUpdated(path []byte, node *memoryNode, prev []byte) { + set.updates.order = append(set.updates.order, string(path)) + set.updates.nodes[string(path)] = &nodeWithPrev{ + memoryNode: node, + prev: prev, } } -// add caches node with provided path and node object. -func (set *NodeSet) add(path string, node *memoryNode) { - set.paths = append(set.paths, path) - set.nodes[path] = node +// markDeleted marks the node as deleted with provided path and previous value. +func (set *NodeSet) markDeleted(path []byte, prev []byte) { + set.deletes[string(path)] = prev } -// addLeaf caches the provided leaf node. +// addLeaf collects the provided leaf node into set. func (set *NodeSet) addLeaf(leaf *leaf) { set.leaves = append(set.leaves, leaf) } -// Len returns the number of dirty nodes contained in the set. -func (set *NodeSet) Len() int { - return len(set.nodes) +// Size returns the number of updated and deleted nodes contained in the set. +func (set *NodeSet) Size() (int, int) { + return len(set.updates.order), len(set.deletes) +} + +// Hashes returns the hashes of all updated nodes. +func (set *NodeSet) Hashes() []common.Hash { + var ret []common.Hash + for _, node := range set.updates.nodes { + ret = append(ret, node.hash) + } + return ret +} + +// Summary returns a string-representation of the NodeSet. +func (set *NodeSet) Summary() string { + var out = new(strings.Builder) + fmt.Fprintf(out, "nodeset owner: %v\n", set.owner) + if set.updates != nil { + for _, key := range set.updates.order { + updated := set.updates.nodes[key] + if updated.prev != nil { + fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", key, updated.hash, updated.prev) + } else { + fmt.Fprintf(out, " [+]: %x -> %v\n", key, updated.hash) + } + } + } + for k, n := range set.deletes { + fmt.Fprintf(out, " [-]: %x -> %x\n", k, n) + } + for _, n := range set.leaves { + fmt.Fprintf(out, "[leaf]: %v\n", n) + } + return out.String() } // MergedNodeSet represents a merged dirty node set for a group of tries. diff --git a/trie/proof.go b/trie/proof.go index db113eecb..c58997197 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -22,7 +22,6 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -61,8 +60,13 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e key = key[1:] nodes = append(nodes, n) case hashNode: + // Retrieve the specified node from the underlying node reader. + // trie.resolveAndTrack is not used since in that function the + // loaded blob will be tracked, while it's not required here since + // all loaded nodes won't be linked to trie at all and track nodes + // may lead to out-of-memory issue var err error - tn, err = t.resolveHash(n, prefix) + tn, err = t.reader.node(prefix, common.BytesToHash(n)) if err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) return err @@ -558,7 +562,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key } // Rebuild the trie with the leaf stream, the shape of trie // should be same with the original one. - tr := &Trie{root: root, db: NewDatabase(rawdb.NewMemoryDatabase())} + tr := &Trie{root: root, reader: newEmptyReader()} if empty { tr.root = nil } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 2cf1d325f..ce69b839b 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -54,11 +54,11 @@ type SecureTrie struct { // Loaded nodes are kept around until their 'cache generation' expires. // A new cache generation is created by each call to Commit. // cachelimit sets the number of past cache generations to keep. -func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) { +func NewSecure(id *ID, db *Database) (*SecureTrie, error) { if db == nil { panic("trie.NewSecure called without a database") } - trie, err := New(owner, root, db) + trie, err := New(id, db) if err != nil { return nil, err } diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index 5030c5b3a..835608a0e 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -28,7 +28,7 @@ import ( ) func newEmptySecure() *SecureTrie { - trie, _ := NewSecure(common.Hash{}, common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) + trie, _ := NewSecure(TrieID(common.Hash{}), NewDatabase(rawdb.NewMemoryDatabase())) return trie } @@ -36,7 +36,7 @@ func newEmptySecure() *SecureTrie { func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(rawdb.NewMemoryDatabase()) - trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb) + trie, _ := NewSecure(TrieID(common.Hash{}), triedb) // Fill it with some arbitrary data content := make(map[string][]byte) diff --git a/trie/sync_test.go b/trie/sync_test.go index 095892e16..c964608aa 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -31,7 +31,7 @@ import ( func makeTestTrie() (*Database, *SecureTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(rawdb.NewMemoryDatabase()) - trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb) + trie, _ := NewSecure(TrieID(common.Hash{}), triedb) // Fill it with some arbitrary data content := make(map[string][]byte) @@ -68,7 +68,7 @@ func makeTestTrie() (*Database, *SecureTrie, map[string][]byte) { // content map. func checkTrieContents(t *testing.T, db *Database, root []byte, content map[string][]byte) { // Check root availability and trie contents - trie, err := NewSecure(common.Hash{}, common.BytesToHash(root), db) + trie, err := NewSecure(TrieID(common.BytesToHash(root)), db) if err != nil { t.Fatalf("failed to create trie at %x: %v", root, err) } @@ -85,7 +85,7 @@ func checkTrieContents(t *testing.T, db *Database, root []byte, content map[stri // checkTrieConsistency checks that all nodes in a trie are indeed present. func checkTrieConsistency(db *Database, root common.Hash) error { // Create and iterate a trie rooted in a subnode - trie, err := NewSecure(common.Hash{}, root, db) + trie, err := NewSecure(TrieID(root), db) if err != nil { return nil // Consider a non existent state consistent } @@ -107,7 +107,7 @@ func TestEmptySync(t *testing.T) { dbA := NewDatabase(rawdb.NewMemoryDatabase()) dbB := NewDatabase(rawdb.NewMemoryDatabase()) emptyA := NewEmpty(dbA) - emptyB, _ := New(common.Hash{}, emptyRoot, dbB) + emptyB, _ := New(TrieID(emptyRoot), dbB) for i, trie := range []*Trie{emptyA, emptyB} { sync := NewSync(trie.Hash(), memorydb.New(), nil, NewSyncBloom(1, memorydb.New()), []*Database{dbA, dbB}[i].Scheme()) diff --git a/trie/trie.go b/trie/trie.go index a1cc31c5c..b1c3d7136 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -51,7 +51,7 @@ type Trie struct { // db is the handler trie can retrieve nodes from. It's // only for reading purpose and not available for writing. - db *Database + reader *trieReader // tracer is the tool to track the trie changes. // It will be reset after each commit operation. @@ -63,21 +63,24 @@ func (t *Trie) newFlag() nodeFlag { return nodeFlag{dirty: true} } -// New creates a trie with an existing root node from db and an assigned -// owner for storage proximity. -// -// If root is the zero hash or the sha3 hash of an empty string, the -// trie is initially empty and does not require a database. Otherwise, -// New will panic if db is nil and returns a MissingNodeError if root does -// not exist in the database. Accessing the trie loads nodes from db on demand. -func New(owner common.Hash, root common.Hash, db *Database) (*Trie, error) { +// New creates the trie instance with provided trie id and the read-only +// database. The state specified by trie id must be available, otherwise +// an error will be returned. The trie root specified by trie id can be +// zero hash or the sha3 hash of an empty string, then trie is initially +// empty, otherwise, the root node must be present in database or returns +// a MissingNodeError if not. +func New(id *ID, db NodeReader) (*Trie, error) { + reader, err := newTrieReader(id.StateRoot, id.Owner, db) + if err != nil { + return nil, err + } trie := &Trie{ - owner: owner, - db: db, + owner: id.Owner, + reader: reader, //tracer: newTracer(), } - if root != (common.Hash{}) && root != emptyRoot { - rootnode, err := trie.resolveHash(root[:], nil) + if id.Root != (common.Hash{}) && id.Root != emptyRoot { + rootnode, err := trie.resolveAndTrack(id.Root[:], nil) if err != nil { return nil, err } @@ -88,7 +91,7 @@ func New(owner common.Hash, root common.Hash, db *Database) (*Trie, error) { // NewEmpty is a shortcut to create empty tree. It's mostly used in tests. func NewEmpty(db *Database) *Trie { - tr, _ := New(common.Hash{}, common.Hash{}, db) + tr, _ := New(TrieID(common.Hash{}), db) return tr } @@ -144,7 +147,7 @@ func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode } return value, n, didResolve, err case hashNode: - child, err := t.resolveHash(n, key[:pos]) + child, err := t.resolveAndTrack(n, key[:pos]) if err != nil { return nil, n, true, err } @@ -190,7 +193,7 @@ func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, new if hash == nil { return nil, origNode, 0, errors.New("non-consensus node") } - blob, err := t.db.Node(common.BytesToHash(hash)) + blob, err := t.reader.nodeBlob(path, common.BytesToHash(hash)) return blob, origNode, 1, err } @@ -221,7 +224,7 @@ func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, new return item, n, resolved, err case hashNode: - child, err := t.resolveHash(n, path[:pos]) + child, err := t.resolveAndTrack(n, path[:pos]) if err != nil { return nil, n, 1, err } @@ -343,7 +346,7 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error // We've hit a part of the trie that isn't loaded yet. Load // the node and insert into it. This leaves all child nodes on // the path to the value in the trie. - rn, err := t.resolveHash(n, prefix) + rn, err := t.resolveAndTrack(n, prefix) if err != nil { return false, nil, err } @@ -523,7 +526,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { // We've hit a part of the trie that isn't loaded yet. Load // the node and delete from it. This leaves all child nodes on // the path to the value in the trie. - rn, err := t.resolveHash(n, prefix) + rn, err := t.resolveAndTrack(n, prefix) if err != nil { return false, nil, err } @@ -547,19 +550,22 @@ func concat(s1 []byte, s2 ...byte) []byte { func (t *Trie) resolve(n node, prefix []byte) (node, error) { if n, ok := n.(hashNode); ok { - return t.resolveHash(n, prefix) + return t.resolveAndTrack(n, prefix) } return n, nil } -// resolveHash loads node from the underlying database with the provided -// node hash and path prefix. -func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { - hash := common.BytesToHash(n) - if node := t.db.node(hash); node != nil { - return node, nil +// resolveAndTrack loads node from the underlying store with the given node hash +// and path prefix and also tracks the loaded node blob in tracer treated as the +// node's original value. The rlp-encoded blob is preferred to be loaded from +// database because it's easy to decode node while complex to encode node to blob. +func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) { + blob, err := t.reader.nodeBlob(prefix, common.BytesToHash(n)) + if err != nil { + return nil, err } - return nil, &MissingNodeError{Owner: t.owner, NodeHash: hash, Path: prefix} + t.tracer.onRead(prefix, blob) + return mustDecodeNode(n, blob), nil } // Hash returns the root hash of the trie. It does not write to the @@ -595,7 +601,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) { t.root = hashedNode return rootHash, nil, nil } - h := newCommitter(t.owner, collectLeaf) + h := newCommitter(t.owner, t.tracer, collectLeaf) newRoot, nodes, err := h.Commit(t.root) if err != nil { return common.Hash{}, nil, err @@ -632,7 +638,7 @@ func (t *Trie) Copy() *Trie { root: t.root, owner: t.owner, unhashed: t.unhashed, - db: t.db, + reader: t.reader, tracer: t.tracer.copy(), } } diff --git a/trie/trie_id.go b/trie/trie_id.go new file mode 100644 index 000000000..8ab490ca3 --- /dev/null +++ b/trie/trie_id.go @@ -0,0 +1,55 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package trie + +import "github.com/ethereum/go-ethereum/common" + +// ID is the identifier for uniquely identifying a trie. +type ID struct { + StateRoot common.Hash // The root of the corresponding state(block.root) + Owner common.Hash // The contract address hash which the trie belongs to + Root common.Hash // The root hash of trie +} + +// StateTrieID constructs an identifier for state trie with the provided state root. +func StateTrieID(root common.Hash) *ID { + return &ID{ + StateRoot: root, + Owner: common.Hash{}, + Root: root, + } +} + +// StorageTrieID constructs an identifier for storage trie which belongs to a certain +// state and contract specified by the stateRoot and owner. +func StorageTrieID(stateRoot common.Hash, owner common.Hash, root common.Hash) *ID { + return &ID{ + StateRoot: stateRoot, + Owner: owner, + Root: root, + } +} + +// TrieID constructs an identifier for a standard trie(not a second-layer trie) +// with provided root. It's mostly used in tests and some other tries like CHT trie. +func TrieID(root common.Hash) *ID { + return &ID{ + StateRoot: root, + Owner: common.Hash{}, + Root: root, + } +} diff --git a/trie/trie_reader.go b/trie/trie_reader.go new file mode 100644 index 000000000..14186159b --- /dev/null +++ b/trie/trie_reader.go @@ -0,0 +1,106 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +// Reader wraps the Node and NodeBlob method of a backing trie store. +type Reader interface { + // Node retrieves the trie node with the provided trie identifier, hexary + // node path and the corresponding node hash. + // No error will be returned if the node is not found. + Node(owner common.Hash, path []byte, hash common.Hash) (node, error) + + // NodeBlob retrieves the RLP-encoded trie node blob with the provided trie + // identifier, hexary node path and the corresponding node hash. + // No error will be returned if the node is not found. + NodeBlob(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) +} + +// NodeReader wraps all the necessary functions for accessing trie node. +type NodeReader interface { + // GetReader returns a reader for accessing all trie nodes with provided + // state root. Nil is returned in case the state is not available. + GetReader(root common.Hash) Reader +} + +// trieReader is a wrapper of the underlying node reader. It's not safe +// for concurrent usage. +type trieReader struct { + owner common.Hash + reader Reader + banned map[string]struct{} // Marker to prevent node from being accessed, for tests +} + +// newTrieReader initializes the trie reader with the given node reader. +func newTrieReader(stateRoot, owner common.Hash, db NodeReader) (*trieReader, error) { + reader := db.GetReader(stateRoot) + if reader == nil { + return nil, fmt.Errorf("state not found #%x", stateRoot) + } + return &trieReader{owner: owner, reader: reader}, nil +} + +// newEmptyReader initializes the pure in-memory reader. All read operations +// should be forbidden and returns the MissingNodeError. +func newEmptyReader() *trieReader { + return &trieReader{} +} + +// node retrieves the trie node with the provided trie node information. +// An MissingNodeError will be returned in case the node is not found or +// any error is encountered. +func (r *trieReader) node(path []byte, hash common.Hash) (node, error) { + // Perform the logics in tests for preventing trie node access. + if r.banned != nil { + if _, ok := r.banned[string(path)]; ok { + return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path} + } + } + if r.reader == nil { + return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path} + } + node, err := r.reader.Node(r.owner, path, hash) + if err != nil || node == nil { + return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path, err: err} + } + return node, nil +} + +// node retrieves the rlp-encoded trie node with the provided trie node +// information. An MissingNodeError will be returned in case the node is +// not found or any error is encountered. +func (r *trieReader) nodeBlob(path []byte, hash common.Hash) ([]byte, error) { + // Perform the logics in tests for preventing trie node access. + if r.banned != nil { + if _, ok := r.banned[string(path)]; ok { + return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path} + } + } + if r.reader == nil { + return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path} + } + blob, err := r.reader.NodeBlob(r.owner, path, hash) + if err != nil || len(blob) == 0 { + return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path, err: err} + } + return blob, nil +} diff --git a/trie/trie_test.go b/trie/trie_test.go index c9533060c..957b7926a 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -71,7 +71,7 @@ func TestNull(t *testing.T) { } func TestMissingRoot(t *testing.T) { - trie, err := New(common.Hash{}, common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), NewDatabase(rawdb.NewMemoryDatabase())) + trie, err := New(TrieID(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")), NewDatabase(rawdb.NewMemoryDatabase())) if trie != nil { t.Error("New returned non-nil trie for invalid root") } @@ -96,27 +96,27 @@ func testMissingNode(t *testing.T, memonly bool) { triedb.Commit(root, true, nil) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) _, err := trie.TryGet([]byte("120000")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) _, err = trie.TryGet([]byte("120099")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) _, err = trie.TryGet([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) err = trie.TryUpdate([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) err = trie.TryDelete([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -129,27 +129,27 @@ func testMissingNode(t *testing.T, memonly bool) { diskdb.Delete(hash[:]) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) _, err = trie.TryGet([]byte("120000")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) _, err = trie.TryGet([]byte("120099")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) _, err = trie.TryGet([]byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) err = trie.TryUpdate([]byte("120099"), []byte("zxcv")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } - trie, _ = New(common.Hash{}, root, triedb) + trie, _ = New(TrieID(root), triedb) err = trie.TryDelete([]byte("123456")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) @@ -205,7 +205,7 @@ func TestGet(t *testing.T) { } root, nodes, _ := trie.Commit(false) db.Update(NewWithNodeSet(nodes)) - trie, _ = New(common.Hash{}, root, db) + trie, _ = New(TrieID(root), db) } } @@ -282,7 +282,7 @@ func TestReplication(t *testing.T) { triedb.Update(NewWithNodeSet(nodes)) // create a new trie on top of the database and check that lookups work. - trie2, err := New(common.Hash{}, exp, triedb) + trie2, err := New(TrieID(exp), triedb) if err != nil { t.Fatalf("can't recreate trie at %x: %v", exp, err) } @@ -302,7 +302,7 @@ func TestReplication(t *testing.T) { if nodes != nil { triedb.Update(NewWithNodeSet(nodes)) } - trie2, err = New(common.Hash{}, hash, triedb) + trie2, err = New(TrieID(hash), triedb) if err != nil { t.Fatalf("can't recreate trie at %x: %v", exp, err) } @@ -386,6 +386,7 @@ const ( opCommit opHash opItercheckhash + opProve opMax // boundary value, not an actual op ) @@ -411,7 +412,7 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value { step.key = genKey() step.value = make([]byte, 8) binary.BigEndian.PutUint64(step.value, uint64(i)) - case opGet, opDelete: + case opGet, opDelete, opProve: step.key = genKey() } steps = append(steps, step) @@ -421,9 +422,10 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value { func runRandTest(rt randTest) bool { var ( - triedb = NewDatabase(rawdb.NewMemoryDatabase()) - tr = NewEmpty(triedb) - values = make(map[string]string) // tracks content of the trie + triedb = NewDatabase(rawdb.NewMemoryDatabase()) + tr = NewEmpty(triedb) + origTrie = NewEmpty(triedb) + values = make(map[string]string) // tracks content of the trie ) tr.tracer = newTracer() @@ -431,6 +433,7 @@ func runRandTest(rt randTest) bool { fmt.Printf("{op: %d, key: common.Hex2Bytes(\"%x\"), value: common.Hex2Bytes(\"%x\")}, // step %d\n", step.op, step.key, step.value, i) switch step.op { + case opUpdate: tr.Update(step.key, step.value) values[string(step.key)] = string(step.value) @@ -443,23 +446,62 @@ func runRandTest(rt randTest) bool { if string(v) != want { rt[i].err = fmt.Errorf("mismatch for key 0x%x, got 0x%x want 0x%x", step.key, v, want) } + case opProve: + hash := tr.Hash() + if hash == emptyRoot { + continue + } + proofDb := rawdb.NewMemoryDatabase() + err := tr.Prove(step.key, 0, proofDb) + if err != nil { + rt[i].err = fmt.Errorf("failed for proving key %#x, %v", step.key, err) + } + _, err = VerifyProof(hash, step.key, proofDb) + if err != nil { + rt[i].err = fmt.Errorf("failed for verifying key %#x, %v", step.key, err) + } case opHash: tr.Hash() case opCommit: - hash, nodes, err := tr.Commit(false) + root, nodes, err := tr.Commit(true) if err != nil { rt[i].err = err return false } + // Validity the returned nodeset + if nodes != nil { + for path, node := range nodes.updates.nodes { + blob, _, _ := origTrie.TryGetNode(hexToCompact([]byte(path))) + got := node.prev + if !bytes.Equal(blob, got) { + rt[i].err = fmt.Errorf("prevalue mismatch for 0x%x, got 0x%x want 0x%x", path, got, blob) + panic(rt[i].err) + } + } + for path, prev := range nodes.deletes { + blob, _, _ := origTrie.TryGetNode(hexToCompact([]byte(path))) + if !bytes.Equal(blob, prev) { + rt[i].err = fmt.Errorf("prevalue mismatch for 0x%x, got 0x%x want 0x%x", path, prev, blob) + return false + } + } + } if nodes != nil { triedb.Update(NewWithNodeSet(nodes)) } - newtr, err := New(common.Hash{}, hash, triedb) + newtr, err := New(TrieID(root), triedb) if err != nil { rt[i].err = err return false } tr = newtr + + // Enable node tracing. Resolve the root node again explicitly + // since it's not captured at the beginning. + tr.tracer = newTracer() + tr.resolveAndTrack(root.Bytes(), nil) + origTrie = tr.Copy() + case opItercheckhash: checktr := NewEmpty(triedb) it := NewIterator(tr.NodeIterator(nil)) @@ -604,7 +646,7 @@ func TestTinyTrie(t *testing.T) { if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root { t.Errorf("3: got %x, exp %x", root, exp) } - checktr := NewEmpty(trie.db) + checktr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) it := NewIterator(trie.NodeIterator(nil)) for it.Next() { checktr.Update(it.Key, it.Value) @@ -1061,7 +1103,7 @@ func benchmarkDerefRootFixedSize(b *testing.B, addresses [][20]byte, accounts [] _, nodes, _ := trie.Commit(false) triedb.Update(NewWithNodeSet(nodes)) b.StartTimer() - trie.db.Dereference(h) + triedb.Dereference(h) b.StopTimer() } diff --git a/trie/utils.go b/trie/utils.go index 503c033fb..d1cd3bdd2 100644 --- a/trie/utils.go +++ b/trie/utils.go @@ -52,43 +52,43 @@ func newTracer() *tracer { // onRead tracks the newly loaded trie node and caches the rlp-encoded blob internally. // Don't change the value outside of function since it's not deep-copied. -func (t *tracer) onRead(key []byte, val []byte) { +func (t *tracer) onRead(path []byte, val []byte) { // Tracer isn't used right now, remove this check later. if t == nil { return } - t.origin[string(key)] = val + t.origin[string(path)] = val } // onInsert tracks the newly inserted trie node. If it's already // in the delete set(resurrected node), then just wipe it from // the deletion set as it's untouched. -func (t *tracer) onInsert(key []byte) { +func (t *tracer) onInsert(path []byte) { // Tracer isn't used right now, remove this check latter. if t == nil { return } - // If the key is in the delete set, then it's a resurrected node, then wipe it. - if _, present := t.delete[string(key)]; present { - delete(t.delete, string(key)) + // If the path is in the delete set, then it's a resurrected node, then wipe it. + if _, present := t.delete[string(path)]; present { + delete(t.delete, string(path)) return } - t.insert[string(key)] = struct{}{} + t.insert[string(path)] = struct{}{} } // OnDelete tracks the newly deleted trie node. If it's already // in the addition set, then just wipe it from the addtion set // as it's untouched. -func (t *tracer) onDelete(key []byte) { +func (t *tracer) onDelete(path []byte) { // Tracer isn't used right now, remove this check latter. if t == nil { return } - if _, present := t.insert[string(key)]; present { - delete(t.insert, string(key)) + if _, present := t.insert[string(path)]; present { + delete(t.insert, string(path)) return } - t.delete[string(key)] = struct{}{} + t.delete[string(path)] = struct{}{} } // insertList returns the tracked inserted trie nodes in list format. @@ -98,8 +98,8 @@ func (t *tracer) insertList() [][]byte { return nil } var ret [][]byte - for key := range t.insert { - ret = append(ret, []byte(key)) + for path := range t.insert { + ret = append(ret, []byte(path)) } return ret } @@ -111,19 +111,36 @@ func (t *tracer) deleteList() [][]byte { return nil } var ret [][]byte - for key := range t.delete { - ret = append(ret, []byte(key)) + for path := range t.delete { + ret = append(ret, []byte(path)) } return ret } +// prevList returns the tracked node blobs in list format. +func (t *tracer) prevList() ([][]byte, [][]byte) { + // Tracer isn't used right now, remove this check later. + if t == nil { + return nil, nil + } + var ( + paths [][]byte + blobs [][]byte + ) + for path, blob := range t.origin { + paths = append(paths, []byte(path)) + blobs = append(blobs, blob) + } + return paths, blobs +} + // getPrev returns the cached original value of the specified node. -func (t *tracer) getPrev(key []byte) []byte { +func (t *tracer) getPrev(path []byte) []byte { // Don't panic on uninitialized tracer, it's possible in testing. if t == nil { return nil } - return t.origin[string(key)] + return t.origin[string(path)] } // reset clears the content tracked by tracer. diff --git a/trie/utils_test.go b/trie/utils_test.go index ffae9ffad..d9e229544 100644 --- a/trie/utils_test.go +++ b/trie/utils_test.go @@ -17,6 +17,7 @@ package trie import ( + "bytes" "testing" "github.com/ethereum/go-ethereum/common" @@ -70,7 +71,7 @@ func TestTrieTracer(t *testing.T) { // Commit the changes root, nodes, _ := trie.Commit(false) db.Update(NewWithNodeSet(nodes)) - trie, _ = New(common.Hash{}, root, db) + trie, _ = New(TrieID(root), db) trie.tracer = newTracer() // Delete all the elements, check deletion set @@ -122,3 +123,123 @@ func TestTrieTracerNoop(t *testing.T) { t.Fatalf("Unexpected deleted node tracked %d", len(trie.tracer.deleteList())) } } +func TestTrieTracePrevValue(t *testing.T) { + db := NewDatabase(rawdb.NewMemoryDatabase()) + trie := NewEmpty(db) + trie.tracer = newTracer() + + paths, blobs := trie.tracer.prevList() + if len(paths) != 0 || len(blobs) != 0 { + t.Fatalf("Nothing should be tracked") + } + // Insert a batch of entries, all the nodes should be marked as inserted + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"dog", "puppy"}, + {"somethingveryoddindeedthis is", "myothernodedata"}, + } + for _, val := range vals { + trie.Update([]byte(val.k), []byte(val.v)) + } + paths, blobs = trie.tracer.prevList() + if len(paths) != 0 || len(blobs) != 0 { + t.Fatalf("Nothing should be tracked") + } + + // Commit the changes and re-create with new root + root, nodes, _ := trie.Commit(false) + if err := db.Update(NewWithNodeSet(nodes)); err != nil { + t.Fatal(err) + } + trie, _ = New(TrieID(root), db) + trie.tracer = newTracer() + trie.resolveAndTrack(root.Bytes(), nil) + + // Load all nodes in trie + for _, val := range vals { + trie.TryGet([]byte(val.k)) + } + + // Ensure all nodes are tracked by tracer with correct prev-values + iter := trie.NodeIterator(nil) + seen := make(map[string][]byte) + for iter.Next(true) { + // Embedded nodes are ignored since they are not present in + // database. + if iter.Hash() == (common.Hash{}) { + continue + } + blob, err := trie.reader.nodeBlob(iter.Path(), iter.Hash()) + if err != nil { + t.Fatal(err) + } + seen[string(iter.Path())] = common.CopyBytes(blob) + } + + paths, blobs = trie.tracer.prevList() + if len(paths) != len(seen) || len(blobs) != len(seen) { + t.Fatalf("Unexpected tracked values") + } + for i, path := range paths { + blob := blobs[i] + prev, ok := seen[string(path)] + if !ok { + t.Fatalf("Missing node %v", path) + } + if !bytes.Equal(blob, prev) { + t.Fatalf("Unexpected value path: %v, want: %v, got: %v", path, prev, blob) + } + } + + // Re-open the trie and iterate the trie, ensure nothing will be tracked. + // Iterator will not link any loaded nodes to trie. + trie, _ = New(TrieID(root), db) + trie.tracer = newTracer() + + iter = trie.NodeIterator(nil) + for iter.Next(true) { + } + paths, blobs = trie.tracer.prevList() + if len(paths) != 0 || len(blobs) != 0 { + t.Fatalf("Nothing should be tracked") + } + + // Re-open the trie and generate proof for entries, ensure nothing will + // be tracked. Prover will not link any loaded nodes to trie. + trie, _ = New(TrieID(root), db) + trie.tracer = newTracer() + for _, val := range vals { + trie.Prove([]byte(val.k), 0, rawdb.NewMemoryDatabase()) + } + paths, blobs = trie.tracer.prevList() + if len(paths) != 0 || len(blobs) != 0 { + t.Fatalf("Nothing should be tracked") + } + + // Delete entries from trie, ensure all previous values are correct. + trie, _ = New(TrieID(root), db) + trie.tracer = newTracer() + trie.resolveAndTrack(root.Bytes(), nil) + + for _, val := range vals { + trie.TryDelete([]byte(val.k)) + } + paths, blobs = trie.tracer.prevList() + if len(paths) != len(seen) || len(blobs) != len(seen) { + t.Fatalf("Unexpected tracked values") + } + for i, path := range paths { + blob := blobs[i] + prev, ok := seen[string(path)] + if !ok { + t.Fatalf("Missing node %v", path) + } + if !bytes.Equal(blob, prev) { + t.Fatalf("Unexpected value path: %v, want: %v, got: %v", path, prev, blob) + } + } +}