diff --git a/cmd/XDC/chaincmd.go b/cmd/XDC/chaincmd.go index cb916dbfde..d7e136b0a8 100644 --- a/cmd/XDC/chaincmd.go +++ b/cmd/XDC/chaincmd.go @@ -39,6 +39,7 @@ import ( xdc_genesis "github.com/XinFinOrg/XDPoSChain/genesis" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/node" + "github.com/XinFinOrg/XDPoSChain/trie" "github.com/urfave/cli/v2" ) @@ -48,7 +49,10 @@ var ( Name: "init", Usage: "Bootstrap and initialize a new genesis block", ArgsUsage: "", - Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags), + Flags: slices.Concat( + []cli.Flag{utils.CachePreimagesFlag}, + utils.NetworkFlags, + utils.DatabaseFlags), Description: ` The init command initializes a new genesis block and definition for the network. This is a destructive action and changes the network in which you will be @@ -424,7 +428,10 @@ func dump(ctx *cli.Context) error { if err != nil { return err } - state, err := state.New(root, state.NewDatabase(db)) + config := &trie.Config{ + Preimages: true, // always enable preimage lookup + } + state, err := state.New(root, state.NewDatabaseWithConfig(db, config)) if err != nil { return err } diff --git a/core/blockchain.go b/core/blockchain.go index 1d2289bafb..384fc9f85e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -167,10 +167,12 @@ type BlockChain struct { chainConfig *params.ChainConfig // Chain & network configuration cacheConfig *CacheConfig // Cache configuration for pruning - db ethdb.Database // Low level persistent database to store final content in - XDCxDb ethdb.XDCxDatabase - triegc *prque.Prque[int64, common.Hash] // Priority queue mapping block numbers to tries to gc - gcproc time.Duration // Accumulates canonical block processing for trie dumping + db ethdb.Database // Low level persistent database to store final content in + XDCxDb ethdb.XDCxDatabase // XDCx database + triegc *prque.Prque[int64, common.Hash] // Priority queue mapping block numbers to tries to gc + gcproc time.Duration // Accumulates canonical block processing for trie dumping + triedb *trie.Database // The database handler for maintaining trie nodes. + stateCache state.Database // State database to reuse between imports (contains state cache) hc *HeaderChain rmLogsFeed event.Feed @@ -188,8 +190,6 @@ type BlockChain struct { currentBlock atomic.Value // Current head of the block chain currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!) - stateCache state.Database // State database to reuse between imports (contains state cache) - bodyCache *lru.Cache[common.Hash, *types.Body] // Cache for the most recent block bodies bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] // Cache for the most recent block bodies in RLP encoded format receiptsCache *lru.Cache[common.Hash, types.Receipts] // Cache for the most recent block receipts @@ -239,10 +239,16 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } } + // Open trie database with provided config + triedb := trie.NewDatabaseWithConfig(db, &trie.Config{ + Cache: cacheConfig.TrieCleanLimit, + Preimages: cacheConfig.Preimages, + }) bc := &BlockChain{ chainConfig: chainConfig, cacheConfig: cacheConfig, db: db, + triedb: triedb, triegc: prque.New[int64, common.Hash](nil), stateCache: state.NewDatabaseWithConfig(db, &trie.Config{ Cache: cacheConfig.TrieCleanLimit, @@ -268,6 +274,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par rejectedLendingItem: lru.NewCache[common.Hash, interface{}](tradingstate.OrderCacheLimit), finalizedTrade: lru.NewCache[common.Hash, interface{}](tradingstate.OrderCacheLimit), } + bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb) bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) bc.processor = NewStateProcessor(chainConfig, bc, engine) @@ -373,8 +380,7 @@ func (bc *BlockChain) loadLastState() error { } // Make sure the state associated with the block is available repair := false - _, err := state.New(currentBlock.Root(), bc.stateCache) - if err != nil { + if !bc.HasState(currentBlock.Root()) { repair = true } else { engine, ok := bc.Engine().(*XDPoS.XDPoS) @@ -487,7 +493,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64) error { if newHeadBlock == nil { newHeadBlock = bc.genesisBlock } else { - if _, err := state.New(newHeadBlock.Root(), bc.stateCache); err != nil { + if !bc.HasState(newHeadBlock.Root()) { // Rewound state missing, rolled back to before pivot, reset to genesis newHeadBlock = bc.genesisBlock } @@ -712,7 +718,7 @@ func (bc *BlockChain) repair(head **types.Block) error { for { // Abort if we've rewound to a head block that does have associated state if (common.RollbackNumber == 0) || ((*head).Number().Uint64() < common.RollbackNumber) { - if _, err := state.New((*head).Root(), bc.stateCache); err == nil { + if bc.HasState((*head).Root()) { log.Info("Rewound blockchain to past state", "number", (*head).Number(), "hash", (*head).Hash()) engine, ok := bc.Engine().(*XDPoS.XDPoS) if ok { @@ -1082,10 +1088,10 @@ func (bc *BlockChain) saveData() { if !bc.cacheConfig.TrieDirtyDisabled { var tradingTriedb *trie.Database var lendingTriedb *trie.Database - engine, _ := bc.Engine().(*XDPoS.XDPoS) - triedb := bc.stateCache.TrieDB() var tradingService utils.TradingService var lendingService utils.LendingService + triedb := bc.triedb + engine, _ := bc.Engine().(*XDPoS.XDPoS) if bc.Config().IsTIPXDCX(bc.CurrentBlock().Number()) && bc.chainConfig.XDPoS != nil && bc.CurrentBlock().NumberU64() > bc.chainConfig.XDPoS.Epoch && engine != nil { tradingService = engine.GetXDCXService() if tradingService != nil && tradingService.GetStateCache() != nil { @@ -1439,7 +1445,6 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. if err != nil { return NonStatTy, err } - triedb := bc.stateCache.TrieDB() tradingRoot := common.Hash{} if tradingState != nil { @@ -1474,7 +1479,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // If we're running an archive node, always flush if bc.cacheConfig.TrieDirtyDisabled { - if err := triedb.Commit(root, false); err != nil { + if err := bc.triedb.Commit(root, false); err != nil { return NonStatTy, err } if tradingTrieDb != nil { @@ -1489,7 +1494,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. } } else { // Full but not archive node, do proper garbage collection - triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive + bc.triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive bc.triegc.Push(root, -int64(block.NumberU64())) if tradingTrieDb != nil { tradingTrieDb.Reference(tradingRoot, common.Hash{}) @@ -1516,11 +1521,11 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // size = size + lendingTrieDb.Size() //} var ( - nodes, imgs = triedb.Size() + nodes, imgs = bc.triedb.Size() limit = common.StorageSize(bc.cacheConfig.TrieDirtyLimit) * 1024 * 1024 ) if nodes > limit || imgs > 4*1024*1024 { - triedb.Cap(limit - ethdb.IdealBatchSize) + bc.triedb.Cap(limit - ethdb.IdealBatchSize) } if bc.gcproc > bc.cacheConfig.TrieTimeLimit || chosen > lastWrite+triesInMemory { // If the header is missing (canonical chain behind), we're reorging a low @@ -1535,7 +1540,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", bc.cacheConfig.TrieTimeLimit, "optimum", float64(chosen-lastWrite)/triesInMemory) } // Flush an entire trie and restart the counters - triedb.Commit(header.Root, true) + bc.triedb.Commit(header.Root, true) lastWrite = chosen bc.gcproc = 0 if tradingTrieDb != nil && lendingTrieDb != nil { @@ -1555,7 +1560,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. bc.triegc.Push(root, number) break } - triedb.Dereference(root) + bc.triedb.Dereference(root) } if tradingService != nil { for !tradingService.GetTriegc().Empty() { @@ -1831,7 +1836,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] bc.UpdateBlocksHashCache(block) } - dirty, _ := bc.stateCache.TrieDB().Size() + dirty, _ := bc.triedb.Size() stats.report(chain, it.index, dirty) if bc.chainConfig.XDPoS != nil { engine, _ := bc.Engine().(*XDPoS.XDPoS) @@ -2287,7 +2292,7 @@ func (bc *BlockChain) insertBlock(block *types.Block) ([]interface{}, []*types.L } stats.processed++ stats.usedGas += result.usedGas - dirty, _ := bc.stateCache.TrieDB().Size() + dirty, _ := bc.triedb.Size() stats.report(types.Blocks{block}, 0, dirty) if bc.chainConfig.XDPoS != nil { // epoch block diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go new file mode 100644 index 0000000000..2d249437b8 --- /dev/null +++ b/core/blockchain_reader.go @@ -0,0 +1,26 @@ +// Copyright 2021 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 core + +import ( + "github.com/XinFinOrg/XDPoSChain/trie" +) + +// TrieDB retrieves the low level trie database used for data storage. +func (bc *BlockChain) TrieDB() *trie.Database { + return bc.triedb +} diff --git a/core/state/database.go b/core/state/database.go index c1da42e5d7..75d3f8770d 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -130,23 +130,33 @@ func NewDatabase(db ethdb.Database) Database { // large memory cache. func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { return &cachingDB{ - db: trie.NewDatabaseWithConfig(db, config), disk: db, - codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: trie.NewDatabaseWithConfig(db, config), + } +} + +// NewDatabaseWithNodeDB creates a state database with an already initialized node database. +func NewDatabaseWithNodeDB(db ethdb.Database, triedb *trie.Database) Database { + return &cachingDB{ + disk: db, + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: triedb, } } type cachingDB struct { - db *trie.Database disk ethdb.KeyValueStore - codeCache *lru.SizeConstrainedCache[common.Hash, []byte] codeSizeCache *lru.Cache[common.Hash, int] + codeCache *lru.SizeConstrainedCache[common.Hash, []byte] + triedb *trie.Database } // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { - tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.db) + tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { return nil, err } @@ -155,7 +165,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // OpenStorageTrie opens the storage trie of an account. func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error) { - tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, addrHash, root), db.db) + tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, addrHash, root), db.triedb) if err != nil { return nil, err } @@ -218,5 +228,5 @@ func (db *cachingDB) DiskDB() ethdb.KeyValueStore { // TrieDB retrieves any intermediate trie-node caching layer. func (db *cachingDB) TrieDB() *trie.Database { - return db.db + return db.triedb } diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go index 4056be5f45..3313103e98 100644 --- a/core/state/iterator_test.go +++ b/core/state/iterator_test.go @@ -21,15 +21,15 @@ import ( "testing" "github.com/XinFinOrg/XDPoSChain/common" - "github.com/XinFinOrg/XDPoSChain/ethdb" ) // Tests that the node iterator indeed walks over the entire database contents. func TestNodeIteratorCoverage(t *testing.T) { // Create some arbitrary test state to iterate - db, root, _ := makeTestState() + db, sdb, root, _ := makeTestState() + sdb.TrieDB().Commit(root, false) - state, err := New(root, db) + state, err := New(root, sdb) if err != nil { t.Fatalf("failed to create state trie at %x: %v", root, err) } @@ -42,19 +42,19 @@ func TestNodeIteratorCoverage(t *testing.T) { } // Cross check the iterated hashes and the database/nodepool content for hash := range hashes { - if _, err = db.TrieDB().Node(hash); err != nil { - _, err = db.ContractCode(common.Hash{}, hash) + if _, err = sdb.TrieDB().Node(hash); err != nil { + _, err = sdb.ContractCode(common.Hash{}, hash) } if err != nil { t.Errorf("failed to retrieve reported node %x", hash) } } - for _, hash := range db.TrieDB().Nodes() { + for _, hash := range sdb.TrieDB().Nodes() { if _, ok := hashes[hash]; !ok { t.Errorf("state entry not reported %x", hash) } } - it := db.DiskDB().(ethdb.Database).NewIterator(nil, nil) + it := db.NewIterator(nil, nil) for it.Next() { key := it.Key() if bytes.HasPrefix(key, []byte("secure-key-")) { diff --git a/core/state/sync.go b/core/state/sync.go index e2046132c5..1e5bac164b 100644 --- a/core/state/sync.go +++ b/core/state/sync.go @@ -27,7 +27,7 @@ import ( ) // NewStateSync create a new state trie download scheduler. -func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(keys [][]byte, leaf []byte) error) *trie.Sync { +func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(keys [][]byte, leaf []byte) error, scheme trie.NodeScheme) *trie.Sync { // Register the storage slot callback if the external callback is specified. var onSlot func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error if onLeaf != nil { @@ -52,6 +52,6 @@ func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(k syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), path, parent, parentPath) return nil } - syncer = trie.NewSync(root, database, onAccount) + syncer = trie.NewSync(root, database, onAccount, scheme) return syncer } diff --git a/core/state/sync_test.go b/core/state/sync_test.go index f3e2ed3f0e..9cb4d55b79 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -40,10 +40,11 @@ type testAccount struct { } // makeTestState create a sample test state to test node-wise reconstruction. -func makeTestState() (Database, common.Hash, []*testAccount) { +func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) { // Create an empty state - db := NewDatabase(rawdb.NewMemoryDatabase()) - state, _ := New(types.EmptyRootHash, db) + db := rawdb.NewMemoryDatabase() + sdb := NewDatabase(db) + state, _ := New(types.EmptyRootHash, sdb) // Fill it with some arbitrary data var accounts []*testAccount @@ -64,7 +65,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) { if i%5 == 0 { for j := byte(0); j < 5; j++ { hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}) - obj.SetState(db, hash, hash) + obj.SetState(sdb, hash, hash) } } state.updateStateObject(obj) @@ -73,7 +74,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) { root, _ := state.Commit(false) // Return the generated state - return db, root, accounts + return db, sdb, root, accounts } // checkStateAccounts cross references a reconstructed state with an expected @@ -101,7 +102,7 @@ func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accou } // checkTrieConsistency checks that all nodes in a (sub-)trie are indeed present. -func checkTrieConsistency(db ethdb.KeyValueStore, root common.Hash) error { +func checkTrieConsistency(db ethdb.Database, root common.Hash) error { if v, _ := db.Get(root[:]); v == nil { return nil // Consider a non existent state consistent. } @@ -133,7 +134,9 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error { // Tests that an empty state is not scheduled for syncing. func TestEmptyStateSync(t *testing.T) { - sync := NewStateSync(types.EmptyRootHash, rawdb.NewMemoryDatabase(), nil) + db := trie.NewDatabase(rawdb.NewMemoryDatabase()) + empty := common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), nil, db.Scheme()) if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 { t.Errorf("content requested for empty state: %v, %v, %v", nodes, paths, codes) } @@ -170,7 +173,7 @@ type stateElement struct { func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { // Create a random state to copy - srcDb, srcRoot, srcAccounts := makeTestState() + _, srcDb, srcRoot, srcAccounts := makeTestState() if commit { srcDb.TrieDB().Commit(srcRoot, false) } @@ -178,7 +181,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil) + sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) var ( nodeElements []stateElement @@ -281,11 +284,11 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { // partial results are returned, and the others sent only later. func TestIterativeDelayedStateSync(t *testing.T) { // Create a random state to copy - srcDb, srcRoot, srcAccounts := makeTestState() + _, srcDb, srcRoot, srcAccounts := makeTestState() // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil) + sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) var ( nodeElements []stateElement @@ -374,11 +377,11 @@ func TestIterativeRandomStateSyncBatched(t *testing.T) { testIterativeRandomS func testIterativeRandomStateSync(t *testing.T, count int) { // Create a random state to copy - srcDb, srcRoot, srcAccounts := makeTestState() + _, srcDb, srcRoot, srcAccounts := makeTestState() // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil) + sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) nodeQueue := make(map[string]stateElement) codeQueue := make(map[common.Hash]struct{}) @@ -454,11 +457,11 @@ func testIterativeRandomStateSync(t *testing.T, count int) { // partial results are returned (Even those randomly), others sent only later. func TestIterativeRandomDelayedStateSync(t *testing.T) { // Create a random state to copy - srcDb, srcRoot, srcAccounts := makeTestState() + _, srcDb, srcRoot, srcAccounts := makeTestState() // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil) + sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) nodeQueue := make(map[string]stateElement) codeQueue := make(map[common.Hash]struct{}) @@ -544,7 +547,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { // the database. func TestIncompleteStateSync(t *testing.T) { // Create a random state to copy - srcDb, srcRoot, srcAccounts := makeTestState() + db, srcDb, srcRoot, srcAccounts := makeTestState() /// isCodeLookup to save some hashing var isCode = make(map[common.Hash]struct{}) @@ -554,15 +557,16 @@ func TestIncompleteStateSync(t *testing.T) { } } isCode[types.EmptyCodeHash] = struct{}{} - checkTrieConsistency(srcDb.DiskDB(), srcRoot) + checkTrieConsistency(db, srcRoot) // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil) + sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) var ( - addedCodes []common.Hash - addedNodes []common.Hash + addedCodes []common.Hash + addedPaths []string + addedHashes []common.Hash ) nodeQueue := make(map[string]stateElement) codeQueue := make(map[common.Hash]struct{}) @@ -599,15 +603,16 @@ func TestIncompleteStateSync(t *testing.T) { var nodehashes []common.Hash if len(nodeQueue) > 0 { results := make([]trie.NodeSyncResult, 0, len(nodeQueue)) - for key, element := range nodeQueue { + for path, element := range nodeQueue { data, err := srcDb.TrieDB().Node(element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x", element.hash) } - results = append(results, trie.NodeSyncResult{Path: key, Data: data}) + results = append(results, trie.NodeSyncResult{Path: path, Data: data}) if element.hash != srcRoot { - addedNodes = append(addedNodes, element.hash) + addedPaths = append(addedPaths, element.path) + addedHashes = append(addedHashes, element.hash) } nodehashes = append(nodehashes, element.hash) } @@ -655,12 +660,18 @@ func TestIncompleteStateSync(t *testing.T) { } rawdb.WriteCode(dstDb, node, val) } - for _, node := range addedNodes { - val := rawdb.ReadTrieNode(dstDb, node) - rawdb.DeleteTrieNode(dstDb, node) - if err := checkStateConsistency(dstDb, srcRoot); err == nil { - t.Errorf("trie inconsistency not caught, missing: %v", node.Hex()) + scheme := srcDb.TrieDB().Scheme() + for i, path := range addedPaths { + owner, inner := trie.ResolvePath([]byte(path)) + hash := addedHashes[i] + val := scheme.ReadTrieNode(dstDb, owner, inner, hash) + if val == nil { + t.Error("missing trie node") } - rawdb.WriteTrieNode(dstDb, node, val) + scheme.DeleteTrieNode(dstDb, owner, inner, hash) + if err := checkStateConsistency(dstDb, srcRoot); err == nil { + t.Errorf("trie inconsistency not caught, missing: %v", path) + } + scheme.WriteTrieNode(dstDb, owner, inner, hash, val) } } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index b48e7a94c3..f523420191 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -34,6 +34,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/metrics" "github.com/XinFinOrg/XDPoSChain/params" + "github.com/XinFinOrg/XDPoSChain/trie" ) // proposeBlockHandlerFn is a callback type to handle a block by the consensus @@ -205,6 +206,10 @@ type BlockChain interface { // InsertReceiptChain inserts a batch of receipts into the local chain. InsertReceiptChain(types.Blocks, []types.Receipts) (int, error) + + // TrieDB retrieves the low level trie database used for interacting + // with trie nodes. + TrieDB() *trie.Database } // New creates a new downloader to fetch hashes and blocks from remote peers. diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 712f04d316..f26e23c971 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -44,8 +44,10 @@ func init() { } // downloadTester is a test simulator for mocking out local block chain. +// TODO(daniel): remove field triedb type downloadTester struct { downloader *Downloader + triedb *trie.Database genesis *types.Block // Genesis blocks used by the tester and peers stateDb ethdb.Database // Database used by the tester for syncing from peers @@ -74,11 +76,16 @@ func newTester() *downloadTester { ownChainTd: map[common.Hash]*big.Int{testGenesis.Hash(): testGenesis.Difficulty()}, } tester.stateDb = rawdb.NewMemoryDatabase() + tester.triedb = trie.NewDatabase(tester.stateDb) tester.stateDb.Put(testGenesis.Root().Bytes(), []byte{0x00}) tester.downloader = New(tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer, tester.handleProposedBlock) return tester } +func (db *downloadTester) TrieDB() *trie.Database { + return db.triedb +} + // terminate aborts any operations on the embedded downloader and releases all // held resources. func (dl *downloadTester) terminate() { diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index c10536d931..112e7913e6 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -291,18 +291,19 @@ type codeTask struct { // newStateSync creates a new state trie download scheduler. This method does not // yet start the sync. The user needs to call run to initiate. // only use fast sync but XDC only run full sync +// TODO(daniel): remove field sched func newStateSync(d *Downloader, root common.Hash) *stateSync { return &stateSync{ d: d, - sched: state.NewStateSync(root, d.stateDB, nil), + root: root, + cancel: make(chan struct{}), + done: make(chan struct{}), + started: make(chan struct{}), + sched: state.NewStateSync(root, d.stateDB, nil, d.blockchain.TrieDB().Scheme()), keccak: sha3.NewLegacyKeccak256(), trieTasks: make(map[string]*trieTask), codeTasks: make(map[common.Hash]*codeTask), deliver: make(chan *stateReq), - cancel: make(chan struct{}), - done: make(chan struct{}), - started: make(chan struct{}), - root: root, } } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 4bd26f9b81..a4c1b50a91 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -103,10 +103,8 @@ func (t *BlockTest) Run() error { // import pre accounts & construct test genesis block & state root db := rawdb.NewMemoryDatabase() - gblock, err := t.genesis(config).Commit(db) - if err != nil { - return err - } + gspec := t.genesis(config) + gblock := gspec.MustCommit(db) if gblock.Hash() != t.json.Genesis.Hash { return fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x", gblock.Hash().Bytes(), t.json.Genesis.Hash) } diff --git a/trie/database.go b/trie/database.go index 7fb5b27f07..ab8bb871e0 100644 --- a/trie/database.go +++ b/trie/database.go @@ -67,7 +67,7 @@ var ( // behind this split design is to provide read access to RPC handlers and sync // servers even while the trie is executing expensive garbage collection. type Database struct { - diskdb ethdb.KeyValueStore // Persistent storage for matured trie nodes + diskdb ethdb.Database // Persistent storage for matured trie nodes cleans *fastcache.Cache // GC friendly memory Cache of clean Node RLPs dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes @@ -271,14 +271,14 @@ type Config struct { // NewDatabase creates a new trie database to store ephemeral trie content before // its written out to disk or garbage collected. No read Cache is created, so all // data retrievals will hit the underlying disk database. -func NewDatabase(diskdb ethdb.KeyValueStore) *Database { +func NewDatabase(diskdb ethdb.Database) *Database { return NewDatabaseWithConfig(diskdb, nil) } // NewDatabaseWithConfig creates a new trie database to store ephemeral trie content // before its written out to disk or garbage collected. It also acts as a read cache // for nodes loaded from disk. -func NewDatabaseWithConfig(diskdb ethdb.KeyValueStore, config *Config) *Database { +func NewDatabaseWithConfig(diskdb ethdb.Database, config *Config) *Database { var cleans *fastcache.Cache if config != nil && config.Cache > 0 { cleans = fastcache.New(config.Cache * 1024 * 1024) @@ -891,3 +891,8 @@ func (db *Database) CommitPreimages() error { } return db.preimages.commit(true) } + +// Scheme returns the node scheme used in the database. +func (db *Database) Scheme() NodeScheme { + return &hashScheme{} +} diff --git a/trie/database_test.go b/trie/database_test.go index ce9ecd03ee..654e6d885a 100644 --- a/trie/database_test.go +++ b/trie/database_test.go @@ -20,13 +20,13 @@ import ( "testing" "github.com/XinFinOrg/XDPoSChain/common" - "github.com/XinFinOrg/XDPoSChain/ethdb/memorydb" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" ) // Tests that the trie database returns a missing trie Node error if attempting // to retrieve the meta root. func TestDatabaseMetarootFetch(t *testing.T) { - db := NewDatabase(memorydb.New()) + db := NewDatabase(rawdb.NewMemoryDatabase()) if _, err := db.Node(common.Hash{}); err == nil { t.Fatalf("metaroot retrieval succeeded") } diff --git a/trie/iterator_test.go b/trie/iterator_test.go index cb393e738b..4c329d9a1b 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -327,7 +327,7 @@ func TestIteratorContinueAfterErrorDisk(t *testing.T) { testIteratorContinueA func TestIteratorContinueAfterErrorMemonly(t *testing.T) { testIteratorContinueAfterError(t, true) } func testIteratorContinueAfterError(t *testing.T, memonly bool) { - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) tr := NewEmpty(triedb) @@ -418,8 +418,8 @@ func TestIteratorContinueAfterSeekErrorMemonly(t *testing.T) { } func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { - // Commit test trie to Db, then remove the Node containing "bars". - diskdb := memorydb.New() + // Commit test trie to db, then remove the node containing "bars". + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) ctr := NewEmpty(triedb) @@ -528,7 +528,7 @@ func (l *loggingDb) Close() error { func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) { // Create an empty trie logDb := &loggingDb{0, memorydb.New()} - triedb := NewDatabase(logDb) + triedb := NewDatabase(rawdb.NewDatabase(logDb)) trie, _ := NewStateTrie(TrieID(common.Hash{}), triedb) // Fill it with some arbitrary data @@ -563,7 +563,7 @@ func TestNodeIteratorLargeTrie(t *testing.T) { func TestIteratorNodeBlob(t *testing.T) { var ( - db = memorydb.New() + db = rawdb.NewMemoryDatabase() triedb = NewDatabase(db) trie = NewEmpty(triedb) ) diff --git a/trie/schema.go b/trie/schema.go new file mode 100644 index 0000000000..35336771ab --- /dev/null +++ b/trie/schema.go @@ -0,0 +1,96 @@ +// Copyright 2021 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" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" + "github.com/XinFinOrg/XDPoSChain/ethdb" +) + +const ( + HashScheme = "hashScheme" // Identifier of hash based node scheme + + // Path-based scheme will be introduced in the following PRs. + // PathScheme = "pathScheme" // Identifier of path based node scheme +) + +// NodeScheme describes the scheme for interacting nodes in disk. +type NodeScheme interface { + // Name returns the identifier of node scheme. + Name() string + + // HasTrieNode checks the trie node presence with the provided node info and + // the associated node hash. + HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash) bool + + // ReadTrieNode retrieves the trie node from database with the provided node + // info and the associated node hash. + ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash) []byte + + // WriteTrieNode writes the trie node into database with the provided node + // info and associated node hash. + WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte) + + // DeleteTrieNode deletes the trie node from database with the provided node + // info and associated node hash. + DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash) + + // IsTrieNode returns an indicator if the given database key is the key of + // trie node according to the scheme. + IsTrieNode(key []byte) (bool, []byte) +} + +type hashScheme struct{} + +// Name returns the identifier of hash based scheme. +func (scheme *hashScheme) Name() string { + return HashScheme +} + +// HasTrieNode checks the trie node presence with the provided node info and +// the associated node hash. +func (scheme *hashScheme) HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash) bool { + return rawdb.HasTrieNode(db, hash) +} + +// ReadTrieNode retrieves the trie node from database with the provided node info +// and associated node hash. +func (scheme *hashScheme) ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash) []byte { + return rawdb.ReadTrieNode(db, hash) +} + +// WriteTrieNode writes the trie node into database with the provided node info +// and associated node hash. +func (scheme *hashScheme) WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte) { + rawdb.WriteTrieNode(db, hash, node) +} + +// DeleteTrieNode deletes the trie node from database with the provided node info +// and associated node hash. +func (scheme *hashScheme) DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash) { + rawdb.DeleteTrieNode(db, hash) +} + +// IsTrieNode returns an indicator if the given database key is the key of trie +// node according to the scheme. +func (scheme *hashScheme) IsTrieNode(key []byte) (bool, []byte) { + if len(key) == common.HashLength { + return true, key + } + return false, nil +} diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index f8f3db2717..ba0bc27cd6 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -24,19 +24,19 @@ import ( "testing" "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/crypto" - "github.com/XinFinOrg/XDPoSChain/ethdb/memorydb" ) func newEmptySecure() *StateTrie { - trie, _ := NewStateTrie(TrieID(common.Hash{}), NewDatabase(memorydb.New())) + trie, _ := NewStateTrie(TrieID(common.Hash{}), NewDatabase(rawdb.NewMemoryDatabase())) return trie } // makeTestStateTrie creates a large enough secure trie for testing. func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie - triedb := NewDatabase(memorydb.New()) + triedb := NewDatabase(rawdb.NewMemoryDatabase()) trie, _ := NewStateTrie(TrieID(common.Hash{}), triedb) // Fill it with some arbitrary data diff --git a/trie/stacktrie.go b/trie/stacktrie.go index ca7e64f30c..253cb5f21e 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -26,7 +26,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/types" - "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/log" ) @@ -38,10 +37,14 @@ var stPool = sync.Pool{ }, } -func stackTrieFromPool(db ethdb.KeyValueWriter, owner common.Hash) *StackTrie { +// NodeWriteFunc is used to provide all information of a dirty node for committing +// so that callers can flush nodes into database with desired scheme. +type NodeWriteFunc = func(owner common.Hash, path []byte, hash common.Hash, blob []byte) + +func stackTrieFromPool(writeFn NodeWriteFunc, owner common.Hash) *StackTrie { st := stPool.Get().(*StackTrie) - st.db = db st.owner = owner + st.writeFn = writeFn return st } @@ -54,41 +57,41 @@ func returnToPool(st *StackTrie) { // in order. Once it determines that a subtree will no longer be inserted // into, it will hash it and free up the memory it uses. type StackTrie struct { - owner common.Hash // the owner of the trie - nodeType uint8 // node type (as in branch, ext, leaf) - val []byte // value contained by this node if it's a leaf - key []byte // key chunk covered by this (leaf|ext) node - children [16]*StackTrie // list of children (for branch and exts) - db ethdb.KeyValueWriter // Pointer to the commit db, can be nil + owner common.Hash // the owner of the trie + nodeType uint8 // node type (as in branch, ext, leaf) + val []byte // value contained by this node if it's a leaf + key []byte // key chunk covered by this (leaf|ext) node + children [16]*StackTrie // list of children (for branch and exts) + writeFn NodeWriteFunc // function for committing nodes, can be nil } // NewStackTrie allocates and initializes an empty trie. -func NewStackTrie(db ethdb.KeyValueWriter) *StackTrie { +func NewStackTrie(writeFn NodeWriteFunc) *StackTrie { return &StackTrie{ nodeType: emptyNode, - db: db, + writeFn: writeFn, } } // NewStackTrieWithOwner allocates and initializes an empty trie, but with // the additional owner field. -func NewStackTrieWithOwner(db ethdb.KeyValueWriter, owner common.Hash) *StackTrie { +func NewStackTrieWithOwner(writeFn NodeWriteFunc, owner common.Hash) *StackTrie { return &StackTrie{ owner: owner, nodeType: emptyNode, - db: db, + writeFn: writeFn, } } // NewFromBinary initialises a serialized stacktrie with the given db. -func NewFromBinary(data []byte, db ethdb.KeyValueWriter) (*StackTrie, error) { +func NewFromBinary(data []byte, writeFn NodeWriteFunc) (*StackTrie, error) { var st StackTrie if err := st.UnmarshalBinary(data); err != nil { return nil, err } // If a database is used, we need to recursively add it to every child - if db != nil { - st.setDb(db) + if writeFn != nil { + st.setWriter(writeFn) } return &st, nil } @@ -161,25 +164,25 @@ func (st *StackTrie) unmarshalBinary(r io.Reader) error { return nil } -func (st *StackTrie) setDb(db ethdb.KeyValueWriter) { - st.db = db +func (st *StackTrie) setWriter(writeFn NodeWriteFunc) { + st.writeFn = writeFn for _, child := range st.children { if child != nil { - child.setDb(db) + child.setWriter(writeFn) } } } -func newLeaf(owner common.Hash, key, val []byte, db ethdb.KeyValueWriter) *StackTrie { - st := stackTrieFromPool(db, owner) +func newLeaf(owner common.Hash, key, val []byte, writeFn NodeWriteFunc) *StackTrie { + st := stackTrieFromPool(writeFn, owner) st.nodeType = leafNode st.key = append(st.key, key...) st.val = val return st } -func newExt(owner common.Hash, key []byte, child *StackTrie, db ethdb.KeyValueWriter) *StackTrie { - st := stackTrieFromPool(db, owner) +func newExt(owner common.Hash, key []byte, child *StackTrie, writeFn NodeWriteFunc) *StackTrie { + st := stackTrieFromPool(writeFn, owner) st.nodeType = extNode st.key = append(st.key, key...) st.children[0] = child @@ -201,7 +204,7 @@ func (st *StackTrie) TryUpdate(key, value []byte) error { if len(value) == 0 { panic("deletion not supported") } - st.insert(k[:len(k)-1], value) + st.insert(k[:len(k)-1], value, nil) return nil } @@ -213,7 +216,7 @@ func (st *StackTrie) Update(key, value []byte) { func (st *StackTrie) Reset() { st.owner = common.Hash{} - st.db = nil + st.writeFn = nil st.key = st.key[:0] st.val = nil for i := range st.children { @@ -236,7 +239,7 @@ func (st *StackTrie) getDiffIndex(key []byte) int { // Helper function to that inserts a (key, value) pair into // the trie. -func (st *StackTrie) insert(key, value []byte) { +func (st *StackTrie) insert(key, value []byte, prefix []byte) { switch st.nodeType { case branchNode: /* Branch */ idx := int(key[0]) @@ -245,7 +248,7 @@ func (st *StackTrie) insert(key, value []byte) { for i := idx - 1; i >= 0; i-- { if st.children[i] != nil { if st.children[i].nodeType != hashedNode { - st.children[i].hash() + st.children[i].hash(append(prefix, byte(i))) } break } @@ -253,9 +256,9 @@ func (st *StackTrie) insert(key, value []byte) { // Add new child if st.children[idx] == nil { - st.children[idx] = newLeaf(st.owner, key[1:], value, st.db) + st.children[idx] = newLeaf(st.owner, key[1:], value, st.writeFn) } else { - st.children[idx].insert(key[1:], value) + st.children[idx].insert(key[1:], value, append(prefix, key[0])) } case extNode: /* Ext */ @@ -270,7 +273,7 @@ func (st *StackTrie) insert(key, value []byte) { if diffidx == len(st.key) { // Ext key and key segment are identical, recurse into // the child node. - st.children[0].insert(key[diffidx:], value) + st.children[0].insert(key[diffidx:], value, append(prefix, key[:diffidx]...)) return } // Save the original part. Depending if the break is @@ -279,14 +282,19 @@ func (st *StackTrie) insert(key, value []byte) { // node directly. var n *StackTrie if diffidx < len(st.key)-1 { - n = newExt(st.owner, st.key[diffidx+1:], st.children[0], st.db) + // Break on the non-last byte, insert an intermediate + // extension. The path prefix of the newly-inserted + // extension should also contain the different byte. + n = newExt(st.owner, st.key[diffidx+1:], st.children[0], st.writeFn) + n.hash(append(prefix, st.key[:diffidx+1]...)) } else { // Break on the last byte, no need to insert - // an extension node: reuse the current node + // an extension node: reuse the current node. + // The path prefix of the original part should + // still be same. n = st.children[0] + n.hash(append(prefix, st.key...)) } - // Convert to hash - n.hash() var p *StackTrie if diffidx == 0 { // the break is on the first byte, so @@ -299,12 +307,12 @@ func (st *StackTrie) insert(key, value []byte) { // the common prefix is at least one byte // long, insert a new intermediate branch // node. - st.children[0] = stackTrieFromPool(st.db, st.owner) + st.children[0] = stackTrieFromPool(st.writeFn, st.owner) st.children[0].nodeType = branchNode p = st.children[0] } // Create a leaf for the inserted part - o := newLeaf(st.owner, key[diffidx+1:], value, st.db) + o := newLeaf(st.owner, key[diffidx+1:], value, st.writeFn) // Insert both child leaves where they belong: origIdx := st.key[diffidx] @@ -340,7 +348,7 @@ func (st *StackTrie) insert(key, value []byte) { // Convert current node into an ext, // and insert a child branch node. st.nodeType = extNode - st.children[0] = NewStackTrieWithOwner(st.db, st.owner) + st.children[0] = NewStackTrieWithOwner(st.writeFn, st.owner) st.children[0].nodeType = branchNode p = st.children[0] } @@ -349,11 +357,11 @@ func (st *StackTrie) insert(key, value []byte) { // value and another containing the new value. The child leaf // is hashed directly in order to free up some memory. origIdx := st.key[diffidx] - p.children[origIdx] = newLeaf(st.owner, st.key[diffidx+1:], st.val, st.db) - p.children[origIdx].hash() + p.children[origIdx] = newLeaf(st.owner, st.key[diffidx+1:], st.val, st.writeFn) + p.children[origIdx].hash(append(prefix, st.key[:diffidx+1]...)) newIdx := key[diffidx] - p.children[newIdx] = newLeaf(st.owner, key[diffidx+1:], value, st.db) + p.children[newIdx] = newLeaf(st.owner, key[diffidx+1:], value, st.writeFn) // Finally, cut off the key part that has been passed // over to the children. @@ -384,14 +392,14 @@ func (st *StackTrie) insert(key, value []byte) { // - And the 'st.type' will be 'hashedNode' AGAIN // // This method also sets 'st.type' to hashedNode, and clears 'st.key'. -func (st *StackTrie) hash() { +func (st *StackTrie) hash(path []byte) { h := newHasher(false) defer returnHasherToPool(h) - st.hashRec(h) + st.hashRec(h, path) } -func (st *StackTrie) hashRec(hasher *hasher) { +func (st *StackTrie) hashRec(hasher *hasher, path []byte) { // The switch below sets this to the RLP-encoding of this node. var encodedNode []byte @@ -412,8 +420,7 @@ func (st *StackTrie) hashRec(hasher *hasher) { nodes[i] = nilValueNode continue } - - child.hashRec(hasher) + child.hashRec(hasher, append(path, byte(i))) if len(child.val) < 32 { nodes[i] = rawNode(child.val) } else { @@ -429,10 +436,9 @@ func (st *StackTrie) hashRec(hasher *hasher) { encodedNode = hasher.encodedBytes() case extNode: - st.children[0].hashRec(hasher) + st.children[0].hashRec(hasher, append(path, st.key...)) - sz := hexToCompactInPlace(st.key) - n := rawShortNode{Key: st.key[:sz]} + n := rawShortNode{Key: hexToCompact(st.key)} if len(st.children[0].val) < 32 { n.Val = rawNode(st.children[0].val) } else { @@ -448,8 +454,7 @@ func (st *StackTrie) hashRec(hasher *hasher) { case leafNode: st.key = append(st.key, byte(16)) - sz := hexToCompactInPlace(st.key) - n := rawShortNode{Key: st.key[:sz], Val: valueNode(st.val)} + n := rawShortNode{Key: hexToCompact(st.key), Val: valueNode(st.val)} n.encode(hasher.encbuf) encodedNode = hasher.encodedBytes() @@ -468,10 +473,8 @@ func (st *StackTrie) hashRec(hasher *hasher) { // Write the hash to the 'val'. We allocate a new val here to not mutate // input values st.val = hasher.hashData(encodedNode) - if st.db != nil { - // TODO! Is it safe to Put the slice here? - // Do all db implementations copy the value provided? - st.db.Put(st.val, encodedNode) + if st.writeFn != nil { + st.writeFn(st.owner, path, common.BytesToHash(st.val), encodedNode) } } @@ -480,12 +483,11 @@ func (st *StackTrie) Hash() (h common.Hash) { hasher := newHasher(false) defer returnHasherToPool(hasher) - st.hashRec(hasher) + st.hashRec(hasher, nil) if len(st.val) == 32 { copy(h[:], st.val) return h } - // If the node's RLP isn't 32 bytes long, the node will not // be hashed, and instead contain the rlp-encoding of the // node. For the top level node, we need to force the hashing. @@ -503,25 +505,24 @@ func (st *StackTrie) Hash() (h common.Hash) { // The associated database is expected, otherwise the whole commit // functionality should be disabled. func (st *StackTrie) Commit() (h common.Hash, err error) { - if st.db == nil { + if st.writeFn == nil { return common.Hash{}, ErrCommitDisabled } - hasher := newHasher(false) defer returnHasherToPool(hasher) - st.hashRec(hasher) + st.hashRec(hasher, nil) if len(st.val) == 32 { copy(h[:], st.val) return h, nil } - // If the node's RLP isn't 32 bytes long, the node will not - // be hashed (and committed), and instead contain the rlp-encoding of the + // be hashed (and committed), and instead contain the rlp-encoding of the // node. For the top level node, we need to force the hashing+commit. hasher.sha.Reset() hasher.sha.Write(st.val) hasher.sha.Read(h[:]) - st.db.Put(h[:], st.val) + + st.writeFn(st.owner, nil, h, st.val) return h, nil } diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index 0ef12bdbd0..3250c497af 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -22,8 +22,8 @@ import ( "testing" "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/crypto" - "github.com/XinFinOrg/XDPoSChain/ethdb/memorydb" ) func TestStackTrieInsertAndHash(t *testing.T) { @@ -188,7 +188,7 @@ func TestStackTrieInsertAndHash(t *testing.T) { func TestSizeBug(t *testing.T) { st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") @@ -203,7 +203,7 @@ func TestSizeBug(t *testing.T) { func TestEmptyBug(t *testing.T) { st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") @@ -229,7 +229,7 @@ func TestEmptyBug(t *testing.T) { func TestValLength56(t *testing.T) { st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") @@ -254,7 +254,7 @@ func TestValLength56(t *testing.T) { // which causes a lot of node-within-node. This case was found via fuzzing. func TestUpdateSmallNodes(t *testing.T) { st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) kvs := []struct { K string @@ -283,7 +283,7 @@ func TestUpdateSmallNodes(t *testing.T) { func TestUpdateVariableKeys(t *testing.T) { t.SkipNow() st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(memorydb.New())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) kvs := []struct { K string @@ -353,7 +353,7 @@ func TestStacktrieNotModifyValues(t *testing.T) { func TestStacktrieSerialization(t *testing.T) { var ( st = NewStackTrie(nil) - nt = NewEmpty(NewDatabase(memorydb.New())) + nt = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) keyB = big.NewInt(1) keyDelta = big.NewInt(1) vals [][]byte diff --git a/trie/sync.go b/trie/sync.go index 7d37e6244b..2543676125 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -65,7 +65,7 @@ type SyncPath [][]byte // version that can be sent over the network. func NewSyncPath(path []byte) SyncPath { // If the hash is from the account trie, append a single item, if it - // is from the a storage trie, append a tuple. Note, the length 64 is + // is from a storage trie, append a tuple. Note, the length 64 is // clashing between account leaf and storage root. It's fine though // because having a trie node at 64 depth means a hash collision was // found and we're long dead. @@ -75,6 +75,22 @@ func NewSyncPath(path []byte) SyncPath { return SyncPath{hexToKeybytes(path[:64]), hexToCompact(path[64:])} } +// LeafCallback is a callback type invoked when a trie operation reaches a leaf +// node. +// +// The keys is a path tuple identifying a particular trie node either in a single +// trie (account) or a layered trie (account -> storage). Each key in the tuple +// is in the raw format(32 bytes). +// +// The path is a composite hexary path identifying the trie node. All the key +// bytes are converted to the hexary nibbles and composited with the parent path +// if the trie node is in a layered trie. +// +// It's used by state sync and commit to allow handling external references +// between account and storage tries. And also it's used in the state healing +// for extracting the raw states(leaf nodes) with corresponding paths. +type LeafCallback func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error + // nodeRequest represents a scheduled or already in-flight trie node retrieval request. type nodeRequest struct { hash common.Hash // Hash of the trie node to retrieve @@ -139,6 +155,7 @@ func (batch *syncMemBatch) hasCode(hash common.Hash) bool { // unknown trie hashes to retrieve, accepts Node data associated with said hashes // and reconstructs the trie step by step until all is done. type Sync struct { + scheme NodeScheme // Node scheme descriptor used in database. database ethdb.KeyValueReader // Persistent database to check for existing entries membatch *syncMemBatch // Memory buffer to avoid frequent database writes nodeReqs map[string]*nodeRequest // Pending requests pertaining to a trie node path @@ -148,8 +165,9 @@ type Sync struct { } // NewSync creates a new trie data download scheduler. -func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallback) *Sync { +func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallback, scheme NodeScheme) *Sync { ts := &Sync{ + scheme: scheme, database: database, membatch: newSyncMemBatch(), nodeReqs: make(map[string]*nodeRequest), @@ -172,7 +190,8 @@ func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, par if s.membatch.hasNode(path) { return } - if rawdb.HasTrieNode(s.database, root) { + owner, inner := ResolvePath(path) + if s.scheme.HasTrieNode(s.database, owner, inner, root) { return } // Assemble the new sub-trie sync request @@ -205,7 +224,7 @@ func (s *Sync) AddCodeEntry(hash common.Hash, path []byte, parent common.Hash, p return } // If database says duplicate, the blob is present for sure. - // Note we only check the existence with new code scheme, fast + // Note we only check the existence with new code scheme, snap // sync is expected to run with a fresh new node. Even there // exists the code with legacy format, fetch and store with // new scheme anyway. @@ -329,7 +348,8 @@ func (s *Sync) ProcessNode(result NodeSyncResult) error { func (s *Sync) Commit(dbw ethdb.Batch) error { // Dump the membatch into a database dbw for path, value := range s.membatch.nodes { - rawdb.WriteTrieNode(dbw, s.membatch.hashes[path], value) + owner, inner := ResolvePath([]byte(path)) + s.scheme.WriteTrieNode(dbw, owner, inner, s.membatch.hashes[path], value) } for hash, value := range s.membatch.codes { rawdb.WriteCode(dbw, hash, value) @@ -445,8 +465,11 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { // If database says duplicate, then at least the trie node is present // and we hold the assumption that it's NOT legacy contract code. - chash := common.BytesToHash(node) - if rawdb.HasTrieNode(s.database, chash) { + var ( + chash = common.BytesToHash(node) + owner, inner = ResolvePath(child.path) + ) + if s.scheme.HasTrieNode(s.database, owner, inner, chash) { return } // Locally unknown node, schedule for retrieval @@ -516,3 +539,14 @@ func (s *Sync) commitCodeRequest(req *codeRequest) error { } return nil } + +// ResolvePath resolves the provided composite node path by separating the +// path in account trie if it's existent. +func ResolvePath(path []byte) (common.Hash, []byte) { + var owner common.Hash + if len(path) >= 2*common.HashLength { + owner = common.BytesToHash(hexToKeybytes(path[:2*common.HashLength])) + path = path[2*common.HashLength:] + } + return owner, path +} diff --git a/trie/sync_test.go b/trie/sync_test.go index 2e7502fd8e..4afc5aa645 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/ethdb/memorydb" @@ -30,7 +31,7 @@ import ( // makeTestTrie create a sample test trie to test node-wise reconstruction. func makeTestTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie - triedb := NewDatabase(memorydb.New()) + triedb := NewDatabase(rawdb.NewMemoryDatabase()) trie, _ := NewStateTrie(TrieID(common.Hash{}), triedb) // Fill it with some arbitrary data @@ -104,13 +105,13 @@ type trieElement struct { // Tests that an empty trie is not scheduled for syncing. func TestEmptySync(t *testing.T) { - dbA := NewDatabase(memorydb.New()) - dbB := NewDatabase(memorydb.New()) + dbA := NewDatabase(rawdb.NewMemoryDatabase()) + dbB := NewDatabase(rawdb.NewMemoryDatabase()) 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) + sync := NewSync(trie.Hash(), memorydb.New(), nil, []*Database{dbA, dbB}[i].Scheme()) if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 { t.Errorf("test %d: content requested for empty trie: %v, %v, %v", i, paths, nodes, codes) } @@ -129,9 +130,9 @@ func testIterativeSync(t *testing.T, count int, bypath bool) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) - sched := NewSync(srcTrie.Hash(), diskdb, nil) + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) // The code requests are ignored here since there is no code // at the testing trie. @@ -195,9 +196,9 @@ func TestIterativeDelayedSync(t *testing.T) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) - sched := NewSync(srcTrie.Hash(), diskdb, nil) + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) // The code requests are ignored here since there is no code // at the testing trie. @@ -256,9 +257,9 @@ func testIterativeRandomSync(t *testing.T, count int) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) - sched := NewSync(srcTrie.Hash(), diskdb, nil) + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) // The code requests are ignored here since there is no code // at the testing trie. @@ -314,9 +315,9 @@ func TestIterativeRandomDelayedSync(t *testing.T) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) - sched := NewSync(srcTrie.Hash(), diskdb, nil) + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) // The code requests are ignored here since there is no code // at the testing trie. @@ -377,9 +378,9 @@ func TestDuplicateAvoidanceSync(t *testing.T) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) - sched := NewSync(srcTrie.Hash(), diskdb, nil) + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) // The code requests are ignored here since there is no code // at the testing trie. @@ -440,9 +441,9 @@ func TestIncompleteSync(t *testing.T) { srcDb, srcTrie, _ := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) - sched := NewSync(srcTrie.Hash(), diskdb, nil) + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) // The code requests are ignored here since there is no code // at the testing trie. @@ -520,9 +521,9 @@ func TestSyncOrdering(t *testing.T) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler, tracking the requests - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) - sched := NewSync(srcTrie.Hash(), diskdb, nil) + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) // The code requests are ignored here since there is no code // at the testing trie. diff --git a/trie/trie.go b/trie/trie.go index a49dc3ea94..b23761b5bd 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -27,22 +27,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/log" ) -// LeafCallback is a callback type invoked when a trie operation reaches a leaf -// node. -// -// The keys is a path tuple identifying a particular trie node either in a single -// trie (account) or a layered trie (account -> storage). Each key in the tuple -// is in the raw format(32 bytes). -// -// The path is a composite hexary path identifying the trie node. All the key -// bytes are converted to the hexary nibbles and composited with the parent path -// if the trie node is in a layered trie. -// -// It's used by state sync and commit to allow handling external references -// between account and storage tries. And also it's used in the state healing -// for extracting the raw states(leaf nodes) with corresponding paths. -type LeafCallback func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error - // Trie is a Merkle Patricia Trie. Use New to create a trie that sits on // top of a database. Whenever trie performs a commit operation, the generated // nodes will be gathered and returned in a set. Once the trie is committed, diff --git a/trie/trie_test.go b/trie/trie_test.go index 1a57122262..61a5929791 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -35,7 +35,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/ethdb" - "github.com/XinFinOrg/XDPoSChain/ethdb/memorydb" "github.com/XinFinOrg/XDPoSChain/rlp" "github.com/davecgh/go-spew/spew" "golang.org/x/crypto/sha3" @@ -67,7 +66,7 @@ func TestNull(t *testing.T) { func TestMissingRoot(t *testing.T) { root := common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33") - trie, err := New(TrieID(root), NewDatabase(memorydb.New())) + trie, err := New(TrieID(root), NewDatabase(rawdb.NewMemoryDatabase())) if trie != nil { t.Error("New returned non-nil trie for invalid root") } @@ -80,7 +79,7 @@ func TestMissingNodeDisk(t *testing.T) { testMissingNode(t, false) } func TestMissingNodeMemonly(t *testing.T) { testMissingNode(t, true) } func testMissingNode(t *testing.T, memonly bool) { - diskdb := memorydb.New() + diskdb := rawdb.NewMemoryDatabase() triedb := NewDatabase(diskdb) trie := NewEmpty(triedb) @@ -435,7 +434,7 @@ func runRandTestBool(rt randTest) bool { func runRandTest(rt randTest) error { var ( - triedb = NewDatabase(memorydb.New()) + triedb = NewDatabase(rawdb.NewMemoryDatabase()) tr = NewEmpty(triedb) values = make(map[string]string) // tracks content of the trie origTrie = NewEmpty(triedb) @@ -815,11 +814,13 @@ func TestCommitSequenceStackTrie(t *testing.T) { prng := rand.New(rand.NewSource(int64(count))) // This spongeDb is used to check the sequence of disk-db-writes s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"} - db := NewDatabase(s) + db := NewDatabase(rawdb.NewDatabase(s)) trie := NewEmpty(db) // Another sponge is used for the stacktrie commits stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"} - stTrie := NewStackTrie(stackTrieSponge) + stTrie := NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, blob []byte) { + db.Scheme().WriteTrieNode(stackTrieSponge, owner, path, hash, blob) + }) // Fill the trie with elements for i := 0; i < count; i++ { // For the stack trie, we need to do inserts in proper order @@ -872,11 +873,13 @@ func TestCommitSequenceStackTrie(t *testing.T) { // not fit into 32 bytes, rlp-encoded. However, it's still the correct thing to do. func TestCommitSequenceSmallRoot(t *testing.T) { s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"} - db := NewDatabase(s) + db := NewDatabase(rawdb.NewDatabase(s)) trie := NewEmpty(db) // Another sponge is used for the stacktrie commits stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"} - stTrie := NewStackTrie(stackTrieSponge) + stTrie := NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, blob []byte) { + db.Scheme().WriteTrieNode(stackTrieSponge, owner, path, hash, blob) + }) // Add a single small-element to the trie(s) key := make([]byte, 5) key[0] = 1