diff --git a/XDCx/tradingstate/XDCx_trie.go b/XDCx/tradingstate/XDCx_trie.go index 202a9f62cf90..73cf353c0267 100644 --- a/XDCx/tradingstate/XDCx_trie.go +++ b/XDCx/tradingstate/XDCx_trie.go @@ -57,7 +57,7 @@ func NewXDCXTrie(root common.Hash, db *trie.Database) (*XDCXTrie, error) { if db == nil { panic("trie.NewXDCXTrie called without a database") } - trie, err := trie.New(common.Hash{}, root, db) + trie, err := trie.New(trie.TrieID(root), db) if err != nil { return nil, err } diff --git a/XDCxlending/lendingstate/XDCx_trie.go b/XDCxlending/lendingstate/XDCx_trie.go index 4c0bd5667424..87e89262318e 100644 --- a/XDCxlending/lendingstate/XDCx_trie.go +++ b/XDCxlending/lendingstate/XDCx_trie.go @@ -57,7 +57,7 @@ func NewXDCXTrie(root common.Hash, db *trie.Database) (*XDCXTrie, error) { if db == nil { panic("trie.NewXDCXTrie called without a database") } - trie, err := trie.New(common.Hash{}, root, db) + trie, err := trie.New(trie.TrieID(root), db) if err != nil { return nil, err } diff --git a/cmd/XDC/dbcmd.go b/cmd/XDC/dbcmd.go index 1c74e8eb728d..536352958fa2 100644 --- a/cmd/XDC/dbcmd.go +++ b/cmd/XDC/dbcmd.go @@ -21,6 +21,7 @@ import ( "os" "path/filepath" "slices" + "strconv" "time" "github.com/XinFinOrg/XDPoSChain/cmd/utils" @@ -30,6 +31,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/trie" "github.com/urfave/cli/v2" ) @@ -54,6 +56,7 @@ Remove blockchain and state databases`, dbGetCmd, dbDeleteCmd, dbPutCmd, + dbGetSlotsCmd, }, } dbInspectCmd = &cli.Command{ @@ -119,6 +122,16 @@ WARNING: This is a low-level operation which may cause database corruption!`, Description: `This command sets a given database key to the given value. WARNING: This is a low-level operation which may cause database corruption!`, } + dbGetSlotsCmd = &cli.Command{ + Action: dbDumpTrie, + Name: "dumptrie", + Usage: "Show the storage key/values of a given storage trie", + ArgsUsage: " ", + Flags: slices.Concat([]cli.Flag{ + utils.SyncModeFlag, + }, utils.NetworkFlags, utils.DatabaseFlags), + Description: "This command looks up the specified database key from the database.", + } ) func removeDB(ctx *cli.Context) error { @@ -328,3 +341,64 @@ func dbPut(ctx *cli.Context) error { } return db.Put(key, value) } + +// dbDumpTrie shows the key-value slots of a given storage trie +func dbDumpTrie(ctx *cli.Context) error { + if ctx.NArg() < 3 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + var ( + state []byte + storage []byte + account []byte + start []byte + max = int64(-1) + err error + ) + if state, err = hexutil.Decode(ctx.Args().Get(0)); err != nil { + log.Info("Could not decode the state root", "error", err) + return err + } + 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() > 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 + } + } + 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 + } + var count int64 + it := trie.NewIterator(theTrie.NodeIterator(start)) + for it.Next() { + if max > 0 && count == max { + fmt.Printf("Exiting after %d values\n", count) + break + } + fmt.Printf(" %d. key %#x: %#x\n", count, it.Key, it.Value) + count++ + } + return it.Err +} diff --git a/core/blockchain.go b/core/blockchain.go index a3bcdb7feeb0..93b494820426 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -72,6 +72,8 @@ var ( storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil) storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil) + triedbCommitTimer = metrics.NewRegisteredTimer("chain/triedb/commits", nil) + blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil) blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil) blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil) @@ -561,10 +563,10 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { if block == nil { return fmt.Errorf("non existent block [%x..]", hash[:4]) } - if _, err := trie.NewStateTrie(common.Hash{}, block.Root(), bc.stateCache.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 @@ -1932,6 +1934,7 @@ func (bc *BlockChain) processBlock(block *types.Block, parent *types.Header, sta storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) + triedbCommitTimer.Update(statedb.TrieDBCommits) // Triedb commits are complete, we can mark them triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing diff --git a/core/state/database.go b/core/state/database.go index 134953b3cb53..c1da42e5d766 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -42,7 +42,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 common.Hash, addrHash, root common.Hash) (Trie, error) // CopyTrie returns an independent copy of the given trie. CopyTrie(Trie) Trie @@ -146,7 +146,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.NewStateTrie(common.Hash{}, root, db.db) + tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.db) if err != nil { return nil, err } @@ -154,8 +154,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.NewStateTrie(addrHash, root, db.db) +func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error) { + tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, addrHash, root), db.db) if err != nil { return nil, err } diff --git a/core/state/iterator.go b/core/state/iterator.go index 2053b530d54f..3847321a818e 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 961574aac841..800f37d614c2 100644 --- a/core/state/metrics.go +++ b/core/state/metrics.go @@ -19,10 +19,12 @@ package state import "github.com/XinFinOrg/XDPoSChain/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/state_object.go b/core/state/state_object.go index 996f2cd6d2ce..64c9ee856784 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -158,7 +158,7 @@ func (s *stateObject) touch() { // be loaded. func (s *stateObject) getTrie(db Database) (Trie, error) { if s.trie == nil { - tr, err := db.OpenStorageTrie(s.addrHash, s.data.Root) + tr, err := db.OpenStorageTrie(s.db.originalRoot, s.addrHash, s.data.Root) if err != nil { return nil, err } diff --git a/core/state/statedb.go b/core/state/statedb.go index 53b595705f80..790963c19d41 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -50,6 +50,10 @@ type StateDB struct { trie Trie logger *tracing.Hooks + // originalRoot is the pre-state root, before any changes were made. + // It will be updated when the Commit is called. + originalRoot common.Hash + // This map holds 'live' objects, which will get modified while processing a state transition. stateObjects map[common.Address]*stateObject stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie @@ -96,6 +100,7 @@ type StateDB struct { StorageHashes time.Duration StorageUpdates time.Duration StorageCommits time.Duration + TrieDBCommits time.Duration AccountUpdated int StorageUpdated int @@ -120,6 +125,7 @@ func New(root common.Hash, db Database) (*StateDB, error) { return &StateDB{ db: db, trie: tr, + originalRoot: root, stateObjects: make(map[common.Address]*stateObject), stateObjectsPending: make(map[common.Address]struct{}), stateObjectsDirty: make(map[common.Address]struct{}), @@ -665,6 +671,7 @@ func (s *StateDB) Copy() *StateDB { state := &StateDB{ db: s.db, trie: s.db.CopyTrie(s.trie), + originalRoot: s.originalRoot, stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), @@ -852,9 +859,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.DiskDB().NewBatch() for addr := range s.stateObjectsDirty { @@ -874,7 +883,9 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if err := nodes.Merge(set); err != nil { return common.Hash{}, err } - storageTrieNodes += set.Len() + updates, deleted := set.Size() + storageTrieNodesUpdated += updates + storageTrieNodesDeleted += deleted } } // If the contract is destructed, the storage is still left in the @@ -904,7 +915,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if err := nodes.Merge(set); err != nil { return common.Hash{}, err } - accountTrieNodes = set.Len() + accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size() } // Report the commit metrics s.AccountCommits += time.Since(start) @@ -913,18 +924,32 @@ 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 len(s.stateObjectsDestruct) > 0 { s.stateObjectsDestruct = make(map[common.Address]struct{}) } - if err := s.db.TrieDB().Update(nodes); err != nil { - return common.Hash{}, err + if root == (common.Hash{}) { + root = types.EmptyRootHash + } + origin := s.originalRoot + if origin == (common.Hash{}) { + origin = types.EmptyRootHash + } + if root != origin { + start := time.Now() + if err := s.db.TrieDB().Update(nodes); err != nil { + return common.Hash{}, err + } + s.originalRoot = root + s.TrieDBCommits += time.Since(start) } - return root, err + return root, nil } // Prepare handles the preparatory steps for executing a state transition with. diff --git a/core/state/sync_test.go b/core/state/sync_test.go index ca278d2099f9..f3e2ed3f0ea8 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -105,7 +105,7 @@ func checkTrieConsistency(db ethdb.KeyValueStore, 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 } @@ -174,7 +174,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { if commit { srcDb.TrieDB().Commit(srcRoot, false) } - 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() @@ -222,7 +222,8 @@ 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()) + id := trie.StorageTrieID(srcRoot, common.BytesToHash(node.syncPath[0]), acc.Root) + stTrie, err := trie.New(id, srcDb.TrieDB()) if err != nil { t.Fatalf("failed to retriev storage trie for path %x: %v", node.syncPath[1], err) } diff --git a/eth/api_debug.go b/eth/api_debug.go index 22c1b0655d72..45edce5a54ec 100644 --- a/eth/api_debug.go +++ b/eth/api_debug.go @@ -286,11 +286,11 @@ func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]c } triedb := api.eth.BlockChain().StateCache().TrieDB() - oldTrie, err := trie.NewStateTrie(common.Hash{}, startBlock.Root(), triedb) + oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startBlock.Root()), triedb) if err != nil { return nil, err } - newTrie, err := trie.NewStateTrie(common.Hash{}, endBlock.Root(), triedb) + newTrie, err := trie.NewStateTrie(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 1f48a29259d1..6d5cfbe50f85 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -192,7 +192,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.NewStateTrie(common.Hash{}, block.Root(), trie.NewDatabase(dl.stateDb)) + _, err := trie.NewStateTrie(trie.StateTrieID(block.Root()), trie.NewDatabase(dl.stateDb)) return err } return fmt.Errorf("non existent block: %x", hash[:4]) diff --git a/trie/committer.go b/trie/committer.go index 4ad6e04ac47b..7f364ae62412 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -33,13 +33,15 @@ type leaf struct { // insertion order. type committer struct { nodes *NodeSet + tracer *tracer collectLeaf bool } // 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, } } @@ -51,6 +53,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 } @@ -83,6 +99,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) @@ -96,6 +118,12 @@ func (c *committer) commit(path []byte, n node) (node, error) { if hn, ok := hashedNode.(hashNode); ok { return hn, nil } + // The full 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 @@ -161,7 +189,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 diff --git a/trie/database.go b/trie/database.go index c14ecb3852d7..58fdb3e30375 100644 --- a/trie/database.go +++ b/trie/database.go @@ -807,8 +807,8 @@ func (db *Database) Update(nodes *MergedNodeSet) error { } for _, owner := range order { subset := nodes.sets[owner] - 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) } @@ -849,6 +849,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 +} + // CommitPreimages flushes the dangling preimages to disk. It is meant to be // called when closing the blockchain object, so that preimages are persisted // to the database. diff --git a/trie/iterator.go b/trie/iterator.go index eb8f853db19b..a42ce9f36dc2 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -376,7 +376,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 (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error) { @@ -385,7 +390,12 @@ func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error) return blob, nil } } - return it.trie.resolveBlob(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.nodeBlob(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 cff02ded4155..cb393e738b3f 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -66,7 +66,7 @@ func TestIterator(t *testing.T) { } 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() { @@ -227,7 +227,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) @@ -236,7 +236,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)) @@ -269,7 +269,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) @@ -278,7 +278,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) @@ -356,7 +356,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. @@ -445,7 +445,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 { @@ -529,7 +529,7 @@ func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) { // Create an empty trie logDb := &loggingDb{0, memorydb.New()} triedb := NewDatabase(logDb) - trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb) + trie, _ := NewStateTrie(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 c055ffd84c67..1bab68cd6f8b 100644 --- a/trie/nodeset.go +++ b/trie/nodeset.go @@ -18,6 +18,8 @@ package trie import ( "fmt" + "reflect" + "strings" "github.com/XinFinOrg/XDPoSChain/common" ) @@ -25,18 +27,77 @@ import ( // 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 + } + return nodeToBytes(n.node) +} + +// 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 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 @@ -45,24 +106,78 @@ type NodeSet struct { 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 } -// 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 +// 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, + } +} + +// 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(node *leaf) { set.leaves = append(set.leaves, node) } -// 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. TODO(rjl493456442) how can +// we get rid of it? +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 42b29a827679..2faa03794dc5 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -22,7 +22,6 @@ import ( "fmt" "github.com/XinFinOrg/XDPoSChain/common" - "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/log" ) @@ -60,8 +59,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("Unhandled trie error in Trie.Prove", "err", err) return err @@ -559,7 +563,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 9741d7179c3d..ef722827f211 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -29,8 +29,13 @@ type SecureTrie = StateTrie // NewSecure creates a new StateTrie. // Deprecated: use NewStateTrie. -func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) { - return NewStateTrie(owner, root, db) +func NewSecure(stateRoot common.Hash, owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) { + id := &ID{ + StateRoot: stateRoot, + Owner: owner, + Root: root, + } + return NewStateTrie(id, db) } // StateTrie wraps a trie with key hashing. In a stateTrie trie, all @@ -56,11 +61,11 @@ type StateTrie struct { // If root is the zero hash or the sha3 hash of an empty string, the // trie is initially empty. Otherwise, New will panic if db is nil // and returns MissingNodeError if the root node cannot be found. -func NewStateTrie(owner common.Hash, root common.Hash, db *Database) (*StateTrie, error) { +func NewStateTrie(id *ID, db *Database) (*StateTrie, error) { if db == nil { panic("trie.NewStateTrie 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 f9dd25e9f9e9..f8f3db271791 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -29,7 +29,7 @@ import ( ) func newEmptySecure() *StateTrie { - trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, NewDatabase(memorydb.New())) + trie, _ := NewStateTrie(TrieID(common.Hash{}), NewDatabase(memorydb.New())) return trie } @@ -37,7 +37,7 @@ func newEmptySecure() *StateTrie { func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(memorydb.New()) - trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb) + trie, _ := NewStateTrie(TrieID(common.Hash{}), triedb) // Fill it with some arbitrary data content := make(map[string][]byte) @@ -66,7 +66,7 @@ func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { panic(fmt.Errorf("failed to commit db %v", err)) } // Re-create the trie based on the new state - trie, _ = NewSecure(common.Hash{}, root, triedb) + trie, _ = NewStateTrie(TrieID(root), triedb) return triedb, trie, content } diff --git a/trie/sync_test.go b/trie/sync_test.go index ecea87ab6aa0..3b594bde459f 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -31,7 +31,7 @@ import ( func makeTestTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(memorydb.New()) - trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb) + trie, _ := NewStateTrie(TrieID(common.Hash{}), triedb) // Fill it with some arbitrary data content := make(map[string][]byte) @@ -60,7 +60,7 @@ func makeTestTrie() (*Database, *StateTrie, map[string][]byte) { panic(fmt.Errorf("failed to commit db %v", err)) } // Re-create the trie based on the new state - trie, _ = NewSecure(common.Hash{}, root, triedb) + trie, _ = NewStateTrie(TrieID(root), triedb) return triedb, trie, content } @@ -68,7 +68,7 @@ func makeTestTrie() (*Database, *StateTrie, 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 := NewStateTrie(common.Hash{}, common.BytesToHash(root), db) + trie, err := NewStateTrie(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 := NewStateTrie(common.Hash{}, root, db) + trie, err := NewStateTrie(TrieID(root), db) if err != nil { return nil // Consider a non existent state consistent } @@ -106,8 +106,8 @@ type trieElement struct { func TestEmptySync(t *testing.T) { dbA := NewDatabase(memorydb.New()) dbB := NewDatabase(memorydb.New()) - emptyA := NewEmpty(dbA) - emptyB, _ := New(common.Hash{}, types.EmptyRootHash, dbB) + emptyA, _ := New(TrieID(common.Hash{}), dbA) + emptyB, _ := New(TrieID(types.EmptyRootHash), dbB) for i, trie := range []*Trie{emptyA, emptyB} { sync := NewSync(trie.Hash(), memorydb.New(), nil) diff --git a/trie/trie.go b/trie/trie.go index 4c3496bff29f..524c55d9e2a1 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -63,6 +63,9 @@ type Trie struct { // only for reading purpose and not available for writing. db *Database + // reader is the handler trie can retrieve nodes from. + reader *trieReader + // tracer is the tool to track the trie changes. // It will be reset after each commit operation. tracer *tracer @@ -84,25 +87,30 @@ func (t *Trie) Copy() *Trie { owner: t.owner, unhashed: t.unhashed, db: t.db, + reader: t.reader, tracer: t.tracer.copy(), } } -// 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 *Database) (*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, + db: db, + reader: reader, //tracer: newTracer(), } - if root != (common.Hash{}) && root != types.EmptyRootHash { - rootnode, err := trie.resolveHash(root[:], nil) + if id.Root != (common.Hash{}) && id.Root != types.EmptyRootHash { + rootnode, err := trie.resolveAndTrack(id.Root[:], nil) if err != nil { return nil, err } @@ -113,7 +121,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 } @@ -169,7 +177,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 } @@ -215,7 +223,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 } // Path still needs to be traversed, descend into children @@ -245,7 +253,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 } @@ -294,7 +302,7 @@ func (t *Trie) tryGetBestLeftKeyAndValue(origNode node, prefix []byte) (key []by return key, value, n, didResolve, err } case hashNode: - child, err := t.resolveHash(n, nil) + child, err := t.resolveAndTrack(n, nil) if err != nil { return nil, nil, n, true, err } @@ -362,7 +370,7 @@ func (t *Trie) tryGetAllLeftKeyAndValue(origNode node, prefix []byte, limit []by } return keys, values, n, didResolve, err case hashNode: - child, err := t.resolveHash(n, nil) + child, err := t.resolveAndTrack(n, nil) if err != nil { return nil, nil, n, true, err } @@ -410,7 +418,7 @@ func (t *Trie) tryGetBestRightKeyAndValue(origNode node, prefix []byte) (key []b return key, value, n, didResolve, err } case hashNode: - child, err := t.resolveHash(n, nil) + child, err := t.resolveAndTrack(n, nil) if err != nil { return nil, nil, n, true, err } @@ -531,7 +539,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 } @@ -685,7 +693,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 } @@ -709,30 +717,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 - } - return nil, &MissingNodeError{Owner: t.owner, NodeHash: hash, Path: prefix} -} - -// resolveHash loads rlp-encoded node blob from the underlying database -// with the provided node hash and path prefix. -func (t *Trie) resolveBlob(n hashNode, prefix []byte) ([]byte, error) { - hash := common.BytesToHash(n) - blob, _ := t.db.Node(hash) - if len(blob) != 0 { - return blob, 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 @@ -750,9 +750,6 @@ func (t *Trie) Hash() common.Hash { // Once the trie is committed, it's not usable anymore. A new trie must // be created with new root and updated trie database for following usage func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) { - if t.db == nil { - panic("commit called on trie with nil database") - } defer t.tracer.reset() if t.root == nil { @@ -770,7 +767,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 @@ -797,6 +794,5 @@ func (t *Trie) Reset() { t.root = nil t.owner = common.Hash{} t.unhashed = 0 - //t.db = nil t.tracer.reset() } diff --git a/trie/trie_id.go b/trie/trie_id.go new file mode 100644 index 000000000000..785db0199acb --- /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/XinFinOrg/XDPoSChain/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 000000000000..ce70bfe8e152 --- /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/XinFinOrg/XDPoSChain/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 9d19dec90fd9..1a57122262a1 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -66,7 +66,8 @@ func TestNull(t *testing.T) { } func TestMissingRoot(t *testing.T) { - trie, err := New(common.Hash{}, common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), NewDatabase(memorydb.New())) + root := common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33") + trie, err := New(TrieID(root), NewDatabase(memorydb.New())) if trie != nil { t.Error("New returned non-nil trie for invalid root") } @@ -91,27 +92,27 @@ func testMissingNode(t *testing.T, memonly bool) { triedb.Commit(root, true) } - 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) @@ -124,27 +125,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) @@ -198,7 +199,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) } } @@ -275,7 +276,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) } @@ -296,7 +297,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) } @@ -381,6 +382,7 @@ const ( opCommit opItercheckhash opNodeDiff + opProve opMax // boundary value, not an actual op ) @@ -418,7 +420,7 @@ func generateSteps(finished func() bool, r io.Reader) randTest { step.key = genKey() step.value = make([]byte, 8) binary.BigEndian.PutUint64(step.value, uint64(len(steps))) - case opGet, opDelete: + case opGet, opDelete, opProve: step.key = genKey() } steps = append(steps, step) @@ -456,24 +458,60 @@ func runRandTest(rt randTest) error { if string(v) != want { rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want) } + case opProve: + hash := tr.Hash() + if hash == types.EmptyRootHash { + 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 err } + // 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) + panic(rt[i].err) + } + } + } 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 err } 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: diff --git a/trie/util_test.go b/trie/util_test.go index bcb9b7279085..5a4466192af9 100644 --- a/trie/util_test.go +++ b/trie/util_test.go @@ -17,6 +17,7 @@ package trie import ( + "bytes" "testing" "github.com/XinFinOrg/XDPoSChain/common" @@ -72,7 +73,7 @@ func TestTrieTracer(t *testing.T) { if err := db.Update(NewWithNodeSet(nodes)); err != nil { t.Fatal(err) } - trie, _ = New(common.Hash{}, root, db) + trie, _ = New(TrieID(root), db) trie.tracer = newTracer() // Delete all the elements, check deletion set @@ -124,3 +125,120 @@ 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 + } + seen[string(iter.Path())] = common.CopyBytes(iter.NodeBlob()) + } + + 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) + } + } +} diff --git a/trie/utils.go b/trie/utils.go index 7e26915041ef..d462b31bd205 100644 --- a/trie/utils.go +++ b/trie/utils.go @@ -50,45 +50,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 deletion set // (resurrected node), then just wipe it from the deletion set as the "untouched". -func (t *tracer) onInsert(key []byte) { +func (t *tracer) onInsert(path []byte) { // Tracer isn't used right now, remove this check later. if t == nil { return } - if _, present := t.delete[string(key)]; present { - delete(t.delete, string(key)) + 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 addition 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 later. 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 +96,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,22 +109,37 @@ 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 { - // Don't panic on uninitialized tracer, it's possible in testing. +func (t *tracer) getPrev(path []byte) []byte { + // Tracer isn't used right now, remove this check later. if t == nil { return nil } - return t.origin[string(key)] + return t.origin[string(path)] } -*/ // reset clears the content tracked by tracer. func (t *tracer) reset() {