core, eth, trie: abstract node scheme #25532 (#1123)

This PR introduces a node scheme abstraction. The interface is only implemented by `hashScheme` at the moment, but will be extended by `pathScheme` very soon.

Apart from that, a few changes are also included which is worth mentioning:

-  port the changes in the stacktrie, tracking the path prefix of nodes during commit
-  use ethdb.Database for constructing trie.Database. This is not necessary right now, but it is required for path-based used to open reverse diff freezer

Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
This commit is contained in:
Daniel Liu 2025-11-17 13:55:08 +08:00 committed by GitHub
parent a4fbda2924
commit 07328dcec4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 402 additions and 208 deletions

View file

@ -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: "<genesisPath>",
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
}

View file

@ -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

26
core/blockchain_reader.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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
}

View file

@ -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-")) {

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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.

View file

@ -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() {

View file

@ -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,
}
}

View file

@ -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)
}

View file

@ -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{}
}

View file

@ -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")
}

View file

@ -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)
)

96
trie/schema.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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.

View file

@ -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,

View file

@ -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