core, trie: finalize the new trie node-caching db layer

This commit is contained in:
Péter Szilágyi 2018-01-17 10:25:31 +02:00
parent d2ec96e861
commit 34d766a243
No known key found for this signature in database
GPG key ID: E9AE538CEDF8293D
45 changed files with 849 additions and 790 deletions

View file

@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
) )
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend. // This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
@ -103,7 +104,7 @@ func (b *SimulatedBackend) Rollback() {
func (b *SimulatedBackend) rollback() { func (b *SimulatedBackend) rollback() {
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
b.pendingBlock = blocks[0] b.pendingBlock = blocks[0]
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database, nil)) b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(trie.NewDatabase(b.database)))
} }
// CodeAt returns the code associated with a certain account in the blockchain. // CodeAt returns the code associated with a certain account in the blockchain.
@ -310,7 +311,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
block.AddTx(tx) block.AddTx(tx)
}) })
b.pendingBlock = blocks[0] b.pendingBlock = blocks[0]
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database, nil)) b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(trie.NewDatabase(b.database)))
return nil return nil
} }
@ -387,7 +388,7 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
block.OffsetTime(int64(adjustment.Seconds())) block.OffsetTime(int64(adjustment.Seconds()))
}) })
b.pendingBlock = blocks[0] b.pendingBlock = blocks[0]
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database, nil)) b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(trie.NewDatabase(b.database)))
return nil return nil
} }

View file

@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
cli "gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
) )
@ -96,11 +97,11 @@ func runCmd(ctx *cli.Context) error {
} }
if ctx.GlobalString(GenesisFlag.Name) != "" { if ctx.GlobalString(GenesisFlag.Name) != "" {
gen := readGenesis(ctx.GlobalString(GenesisFlag.Name)) gen := readGenesis(ctx.GlobalString(GenesisFlag.Name))
_, statedb = gen.ToBlock() _, statedb, _ = gen.ToBlock()
chainConfig = gen.Config chainConfig = gen.Config
} else { } else {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ = state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ = state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
} }
if ctx.GlobalString(SenderFlag.Name) != "" { if ctx.GlobalString(SenderFlag.Name) != "" {
sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name)) sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name))

View file

@ -379,7 +379,7 @@ func dump(ctx *cli.Context) error {
fmt.Println("{}") fmt.Println("{}")
utils.Fatalf("block not found") utils.Fatalf("block not found")
} else { } else {
state, err := state.New(block.Root(), state.NewDatabase(chainDb, nil)) state, err := state.New(block.Root(), state.NewDatabase(trie.NewDatabase(chainDb)))
if err != nil { if err != nil {
utils.Fatalf("could not create new state: %v", err) utils.Fatalf("could not create new state: %v", err)
} }

View file

@ -102,6 +102,8 @@ type BlockChain struct {
blockCache *lru.Cache // Cache for the most recent entire blocks blockCache *lru.Cache // Cache for the most recent entire blocks
futureBlocks *lru.Cache // future blocks are blocks added for later processing futureBlocks *lru.Cache // future blocks are blocks added for later processing
procTime time.Duration // Accumulator for measuring total block processing for the trie node dumping
quit chan struct{} // blockchain quit channel quit chan struct{} // blockchain quit channel
running int32 // running must be called atomically running int32 // running must be called atomically
// procInterrupt must be atomically called // procInterrupt must be atomically called
@ -129,7 +131,7 @@ func NewBlockChain(chainDb ethdb.Database, config *params.ChainConfig, engine co
bc := &BlockChain{ bc := &BlockChain{
config: config, config: config,
chainDb: chainDb, chainDb: chainDb,
stateCache: state.NewDatabase(chainDb, trie.NewNodePool()), stateCache: state.NewDatabase(trie.NewDatabase(chainDb)),
quit: make(chan struct{}), quit: make(chan struct{}),
bodyCache: bodyCache, bodyCache: bodyCache,
bodyRLPCache: bodyRLPCache, bodyRLPCache: bodyRLPCache,
@ -292,7 +294,7 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
if block == nil { if block == nil {
return fmt.Errorf("non existent block [%x…]", hash[:4]) return fmt.Errorf("non existent block [%x…]", hash[:4])
} }
if _, err := trie.NewSecure(block.Root(), bc.chainDb, nil, 0); err != nil { if _, err := trie.NewSecure(block.Root(), bc.stateCache.TrieDB(), 0); err != nil {
return err return err
} }
// If all checks out, manually set the head block // If all checks out, manually set the head block
@ -594,7 +596,7 @@ func (bc *BlockChain) Stop() {
root := bc.CurrentHeader().Root root := bc.CurrentHeader().Root
batch := bc.chainDb.NewBatch() batch := bc.chainDb.NewBatch()
if err := bc.stateCache.NodePool().Commit(root, batch); err != nil { if err := bc.stateCache.TrieDB().Commit(root, batch); err != nil {
log.Error("Failed to commit latest state trie", "err", err) log.Error("Failed to commit latest state trie", "err", err)
} }
if err := batch.Write(); err != nil { if err := batch.Write(); err != nil {
@ -776,6 +778,8 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
return 0, nil return 0, nil
} }
var lastWrite uint64
// WriteBlock writes the block to the chain. // WriteBlock writes the block to the chain.
func (bc *BlockChain) WriteBlockAndState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) (status WriteStatus, err error) { func (bc *BlockChain) WriteBlockAndState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) (status WriteStatus, err error) {
bc.wg.Add(1) bc.wg.Add(1)
@ -802,18 +806,37 @@ func (bc *BlockChain) WriteBlockAndState(block *types.Block, receipts []*types.R
if err := WriteBlock(batch, block); err != nil { if err := WriteBlock(batch, block); err != nil {
return NonStatTy, err return NonStatTy, err
} }
root, err := state.CommitTo(batch, bc.config.IsEIP158(block.Number())) root, err := state.Commit(bc.config.IsEIP158(block.Number()))
if err != nil { if err != nil {
return NonStatTy, err return NonStatTy, err
} }
pool := bc.stateCache.NodePool() db := bc.stateCache.TrieDB()
pool.Reference(root, common.Hash{}) // metadata reference to keep trie alive
if number := block.NumberU64(); number > 192 { var (
if (number-192)%128 == 0 { writeRetention = uint64(128) // Number of trie nodes we need to retain in memory
pool.Commit(root, batch) memoryAllowance = common.StorageSize(64 * 1024 * 1024) // Memory allowance below which to avoid writing to disk
timeAllowance = 3 * time.Minute // Time allowance below which to avoid writing to disk
)
db.Reference(root, common.Hash{}) // metadata reference to keep trie alive
if current := block.NumberU64(); current > writeRetention {
// Find the next state trie we need to get rid of or commit
header := bc.GetHeaderByNumber(current - writeRetention)
chosen := header.Number.Uint64()
// Only write to disk if we exceeded our memory allowance *and* also have at
// least a given number of tries gapped.
if (db.Size() > memoryAllowance || bc.procTime > timeAllowance) && chosen >= lastWrite+writeRetention {
db.Commit(header.Root, batch)
lastWrite = chosen
bc.procTime = 0
}
// Garbage collect anything below our required write retention
db.Dereference(header.Root, common.Hash{})
if current%10000 == 0 {
log.Warn("Current trie pruning state", "size", db.Size(), "elapsed", bc.procTime)
} }
header := bc.GetHeaderByNumber(block.NumberU64() - 192)
pool.Dereference(header.Root, common.Hash{})
} }
if err := WriteBlockReceipts(batch, block.Hash(), block.NumberU64(), receipts); err != nil { if err := WriteBlockReceipts(batch, block.Hash(), block.NumberU64(), receipts); err != nil {
return NonStatTy, err return NonStatTy, err
@ -983,6 +1006,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
bc.reportBlock(block, receipts, err) bc.reportBlock(block, receipts, err)
return i, events, coalescedLogs, err return i, events, coalescedLogs, err
} }
bc.procTime += time.Since(bstart)
// Write the block to the chain and get the status. // Write the block to the chain and get the status.
status, err := bc.WriteBlockAndState(block, receipts, state) status, err := bc.WriteBlockAndState(block, receipts, state)
if err != nil { if err != nil {

View file

@ -150,7 +150,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
blockchain.mu.Lock() blockchain.mu.Lock()
WriteTd(blockchain.chainDb, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTdByHash(block.ParentHash()))) WriteTd(blockchain.chainDb, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTdByHash(block.ParentHash())))
WriteBlock(blockchain.chainDb, block) WriteBlock(blockchain.chainDb, block)
statedb.CommitTo(blockchain.chainDb, false) statedb.Commit(false)
blockchain.mu.Unlock() blockchain.mu.Unlock()
} }
return nil return nil

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
) )
// So we can deterministically seed different blockchains // So we can deterministically seed different blockchains
@ -162,6 +163,8 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
if config == nil { if config == nil {
config = params.TestChainConfig config = params.TestChainConfig
} }
triedb := trie.NewDatabase(db)
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n) blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) { genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
// TODO(karalabe): This is needed for clique, which depends on multiple blocks. // TODO(karalabe): This is needed for clique, which depends on multiple blocks.
@ -192,16 +195,19 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
if b.engine != nil { if b.engine != nil {
block, _ := b.engine.Finalize(b.chainReader, b.header, statedb, b.txs, b.uncles, b.receipts) block, _ := b.engine.Finalize(b.chainReader, b.header, statedb, b.txs, b.uncles, b.receipts)
// Write state changes to db // Write state changes to db
_, err := statedb.CommitTo(db, config.IsEIP158(b.header.Number)) root, err := statedb.Commit(config.IsEIP158(b.header.Number))
if err != nil { if err != nil {
panic(fmt.Sprintf("state write error: %v", err)) panic(fmt.Sprintf("state write error: %v", err))
} }
if err := triedb.Commit(root, db); err != nil {
panic(fmt.Sprintf("trie write error: %v", err))
}
return block, b.receipts return block, b.receipts
} }
return nil, nil return nil, nil
} }
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), state.NewDatabase(db, nil)) statedb, err := state.New(parent.Root(), state.NewDatabase(triedb))
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -79,7 +79,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
if _, err := bc.InsertChain(blocks); err != nil { if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import contra-fork chain for expansion: %v", err) t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
} }
if err := bc.stateCache.NodePool().Commit(bc.CurrentHeader().Root, db); err != nil { if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, db); err != nil {
t.Fatalf("failed to commit contra-fork head for expansion: %v", err) t.Fatalf("failed to commit contra-fork head for expansion: %v", err)
} }
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
@ -104,7 +104,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
if _, err := bc.InsertChain(blocks); err != nil { if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import pro-fork chain for expansion: %v", err) t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
} }
if err := bc.stateCache.NodePool().Commit(bc.CurrentHeader().Root, db); err != nil { if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, db); err != nil {
t.Fatalf("failed to commit pro-fork head for expansion: %v", err) t.Fatalf("failed to commit pro-fork head for expansion: %v", err)
} }
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
@ -130,7 +130,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
if _, err := bc.InsertChain(blocks); err != nil { if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import contra-fork chain for expansion: %v", err) t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
} }
if err := bc.stateCache.NodePool().Commit(bc.CurrentHeader().Root, db); err != nil { if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, db); err != nil {
t.Fatalf("failed to commit contra-fork head for expansion: %v", err) t.Fatalf("failed to commit contra-fork head for expansion: %v", err)
} }
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
@ -150,7 +150,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
if _, err := bc.InsertChain(blocks); err != nil { if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import pro-fork chain for expansion: %v", err) t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
} }
if err := bc.stateCache.NodePool().Commit(bc.CurrentHeader().Root, db); err != nil { if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, db); err != nil {
t.Fatalf("failed to commit pro-fork head for expansion: %v", err) t.Fatalf("failed to commit pro-fork head for expansion: %v", err)
} }
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
) )
//go:generate gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go //go:generate gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go
@ -169,7 +170,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
// Check whether the genesis block is already written. // Check whether the genesis block is already written.
if genesis != nil { if genesis != nil {
block, _ := genesis.ToBlock() block, _, _ := genesis.ToBlock()
hash := block.Hash() hash := block.Hash()
if hash != stored { if hash != stored {
return genesis.Config, block.Hash(), &GenesisMismatchError{stored, hash} return genesis.Config, block.Hash(), &GenesisMismatchError{stored, hash}
@ -221,9 +222,10 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
} }
// ToBlock creates the block and state of a genesis specification. // ToBlock creates the block and state of a genesis specification.
func (g *Genesis) ToBlock() (*types.Block, *state.StateDB) { func (g *Genesis) ToBlock() (*types.Block, *state.StateDB, *trie.Database) {
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) triedb := trie.NewDatabase(diskdb)
statedb, _ := state.New(common.Hash{}, state.NewDatabase(triedb))
for addr, account := range g.Alloc { for addr, account := range g.Alloc {
statedb.AddBalance(addr, account.Balance) statedb.AddBalance(addr, account.Balance)
statedb.SetCode(addr, account.Code) statedb.SetCode(addr, account.Code)
@ -252,19 +254,22 @@ func (g *Genesis) ToBlock() (*types.Block, *state.StateDB) {
if g.Difficulty == nil { if g.Difficulty == nil {
head.Difficulty = params.GenesisDifficulty head.Difficulty = params.GenesisDifficulty
} }
return types.NewBlock(head, nil, nil, nil), statedb return types.NewBlock(head, nil, nil, nil), statedb, triedb
} }
// Commit writes the block and state of a genesis specification to the database. // Commit writes the block and state of a genesis specification to the database.
// The block is committed as the canonical head block. // The block is committed as the canonical head block.
func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) { func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
block, statedb := g.ToBlock() block, statedb, triedb := g.ToBlock()
if block.Number().Sign() != 0 { if block.Number().Sign() != 0 {
return nil, fmt.Errorf("can't commit genesis block with number > 0") return nil, fmt.Errorf("can't commit genesis block with number > 0")
} }
if _, err := statedb.CommitTo(db, false); err != nil { if _, err := statedb.Commit(false); err != nil {
return nil, fmt.Errorf("cannot write state: %v", err) return nil, fmt.Errorf("cannot write state: %v", err)
} }
if err := triedb.Commit(block.Root(), db); err != nil {
return nil, err
}
if err := WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty); err != nil { if err := WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty); err != nil {
return nil, err return nil, err
} }

View file

@ -30,11 +30,11 @@ import (
) )
func TestDefaultGenesisBlock(t *testing.T) { func TestDefaultGenesisBlock(t *testing.T) {
block, _ := DefaultGenesisBlock().ToBlock() block, _, _ := DefaultGenesisBlock().ToBlock()
if block.Hash() != params.MainnetGenesisHash { if block.Hash() != params.MainnetGenesisHash {
t.Errorf("wrong mainnet genesis hash, got %v, want %v", block.Hash(), params.MainnetGenesisHash) t.Errorf("wrong mainnet genesis hash, got %v, want %v", block.Hash(), params.MainnetGenesisHash)
} }
block, _ = DefaultTestnetGenesisBlock().ToBlock() block, _, _ = DefaultTestnetGenesisBlock().ToBlock()
if block.Hash() != params.TestnetGenesisHash { if block.Hash() != params.TestnetGenesisHash {
t.Errorf("wrong testnet genesis hash, got %v, want %v", block.Hash(), params.TestnetGenesisHash) t.Errorf("wrong testnet genesis hash, got %v, want %v", block.Hash(), params.TestnetGenesisHash)
} }

View file

@ -21,7 +21,6 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
) )
@ -55,8 +54,8 @@ type Database interface {
// ContractCodeSize retrieves a particular contracts code's size. // ContractCodeSize retrieves a particular contracts code's size.
ContractCodeSize(addrHash, codeHash common.Hash) (int, error) ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
// NodePool retrieves any intermediate trie-node caching layer. // TrieDB retrieves the low level trie database used for data storage.
NodePool() *trie.NodePool TrieDB() *trie.Database
} }
// Trie is a Ethereum Merkle Trie. // Trie is a Ethereum Merkle Trie.
@ -64,7 +63,7 @@ type Trie interface {
TryGet(key []byte) ([]byte, error) TryGet(key []byte) ([]byte, error)
TryUpdate(key, value []byte) error TryUpdate(key, value []byte) error
TryDelete(key []byte) error TryDelete(key []byte) error
CommitTo(trie.DatabaseWriter) (common.Hash, error) Commit(onleaf trie.LeafCallback) (common.Hash, error)
Hash() common.Hash Hash() common.Hash
NodeIterator(startKey []byte) trie.NodeIterator NodeIterator(startKey []byte) trie.NodeIterator
GetKey([]byte) []byte // TODO(fjl): remove this when SecureTrie is removed GetKey([]byte) []byte // TODO(fjl): remove this when SecureTrie is removed
@ -74,16 +73,18 @@ type Trie interface {
// concurrent use and retains cached trie nodes in memory. The pool is an optional // concurrent use and retains cached trie nodes in memory. The pool is an optional
// intermediate trie-node memory pool between the low level storage layer and the // intermediate trie-node memory pool between the low level storage layer and the
// high level trie abstraction. // high level trie abstraction.
func NewDatabase(db ethdb.Database, pool *trie.NodePool) Database { func NewDatabase(db *trie.Database) Database {
csc, _ := lru.New(codeSizeCacheSize) csc, _ := lru.New(codeSizeCacheSize)
return &cachingDB{db: db, pastNodes: pool, codeSizeCache: csc} return &cachingDB{
db: db,
codeSizeCache: csc,
}
} }
type cachingDB struct { type cachingDB struct {
db ethdb.Database db *trie.Database
mu sync.Mutex mu sync.Mutex
pastTries []*trie.SecureTrie pastTries []*trie.SecureTrie
pastNodes *trie.NodePool
codeSizeCache *lru.Cache codeSizeCache *lru.Cache
} }
@ -97,7 +98,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
return cachedTrie{db.pastTries[i].Copy(), db}, nil return cachedTrie{db.pastTries[i].Copy(), db}, nil
} }
} }
tr, err := trie.NewSecure(root, db.db, db.pastNodes, MaxTrieCacheGen) tr, err := trie.NewSecure(root, db.db, MaxTrieCacheGen)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -118,7 +119,7 @@ func (db *cachingDB) pushTrie(t *trie.SecureTrie) {
// OpenStorageTrie opens the storage trie of an account. // OpenStorageTrie opens the storage trie of an account.
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) { func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
return trie.NewSecure(root, db.db, db.pastNodes, 0) return trie.NewSecure(root, db.db, 0)
} }
// CopyTrie returns an independent copy of the given trie. // CopyTrie returns an independent copy of the given trie.
@ -135,7 +136,7 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
// ContractCode retrieves a particular contract's code. // ContractCode retrieves a particular contract's code.
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) { func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
code, err := db.db.Get(codeHash[:]) code, err := db.db.Node(codeHash)
if err == nil { if err == nil {
db.codeSizeCache.Add(codeHash, len(code)) db.codeSizeCache.Add(codeHash, len(code))
} }
@ -154,9 +155,9 @@ func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, erro
return len(code), err return len(code), err
} }
// NodePool retrieves any intermediate trie-node caching layer. // TrieDB retrieves any intermediate trie-node caching layer.
func (db *cachingDB) NodePool() *trie.NodePool { func (db *cachingDB) TrieDB() *trie.Database {
return db.pastNodes return db.db
} }
// cachedTrie inserts its trie into a cachingDB on commit. // cachedTrie inserts its trie into a cachingDB on commit.
@ -165,8 +166,8 @@ type cachedTrie struct {
db *cachingDB db *cachingDB
} }
func (m cachedTrie) CommitTo(dbw trie.DatabaseWriter) (common.Hash, error) { func (m cachedTrie) Commit(onleaf trie.LeafCallback) (common.Hash, error) {
root, err := m.SecureTrie.CommitTo(dbw) root, err := m.SecureTrie.Commit(onleaf)
if err == nil { if err == nil {
m.db.pushTrie(m.SecureTrie) m.db.pushTrie(m.SecureTrie)
} }

View file

@ -21,12 +21,13 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
) )
// Tests that the node iterator indeed walks over the entire database contents. // Tests that the node iterator indeed walks over the entire database contents.
func TestNodeIteratorCoverage(t *testing.T) { func TestNodeIteratorCoverage(t *testing.T) {
// Create some arbitrary test state to iterate // Create some arbitrary test state to iterate
db, mem, root, _ := makeTestState() db, root, _ := makeTestState()
state, err := New(root, db) state, err := New(root, db)
if err != nil { if err != nil {
@ -41,18 +42,16 @@ func TestNodeIteratorCoverage(t *testing.T) {
} }
// Cross check the iterated hashes and the database/nodepool content // Cross check the iterated hashes and the database/nodepool content
for hash := range hashes { for hash := range hashes {
if db.NodePool().Fetch(hash) == nil { if _, err := db.TrieDB().Node(hash); err != nil {
if _, err := mem.Get(hash.Bytes()); err != nil {
t.Errorf("failed to retrieve reported node %x", hash) t.Errorf("failed to retrieve reported node %x", hash)
} }
} }
} for _, hash := range db.TrieDB().Nodes() {
for _, hash := range db.NodePool().Nodes() {
if _, ok := hashes[hash]; !ok { if _, ok := hashes[hash]; !ok {
t.Errorf("state entry not reported %x", hash) t.Errorf("state entry not reported %x", hash)
} }
} }
for _, key := range mem.Keys() { for _, key := range db.TrieDB().DiskDB().(*ethdb.MemDatabase).Keys() {
if bytes.HasPrefix(key, []byte("secure-key-")) { if bytes.HasPrefix(key, []byte("secure-key-")) {
continue continue
} }

View file

@ -27,8 +27,8 @@ import (
var addr = common.BytesToAddress([]byte("test")) var addr = common.BytesToAddress([]byte("test"))
func create() (*ManagedState, *account) { func create() (*ManagedState, *account) {
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
statedb, _ := New(common.Hash{}, NewDatabase(db, trie.NewNodePool())) statedb, _ := New(common.Hash{}, NewDatabase(trie.NewDatabase(diskdb)))
ms := ManageState(statedb) ms := ManageState(statedb)
ms.StateDB.SetNonce(addr, 100) ms.StateDB.SetNonce(addr, 100)
ms.accounts[addr] = newAccount(ms.StateDB.getStateObject(addr)) ms.accounts[addr] = newAccount(ms.StateDB.getStateObject(addr))

View file

@ -25,7 +25,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
) )
var emptyCodeHash = crypto.Keccak256(nil) var emptyCodeHash = crypto.Keccak256(nil)
@ -238,12 +237,12 @@ func (self *stateObject) updateRoot(db Database) {
// CommitTrie the storage trie of the object to dwb. // CommitTrie the storage trie of the object to dwb.
// This updates the trie root. // This updates the trie root.
func (self *stateObject) CommitTrie(db Database, dbw trie.DatabaseWriter) error { func (self *stateObject) CommitTrie(db Database) error {
self.updateTrie(db) self.updateTrie(db)
if self.dbErr != nil { if self.dbErr != nil {
return self.dbErr return self.dbErr
} }
root, err := self.trie.CommitTo(dbw) root, err := self.trie.Commit(nil)
if err == nil { if err == nil {
self.data.Root = root self.data.Root = root
} }

View file

@ -49,7 +49,7 @@ func (s *StateSuite) TestDump(c *checker.C) {
// write some of them to the trie // write some of them to the trie
s.state.updateStateObject(obj1) s.state.updateStateObject(obj1)
s.state.updateStateObject(obj2) s.state.updateStateObject(obj2)
s.state.CommitTo(s.db, false) s.state.Commit(false)
// check that dump contains the state objects that are in trie // check that dump contains the state objects that are in trie
got := string(s.state.Dump()) got := string(s.state.Dump())
@ -89,7 +89,7 @@ func (s *StateSuite) TestDump(c *checker.C) {
func (s *StateSuite) SetUpTest(c *checker.C) { func (s *StateSuite) SetUpTest(c *checker.C) {
s.db, _ = ethdb.NewMemDatabase() s.db, _ = ethdb.NewMemDatabase()
s.state, _ = New(common.Hash{}, NewDatabase(s.db, trie.NewNodePool())) s.state, _ = New(common.Hash{}, NewDatabase(trie.NewDatabase(s.db)))
} }
func (s *StateSuite) TestNull(c *checker.C) { func (s *StateSuite) TestNull(c *checker.C) {
@ -98,7 +98,7 @@ func (s *StateSuite) TestNull(c *checker.C) {
//value := common.FromHex("0x823140710bf13990e4500136726d8b55") //value := common.FromHex("0x823140710bf13990e4500136726d8b55")
var value common.Hash var value common.Hash
s.state.SetState(address, common.Hash{}, value) s.state.SetState(address, common.Hash{}, value)
s.state.CommitTo(s.db, false) s.state.Commit(false)
value = s.state.GetState(address, common.Hash{}) value = s.state.GetState(address, common.Hash{})
if !common.EmptyHash(value) { if !common.EmptyHash(value) {
c.Errorf("expected empty hash. got %x", value) c.Errorf("expected empty hash. got %x", value)
@ -134,8 +134,9 @@ func (s *StateSuite) TestSnapshotEmpty(c *checker.C) {
// use testing instead of checker because checker does not support // use testing instead of checker because checker does not support
// printing/logging in tests (-check.vv does not work) // printing/logging in tests (-check.vv does not work)
func TestSnapshot2(t *testing.T) { func TestSnapshot2(t *testing.T) {
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
state, _ := New(common.Hash{}, NewDatabase(db, trie.NewNodePool())) triedb := trie.NewDatabase(diskdb)
state, _ := New(common.Hash{}, NewDatabase(triedb))
stateobjaddr0 := toAddr([]byte("so0")) stateobjaddr0 := toAddr([]byte("so0"))
stateobjaddr1 := toAddr([]byte("so1")) stateobjaddr1 := toAddr([]byte("so1"))
@ -156,7 +157,7 @@ func TestSnapshot2(t *testing.T) {
so0.deleted = false so0.deleted = false
state.setStateObject(so0) state.setStateObject(so0)
root, _ := state.CommitTo(db, false) root, _ := state.Commit(false)
state.Reset(root) state.Reset(root)
// and one with deleted == true // and one with deleted == true

View file

@ -36,6 +36,14 @@ type revision struct {
journalIndex int journalIndex int
} }
var (
// emptyState is the known hash of an empty state trie entry.
emptyState = crypto.Keccak256Hash(nil)
// emptyCode is the known hash of the empty EVM bytecode.
emptyCode = crypto.Keccak256Hash(nil)
)
// StateDBs within the ethereum protocol are used to store anything // StateDBs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing // within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve: // nested states. It's the general query interface to retrieve:
@ -568,8 +576,8 @@ func (s *StateDB) clearJournalAndRefund() {
s.refund = 0 s.refund = 0
} }
// CommitTo writes the state to the given database. // Commit writes the state to the underlying in-memory trie database.
func (s *StateDB) CommitTo(dbw trie.DatabaseWriter, deleteEmptyObjects bool) (root common.Hash, err error) { func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) {
defer s.clearJournalAndRefund() defer s.clearJournalAndRefund()
// Commit objects to the trie. // Commit objects to the trie.
@ -583,13 +591,11 @@ func (s *StateDB) CommitTo(dbw trie.DatabaseWriter, deleteEmptyObjects bool) (ro
case isDirty: case isDirty:
// Write any contract code associated with the state object // Write any contract code associated with the state object
if stateObject.code != nil && stateObject.dirtyCode { if stateObject.code != nil && stateObject.dirtyCode {
if err := dbw.Put(stateObject.CodeHash(), stateObject.code); err != nil { s.db.TrieDB().Insert(common.BytesToHash(stateObject.CodeHash()), stateObject.code)
return common.Hash{}, err
}
stateObject.dirtyCode = false stateObject.dirtyCode = false
} }
// Write any storage changes in the state object to its storage trie. // Write any storage changes in the state object to its storage trie.
if err := stateObject.CommitTrie(s.db, dbw); err != nil { if err := stateObject.CommitTrie(s.db); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// Update the object in the main account trie. // Update the object in the main account trie.
@ -598,7 +604,20 @@ func (s *StateDB) CommitTo(dbw trie.DatabaseWriter, deleteEmptyObjects bool) (ro
delete(s.stateObjectsDirty, addr) delete(s.stateObjectsDirty, addr)
} }
// Write trie changes. // Write trie changes.
root, err = s.trie.CommitTo(dbw) root, err = s.trie.Commit(func(leaf []byte, parent common.Hash) error {
var account Account
if err := rlp.DecodeBytes(leaf, &account); err != nil {
return nil
}
if account.Root != emptyState {
s.db.TrieDB().Reference(account.Root, parent)
}
code := common.BytesToHash(account.CodeHash)
if code != emptyCode {
s.db.TrieDB().Reference(code, parent)
}
return nil
})
log.Debug("Trie cache stats after commit", "misses", trie.CacheMisses(), "unloads", trie.CacheUnloads()) log.Debug("Trie cache stats after commit", "misses", trie.CacheMisses(), "unloads", trie.CacheUnloads())
return root, err return root, err
} }

View file

@ -41,7 +41,7 @@ import (
func TestUpdateLeaks(t *testing.T) { func TestUpdateLeaks(t *testing.T) {
// Create an empty state database // Create an empty state database
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
state, _ := New(common.Hash{}, NewDatabase(db, trie.NewNodePool())) state, _ := New(common.Hash{}, NewDatabase(trie.NewDatabase(db)))
// Update it with some accounts // Update it with some accounts
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
@ -69,8 +69,8 @@ func TestIntermediateLeaks(t *testing.T) {
// Create two state databases, one transitioning to the final state, the other final from the beginning // Create two state databases, one transitioning to the final state, the other final from the beginning
transDb, _ := ethdb.NewMemDatabase() transDb, _ := ethdb.NewMemDatabase()
finalDb, _ := ethdb.NewMemDatabase() finalDb, _ := ethdb.NewMemDatabase()
transState, _ := New(common.Hash{}, NewDatabase(transDb, trie.NewNodePool())) transState, _ := New(common.Hash{}, NewDatabase(trie.NewDatabase(transDb)))
finalState, _ := New(common.Hash{}, NewDatabase(finalDb, trie.NewNodePool())) finalState, _ := New(common.Hash{}, NewDatabase(trie.NewDatabase(finalDb)))
modify := func(state *StateDB, addr common.Address, i, tweak byte) { modify := func(state *StateDB, addr common.Address, i, tweak byte) {
state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak))) state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak)))
@ -98,10 +98,10 @@ func TestIntermediateLeaks(t *testing.T) {
} }
// Commit and cross check the databases. // Commit and cross check the databases.
if _, err := transState.CommitTo(transDb, false); err != nil { if _, err := transState.Commit(false); err != nil {
t.Fatalf("failed to commit transition state: %v", err) t.Fatalf("failed to commit transition state: %v", err)
} }
if _, err := finalState.CommitTo(finalDb, false); err != nil { if _, err := finalState.Commit(false); err != nil {
t.Fatalf("failed to commit final state: %v", err) t.Fatalf("failed to commit final state: %v", err)
} }
for _, key := range finalDb.Keys() { for _, key := range finalDb.Keys() {
@ -123,8 +123,8 @@ func TestIntermediateLeaks(t *testing.T) {
// https://github.com/ethereum/go-ethereum/pull/15549. // https://github.com/ethereum/go-ethereum/pull/15549.
func TestCopy(t *testing.T) { func TestCopy(t *testing.T) {
// Create a random state test to copy and modify "independently" // Create a random state test to copy and modify "independently"
mem, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
orig, _ := New(common.Hash{}, NewDatabase(mem, trie.NewNodePool())) orig, _ := New(common.Hash{}, NewDatabase(trie.NewDatabase(diskdb)))
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i})) obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
@ -335,9 +335,9 @@ func (test *snapshotTest) String() string {
func (test *snapshotTest) run() bool { func (test *snapshotTest) run() bool {
// Run all actions and create snapshots. // Run all actions and create snapshots.
var ( var (
db, _ = ethdb.NewMemDatabase() diskdb, _ = ethdb.NewMemDatabase()
mp = trie.NewNodePool() triedb = trie.NewDatabase(diskdb)
state, _ = New(common.Hash{}, NewDatabase(db, trie.NewNodePool())) state, _ = New(common.Hash{}, NewDatabase(triedb))
snapshotRevs = make([]int, len(test.snapshots)) snapshotRevs = make([]int, len(test.snapshots))
sindex = 0 sindex = 0
) )
@ -352,7 +352,7 @@ func (test *snapshotTest) run() bool {
// Revert all snapshots in reverse order. Each revert must yield a state // Revert all snapshots in reverse order. Each revert must yield a state
// that is equivalent to fresh state with all actions up the snapshot applied. // that is equivalent to fresh state with all actions up the snapshot applied.
for sindex--; sindex >= 0; sindex-- { for sindex--; sindex >= 0; sindex-- {
checkstate, _ := New(common.Hash{}, NewDatabase(db, mp)) checkstate, _ := New(common.Hash{}, NewDatabase(triedb))
for _, action := range test.actions[:test.snapshots[sindex]] { for _, action := range test.actions[:test.snapshots[sindex]] {
action.fn(action, checkstate) action.fn(action, checkstate)
} }
@ -411,7 +411,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
func (s *StateSuite) TestTouchDelete(c *check.C) { func (s *StateSuite) TestTouchDelete(c *check.C) {
s.state.GetOrNewStateObject(common.Address{}) s.state.GetOrNewStateObject(common.Address{})
root, _ := s.state.CommitTo(s.db, false) root, _ := s.state.Commit(false)
s.state.Reset(root) s.state.Reset(root)
snapshot := s.state.Snapshot() snapshot := s.state.Snapshot()
@ -419,7 +419,6 @@ func (s *StateSuite) TestTouchDelete(c *check.C) {
if len(s.state.stateObjectsDirty) != 1 { if len(s.state.stateObjectsDirty) != 1 {
c.Fatal("expected one dirty state object") c.Fatal("expected one dirty state object")
} }
s.state.RevertToSnapshot(snapshot) s.state.RevertToSnapshot(snapshot)
if len(s.state.stateObjectsDirty) != 0 { if len(s.state.stateObjectsDirty) != 0 {
c.Fatal("expected no dirty state object") c.Fatal("expected no dirty state object")

View file

@ -36,10 +36,10 @@ type testAccount struct {
} }
// makeTestState create a sample test state to test node-wise reconstruction. // makeTestState create a sample test state to test node-wise reconstruction.
func makeTestState() (Database, *ethdb.MemDatabase, common.Hash, []*testAccount) { func makeTestState() (Database, common.Hash, []*testAccount) {
// Create an empty state // Create an empty state
mem, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
db := NewDatabase(mem, trie.NewNodePool()) db := NewDatabase(trie.NewDatabase(diskdb))
state, _ := New(common.Hash{}, db) state, _ := New(common.Hash{}, db)
// Fill it with some arbitrary data // Fill it with some arbitrary data
@ -61,17 +61,17 @@ func makeTestState() (Database, *ethdb.MemDatabase, common.Hash, []*testAccount)
state.updateStateObject(obj) state.updateStateObject(obj)
accounts = append(accounts, acc) accounts = append(accounts, acc)
} }
root, _ := state.CommitTo(mem, false) root, _ := state.Commit(false)
// Return the generated state // Return the generated state
return db, mem, root, accounts return db, root, accounts
} }
// checkStateAccounts cross references a reconstructed state with an expected // checkStateAccounts cross references a reconstructed state with an expected
// account array. // account array.
func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accounts []*testAccount) { func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accounts []*testAccount) {
// Check root availability and state contents // Check root availability and state contents
state, err := New(root, NewDatabase(db, trie.NewNodePool())) state, err := New(root, NewDatabase(trie.NewDatabase(db)))
if err != nil { if err != nil {
t.Fatalf("failed to create state trie at %x: %v", root, err) t.Fatalf("failed to create state trie at %x: %v", root, err)
} }
@ -96,7 +96,7 @@ func checkTrieConsistency(db ethdb.Database, root common.Hash) error {
if v, _ := db.Get(root[:]); v == nil { if v, _ := db.Get(root[:]); v == nil {
return nil // Consider a non existent state consistent. return nil // Consider a non existent state consistent.
} }
trie, err := trie.New(root, db, trie.NewNodePool()) trie, err := trie.New(root, trie.NewDatabase(db))
if err != nil { if err != nil {
return err return err
} }
@ -112,7 +112,7 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error {
if _, err := db.Get(root.Bytes()); err != nil { if _, err := db.Get(root.Bytes()); err != nil {
return nil // Consider a non existent state consistent. return nil // Consider a non existent state consistent.
} }
state, err := New(root, NewDatabase(db, trie.NewNodePool())) state, err := New(root, NewDatabase(trie.NewDatabase(db)))
if err != nil { if err != nil {
return err return err
} }
@ -138,7 +138,7 @@ func TestIterativeStateSyncBatched(t *testing.T) { testIterativeStateSync(t,
func testIterativeStateSync(t *testing.T, batch int) { func testIterativeStateSync(t *testing.T, batch int) {
// Create a random state to copy // Create a random state to copy
srcDb, srcMem, srcRoot, srcAccounts := makeTestState() srcDb, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler // Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() dstDb, _ := ethdb.NewMemDatabase()
@ -148,15 +148,10 @@ func testIterativeStateSync(t *testing.T, batch int) {
for len(queue) > 0 { for len(queue) > 0 {
results := make([]trie.SyncResult, len(queue)) results := make([]trie.SyncResult, len(queue))
for i, hash := range queue { for i, hash := range queue {
var ( data, err := srcDb.TrieDB().Node(hash)
data = srcDb.NodePool().Fetch(hash) if err != nil {
err error
)
if data == nil {
if data, err = srcMem.Get(hash.Bytes()); err != nil {
t.Fatalf("failed to retrieve node data for %x", hash) t.Fatalf("failed to retrieve node data for %x", hash)
} }
}
results[i] = trie.SyncResult{Hash: hash, Data: data} results[i] = trie.SyncResult{Hash: hash, Data: data}
} }
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
@ -175,7 +170,7 @@ func testIterativeStateSync(t *testing.T, batch int) {
// partial results are returned, and the others sent only later. // partial results are returned, and the others sent only later.
func TestIterativeDelayedStateSync(t *testing.T) { func TestIterativeDelayedStateSync(t *testing.T) {
// Create a random state to copy // Create a random state to copy
srcDb, srcMem, srcRoot, srcAccounts := makeTestState() srcDb, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler // Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() dstDb, _ := ethdb.NewMemDatabase()
@ -186,15 +181,10 @@ func TestIterativeDelayedStateSync(t *testing.T) {
// Sync only half of the scheduled nodes // Sync only half of the scheduled nodes
results := make([]trie.SyncResult, len(queue)/2+1) results := make([]trie.SyncResult, len(queue)/2+1)
for i, hash := range queue[:len(results)] { for i, hash := range queue[:len(results)] {
var ( data, err := srcDb.TrieDB().Node(hash)
data = srcDb.NodePool().Fetch(hash) if err != nil {
err error
)
if data == nil {
if data, err = srcMem.Get(hash.Bytes()); err != nil {
t.Fatalf("failed to retrieve node data for %x", hash) t.Fatalf("failed to retrieve node data for %x", hash)
} }
}
results[i] = trie.SyncResult{Hash: hash, Data: data} results[i] = trie.SyncResult{Hash: hash, Data: data}
} }
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
@ -217,7 +207,7 @@ func TestIterativeRandomStateSyncBatched(t *testing.T) { testIterativeRandomS
func testIterativeRandomStateSync(t *testing.T, batch int) { func testIterativeRandomStateSync(t *testing.T, batch int) {
// Create a random state to copy // Create a random state to copy
srcDb, srcMem, srcRoot, srcAccounts := makeTestState() srcDb, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler // Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() dstDb, _ := ethdb.NewMemDatabase()
@ -231,15 +221,10 @@ func testIterativeRandomStateSync(t *testing.T, batch int) {
// Fetch all the queued nodes in a random order // Fetch all the queued nodes in a random order
results := make([]trie.SyncResult, 0, len(queue)) results := make([]trie.SyncResult, 0, len(queue))
for hash := range queue { for hash := range queue {
var ( data, err := srcDb.TrieDB().Node(hash)
data = srcDb.NodePool().Fetch(hash) if err != nil {
err error
)
if data == nil {
if data, err = srcMem.Get(hash.Bytes()); err != nil {
t.Fatalf("failed to retrieve node data for %x", hash) t.Fatalf("failed to retrieve node data for %x", hash)
} }
}
results = append(results, trie.SyncResult{Hash: hash, Data: data}) results = append(results, trie.SyncResult{Hash: hash, Data: data})
} }
// Feed the retrieved results back and queue new tasks // Feed the retrieved results back and queue new tasks
@ -262,7 +247,7 @@ func testIterativeRandomStateSync(t *testing.T, batch int) {
// partial results are returned (Even those randomly), others sent only later. // partial results are returned (Even those randomly), others sent only later.
func TestIterativeRandomDelayedStateSync(t *testing.T) { func TestIterativeRandomDelayedStateSync(t *testing.T) {
// Create a random state to copy // Create a random state to copy
srcDb, srcMem, srcRoot, srcAccounts := makeTestState() srcDb, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler // Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() dstDb, _ := ethdb.NewMemDatabase()
@ -278,15 +263,10 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
for hash := range queue { for hash := range queue {
delete(queue, hash) delete(queue, hash)
var ( data, err := srcDb.TrieDB().Node(hash)
data = srcDb.NodePool().Fetch(hash) if err != nil {
err error
)
if data == nil {
if data, err = srcMem.Get(hash.Bytes()); err != nil {
t.Fatalf("failed to retrieve node data for %x", hash) t.Fatalf("failed to retrieve node data for %x", hash)
} }
}
results = append(results, trie.SyncResult{Hash: hash, Data: data}) results = append(results, trie.SyncResult{Hash: hash, Data: data})
if len(results) >= cap(results) { if len(results) >= cap(results) {
@ -312,9 +292,9 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
// the database. // the database.
func TestIncompleteStateSync(t *testing.T) { func TestIncompleteStateSync(t *testing.T) {
// Create a random state to copy // Create a random state to copy
srcDb, srcMem, srcRoot, srcAccounts := makeTestState() srcDb, srcRoot, srcAccounts := makeTestState()
checkTrieConsistency(srcMem, srcRoot) checkTrieConsistency(srcDb.TrieDB().DiskDB().(ethdb.Database), srcRoot)
// Create a destination state and sync with the scheduler // Create a destination state and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() dstDb, _ := ethdb.NewMemDatabase()
@ -326,15 +306,10 @@ func TestIncompleteStateSync(t *testing.T) {
// Fetch a batch of state nodes // Fetch a batch of state nodes
results := make([]trie.SyncResult, len(queue)) results := make([]trie.SyncResult, len(queue))
for i, hash := range queue { for i, hash := range queue {
var ( data, err := srcDb.TrieDB().Node(hash)
data = srcDb.NodePool().Fetch(hash) if err != nil {
err error
)
if data == nil {
if data, err = srcMem.Get(hash.Bytes()); err != nil {
t.Fatalf("failed to retrieve node data for %x", hash) t.Fatalf("failed to retrieve node data for %x", hash)
} }
}
results[i] = trie.SyncResult{Hash: hash, Data: data} results[i] = trie.SyncResult{Hash: hash, Data: data}
} }
// Process each of the state nodes // Process each of the state nodes

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
) )
// testTxPoolConfig is a transaction pool configuration without stateful disk // testTxPoolConfig is a transaction pool configuration without stateful disk
@ -78,8 +79,8 @@ func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ec
} }
func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(diskdb)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
@ -159,7 +160,7 @@ func (c *testChain) State() (*state.StateDB, error) {
stdb := c.statedb stdb := c.statedb
if *c.trigger { if *c.trigger {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(db, nil)) c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
// simulate that the new head block included tx0 and tx1 // simulate that the new head block included tx0 and tx1
c.statedb.SetNonce(c.address, 2) c.statedb.SetNonce(c.address, 2)
c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether)) c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether))
@ -178,7 +179,7 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
db, _ = ethdb.NewMemDatabase() db, _ = ethdb.NewMemDatabase()
key, _ = crypto.GenerateKey() key, _ = crypto.GenerateKey()
address = crypto.PubkeyToAddress(key.PublicKey) address = crypto.PubkeyToAddress(key.PublicKey)
statedb, _ = state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ = state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
trigger = false trigger = false
) )
@ -338,7 +339,7 @@ func TestTransactionChainFork(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey) addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() { resetState := func() {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
statedb.AddBalance(addr, big.NewInt(100000000000000)) statedb.AddBalance(addr, big.NewInt(100000000000000))
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
@ -368,7 +369,7 @@ func TestTransactionDoubleNonce(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey) addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() { resetState := func() {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
statedb.AddBalance(addr, big.NewInt(100000000000000)) statedb.AddBalance(addr, big.NewInt(100000000000000))
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
@ -737,7 +738,7 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) {
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
config := testTxPoolConfig config := testTxPoolConfig
@ -826,7 +827,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
// Create the pool to test the non-expiration enforcement // Create the pool to test the non-expiration enforcement
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
config := testTxPoolConfig config := testTxPoolConfig
@ -981,7 +982,7 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
config := testTxPoolConfig config := testTxPoolConfig
@ -1028,7 +1029,7 @@ func TestTransactionCapClearsFromAll(t *testing.T) {
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
config := testTxPoolConfig config := testTxPoolConfig
@ -1063,7 +1064,7 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
// Create the pool to test the limit enforcement with // Create the pool to test the limit enforcement with
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
config := testTxPoolConfig config := testTxPoolConfig
@ -1112,7 +1113,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
@ -1211,7 +1212,7 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
@ -1274,7 +1275,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
config := testTxPoolConfig config := testTxPoolConfig
@ -1376,7 +1377,7 @@ func TestTransactionReplacement(t *testing.T) {
// Create the pool to test the pricing enforcement with // Create the pool to test the pricing enforcement with
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
@ -1471,7 +1472,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
// Create the original pool to inject transaction into the journal // Create the original pool to inject transaction into the journal
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
config := testTxPoolConfig config := testTxPoolConfig
@ -1570,7 +1571,7 @@ func TestTransactionStatusCheck(t *testing.T) {
// Create the pool to test the status retrievals with // Create the pool to test the status retrievals with
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) statedb, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
) )
// Config is a basic type specifying certain configuration flags for running // Config is a basic type specifying certain configuration flags for running
@ -102,7 +103,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
if cfg.State == nil { if cfg.State == nil {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db, nil)) cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
} }
var ( var (
address = common.StringToAddress("contract") address = common.StringToAddress("contract")
@ -133,7 +134,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
if cfg.State == nil { if cfg.State == nil {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db, nil)) cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
} }
var ( var (
vmenv = NewEnv(cfg) vmenv = NewEnv(cfg)

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
) )
func TestDefaults(t *testing.T) { func TestDefaults(t *testing.T) {
@ -95,7 +96,7 @@ func TestExecute(t *testing.T) {
func TestCall(t *testing.T) { func TestCall(t *testing.T) {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
state, _ := state.New(common.Hash{}, state.NewDatabase(db, nil)) state, _ := state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
address := common.HexToAddress("0x0a") address := common.HexToAddress("0x0a")
state.SetCode(address, []byte{ state.SetCode(address, []byte{
byte(vm.PUSH1), 10, byte(vm.PUSH1), 10,

View file

@ -462,11 +462,11 @@ func (api *PrivateDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Bloc
return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64())
} }
oldTrie, err := trie.NewSecure(startBlock.Root(), api.eth.chainDb, nil, 0) oldTrie, err := trie.NewSecure(startBlock.Root(), trie.NewDatabase(api.eth.chainDb), 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
newTrie, err := trie.NewSecure(endBlock.Root(), api.eth.chainDb, nil, 0) newTrie, err := trie.NewSecure(endBlock.Root(), trie.NewDatabase(api.eth.chainDb), 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
) )
var dumper = spew.ConfigState{Indent: " "} var dumper = spew.ConfigState{Indent: " "}
@ -32,7 +33,7 @@ func TestStorageRangeAt(t *testing.T) {
// Create a state where account 0x010000... has a few storage entries. // Create a state where account 0x010000... has a few storage entries.
var ( var (
db, _ = ethdb.NewMemDatabase() db, _ = ethdb.NewMemDatabase()
state, _ = state.New(common.Hash{}, state.NewDatabase(db, nil)) state, _ = state.New(common.Hash{}, state.NewDatabase(trie.NewDatabase(db)))
addr = common.Address{0x01} addr = common.Address{0x01}
keys = []common.Hash{ // hashes of Keys of storage keys = []common.Hash{ // hashes of Keys of storage
common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"), common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"),

View file

@ -200,7 +200,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
return nil, fmt.Errorf("parent block #%d not found", number-1) return nil, fmt.Errorf("parent block #%d not found", number-1)
} }
} }
statedb, err := state.New(start.Root(), state.NewDatabase(db, nil)) statedb, err := state.New(start.Root(), state.NewDatabase(trie.NewDatabase(db)))
if err != nil { if err != nil {
// If the starting state is missing, allow some number of blocks to be reexecuted // If the starting state is missing, allow some number of blocks to be reexecuted
reexec := defaultTraceReexec reexec := defaultTraceReexec
@ -213,7 +213,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
if start == nil { if start == nil {
break break
} }
if statedb, err = state.New(start.Root(), state.NewDatabase(db, nil)); err == nil { if statedb, err = state.New(start.Root(), state.NewDatabase(trie.NewDatabase(db))); err == nil {
break break
} }
} }
@ -340,7 +340,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
break break
} }
// Finalize the state so any modifications are written to the trie // Finalize the state so any modifications are written to the trie
root, err := statedb.CommitTo(db, true) root, err := statedb.Commit(true)
if err != nil { if err != nil {
failed = err failed = err
break break
@ -367,7 +367,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
db.Prune(root) db.Prune(root)
log.Info("Pruned tracer state entries", "deleted", nodes-db.memdb.Len(), "left", db.memdb.Len(), "elapsed", time.Since(start)) log.Info("Pruned tracer state entries", "deleted", nodes-db.memdb.Len(), "left", db.memdb.Len(), "elapsed", time.Since(start))
statedb, _ = state.New(root, state.NewDatabase(db, nil)) statedb, _ = state.New(root, state.NewDatabase(trie.NewDatabase(db)))
} }
} }
}() }()
@ -555,7 +555,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
if block == nil { if block == nil {
break break
} }
if statedb, err = state.New(block.Root(), state.NewDatabase(db, nil)); err == nil { if statedb, err = state.New(block.Root(), state.NewDatabase(trie.NewDatabase(db))); err == nil {
break break
} }
} }
@ -587,7 +587,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
return nil, err return nil, err
} }
// Finalize the state so any modifications are written to the trie // Finalize the state so any modifications are written to the trie
root, err := statedb.CommitTo(db, true) root, err := statedb.Commit(true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -603,7 +603,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
db.Prune(root) db.Prune(root)
log.Info("Pruned tracer state entries", "deleted", nodes-db.memdb.Len(), "left", db.memdb.Len(), "elapsed", time.Since(begin)) log.Info("Pruned tracer state entries", "deleted", nodes-db.memdb.Len(), "left", db.memdb.Len(), "elapsed", time.Since(begin))
statedb, _ = state.New(root, state.NewDatabase(db, nil)) statedb, _ = state.New(root, state.NewDatabase(trie.NewDatabase(db)))
} }
} }
log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start)) log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start))

View file

@ -293,7 +293,7 @@ func (dl *downloadTester) CurrentFastBlock() *types.Block {
func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error { func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error {
// For now only check that the state trie is correct // For now only check that the state trie is correct
if block := dl.GetBlockByHash(hash); block != nil { if block := dl.GetBlockByHash(hash); block != nil {
_, err := trie.NewSecure(block.Root(), dl.stateDb, nil, 0) // nil trie node-cache, ensure we have everything on disk _, err := trie.NewSecure(block.Root(), trie.NewDatabase(dl.stateDb), 0)
return err return err
} }
return fmt.Errorf("non existent block: %x", hash[:4]) return fmt.Errorf("non existent block: %x", hash[:4])
@ -660,7 +660,7 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng
index = len(tester.ownHashes) - lengths[len(lengths)-1] + int(tester.downloader.queue.fastSyncPivot) index = len(tester.ownHashes) - lengths[len(lengths)-1] + int(tester.downloader.queue.fastSyncPivot)
} }
if index > 0 { if index > 0 {
if statedb, err := state.New(tester.ownHeaders[tester.ownHashes[index]].Root, state.NewDatabase(tester.stateDb, nil)); statedb == nil || err != nil { if statedb, err := state.New(tester.ownHeaders[tester.ownHashes[index]].Root, state.NewDatabase(trie.NewDatabase(tester.stateDb))); statedb == nil || err != nil {
t.Fatalf("state reconstruction failed: %v", err) t.Fatalf("state reconstruction failed: %v", err)
} }
} }

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
) )
// Tests that protocol versions and modes of operations are matched up properly. // Tests that protocol versions and modes of operations are matched up properly.
@ -372,7 +373,7 @@ func testGetNodeData(t *testing.T, protocol int) {
} }
accounts := []common.Address{testBank, acc1Addr, acc2Addr} accounts := []common.Address{testBank, acc1Addr, acc2Addr}
for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ {
trie, _ := state.New(pm.blockchain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb, nil)) trie, _ := state.New(pm.blockchain.GetBlockByNumber(i).Root(), state.NewDatabase(trie.NewDatabase(statedb)))
for j, acc := range accounts { for j, acc := range accounts {
state, _ := pm.blockchain.State() state, _ := pm.blockchain.State()

View file

@ -579,7 +579,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
for _, req := range req.Reqs { for _, req := range req.Reqs {
// Retrieve the requested state entry, stopping if enough was found // Retrieve the requested state entry, stopping if enough was found
if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil { if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil {
if trie, _ := trie.New(header.Root, pm.chainDb, nil); trie != nil { if trie, _ := trie.New(header.Root, trie.NewDatabase(pm.chainDb)); trie != nil {
sdata := trie.Get(req.AccKey) sdata := trie.Get(req.AccKey)
var acc state.Account var acc state.Account
if err := rlp.DecodeBytes(sdata, &acc); err == nil { if err := rlp.DecodeBytes(sdata, &acc); err == nil {
@ -706,13 +706,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
} }
// Retrieve the requested state entry, stopping if enough was found // Retrieve the requested state entry, stopping if enough was found
if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil { if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil {
if tr, _ := trie.New(header.Root, pm.chainDb, nil); tr != nil { if tr, _ := trie.New(header.Root, trie.NewDatabase(pm.chainDb)); tr != nil {
if len(req.AccKey) > 0 { if len(req.AccKey) > 0 {
sdata := tr.Get(req.AccKey) sdata := tr.Get(req.AccKey)
tr = nil tr = nil
var acc state.Account var acc state.Account
if err := rlp.DecodeBytes(sdata, &acc); err == nil { if err := rlp.DecodeBytes(sdata, &acc); err == nil {
tr, _ = trie.New(acc.Root, pm.chainDb, nil) tr, _ = trie.New(acc.Root, trie.NewDatabase(pm.chainDb))
} }
} }
if tr != nil { if tr != nil {
@ -757,7 +757,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
} }
if tr == nil || req.BHash != lastBHash { if tr == nil || req.BHash != lastBHash {
if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil { if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil {
tr, _ = trie.New(header.Root, pm.chainDb, nil) tr, _ = trie.New(header.Root, trie.NewDatabase(pm.chainDb))
} else { } else {
tr = nil tr = nil
} }
@ -771,7 +771,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
str = nil str = nil
var acc state.Account var acc state.Account
if err := rlp.DecodeBytes(sdata, &acc); err == nil { if err := rlp.DecodeBytes(sdata, &acc); err == nil {
str, _ = trie.New(acc.Root, pm.chainDb, nil) str, _ = trie.New(acc.Root, trie.NewDatabase(pm.chainDb))
} }
lastAccKey = common.CopyBytes(req.AccKey) lastAccKey = common.CopyBytes(req.AccKey)
} }
@ -858,7 +858,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil { if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil {
sectionHead := core.GetCanonicalHash(pm.chainDb, req.ChtNum*light.ChtV1Frequency-1) sectionHead := core.GetCanonicalHash(pm.chainDb, req.ChtNum*light.ChtV1Frequency-1)
if root := light.GetChtRoot(pm.chainDb, req.ChtNum-1, sectionHead); root != (common.Hash{}) { if root := light.GetChtRoot(pm.chainDb, req.ChtNum-1, sectionHead); root != (common.Hash{}) {
if tr, _ := trie.New(root, trieDb, nil); tr != nil { if tr, _ := trie.New(root, trie.NewDatabase(trieDb)); tr != nil {
var encNumber [8]byte var encNumber [8]byte
binary.BigEndian.PutUint64(encNumber[:], req.BlockNum) binary.BigEndian.PutUint64(encNumber[:], req.BlockNum)
var proof light.NodeList var proof light.NodeList
@ -910,7 +910,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
var prefix string var prefix string
root, prefix = pm.getHelperTrie(req.HelperTrieType, req.TrieIdx) root, prefix = pm.getHelperTrie(req.HelperTrieType, req.TrieIdx)
if root != (common.Hash{}) { if root != (common.Hash{}) {
if t, err := trie.New(root, ethdb.NewTable(pm.chainDb, prefix), nil); err == nil { if t, err := trie.New(root, trie.NewDatabase(ethdb.NewTable(pm.chainDb, prefix))); err == nil {
tr = t tr = t
} }
} }

View file

@ -359,7 +359,7 @@ func testGetProofs(t *testing.T, protocol int) {
for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ { for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
header := bc.GetHeaderByNumber(i) header := bc.GetHeaderByNumber(i)
root := header.Root root := header.Root
trie, _ := trie.New(root, db, nil) trie, _ := trie.New(root, trie.NewDatabase(db))
for _, acc := range accounts { for _, acc := range accounts {
req := ProofReq{ req := ProofReq{

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
) )
type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte
@ -90,7 +91,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon
for _, addr := range acc { for _, addr := range acc {
if bc != nil { if bc != nil {
header := bc.GetHeaderByHash(bhash) header := bc.GetHeaderByHash(bhash)
st, err = state.New(header.Root, state.NewDatabase(db, nil)) st, err = state.New(header.Root, state.NewDatabase(trie.NewDatabase(db)))
} else { } else {
header := lc.GetHeaderByHash(bhash) header := lc.GetHeaderByHash(bhash)
st = light.NewState(ctx, header, lc.Odr()) st = light.NewState(ctx, header, lc.Odr())
@ -123,7 +124,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
data[35] = byte(i) data[35] = byte(i)
if bc != nil { if bc != nil {
header := bc.GetHeaderByHash(bhash) header := bc.GetHeaderByHash(bhash)
statedb, err := state.New(header.Root, state.NewDatabase(db, nil)) statedb, err := state.New(header.Root, state.NewDatabase(trie.NewDatabase(db)))
if err == nil { if err == nil {
from := statedb.GetOrNewStateObject(testBankAddress) from := statedb.GetOrNewStateObject(testBankAddress)

View file

@ -99,7 +99,7 @@ func (db *NodeSet) NodeList() NodeList {
} }
// Store writes the contents of the set to the given database // Store writes the contents of the set to the given database
func (db *NodeSet) Store(target trie.Database) { func (db *NodeSet) Store(target trie.DatabaseWriter) {
db.lock.RLock() db.lock.RLock()
defer db.lock.RUnlock() defer db.lock.RUnlock()
@ -112,7 +112,7 @@ func (db *NodeSet) Store(target trie.Database) {
type NodeList []rlp.RawValue type NodeList []rlp.RawValue
// Store writes the contents of the list to the given database // Store writes the contents of the list to the given database
func (n NodeList) Store(db trie.Database) { func (n NodeList) Store(db trie.DatabaseWriter) {
for _, node := range n { for _, node := range n {
db.Put(crypto.Keccak256(node), node) db.Put(crypto.Keccak256(node), node)
} }

View file

@ -74,7 +74,7 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error {
case *ReceiptsRequest: case *ReceiptsRequest:
req.Receipts = core.GetBlockReceipts(odr.sdb, req.Hash, core.GetBlockNumber(odr.sdb, req.Hash)) req.Receipts = core.GetBlockReceipts(odr.sdb, req.Hash, core.GetBlockNumber(odr.sdb, req.Hash))
case *TrieRequest: case *TrieRequest:
t, _ := trie.New(req.Id.Root, odr.sdb, nil) t, _ := trie.New(req.Id.Root, trie.NewDatabase(odr.sdb))
nodes := NewNodeSet() nodes := NewNodeSet()
t.Prove(req.Key, 0, nodes) t.Prove(req.Key, 0, nodes)
req.Proof = nodes req.Proof = nodes
@ -131,7 +131,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc
st = NewState(ctx, header, lc.Odr()) st = NewState(ctx, header, lc.Odr())
} else { } else {
header := bc.GetHeaderByHash(bhash) header := bc.GetHeaderByHash(bhash)
st, _ = state.New(header.Root, state.NewDatabase(db, nil)) st, _ = state.New(header.Root, state.NewDatabase(trie.NewDatabase(db)))
} }
var res []byte var res []byte
@ -171,7 +171,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
} else { } else {
chain = bc chain = bc
header = bc.GetHeaderByHash(bhash) header = bc.GetHeaderByHash(bhash)
st, _ = state.New(header.Root, state.NewDatabase(db, nil)) st, _ = state.New(header.Root, state.NewDatabase(trie.NewDatabase(db)))
} }
// Perform read-only call. // Perform read-only call.

View file

@ -141,7 +141,7 @@ func (c *ChtIndexerBackend) Reset(section uint64, lastSectionHead common.Hash) e
root = GetChtRoot(c.db, section-1, lastSectionHead) root = GetChtRoot(c.db, section-1, lastSectionHead)
} }
var err error var err error
c.trie, err = trie.New(root, c.cdb, nil) c.trie, err = trie.New(root, trie.NewDatabase(c.cdb))
c.section = section c.section = section
return err return err
} }
@ -163,17 +163,14 @@ func (c *ChtIndexerBackend) Process(header *types.Header) {
// Commit implements core.ChainIndexerBackend // Commit implements core.ChainIndexerBackend
func (c *ChtIndexerBackend) Commit() error { func (c *ChtIndexerBackend) Commit() error {
batch := c.cdb.NewBatch() root, err := c.trie.Commit(nil)
root, err := c.trie.CommitTo(batch)
if err != nil { if err != nil {
return err return err
} else { }
batch.Write()
if ((c.section+1)*c.sectionSize)%ChtFrequency == 0 { if ((c.section+1)*c.sectionSize)%ChtFrequency == 0 {
log.Info("Storing CHT", "idx", c.section*c.sectionSize/ChtFrequency, "sectionHead", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root)) log.Info("Storing CHT", "idx", c.section*c.sectionSize/ChtFrequency, "sectionHead", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root))
} }
StoreChtRoot(c.db, c.section, c.lastHash, root) StoreChtRoot(c.db, c.section, c.lastHash, root)
}
return nil return nil
} }
@ -236,7 +233,7 @@ func (b *BloomTrieIndexerBackend) Reset(section uint64, lastSectionHead common.H
root = GetBloomTrieRoot(b.db, section-1, lastSectionHead) root = GetBloomTrieRoot(b.db, section-1, lastSectionHead)
} }
var err error var err error
b.trie, err = trie.New(root, b.cdb, nil) b.trie, err = trie.New(root, trie.NewDatabase(b.cdb))
b.section = section b.section = section
return err return err
} }
@ -279,17 +276,13 @@ func (b *BloomTrieIndexerBackend) Commit() error {
b.trie.Delete(encKey[:]) b.trie.Delete(encKey[:])
} }
} }
root, err := b.trie.Commit(nil)
batch := b.cdb.NewBatch()
root, err := b.trie.CommitTo(batch)
if err != nil { if err != nil {
return err return err
} else { }
batch.Write()
sectionHead := b.sectionHeads[b.bloomTrieRatio-1] sectionHead := b.sectionHeads[b.bloomTrieRatio-1]
log.Info("Storing BloomTrie", "section", b.section, "sectionHead", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression ratio", float64(compSize)/float64(decompSize)) log.Info("Storing BloomTrie", "section", b.section, "sectionHead", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression ratio", float64(compSize)/float64(decompSize))
StoreBloomTrieRoot(b.db, b.section, sectionHead, root) StoreBloomTrieRoot(b.db, b.section, sectionHead, root)
}
return nil return nil
} }

View file

@ -83,7 +83,7 @@ func (db *odrDatabase) ContractCodeSize(addrHash, codeHash common.Hash) (int, er
return len(code), err return len(code), err
} }
func (db *odrDatabase) NodePool() *trie.NodePool { func (db *odrDatabase) TrieDB() *trie.Database {
return nil return nil
} }
@ -117,11 +117,11 @@ func (t *odrTrie) TryDelete(key []byte) error {
}) })
} }
func (t *odrTrie) CommitTo(db trie.DatabaseWriter) (common.Hash, error) { func (t *odrTrie) Commit(onleaf trie.LeafCallback) (common.Hash, error) {
if t.trie == nil { if t.trie == nil {
return t.id.Root, nil return t.id.Root, nil
} }
return t.trie.CommitTo(db) return t.trie.Commit(onleaf)
} }
func (t *odrTrie) Hash() common.Hash { func (t *odrTrie) Hash() common.Hash {
@ -145,7 +145,7 @@ func (t *odrTrie) do(key []byte, fn func() error) error {
for { for {
var err error var err error
if t.trie == nil { if t.trie == nil {
t.trie, err = trie.New(t.id.Root, t.db.backend.Database(), nil) t.trie, err = trie.New(t.id.Root, trie.NewDatabase(t.db.backend.Database()))
} }
if err == nil { if err == nil {
err = fn() err = fn()
@ -171,7 +171,7 @@ func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator {
// Open the actual non-ODR trie if that hasn't happened yet. // Open the actual non-ODR trie if that hasn't happened yet.
if t.trie == nil { if t.trie == nil {
it.do(func() error { it.do(func() error {
t, err := trie.New(t.id.Root, t.db.backend.Database(), nil) t, err := trie.New(t.id.Root, trie.NewDatabase(t.db.backend.Database()))
if err == nil { if err == nil {
it.t.trie = t it.t.trie = t
} }

View file

@ -50,7 +50,7 @@ func TestNodeIterator(t *testing.T) {
odr := &testOdr{sdb: fulldb, ldb: lightdb} odr := &testOdr{sdb: fulldb, ldb: lightdb}
head := blockchain.CurrentHeader() head := blockchain.CurrentHeader()
lightTrie, _ := NewStateDatabase(ctx, head, odr).OpenTrie(head.Root) lightTrie, _ := NewStateDatabase(ctx, head, odr).OpenTrie(head.Root)
fullTrie, _ := state.NewDatabase(fulldb, nil).OpenTrie(head.Root) fullTrie, _ := state.NewDatabase(trie.NewDatabase(fulldb)).OpenTrie(head.Root)
if err := diffTries(fullTrie, lightTrie); err != nil { if err := diffTries(fullTrie, lightTrie); err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
) )
// StateTest checks transaction processing without block context. // StateTest checks transaction processing without block context.
@ -125,7 +126,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD
if !ok { if !ok {
return nil, UnsupportedForkError{subtest.Fork} return nil, UnsupportedForkError{subtest.Fork}
} }
block, _ := t.genesis(config).ToBlock() block, _, _ := t.genesis(config).ToBlock()
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb := MakePreState(db, t.json.Pre) statedb := MakePreState(db, t.json.Pre)
@ -147,7 +148,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD
if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) { if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) {
return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs)
} }
root, _ := statedb.CommitTo(db, config.IsEIP158(block.Number())) root, _ := statedb.Commit(config.IsEIP158(block.Number()))
if root != common.Hash(post.Root) { if root != common.Hash(post.Root) {
return statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) return statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root)
} }
@ -159,7 +160,7 @@ func (t *StateTest) gasLimit(subtest StateSubtest) uint64 {
} }
func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB { func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB {
sdb := state.NewDatabase(db, nil) sdb := state.NewDatabase(trie.NewDatabase(db))
statedb, _ := state.New(common.Hash{}, sdb) statedb, _ := state.New(common.Hash{}, sdb)
for addr, a := range accounts { for addr, a := range accounts {
statedb.SetCode(addr, a.Code) statedb.SetCode(addr, a.Code)
@ -170,7 +171,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB
} }
} }
// Commit and re-open to start with a clean state. // Commit and re-open to start with a clean state.
root, _ := statedb.CommitTo(db, false) root, _ := statedb.Commit(false)
statedb, _ = state.New(root, sdb) statedb, _ = state.New(root, sdb)
return statedb return statedb
} }

304
trie/database.go Normal file
View file

@ -0,0 +1,304 @@
// Copyright 2017 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 (
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
// secureKeyPrefix is the database key prefix used to store trie node preimages.
var secureKeyPrefix = []byte("secure-key-")
// secureKeyLength is the length of the above prefix + 32byte hash.
const secureKeyLength = 11 + 32
// DatabaseReader wraps the Get and Has method of a backing store for the trie.
type DatabaseReader interface {
// Get retrieves the value associated with key form the database.
Get(key []byte) (value []byte, err error)
// Has retrieves whether a key is present in the database.
Has(key []byte) (bool, error)
}
// DatabaseWriter wraps the Put method of a backing store for the trie.
type DatabaseWriter interface {
// Put stores the mapping key->value in the database. Implementations must not
// hold onto the value as the trie will reuse the slice across calls to Put.
Put(key, value []byte) error
}
// Database is an intermediate write layer between the trie data structures and
// the disk database. The aim is to accumulate trie writes in-memory and only
// periodically flush a couple tries to disk, garbage collecting the remainder.
type Database struct {
diskdb DatabaseReader // Persistent storage for matured trie nodes
nodes map[common.Hash][]byte // Cached data blocks of the trie nodes
parents map[common.Hash]int // Number of live nodes referencing a given one
children map[common.Hash]map[common.Hash]struct{} // Set of children referenced by given nodes
preimages map[common.Hash][]byte // Preimages of nodes from the secure trie
seckeybuf [secureKeyLength]byte // Ephemeral buffer for calculating preimage keys
gctime time.Duration // Time spent on garbage collection since last commit
gcnodes uint64 // Nodes garbage collected since last commit
gcsize common.StorageSize // Data storage garbage collected since last commit
size common.StorageSize // Storage size of the memory cache
lock sync.RWMutex
}
// NewDatabase creates a new trie database to store ephemeral trie content before
// its written out to disk or garbage collected.
func NewDatabase(diskdb DatabaseReader) *Database {
db := &Database{
diskdb: diskdb,
nodes: make(map[common.Hash][]byte),
parents: make(map[common.Hash]int),
children: make(map[common.Hash]map[common.Hash]struct{}),
preimages: make(map[common.Hash][]byte),
}
db.children[common.Hash{}] = make(map[common.Hash]struct{})
return db
}
// DiskDB retrieves the persistent storage backing the trie database.
func (db *Database) DiskDB() DatabaseReader {
return db.diskdb
}
// Insert writes a new trie node to the memory database if it's yet unknown. The
// method will make a copy of the slice.
func (db *Database) Insert(hash common.Hash, blob []byte) {
db.lock.Lock()
defer db.lock.Unlock()
db.insert(hash, blob)
}
// insert is the private locked version of Insert.
func (db *Database) insert(hash common.Hash, blob []byte) {
if _, ok := db.nodes[hash]; ok {
return
}
db.nodes[hash] = common.CopyBytes(blob)
db.children[hash] = make(map[common.Hash]struct{})
db.size += common.StorageSize(common.HashLength + len(blob))
}
// insertPreimage writes a new trie node pre-image to the memory database if it's
// yet unknown. The method will make a copy of the slice.
//
// Note, this method assumes that the database's lock is held!
func (db *Database) insertPreimage(hash common.Hash, preimage []byte) {
if _, ok := db.preimages[hash]; ok {
return
}
db.preimages[hash] = common.CopyBytes(preimage)
db.size += common.StorageSize(common.HashLength + len(preimage))
}
// Node retrieves a cached trie node from memory. If it cannot be found cached,
// the method queries the persistent database for the content.
func (db *Database) Node(hash common.Hash) ([]byte, error) {
// Retrieve the node from cache if available
db.lock.RLock()
blob := db.nodes[hash]
db.lock.RUnlock()
if blob != nil {
return blob, nil
}
// Content unavailable in memory, attempt to retrieve from disk
return db.diskdb.Get(hash[:])
}
// preimage retrieves a cached trie node pre-image from memory. If it cannot be
// found cached, the method queries the persistent database for the content.
func (db *Database) preimage(hash common.Hash) ([]byte, error) {
// Retrieve the node from cache if available
db.lock.RLock()
preimage := db.preimages[hash]
db.lock.RUnlock()
if preimage != nil {
return preimage, nil
}
// Content unavailable in memory, attempt to retrieve from disk
return db.diskdb.Get(db.secureKey(hash[:]))
}
// secureKey returns the database key for the preimage of key, as an ephemeral
// buffer. The caller must not hold onto the return value because it will become
// invalid on the next call.
func (db *Database) secureKey(key []byte) []byte {
buf := append(db.seckeybuf[:0], secureKeyPrefix...)
buf = append(buf, key...)
return buf
}
// Nodes retrieves the hashes of all the nodes cached within the memory database.
// This method is extremely expensive and should only be used to validate internal
// states in test code.
func (db *Database) Nodes() []common.Hash {
db.lock.RLock()
defer db.lock.RUnlock()
var hashes = make([]common.Hash, 0, len(db.nodes))
for hash := range db.nodes {
hashes = append(hashes, hash)
}
return hashes
}
// Preimages retrieves the hashes of all the node pre-images cached within the
// memory database. This method is extremely expensive and should only be used
// to validate internal states in test code.
func (db *Database) Preimages() []common.Hash {
db.lock.RLock()
defer db.lock.RUnlock()
var hashes = make([]common.Hash, 0, len(db.nodes))
for hash := range db.preimages {
hashes = append(hashes, hash)
}
return hashes
}
// Reference adds a new reference from a parent node to a child node.
func (db *Database) Reference(child common.Hash, parent common.Hash) {
db.lock.RLock()
defer db.lock.RUnlock()
db.reference(child, parent)
}
// reference is the private locked version of Reference.
func (db *Database) reference(child common.Hash, parent common.Hash) {
// If the node does not exist, it's a node pulled from disk, skip
if _, ok := db.nodes[child]; !ok {
return
}
db.parents[child]++
db.children[parent][child] = struct{}{}
}
// Dereference removes an existing reference from a parent node to a child node.
func (db *Database) Dereference(child common.Hash, parent common.Hash) {
db.lock.Lock()
defer db.lock.Unlock()
nodes, storage, start := len(db.nodes), db.size, time.Now()
db.dereference(child, parent)
db.gcnodes += uint64(nodes - len(db.nodes))
db.gcsize += storage - db.size
db.gctime += time.Since(start)
}
// dereference is the private locked version of Dereference.
func (db *Database) dereference(child common.Hash, parent common.Hash) {
// If the node does not exist, it's a previously comitted node.
blob, ok := db.nodes[child]
if !ok {
return
}
delete(db.children[parent], child)
db.parents[child]--
// If there are no more references to the child, delete it and cascade
if db.parents[child] == 0 {
for child := range db.children[child] {
db.dereference(child, child)
}
delete(db.nodes, child)
delete(db.parents, child)
delete(db.children, child)
db.size -= common.StorageSize(common.HashLength + len(blob))
}
}
// Commit iterates over all the children of a particular node, writes them out
// to disk, forcefully tearing down all references in both directions.
//
// As a side effect, all pre-images accumulated up to this point are also written.
func (db *Database) Commit(node common.Hash, writer DatabaseWriter) error {
db.lock.Lock()
defer db.lock.Unlock()
// Write out all the accumulated trie node preimages
for hash, preimage := range db.preimages {
if err := writer.Put(db.secureKey(hash[:]), preimage); err != nil {
log.Error("Failed to commit preimage from mempool", "err", err)
return err
}
db.size -= common.StorageSize(common.HashLength + len(preimage))
}
db.preimages = make(map[common.Hash][]byte)
// Write out the trie itself and dereference any flushed content
nodes, storage, start := len(db.nodes), db.size, time.Now()
if err := db.commit(node, writer); err != nil {
log.Error("Failed to commit trie from mempool", "err", err)
return err
}
log.Debug("Persistend trie from memory database", "nodes", nodes-len(db.nodes), "size", storage-db.size, "time", time.Since(start),
"gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.nodes), "livesize", db.size)
// Reset the garbage collection statistics
db.gcnodes, db.gcsize, db.gctime = 0, 0, 0
return nil
}
// commit is the private locked version of Commit.
func (db *Database) commit(node common.Hash, writer DatabaseWriter) error {
// If the node does not exist, it's a previously comitted node.
blob, ok := db.nodes[node]
if !ok {
return nil
}
for child := range db.children[node] {
if err := db.commit(child, writer); err != nil {
return err
}
}
if err := writer.Put(node[:], blob); err != nil {
return err
}
delete(db.nodes, node)
delete(db.parents, node)
delete(db.children, node)
db.size -= common.StorageSize(common.HashLength + len(blob))
return nil
}
// Size returns the current storage size of the memory cache in front of the
// persistent database layer.
func (db *Database) Size() common.StorageSize {
db.lock.RLock()
defer db.lock.RUnlock()
return db.size
}

View file

@ -19,7 +19,6 @@ package trie
import ( import (
"bytes" "bytes"
"hash" "hash"
"math/big"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -30,19 +29,21 @@ import (
type hasher struct { type hasher struct {
tmp *bytes.Buffer tmp *bytes.Buffer
sha hash.Hash sha hash.Hash
cachegen, cachelimit uint16 cachegen uint16
cachelimit uint16
onleaf LeafCallback
} }
// hashers live in a global pool. // hashers live in a global db.
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { New: func() interface{} {
return &hasher{tmp: new(bytes.Buffer), sha: sha3.NewKeccak256()} return &hasher{tmp: new(bytes.Buffer), sha: sha3.NewKeccak256()}
}, },
} }
func newHasher(cachegen, cachelimit uint16) *hasher { func newHasher(cachegen, cachelimit uint16, onleaf LeafCallback) *hasher {
h := hasherPool.Get().(*hasher) h := hasherPool.Get().(*hasher)
h.cachegen, h.cachelimit = cachegen, cachelimit h.cachegen, h.cachelimit, h.onleaf = cachegen, cachelimit, onleaf
return h return h
} }
@ -52,10 +53,10 @@ func returnHasherToPool(h *hasher) {
// hash collapses a node down into a hash node, also returning a copy of the // hash collapses a node down into a hash node, also returning a copy of the
// original node initialized with the computed hash to replace the original one. // original node initialized with the computed hash to replace the original one.
func (h *hasher) hash(n node, pool *NodePool, force bool) (node, node, error) { func (h *hasher) hash(n node, db *Database, force bool) (node, node, error) {
// If we're not storing the node, just hashing, use available cached data // If we're not storing the node, just hashing, use available cached data
if hash, dirty := n.cache(); hash != nil { if hash, dirty := n.cache(); hash != nil {
if pool == nil { if db == nil {
return hash, n, nil return hash, n, nil
} }
if n.canUnload(h.cachegen, h.cachelimit) { if n.canUnload(h.cachegen, h.cachelimit) {
@ -69,11 +70,11 @@ func (h *hasher) hash(n node, pool *NodePool, force bool) (node, node, error) {
} }
} }
// Trie not processed yet or needs storage, walk the children // Trie not processed yet or needs storage, walk the children
collapsed, cached, refs, err := h.hashChildren(n, pool) collapsed, cached, err := h.hashChildren(n, db)
if err != nil { if err != nil {
return hashNode{}, n, err return hashNode{}, n, err
} }
hashed, refs, err := h.store(collapsed, refs, pool, force) hashed, err := h.store(collapsed, db, force)
if err != nil { if err != nil {
return hashNode{}, n, err return hashNode{}, n, err
} }
@ -84,12 +85,12 @@ func (h *hasher) hash(n node, pool *NodePool, force bool) (node, node, error) {
switch cn := cached.(type) { switch cn := cached.(type) {
case *shortNode: case *shortNode:
cn.flags.hash = cachedHash cn.flags.hash = cachedHash
if pool != nil { if db != nil {
cn.flags.dirty = false cn.flags.dirty = false
} }
case *fullNode: case *fullNode:
cn.flags.hash = cachedHash cn.flags.hash = cachedHash
if pool != nil { if db != nil {
cn.flags.dirty = false cn.flags.dirty = false
} }
} }
@ -99,7 +100,7 @@ func (h *hasher) hash(n node, pool *NodePool, force bool) (node, node, error) {
// hashChildren replaces the children of a node with their hashes if the encoded // hashChildren replaces the children of a node with their hashes if the encoded
// size of the child is larger than a hash, returning the collapsed node as well // size of the child is larger than a hash, returning the collapsed node as well
// as a replacement for the original node with the child hashes cached in. // as a replacement for the original node with the child hashes cached in.
func (h *hasher) hashChildren(original node, pool *NodePool) (node, node, []common.Hash, error) { func (h *hasher) hashChildren(original node, db *Database) (node, node, error) {
var err error var err error
switch n := original.(type) { switch n := original.(type) {
@ -110,15 +111,15 @@ func (h *hasher) hashChildren(original node, pool *NodePool) (node, node, []comm
cached.Key = common.CopyBytes(n.Key) cached.Key = common.CopyBytes(n.Key)
if _, ok := n.Val.(valueNode); !ok { if _, ok := n.Val.(valueNode); !ok {
collapsed.Val, cached.Val, err = h.hash(n.Val, pool, false) collapsed.Val, cached.Val, err = h.hash(n.Val, db, false)
if err != nil { if err != nil {
return original, original, nil, err return original, original, err
} }
} }
if collapsed.Val == nil { if collapsed.Val == nil {
collapsed.Val = valueNode(nil) // Ensure that nil children are encoded as empty strings. collapsed.Val = valueNode(nil) // Ensure that nil children are encoded as empty strings.
} }
return collapsed, cached, h.externals(collapsed.Val), nil return collapsed, cached, nil
case *fullNode: case *fullNode:
// Hash the full node's children, caching the newly hashed subtrees // Hash the full node's children, caching the newly hashed subtrees
@ -126,9 +127,9 @@ func (h *hasher) hashChildren(original node, pool *NodePool) (node, node, []comm
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
if n.Children[i] != nil { if n.Children[i] != nil {
collapsed.Children[i], cached.Children[i], err = h.hash(n.Children[i], pool, false) collapsed.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false)
if err != nil { if err != nil {
return original, original, nil, err return original, original, err
} }
} else { } else {
collapsed.Children[i] = valueNode(nil) // Ensure that nil children are encoded as empty strings. collapsed.Children[i] = valueNode(nil) // Ensure that nil children are encoded as empty strings.
@ -138,65 +139,29 @@ func (h *hasher) hashChildren(original node, pool *NodePool) (node, node, []comm
if collapsed.Children[16] == nil { if collapsed.Children[16] == nil {
collapsed.Children[16] = valueNode(nil) collapsed.Children[16] = valueNode(nil)
} }
var refs []common.Hash return collapsed, cached, nil
for i := 0; i < 16; i++ {
refs = append(refs, h.externals(collapsed.Children[i])...)
}
return collapsed, cached, refs, nil
default: default:
// Value and hash nodes don't have children so they're left as were // Value and hash nodes don't have children so they're left as were
return n, original, h.externals(n), nil return n, original, nil
} }
} }
// externals returns any external nodes referenced by a particular node. The only
// current case for it is when an account trie references its storage trie.
func (h *hasher) externals(n node) []common.Hash {
// Only value nodes can reference external nodes
val, ok := n.(valueNode)
if !ok {
return nil
}
// Account nodes have very specific sizes, discard anything else
// TODO(karalabe): Seriously? Dafuq man?!
if size := len(val); size < 70 || size > 102 {
return nil
}
// Only account nodes can reference external storage tries
var account struct {
Nonce uint64
Balance *big.Int
Root common.Hash
CodeHash []byte
}
if err := rlp.DecodeBytes(val, &account); err != nil {
//fmt.Printf(".")
return nil
}
// Empty tries are not referenced
if account.Root == emptyState {
return nil
}
return []common.Hash{account.Root}
}
// store hashes the node n and if we have a storage layer specified, it writes // store hashes the node n and if we have a storage layer specified, it writes
// the key/value pair to it and tracks any node->child references as well as any // the key/value pair to it and tracks any node->child references as well as any
// node->external trie references. // node->external trie references.
func (h *hasher) store(n node, refs []common.Hash, pool *NodePool, force bool) (node, []common.Hash, error) { func (h *hasher) store(n node, db *Database, force bool) (node, error) {
// Don't store hashes or empty nodes. // Don't store hashes or empty nodes.
if _, isHash := n.(hashNode); n == nil || isHash { if _, isHash := n.(hashNode); n == nil || isHash {
return n, refs, nil return n, nil
} }
// Generate the RLP encoding of the node // Generate the RLP encoding of the node
h.tmp.Reset() h.tmp.Reset()
if err := rlp.Encode(h.tmp, n); err != nil { if err := rlp.Encode(h.tmp, n); err != nil {
panic("encode error: " + err.Error()) panic("encode error: " + err.Error())
} }
if h.tmp.Len() < 32 && !force { if h.tmp.Len() < 32 && !force {
return n, refs, nil // Nodes smaller than 32 bytes are stored inside their parent return n, nil // Nodes smaller than 32 bytes are stored inside their parent
} }
// Larger nodes are replaced by their hash and stored in the database. // Larger nodes are replaced by their hash and stored in the database.
hash, _ := n.cache() hash, _ := n.cache()
@ -205,31 +170,43 @@ func (h *hasher) store(n node, refs []common.Hash, pool *NodePool, force bool) (
h.sha.Write(h.tmp.Bytes()) h.sha.Write(h.tmp.Bytes())
hash = hashNode(h.sha.Sum(nil)) hash = hashNode(h.sha.Sum(nil))
} }
if pool != nil { if db != nil {
// We are pooling the trie nodes into an intermediate memory cache // We are pooling the trie nodes into an intermediate memory cache
pool.lock.Lock() db.lock.Lock()
defer pool.lock.Unlock()
hash := common.BytesToHash(hash) hash := common.BytesToHash(hash)
pool.insert(hash, h.tmp.Bytes()) db.insert(hash, h.tmp.Bytes())
// Track all direct parent->child node references // Track all direct parent->child node references
switch n := n.(type) { switch n := n.(type) {
case *shortNode: case *shortNode:
if child, ok := n.Val.(hashNode); ok { if child, ok := n.Val.(hashNode); ok {
pool.reference(common.BytesToHash(child), hash) db.reference(common.BytesToHash(child), hash)
} }
case *fullNode: case *fullNode:
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
if child, ok := n.Children[i].(hashNode); ok { if child, ok := n.Children[i].(hashNode); ok {
pool.reference(common.BytesToHash(child), hash) db.reference(common.BytesToHash(child), hash)
} }
} }
} }
db.lock.Unlock()
// Track external references from account->storage trie // Track external references from account->storage trie
for _, ext := range refs { if h.onleaf != nil {
pool.reference(ext, hash) switch n := n.(type) {
case *shortNode:
if child, ok := n.Val.(valueNode); ok {
h.onleaf(child, hash)
}
case *fullNode:
for i := 0; i < 16; i++ {
if child, ok := n.Children[i].(valueNode); ok {
h.onleaf(child, hash)
} }
} }
return hash, nil, nil }
}
}
return hash, nil
} }

View file

@ -42,7 +42,7 @@ func TestIterator(t *testing.T) {
all[val.k] = val.v all[val.k] = val.v
trie.Update([]byte(val.k), []byte(val.v)) trie.Update([]byte(val.k), []byte(val.v))
} }
trie.Commit() trie.Commit(nil)
found := make(map[string]string) found := make(map[string]string)
it := NewIterator(trie.NodeIterator(nil)) it := NewIterator(trie.NodeIterator(nil))
@ -109,11 +109,16 @@ func TestNodeIteratorCoverage(t *testing.T) {
} }
// Cross check the hashes and the database itself // Cross check the hashes and the database itself
for hash := range hashes { for hash := range hashes {
if _, err := db.Get(hash.Bytes()); err != nil { if _, err := db.Node(hash); err != nil {
t.Errorf("failed to retrieve reported node %x: %v", hash, err) t.Errorf("failed to retrieve reported node %x: %v", hash, err)
} }
} }
for _, key := range db.(*ethdb.MemDatabase).Keys() { for _, hash := range db.Nodes() {
if _, ok := hashes[hash]; !ok {
t.Errorf("state entry not reported %x", hash)
}
}
for _, key := range db.diskdb.(*ethdb.MemDatabase).Keys() {
if _, ok := hashes[common.BytesToHash(key)]; !ok { if _, ok := hashes[common.BytesToHash(key)]; !ok {
t.Errorf("state entry not reported %x", key) t.Errorf("state entry not reported %x", key)
} }
@ -191,13 +196,13 @@ func TestDifferenceIterator(t *testing.T) {
for _, val := range testdata1 { for _, val := range testdata1 {
triea.Update([]byte(val.k), []byte(val.v)) triea.Update([]byte(val.k), []byte(val.v))
} }
triea.Commit() triea.Commit(nil)
trieb := newEmpty() trieb := newEmpty()
for _, val := range testdata2 { for _, val := range testdata2 {
trieb.Update([]byte(val.k), []byte(val.v)) trieb.Update([]byte(val.k), []byte(val.v))
} }
trieb.Commit() trieb.Commit(nil)
found := make(map[string]string) found := make(map[string]string)
di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil)) di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil))
@ -227,13 +232,13 @@ func TestUnionIterator(t *testing.T) {
for _, val := range testdata1 { for _, val := range testdata1 {
triea.Update([]byte(val.k), []byte(val.v)) triea.Update([]byte(val.k), []byte(val.v))
} }
triea.Commit() triea.Commit(nil)
trieb := newEmpty() trieb := newEmpty()
for _, val := range testdata2 { for _, val := range testdata2 {
trieb.Update([]byte(val.k), []byte(val.v)) trieb.Update([]byte(val.k), []byte(val.v))
} }
trieb.Commit() trieb.Commit(nil)
di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)}) di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)})
it := NewIterator(di) it := NewIterator(di)
@ -278,35 +283,35 @@ func TestIteratorNoDups(t *testing.T) {
} }
// This test checks that nodeIterator.Next can be retried after inserting missing trie nodes. // This test checks that nodeIterator.Next can be retried after inserting missing trie nodes.
func TestIteratorContinueAfterErrorDirect(t *testing.T) { testIteratorContinueAfterError(t, false) } func TestIteratorContinueAfterErrorDisk(t *testing.T) { testIteratorContinueAfterError(t, false) }
func TestIteratorContinueAfterErrorPooled(t *testing.T) { testIteratorContinueAfterError(t, true) } func TestIteratorContinueAfterErrorMemonly(t *testing.T) { testIteratorContinueAfterError(t, true) }
func testIteratorContinueAfterError(t *testing.T, pooled bool) { func testIteratorContinueAfterError(t *testing.T, memonly bool) {
var pool *NodePool diskdb, _ := ethdb.NewMemDatabase()
if pooled { triedb := NewDatabase(diskdb)
pool = NewNodePool()
}
db, _ := ethdb.NewMemDatabase()
tr, _ := New(common.Hash{}, db, pool) tr, _ := New(common.Hash{}, triedb)
for _, val := range testdata1 { for _, val := range testdata1 {
tr.Update([]byte(val.k), []byte(val.v)) tr.Update([]byte(val.k), []byte(val.v))
} }
tr.Commit() tr.Commit(nil)
if !memonly {
triedb.Commit(tr.Hash(), diskdb)
}
wantNodeCount := checkIteratorNoDups(t, tr.NodeIterator(nil), nil) wantNodeCount := checkIteratorNoDups(t, tr.NodeIterator(nil), nil)
var ( var (
dbKeys [][]byte diskKeys [][]byte
poolKeys []common.Hash memKeys []common.Hash
) )
if pooled { if memonly {
poolKeys = pool.Nodes() memKeys = triedb.Nodes()
} else { } else {
dbKeys = db.Keys() diskKeys = diskdb.Keys()
} }
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
// Create trie that will load all nodes from DB. // Create trie that will load all nodes from DB.
tr, _ := New(tr.Hash(), db, pool) tr, _ := New(tr.Hash(), triedb)
// Remove a random node from the database. It can't be the root node // Remove a random node from the database. It can't be the root node
// because that one is already loaded. // because that one is already loaded.
@ -315,21 +320,21 @@ func testIteratorContinueAfterError(t *testing.T, pooled bool) {
rval []byte rval []byte
) )
for { for {
if pooled { if memonly {
rkey = poolKeys[rand.Intn(len(poolKeys))] rkey = memKeys[rand.Intn(len(memKeys))]
} else { } else {
copy(rkey[:], dbKeys[rand.Intn(len(dbKeys))]) copy(rkey[:], diskKeys[rand.Intn(len(diskKeys))])
} }
if rkey != tr.Hash() { if rkey != tr.Hash() {
break break
} }
} }
if pooled { if memonly {
rval, _ = pool.cache[rkey] rval, _ = triedb.nodes[rkey]
delete(pool.cache, rkey) delete(triedb.nodes, rkey)
} else { } else {
rval, _ = db.Get(rkey[:]) rval, _ = diskdb.Get(rkey[:])
db.Delete(rkey[:]) diskdb.Delete(rkey[:])
} }
// Iterate until the error is hit. // Iterate until the error is hit.
seen := make(map[string]bool) seen := make(map[string]bool)
@ -341,10 +346,10 @@ func testIteratorContinueAfterError(t *testing.T, pooled bool) {
} }
// Add the node back and continue iteration. // Add the node back and continue iteration.
if pooled { if memonly {
pool.cache[rkey] = rval triedb.nodes[rkey] = rval
} else { } else {
db.Put(rkey[:], rval) diskdb.Put(rkey[:], rval)
} }
checkIteratorNoDups(t, it, seen) checkIteratorNoDups(t, it, seen)
if it.Error() != nil { if it.Error() != nil {
@ -359,40 +364,39 @@ func testIteratorContinueAfterError(t *testing.T, pooled bool) {
// Similar to the test above, this one checks that failure to create nodeIterator at a // Similar to the test above, this one checks that failure to create nodeIterator at a
// certain key prefix behaves correctly when Next is called. The expectation is that Next // certain key prefix behaves correctly when Next is called. The expectation is that Next
// should retry seeking before returning true for the first time. // should retry seeking before returning true for the first time.
func TestIteratorContinueAfterSeekErrorDirect(t *testing.T) { func TestIteratorContinueAfterSeekErrorDisk(t *testing.T) {
testIteratorContinueAfterSeekError(t, false) testIteratorContinueAfterSeekError(t, false)
} }
func TestIteratorContinueAfterSeekErrorPooled(t *testing.T) { func TestIteratorContinueAfterSeekErrorMemonly(t *testing.T) {
testIteratorContinueAfterSeekError(t, true) testIteratorContinueAfterSeekError(t, true)
} }
func testIteratorContinueAfterSeekError(t *testing.T, pooled bool) { func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) {
// Commit test trie to db, then remove the node containing "bars". // Commit test trie to db, then remove the node containing "bars".
var pool *NodePool diskdb, _ := ethdb.NewMemDatabase()
if pooled { triedb := NewDatabase(diskdb)
pool = NewNodePool()
}
db, _ := ethdb.NewMemDatabase()
ctr, _ := New(common.Hash{}, db, pool) ctr, _ := New(common.Hash{}, triedb)
for _, val := range testdata1 { for _, val := range testdata1 {
ctr.Update([]byte(val.k), []byte(val.v)) ctr.Update([]byte(val.k), []byte(val.v))
} }
root, _ := ctr.Commit() root, _ := ctr.Commit(nil)
if !memonly {
triedb.Commit(root, diskdb)
}
barNodeHash := common.HexToHash("05041990364eb72fcb1127652ce40d8bab765f2bfe53225b1170d276cc101c2e") barNodeHash := common.HexToHash("05041990364eb72fcb1127652ce40d8bab765f2bfe53225b1170d276cc101c2e")
var barNodeBlob []byte var barNodeBlob []byte
if pooled { if memonly {
barNodeBlob = pool.cache[barNodeHash] barNodeBlob = triedb.nodes[barNodeHash]
delete(pool.cache, barNodeHash) delete(triedb.nodes, barNodeHash)
} else { } else {
barNodeBlob, _ = db.Get(barNodeHash[:]) barNodeBlob, _ = diskdb.Get(barNodeHash[:])
db.Delete(barNodeHash[:]) diskdb.Delete(barNodeHash[:])
} }
// Create a new iterator that seeks to "bars". Seeking can't proceed because // Create a new iterator that seeks to "bars". Seeking can't proceed because
// the node is missing. // the node is missing.
tr, _ := New(root, db, pool) tr, _ := New(root, triedb)
it := tr.NodeIterator([]byte("bars")) it := tr.NodeIterator([]byte("bars"))
missing, ok := it.Error().(*MissingNodeError) missing, ok := it.Error().(*MissingNodeError)
if !ok { if !ok {
@ -401,10 +405,10 @@ func testIteratorContinueAfterSeekError(t *testing.T, pooled bool) {
t.Fatal("wrong node missing") t.Fatal("wrong node missing")
} }
// Reinsert the missing node. // Reinsert the missing node.
if pooled { if memonly {
pool.cache[barNodeHash] = barNodeBlob triedb.nodes[barNodeHash] = barNodeBlob
} else { } else {
db.Put(barNodeHash[:], barNodeBlob) diskdb.Put(barNodeHash[:], barNodeBlob)
} }
// Check that iteration produces the right set of values. // Check that iteration produces the right set of values.
if err := checkIteratorOrder(testdata1[2:], NewIterator(it)); err != nil { if err := checkIteratorOrder(testdata1[2:], NewIterator(it)); err != nil {

View file

@ -1,195 +0,0 @@
// Copyright 2017 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 (
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
// NodePool is an intermediate write layer between the trie data structures and
// the disk database. The aim is to accumulate trie writes in-memory and only
// periodically flush a couple tries to disk, garbage collecting the remainder.
type NodePool struct {
cache map[common.Hash][]byte // Cached data blocks of the trie nodes
parents map[common.Hash]int // Number of live nodes referencing a given one
children map[common.Hash]map[common.Hash]struct{} // Set of children referenced by given nodes
gctime time.Duration // Time spent on garbage collection since last commit
gcnodes uint64 // Nodes garbage collected since last commit
gcsize common.StorageSize // Data storage garbage collected since last commit
size common.StorageSize // Storage size of the memory pool
lock sync.RWMutex
}
// NewNodePool creates a new memory pool to store ephemeral trie nodes before they
// are written out to disk or garbage collected.
func NewNodePool() *NodePool {
pool := &NodePool{
cache: make(map[common.Hash][]byte),
parents: make(map[common.Hash]int),
children: make(map[common.Hash]map[common.Hash]struct{}),
}
pool.children[common.Hash{}] = make(map[common.Hash]struct{})
return pool
}
// insert writes a new trie node to the memory pool if it's yet unknown. The pool
// will make a copy of the slice.
//
// Note, this method assumes that the pool's lock is held!
func (pool *NodePool) insert(hash common.Hash, blob []byte) {
if _, ok := pool.cache[hash]; ok {
return
}
pool.cache[hash] = common.CopyBytes(blob)
pool.children[hash] = make(map[common.Hash]struct{})
pool.size += common.StorageSize(common.HashLength + len(blob))
}
// Fetch retrieves a cached trie node from memory, or returns nil if the pool
// does not have this particular piece of data.
func (pool *NodePool) Fetch(hash common.Hash) []byte {
pool.lock.RLock()
defer pool.lock.RUnlock()
return pool.cache[hash]
}
// Nodes retrieves the hashes of all the nodes cached within the node pool. This
// method is extremely expensive and should only be used in test code to validate
// internal states.
func (pool *NodePool) Nodes() []common.Hash {
pool.lock.RLock()
defer pool.lock.RUnlock()
var hashes = make([]common.Hash, 0, len(pool.cache))
for hash := range pool.cache {
hashes = append(hashes, hash)
}
return hashes
}
// Reference adds a new reference from parent to node.
func (pool *NodePool) Reference(node common.Hash, parent common.Hash) {
pool.lock.RLock()
defer pool.lock.RUnlock()
pool.reference(node, parent)
}
// reference is the private locked version of Reference.
func (pool *NodePool) reference(node common.Hash, parent common.Hash) {
// If the node does not exist, it's a node pulled from disk, skip
if _, ok := pool.cache[node]; !ok {
return
}
pool.parents[node]++
pool.children[parent][node] = struct{}{}
}
// Dereference removes an existing reference from parent to node.
func (pool *NodePool) Dereference(node common.Hash, parent common.Hash) {
pool.lock.Lock()
defer pool.lock.Unlock()
nodes, storage, start := len(pool.cache), pool.size, time.Now()
pool.dereference(node, parent)
pool.gcnodes += uint64(nodes - len(pool.cache))
pool.gcsize += storage - pool.size
pool.gctime += time.Since(start)
}
// dereference is the private locked version of Dereference.
func (pool *NodePool) dereference(node common.Hash, parent common.Hash) {
// If the node does not exist, it's a previously comitted node.
blob, ok := pool.cache[node]
if !ok {
return
}
delete(pool.children[parent], node)
pool.parents[node]--
// If there are no more references to the child, delete it and cascade
if pool.parents[node] == 0 {
for child := range pool.children[node] {
pool.dereference(child, node)
}
delete(pool.cache, node)
delete(pool.parents, node)
delete(pool.children, node)
pool.size -= common.StorageSize(common.HashLength + len(blob))
}
}
// Commit iterates over all the children of a particular node, writes them out
// to disk, forcefully tearing down all references in both directions.
func (pool *NodePool) Commit(node common.Hash, db DatabaseWriter) error {
pool.lock.Lock()
defer pool.lock.Unlock()
nodes, storage, start := len(pool.cache), pool.size, time.Now()
if err := pool.commit(node, db); err != nil {
log.Error("Failed to commit trie from mempool", "err", err)
return err
}
log.Debug("Committed trie from memory pool", "nodes", nodes-len(pool.cache), "size", storage-pool.size, "time", time.Since(start),
"gcnodes", pool.gcnodes, "gcsize", pool.gcsize, "gctime", pool.gctime, "livenodes", len(pool.cache), "livesize", pool.size)
// Reset the garbage collection statistics
pool.gcnodes, pool.gcsize, pool.gctime = 0, 0, 0
// Sanity check that we don't have dangling nodes in the pool (missing refs)
for hash, refs := range pool.parents {
if refs == 0 {
log.Warn("dangling node in mempool", "hash", hash)
break
}
}
return nil
}
// commit is the private locked version of Commit.
func (pool *NodePool) commit(node common.Hash, db DatabaseWriter) error {
// If the node does not exist, it's a previously comitted node.
blob, ok := pool.cache[node]
if !ok {
return nil
}
for child := range pool.children[node] {
if err := pool.commit(child, db); err != nil {
return err
}
}
if err := db.Put(node[:], blob); err != nil {
return err
}
delete(pool.cache, node)
delete(pool.parents, node)
delete(pool.children, node)
pool.size -= common.StorageSize(common.HashLength + len(blob))
return nil
}

View file

@ -26,15 +26,13 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
// Prove constructs a merkle proof for key. The result contains all // Prove constructs a merkle proof for key. The result contains all encoded nodes
// encoded nodes on the path to the value at key. The value itself is // on the path to the value at key. The value itself is also included in the last
// also included in the last node and can be retrieved by verifying // node and can be retrieved by verifying the proof.
// the proof.
// //
// If the trie does not contain a value for key, the returned proof // If the trie does not contain a value for key, the returned proof contains all
// contains all nodes of the longest existing prefix of the key // nodes of the longest existing prefix of the key (at least the root node), ending
// (at least the root node), ending with the node that proves the // with the node that proves the absence of the key.
// absence of the key.
func (t *Trie) Prove(key []byte, fromLevel uint, proofDb DatabaseWriter) error { func (t *Trie) Prove(key []byte, fromLevel uint, proofDb DatabaseWriter) error {
// Collect all nodes on the path to key. // Collect all nodes on the path to key.
key = keybytesToHex(key) key = keybytesToHex(key)
@ -66,12 +64,12 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb DatabaseWriter) error {
panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) panic(fmt.Sprintf("%T: invalid node: %v", tn, tn))
} }
} }
hasher := newHasher(0, 0) hasher := newHasher(0, 0, nil)
for i, n := range nodes { for i, n := range nodes {
// Don't bother checking for errors here since hasher panics // Don't bother checking for errors here since hasher panics
// if encoding doesn't work and we're not writing to any database. // if encoding doesn't work and we're not writing to any database.
n, _, _, _ = hasher.hashChildren(n, nil) n, _, _ = hasher.hashChildren(n, nil)
hn, _, _ := hasher.store(n, nil, nil, false) hn, _ := hasher.store(n, nil, false)
if hash, ok := hn.(hashNode); ok || i == 0 { if hash, ok := hn.(hashNode); ok || i == 0 {
// If the node's database encoding is a hash (or is the // If the node's database encoding is a hash (or is the
// root node), it becomes a proof element. // root node), it becomes a proof element.
@ -89,19 +87,18 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb DatabaseWriter) error {
return nil return nil
} }
// VerifyProof checks merkle proofs. The given proof must contain the // VerifyProof checks merkle proofs. The given proof must contain the value for
// value for key in a trie with the given root hash. VerifyProof // key in a trie with the given root hash. VerifyProof returns an error if the
// returns an error if the proof contains invalid trie nodes or the // proof contains invalid trie nodes or the wrong value.
// wrong value.
func VerifyProof(rootHash common.Hash, key []byte, proofDb DatabaseReader) (value []byte, err error, nodes int) { func VerifyProof(rootHash common.Hash, key []byte, proofDb DatabaseReader) (value []byte, err error, nodes int) {
key = keybytesToHex(key) key = keybytesToHex(key)
wantHash := rootHash[:] wantHash := rootHash
for i := 0; ; i++ { for i := 0; ; i++ {
buf, _ := proofDb.Get(wantHash) buf, _ := proofDb.Get(wantHash[:])
if buf == nil { if buf == nil {
return nil, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash[:]), i return nil, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash), i
} }
n, err := decodeNode(wantHash, buf, 0) n, err := decodeNode(wantHash[:], buf, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("bad proof node %d: %v", i, err), i return nil, fmt.Errorf("bad proof node %d: %v", i, err), i
} }
@ -112,7 +109,7 @@ func VerifyProof(rootHash common.Hash, key []byte, proofDb DatabaseReader) (valu
return nil, nil, i return nil, nil, i
case hashNode: case hashNode:
key = keyrest key = keyrest
wantHash = cld copy(wantHash[:], cld)
case valueNode: case valueNode:
return cld, nil, i + 1 return cld, nil, i + 1
} }

View file

@ -23,10 +23,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
var secureKeyPrefix = []byte("secure-key-")
const secureKeyLength = 11 + 32 // Length of the above prefix + 32byte hash
// SecureTrie wraps a trie with key hashing. In a secure trie, all // SecureTrie wraps a trie with key hashing. In a secure trie, all
// access operations hash the key using keccak256. This prevents // access operations hash the key using keccak256. This prevents
// calling code from creating long chains of nodes that // calling code from creating long chains of nodes that
@ -39,8 +35,7 @@ const secureKeyLength = 11 + 32 // Length of the above prefix + 32byte hash
// SecureTrie is not safe for concurrent use. // SecureTrie is not safe for concurrent use.
type SecureTrie struct { type SecureTrie struct {
trie Trie trie Trie
hashKeyBuf [secureKeyLength]byte hashKeyBuf [common.HashLength]byte
secKeyBuf [200]byte
secKeyCache map[string][]byte secKeyCache map[string][]byte
secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch
} }
@ -56,11 +51,11 @@ type SecureTrie struct {
// Loaded nodes are kept around until their 'cache generation' expires. // Loaded nodes are kept around until their 'cache generation' expires.
// A new cache generation is created by each call to Commit. // A new cache generation is created by each call to Commit.
// cachelimit sets the number of past cache generations to keep. // cachelimit sets the number of past cache generations to keep.
func NewSecure(root common.Hash, db Database, pool *NodePool, cachelimit uint16) (*SecureTrie, error) { func NewSecure(root common.Hash, db *Database, cachelimit uint16) (*SecureTrie, error) {
if db == nil { if db == nil {
panic("NewSecure called with nil database") panic("trie.NewSecure called without a database")
} }
trie, err := New(root, db, pool) trie, err := New(root, db)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -136,7 +131,7 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte {
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok { if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
return key return key
} }
key, _ := t.trie.db.Get(t.secKey(shaKey)) key, _ := t.trie.db.preimage(common.BytesToHash(shaKey))
return key return key
} }
@ -145,8 +140,19 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte {
// //
// Committing flushes nodes from memory. Subsequent Get calls will load nodes // Committing flushes nodes from memory. Subsequent Get calls will load nodes
// from the database. // from the database.
func (t *SecureTrie) Commit() (root common.Hash, err error) { func (t *SecureTrie) Commit(onleaf LeafCallback) (root common.Hash, err error) {
return t.CommitTo(t.trie.db) // Write all the pre-images to the actual disk database
if len(t.getSecKeyCache()) > 0 {
t.trie.db.lock.Lock()
for hk, key := range t.secKeyCache {
t.trie.db.insertPreimage(common.BytesToHash([]byte(hk)), key)
}
t.trie.db.lock.Unlock()
t.secKeyCache = make(map[string][]byte)
}
// Commit the trie to its intermediate node database
return t.trie.Commit(onleaf)
} }
func (t *SecureTrie) Hash() common.Hash { func (t *SecureTrie) Hash() common.Hash {
@ -168,38 +174,11 @@ func (t *SecureTrie) NodeIterator(start []byte) NodeIterator {
return t.trie.NodeIterator(start) return t.trie.NodeIterator(start)
} }
// CommitTo writes all nodes and the secure hash pre-images to the given database.
// Nodes are stored with their sha3 hash as the key.
//
// Committing flushes nodes from memory. Subsequent Get calls will load nodes from
// the trie's database. Calling code must ensure that the changes made to db are
// written back to the trie's attached database before using the trie.
func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) {
if len(t.getSecKeyCache()) > 0 {
for hk, key := range t.secKeyCache {
if err := db.Put(t.secKey([]byte(hk)), key); err != nil {
return common.Hash{}, err
}
}
t.secKeyCache = make(map[string][]byte)
}
return t.trie.CommitTo(db)
}
// secKey returns the database key for the preimage of key, as an ephemeral buffer.
// The caller must not hold onto the return value because it will become
// invalid on the next call to hashKey or secKey.
func (t *SecureTrie) secKey(key []byte) []byte {
buf := append(t.secKeyBuf[:0], secureKeyPrefix...)
buf = append(buf, key...)
return buf
}
// hashKey returns the hash of key as an ephemeral buffer. // hashKey returns the hash of key as an ephemeral buffer.
// The caller must not hold onto the return value because it will become // The caller must not hold onto the return value because it will become
// invalid on the next call to hashKey or secKey. // invalid on the next call to hashKey or secKey.
func (t *SecureTrie) hashKey(key []byte) []byte { func (t *SecureTrie) hashKey(key []byte) []byte {
h := newHasher(0, 0) h := newHasher(0, 0, nil)
h.sha.Reset() h.sha.Reset()
h.sha.Write(key) h.sha.Write(key)
buf := h.sha.Sum(t.hashKeyBuf[:0]) buf := h.sha.Sum(t.hashKeyBuf[:0])

View file

@ -28,16 +28,20 @@ import (
) )
func newEmptySecure() *SecureTrie { func newEmptySecure() *SecureTrie {
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
trie, _ := NewSecure(common.Hash{}, db, NewNodePool(), 0) triedb := NewDatabase(diskdb)
trie, _ := NewSecure(common.Hash{}, triedb, 0)
return trie return trie
} }
// makeTestSecureTrie creates a large enough secure trie for testing. // makeTestSecureTrie creates a large enough secure trie for testing.
func makeTestSecureTrie() (ethdb.Database, *SecureTrie, map[string][]byte) { func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) {
// Create an empty trie // Create an empty trie
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
trie, _ := NewSecure(common.Hash{}, db, NewNodePool(), 0) triedb := NewDatabase(diskdb)
trie, _ := NewSecure(common.Hash{}, triedb, 0)
// Fill it with some arbitrary data // Fill it with some arbitrary data
content := make(map[string][]byte) content := make(map[string][]byte)
@ -58,10 +62,10 @@ func makeTestSecureTrie() (ethdb.Database, *SecureTrie, map[string][]byte) {
trie.Update(key, val) trie.Update(key, val)
} }
} }
trie.Commit() trie.Commit(nil)
// Return the generated trie // Return the generated trie
return db, trie, content return triedb, trie, content
} }
func TestSecureDelete(t *testing.T) { func TestSecureDelete(t *testing.T) {
@ -137,7 +141,7 @@ func TestSecureTrieConcurrency(t *testing.T) {
tries[index].Update(key, val) tries[index].Update(key, val)
} }
} }
tries[index].Commit() tries[index].Commit(nil)
}(i) }(i)
} }
// Wait for all threads to finish // Wait for all threads to finish

View file

@ -42,7 +42,7 @@ type request struct {
depth int // Depth level within the trie the node is located to prioritise DFS depth int // Depth level within the trie the node is located to prioritise DFS
deps int // Number of dependencies before allowed to commit this node deps int // Number of dependencies before allowed to commit this node
callback TrieSyncLeafCallback // Callback to invoke if a leaf node it reached on this branch callback LeafCallback // Callback to invoke if a leaf node it reached on this branch
} }
// SyncResult is a simple list to return missing nodes along with their request // SyncResult is a simple list to return missing nodes along with their request
@ -67,11 +67,6 @@ func newSyncMemBatch() *syncMemBatch {
} }
} }
// TrieSyncLeafCallback is a callback type invoked when a trie sync reaches a
// leaf node. It's used by state syncing to check if the leaf node requires some
// further data syncing.
type TrieSyncLeafCallback func(leaf []byte, parent common.Hash) error
// TrieSync is the main state trie synchronisation scheduler, which provides yet // TrieSync is the main state trie synchronisation scheduler, which provides yet
// unknown trie hashes to retrieve, accepts node data associated with said hashes // unknown trie hashes to retrieve, accepts node data associated with said hashes
// and reconstructs the trie step by step until all is done. // and reconstructs the trie step by step until all is done.
@ -83,7 +78,7 @@ type TrieSync struct {
} }
// NewTrieSync creates a new trie data download scheduler. // NewTrieSync creates a new trie data download scheduler.
func NewTrieSync(root common.Hash, database DatabaseReader, callback TrieSyncLeafCallback) *TrieSync { func NewTrieSync(root common.Hash, database DatabaseReader, callback LeafCallback) *TrieSync {
ts := &TrieSync{ ts := &TrieSync{
database: database, database: database,
membatch: newSyncMemBatch(), membatch: newSyncMemBatch(),
@ -95,7 +90,7 @@ func NewTrieSync(root common.Hash, database DatabaseReader, callback TrieSyncLea
} }
// AddSubTrie registers a new trie to the sync code, rooted at the designated parent. // AddSubTrie registers a new trie to the sync code, rooted at the designated parent.
func (s *TrieSync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callback TrieSyncLeafCallback) { func (s *TrieSync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callback LeafCallback) {
// Short circuit if the trie is empty or already known // Short circuit if the trie is empty or already known
if root == emptyRoot { if root == emptyRoot {
return return

View file

@ -25,10 +25,11 @@ import (
) )
// makeTestTrie create a sample test trie to test node-wise reconstruction. // makeTestTrie create a sample test trie to test node-wise reconstruction.
func makeTestTrie() (ethdb.Database, *Trie, map[string][]byte) { func makeTestTrie() (*Database, *Trie, map[string][]byte) {
// Create an empty trie // Create an empty trie
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
trie, _ := New(common.Hash{}, db, nil) triedb := NewDatabase(diskdb)
trie, _ := New(common.Hash{}, triedb)
// Fill it with some arbitrary data // Fill it with some arbitrary data
content := make(map[string][]byte) content := make(map[string][]byte)
@ -49,17 +50,17 @@ func makeTestTrie() (ethdb.Database, *Trie, map[string][]byte) {
trie.Update(key, val) trie.Update(key, val)
} }
} }
trie.Commit() trie.Commit(nil)
// Return the generated trie // Return the generated trie
return db, trie, content return triedb, trie, content
} }
// checkTrieContents cross references a reconstructed trie with an expected data // checkTrieContents cross references a reconstructed trie with an expected data
// content map. // content map.
func checkTrieContents(t *testing.T, db Database, root []byte, content map[string][]byte) { func checkTrieContents(t *testing.T, db *Database, root []byte, content map[string][]byte) {
// Check root availability and trie contents // Check root availability and trie contents
trie, err := New(common.BytesToHash(root), db, nil) trie, err := New(common.BytesToHash(root), db)
if err != nil { if err != nil {
t.Fatalf("failed to create trie at %x: %v", root, err) t.Fatalf("failed to create trie at %x: %v", root, err)
} }
@ -74,9 +75,9 @@ func checkTrieContents(t *testing.T, db Database, root []byte, content map[strin
} }
// checkTrieConsistency checks that all nodes in a trie are indeed present. // checkTrieConsistency checks that all nodes in a trie are indeed present.
func checkTrieConsistency(db Database, root common.Hash) error { func checkTrieConsistency(db *Database, root common.Hash) error {
// Create and iterate a trie rooted in a subnode // Create and iterate a trie rooted in a subnode
trie, err := New(root, db, nil) trie, err := New(root, db)
if err != nil { if err != nil {
return nil // Consider a non existent state consistent return nil // Consider a non existent state consistent
} }
@ -88,12 +89,18 @@ func checkTrieConsistency(db Database, root common.Hash) error {
// Tests that an empty trie is not scheduled for syncing. // Tests that an empty trie is not scheduled for syncing.
func TestEmptyTrieSync(t *testing.T) { func TestEmptyTrieSync(t *testing.T) {
emptyA, _ := New(common.Hash{}, nil, nil) diskdbA, _ := ethdb.NewMemDatabase()
emptyB, _ := New(emptyRoot, nil, nil) triedbA := NewDatabase(diskdbA)
diskdbB, _ := ethdb.NewMemDatabase()
triedbB := NewDatabase(diskdbB)
emptyA, _ := New(common.Hash{}, triedbA)
emptyB, _ := New(emptyRoot, triedbB)
for i, trie := range []*Trie{emptyA, emptyB} { for i, trie := range []*Trie{emptyA, emptyB} {
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
if req := NewTrieSync(common.BytesToHash(trie.Root()), db, nil).Missing(1); len(req) != 0 { if req := NewTrieSync(trie.Hash(), diskdb, nil).Missing(1); len(req) != 0 {
t.Errorf("test %d: content requested for empty trie: %v", i, req) t.Errorf("test %d: content requested for empty trie: %v", i, req)
} }
} }
@ -109,14 +116,15 @@ func testIterativeTrieSync(t *testing.T, batch int) {
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil)
queue := append([]common.Hash{}, sched.Missing(batch)...) queue := append([]common.Hash{}, sched.Missing(batch)...)
for len(queue) > 0 { for len(queue) > 0 {
results := make([]SyncResult, len(queue)) results := make([]SyncResult, len(queue))
for i, hash := range queue { for i, hash := range queue {
data, err := srcDb.Get(hash.Bytes()) data, err := srcDb.Node(hash)
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
@ -125,13 +133,13 @@ func testIterativeTrieSync(t *testing.T, batch int) {
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
t.Fatalf("failed to process result #%d: %v", index, err) t.Fatalf("failed to process result #%d: %v", index, err)
} }
if index, err := sched.Commit(dstDb); err != nil { if index, err := sched.Commit(diskdb); err != nil {
t.Fatalf("failed to commit data #%d: %v", index, err) t.Fatalf("failed to commit data #%d: %v", index, err)
} }
queue = append(queue[:0], sched.Missing(batch)...) queue = append(queue[:0], sched.Missing(batch)...)
} }
// Cross check that the two tries are in sync // Cross check that the two tries are in sync
checkTrieContents(t, dstDb, srcTrie.Root(), srcData) checkTrieContents(t, triedb, srcTrie.Root(), srcData)
} }
// Tests that the trie scheduler can correctly reconstruct the state even if only // Tests that the trie scheduler can correctly reconstruct the state even if only
@ -141,15 +149,16 @@ func TestIterativeDelayedTrieSync(t *testing.T) {
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil)
queue := append([]common.Hash{}, sched.Missing(10000)...) queue := append([]common.Hash{}, sched.Missing(10000)...)
for len(queue) > 0 { for len(queue) > 0 {
// Sync only half of the scheduled nodes // Sync only half of the scheduled nodes
results := make([]SyncResult, len(queue)/2+1) results := make([]SyncResult, len(queue)/2+1)
for i, hash := range queue[:len(results)] { for i, hash := range queue[:len(results)] {
data, err := srcDb.Get(hash.Bytes()) data, err := srcDb.Node(hash)
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
@ -158,13 +167,13 @@ func TestIterativeDelayedTrieSync(t *testing.T) {
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
t.Fatalf("failed to process result #%d: %v", index, err) t.Fatalf("failed to process result #%d: %v", index, err)
} }
if index, err := sched.Commit(dstDb); err != nil { if index, err := sched.Commit(diskdb); err != nil {
t.Fatalf("failed to commit data #%d: %v", index, err) t.Fatalf("failed to commit data #%d: %v", index, err)
} }
queue = append(queue[len(results):], sched.Missing(10000)...) queue = append(queue[len(results):], sched.Missing(10000)...)
} }
// Cross check that the two tries are in sync // Cross check that the two tries are in sync
checkTrieContents(t, dstDb, srcTrie.Root(), srcData) checkTrieContents(t, triedb, srcTrie.Root(), srcData)
} }
// Tests that given a root hash, a trie can sync iteratively on a single thread, // Tests that given a root hash, a trie can sync iteratively on a single thread,
@ -178,8 +187,9 @@ func testIterativeRandomTrieSync(t *testing.T, batch int) {
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil)
queue := make(map[common.Hash]struct{}) queue := make(map[common.Hash]struct{})
for _, hash := range sched.Missing(batch) { for _, hash := range sched.Missing(batch) {
@ -189,7 +199,7 @@ func testIterativeRandomTrieSync(t *testing.T, batch int) {
// Fetch all the queued nodes in a random order // Fetch all the queued nodes in a random order
results := make([]SyncResult, 0, len(queue)) results := make([]SyncResult, 0, len(queue))
for hash := range queue { for hash := range queue {
data, err := srcDb.Get(hash.Bytes()) data, err := srcDb.Node(hash)
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
@ -199,7 +209,7 @@ func testIterativeRandomTrieSync(t *testing.T, batch int) {
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
t.Fatalf("failed to process result #%d: %v", index, err) t.Fatalf("failed to process result #%d: %v", index, err)
} }
if index, err := sched.Commit(dstDb); err != nil { if index, err := sched.Commit(diskdb); err != nil {
t.Fatalf("failed to commit data #%d: %v", index, err) t.Fatalf("failed to commit data #%d: %v", index, err)
} }
queue = make(map[common.Hash]struct{}) queue = make(map[common.Hash]struct{})
@ -208,7 +218,7 @@ func testIterativeRandomTrieSync(t *testing.T, batch int) {
} }
} }
// Cross check that the two tries are in sync // Cross check that the two tries are in sync
checkTrieContents(t, dstDb, srcTrie.Root(), srcData) checkTrieContents(t, triedb, srcTrie.Root(), srcData)
} }
// Tests that the trie scheduler can correctly reconstruct the state even if only // Tests that the trie scheduler can correctly reconstruct the state even if only
@ -218,8 +228,9 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) {
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil)
queue := make(map[common.Hash]struct{}) queue := make(map[common.Hash]struct{})
for _, hash := range sched.Missing(10000) { for _, hash := range sched.Missing(10000) {
@ -229,7 +240,7 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) {
// Sync only half of the scheduled nodes, even those in random order // Sync only half of the scheduled nodes, even those in random order
results := make([]SyncResult, 0, len(queue)/2+1) results := make([]SyncResult, 0, len(queue)/2+1)
for hash := range queue { for hash := range queue {
data, err := srcDb.Get(hash.Bytes()) data, err := srcDb.Node(hash)
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
@ -243,7 +254,7 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) {
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
t.Fatalf("failed to process result #%d: %v", index, err) t.Fatalf("failed to process result #%d: %v", index, err)
} }
if index, err := sched.Commit(dstDb); err != nil { if index, err := sched.Commit(diskdb); err != nil {
t.Fatalf("failed to commit data #%d: %v", index, err) t.Fatalf("failed to commit data #%d: %v", index, err)
} }
for _, result := range results { for _, result := range results {
@ -254,7 +265,7 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) {
} }
} }
// Cross check that the two tries are in sync // Cross check that the two tries are in sync
checkTrieContents(t, dstDb, srcTrie.Root(), srcData) checkTrieContents(t, triedb, srcTrie.Root(), srcData)
} }
// Tests that a trie sync will not request nodes multiple times, even if they // Tests that a trie sync will not request nodes multiple times, even if they
@ -264,8 +275,9 @@ func TestDuplicateAvoidanceTrieSync(t *testing.T) {
srcDb, srcTrie, srcData := makeTestTrie() srcDb, srcTrie, srcData := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil)
queue := append([]common.Hash{}, sched.Missing(0)...) queue := append([]common.Hash{}, sched.Missing(0)...)
requested := make(map[common.Hash]struct{}) requested := make(map[common.Hash]struct{})
@ -273,7 +285,7 @@ func TestDuplicateAvoidanceTrieSync(t *testing.T) {
for len(queue) > 0 { for len(queue) > 0 {
results := make([]SyncResult, len(queue)) results := make([]SyncResult, len(queue))
for i, hash := range queue { for i, hash := range queue {
data, err := srcDb.Get(hash.Bytes()) data, err := srcDb.Node(hash)
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
@ -287,13 +299,13 @@ func TestDuplicateAvoidanceTrieSync(t *testing.T) {
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
t.Fatalf("failed to process result #%d: %v", index, err) t.Fatalf("failed to process result #%d: %v", index, err)
} }
if index, err := sched.Commit(dstDb); err != nil { if index, err := sched.Commit(diskdb); err != nil {
t.Fatalf("failed to commit data #%d: %v", index, err) t.Fatalf("failed to commit data #%d: %v", index, err)
} }
queue = append(queue[:0], sched.Missing(0)...) queue = append(queue[:0], sched.Missing(0)...)
} }
// Cross check that the two tries are in sync // Cross check that the two tries are in sync
checkTrieContents(t, dstDb, srcTrie.Root(), srcData) checkTrieContents(t, triedb, srcTrie.Root(), srcData)
} }
// Tests that at any point in time during a sync, only complete sub-tries are in // Tests that at any point in time during a sync, only complete sub-tries are in
@ -303,8 +315,9 @@ func TestIncompleteTrieSync(t *testing.T) {
srcDb, srcTrie, _ := makeTestTrie() srcDb, srcTrie, _ := makeTestTrie()
// Create a destination trie and sync with the scheduler // Create a destination trie and sync with the scheduler
dstDb, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
sched := NewTrieSync(common.BytesToHash(srcTrie.Root()), dstDb, nil) triedb := NewDatabase(diskdb)
sched := NewTrieSync(srcTrie.Hash(), diskdb, nil)
added := []common.Hash{} added := []common.Hash{}
queue := append([]common.Hash{}, sched.Missing(1)...) queue := append([]common.Hash{}, sched.Missing(1)...)
@ -312,7 +325,7 @@ func TestIncompleteTrieSync(t *testing.T) {
// Fetch a batch of trie nodes // Fetch a batch of trie nodes
results := make([]SyncResult, len(queue)) results := make([]SyncResult, len(queue))
for i, hash := range queue { for i, hash := range queue {
data, err := srcDb.Get(hash.Bytes()) data, err := srcDb.Node(hash)
if err != nil { if err != nil {
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
} }
@ -322,7 +335,7 @@ func TestIncompleteTrieSync(t *testing.T) {
if _, index, err := sched.Process(results); err != nil { if _, index, err := sched.Process(results); err != nil {
t.Fatalf("failed to process result #%d: %v", index, err) t.Fatalf("failed to process result #%d: %v", index, err)
} }
if index, err := sched.Commit(dstDb); err != nil { if index, err := sched.Commit(diskdb); err != nil {
t.Fatalf("failed to commit data #%d: %v", index, err) t.Fatalf("failed to commit data #%d: %v", index, err)
} }
for _, result := range results { for _, result := range results {
@ -330,7 +343,7 @@ func TestIncompleteTrieSync(t *testing.T) {
} }
// Check that all known sub-tries in the synced trie are complete // Check that all known sub-tries in the synced trie are complete
for _, root := range added { for _, root := range added {
if err := checkTrieConsistency(dstDb, root); err != nil { if err := checkTrieConsistency(triedb, root); err != nil {
t.Fatalf("trie inconsistent: %v", err) t.Fatalf("trie inconsistent: %v", err)
} }
} }
@ -340,12 +353,12 @@ func TestIncompleteTrieSync(t *testing.T) {
// Sanity check that removing any node from the database is detected // Sanity check that removing any node from the database is detected
for _, node := range added[1:] { for _, node := range added[1:] {
key := node.Bytes() key := node.Bytes()
value, _ := dstDb.Get(key) value, _ := diskdb.Get(key)
dstDb.Delete(key) diskdb.Delete(key)
if err := checkTrieConsistency(dstDb, added[0]); err == nil { if err := checkTrieConsistency(triedb, added[0]); err == nil {
t.Fatalf("trie inconsistency not caught, missing: %x", key) t.Fatalf("trie inconsistency not caught, missing: %x", key)
} }
dstDb.Put(key, value) diskdb.Put(key, value)
} }
} }

View file

@ -22,16 +22,17 @@ import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/rcrowley/go-metrics" "github.com/rcrowley/go-metrics"
) )
var ( var (
// This is the known root hash of an empty trie. // emptyRoot is the known root hash of an empty trie.
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
// This is the known hash of an empty state trie entry.
emptyState common.Hash // emptyState is the known hash of an empty state trie entry.
emptyState = crypto.Keccak256Hash(nil)
) )
var ( var (
@ -53,29 +54,10 @@ func CacheUnloads() int64 {
return cacheUnloadCounter.Count() return cacheUnloadCounter.Count()
} }
func init() { // LeafCallback is a callback type invoked when a trie operation reaches a leaf
sha3.NewKeccak256().Sum(emptyState[:0]) // node. It's used by state sync and commit to allow handling external references
} // between account and storage tries.
type LeafCallback func(leaf []byte, parent common.Hash) error
// Database must be implemented by backing stores for the trie.
type Database interface {
DatabaseReader
DatabaseWriter
}
// DatabaseReader wraps the Get method of a backing store for the trie.
type DatabaseReader interface {
Get(key []byte) (value []byte, err error)
Has(key []byte) (bool, error)
}
// DatabaseWriter wraps the Put method of a backing store for the trie.
type DatabaseWriter interface {
// Put stores the mapping key->value in the database.
// Implementations must not hold onto the value bytes, the trie
// will reuse the slice across calls to Put.
Put(key, value []byte) error
}
// Trie is a Merkle Patricia Trie. // Trie is a Merkle Patricia Trie.
// The zero value is an empty trie with no database. // The zero value is an empty trie with no database.
@ -83,9 +65,8 @@ type DatabaseWriter interface {
// //
// Trie is not safe for concurrent use. // Trie is not safe for concurrent use.
type Trie struct { type Trie struct {
db *Database
root node root node
db Database
pool *NodePool
originalRoot common.Hash originalRoot common.Hash
// Cache generation values. // Cache generation values.
@ -112,12 +93,15 @@ func (t *Trie) newFlag() nodeFlag {
// trie is initially empty and does not require a database. Otherwise, // trie is initially empty and does not require a database. Otherwise,
// New will panic if db is nil and returns a MissingNodeError if root does // New will panic if db is nil and returns a MissingNodeError if root does
// not exist in the database. Accessing the trie loads nodes from db on demand. // not exist in the database. Accessing the trie loads nodes from db on demand.
func New(root common.Hash, db Database, pool *NodePool) (*Trie, error) { func New(root common.Hash, db *Database) (*Trie, error) {
trie := &Trie{db: db, pool: pool, originalRoot: root}
if (root != common.Hash{}) && root != emptyRoot {
if db == nil { if db == nil {
panic("trie.New: cannot use existing root without a database") panic("trie.New called without a database")
} }
trie := &Trie{
db: db,
originalRoot: root,
}
if (root != common.Hash{}) && root != emptyRoot {
rootnode, err := trie.resolveHash(root[:], nil) rootnode, err := trie.resolveHash(root[:], nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -448,15 +432,9 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) {
func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
cacheMissCounter.Inc(1) cacheMissCounter.Inc(1)
// Try to load the node from the recent mempool
hash := common.BytesToHash(n) hash := common.BytesToHash(n)
if t.pool != nil {
if enc := t.pool.Fetch(hash); enc != nil { enc, err := t.db.Node(hash)
return mustDecodeNode(n, enc, t.cachegen), nil
}
}
// Node not in the mempool, load it from disk
enc, err := t.db.Get(n)
if err != nil || enc == nil { if err != nil || enc == nil {
return nil, &MissingNodeError{NodeHash: hash, Path: prefix} return nil, &MissingNodeError{NodeHash: hash, Path: prefix}
} }
@ -470,47 +448,18 @@ func (t *Trie) Root() []byte { return t.Hash().Bytes() }
// Hash returns the root hash of the trie. It does not write to the // Hash returns the root hash of the trie. It does not write to the
// database and can be used even if the trie doesn't have one. // database and can be used even if the trie doesn't have one.
func (t *Trie) Hash() common.Hash { func (t *Trie) Hash() common.Hash {
hash, cached, _ := t.hashRoot(nil) hash, cached, _ := t.hashRoot(nil, nil)
t.root = cached t.root = cached
return common.BytesToHash(hash.(hashNode)) return common.BytesToHash(hash.(hashNode))
} }
// Commit writes all nodes to the trie's database. // Commit writes all nodes to the trie's memory database, tracking the internal
// Nodes are stored with their sha3 hash as the key. // and external (for account tries) references.
// func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) {
// Committing flushes nodes from memory.
// Subsequent Get calls will load nodes from the database.
func (t *Trie) Commit() (root common.Hash, err error) {
if t.db == nil { if t.db == nil {
panic("Commit called on trie with nil database") panic("commit called on trie with nil database")
} }
return t.CommitTo(t.db) hash, cached, err := t.hashRoot(t.db, onleaf)
}
// CommitTo writes all nodes to the given database.
// Nodes are stored with their sha3 hash as the key.
//
// Committing flushes nodes from memory. Subsequent Get calls will
// load nodes from the trie's database. Calling code must ensure that
// the changes made to db are written back to the trie's attached
// database before using the trie.
func (t *Trie) CommitTo(db DatabaseWriter) (root common.Hash, err error) {
// Retrieve the intermedia trie node memory cache if really writing
var pool *NodePool
if db != nil {
if pool = t.pool; pool == nil {
// If the trie has no intermediate memory pool, but actual database write was
// nonetheless requested, store into an emphemeral pool and flush out to disk.
pool = NewNodePool()
defer func() {
for hash, blob := range pool.cache {
db.Put(hash[:], blob)
}
}()
}
}
// Calculate the root hash and store in the mempool if requested
hash, cached, err := t.hashRoot(pool)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -519,11 +468,11 @@ func (t *Trie) CommitTo(db DatabaseWriter) (root common.Hash, err error) {
return common.BytesToHash(hash.(hashNode)), nil return common.BytesToHash(hash.(hashNode)), nil
} }
func (t *Trie) hashRoot(pool *NodePool) (node, node, error) { func (t *Trie) hashRoot(db *Database, onleaf LeafCallback) (node, node, error) {
if t.root == nil { if t.root == nil {
return hashNode(emptyRoot.Bytes()), nil, nil return hashNode(emptyRoot.Bytes()), nil, nil
} }
h := newHasher(t.cachegen, t.cachelimit) h := newHasher(t.cachegen, t.cachelimit, onleaf)
defer returnHasherToPool(h) defer returnHasherToPool(h)
return h.hash(t.root, pool, true) return h.hash(t.root, db, true)
} }

View file

@ -43,8 +43,8 @@ func init() {
// Used for testing // Used for testing
func newEmpty() *Trie { func newEmpty() *Trie {
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
trie, _ := New(common.Hash{}, db, NewNodePool()) trie, _ := New(common.Hash{}, NewDatabase(diskdb))
return trie return trie
} }
@ -68,8 +68,8 @@ func TestNull(t *testing.T) {
} }
func TestMissingRoot(t *testing.T) { func TestMissingRoot(t *testing.T) {
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
trie, err := New(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), db, NewNodePool()) trie, err := New(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), NewDatabase(diskdb))
if trie != nil { if trie != nil {
t.Error("New returned non-nil trie for invalid root") t.Error("New returned non-nil trie for invalid root")
} }
@ -78,75 +78,75 @@ func TestMissingRoot(t *testing.T) {
} }
} }
func TestMissingNodeDirect(t *testing.T) { testMissingNode(t, false) } func TestMissingNodeDisk(t *testing.T) { testMissingNode(t, false) }
func TestMissingNodePooled(t *testing.T) { testMissingNode(t, true) } func TestMissingNodeMemonly(t *testing.T) { testMissingNode(t, true) }
func testMissingNode(t *testing.T, pooled bool) { func testMissingNode(t *testing.T, memonly bool) {
var pool *NodePool diskdb, _ := ethdb.NewMemDatabase()
if pooled { triedb := NewDatabase(diskdb)
pool = NewNodePool()
}
db, _ := ethdb.NewMemDatabase()
trie, _ := New(common.Hash{}, db, pool) trie, _ := New(common.Hash{}, triedb)
updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer") updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer")
updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf")
root, _ := trie.Commit() root, _ := trie.Commit(nil)
if !memonly {
triedb.Commit(root, diskdb)
}
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
_, err := trie.TryGet([]byte("120000")) _, err := trie.TryGet([]byte("120000"))
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
_, err = trie.TryGet([]byte("120099")) _, err = trie.TryGet([]byte("120099"))
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
_, err = trie.TryGet([]byte("123456")) _, err = trie.TryGet([]byte("123456"))
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
err = trie.TryUpdate([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv")) err = trie.TryUpdate([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv"))
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
err = trie.TryDelete([]byte("123456")) err = trie.TryDelete([]byte("123456"))
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
hash := common.HexToHash("0xe1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9") hash := common.HexToHash("0xe1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9")
if pooled { if memonly {
delete(pool.cache, hash) delete(triedb.nodes, hash)
} else { } else {
db.Delete(hash[:]) diskdb.Delete(hash[:])
} }
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
_, err = trie.TryGet([]byte("120000")) _, err = trie.TryGet([]byte("120000"))
if _, ok := err.(*MissingNodeError); !ok { if _, ok := err.(*MissingNodeError); !ok {
t.Errorf("Wrong error: %v", err) t.Errorf("Wrong error: %v", err)
} }
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
_, err = trie.TryGet([]byte("120099")) _, err = trie.TryGet([]byte("120099"))
if _, ok := err.(*MissingNodeError); !ok { if _, ok := err.(*MissingNodeError); !ok {
t.Errorf("Wrong error: %v", err) t.Errorf("Wrong error: %v", err)
} }
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
_, err = trie.TryGet([]byte("123456")) _, err = trie.TryGet([]byte("123456"))
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
err = trie.TryUpdate([]byte("120099"), []byte("zxcv")) err = trie.TryUpdate([]byte("120099"), []byte("zxcv"))
if _, ok := err.(*MissingNodeError); !ok { if _, ok := err.(*MissingNodeError); !ok {
t.Errorf("Wrong error: %v", err) t.Errorf("Wrong error: %v", err)
} }
trie, _ = New(root, db, pool) trie, _ = New(root, triedb)
err = trie.TryDelete([]byte("123456")) err = trie.TryDelete([]byte("123456"))
if _, ok := err.(*MissingNodeError); !ok { if _, ok := err.(*MissingNodeError); !ok {
t.Errorf("Wrong error: %v", err) t.Errorf("Wrong error: %v", err)
@ -170,7 +170,7 @@ func TestInsert(t *testing.T) {
updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab") exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab")
root, err := trie.Commit() root, err := trie.Commit(nil)
if err != nil { if err != nil {
t.Fatalf("commit error: %v", err) t.Fatalf("commit error: %v", err)
} }
@ -199,7 +199,7 @@ func TestGet(t *testing.T) {
if i == 1 { if i == 1 {
return return
} }
trie.Commit() trie.Commit(nil)
} }
} }
@ -268,13 +268,13 @@ func TestReplication(t *testing.T) {
for _, val := range vals { for _, val := range vals {
updateString(trie, val.k, val.v) updateString(trie, val.k, val.v)
} }
exp, err := trie.Commit() exp, err := trie.Commit(nil)
if err != nil { if err != nil {
t.Fatalf("commit error: %v", err) t.Fatalf("commit error: %v", err)
} }
// create a new trie on top of the database and check that lookups work. // create a new trie on top of the database and check that lookups work.
trie2, err := New(exp, trie.db, trie.pool) trie2, err := New(exp, trie.db)
if err != nil { if err != nil {
t.Fatalf("can't recreate trie at %x: %v", exp, err) t.Fatalf("can't recreate trie at %x: %v", exp, err)
} }
@ -283,7 +283,7 @@ func TestReplication(t *testing.T) {
t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v) t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v)
} }
} }
hash, err := trie2.Commit() hash, err := trie2.Commit(nil)
if err != nil { if err != nil {
t.Fatalf("commit error: %v", err) t.Fatalf("commit error: %v", err)
} }
@ -319,13 +319,13 @@ func TestLargeValue(t *testing.T) {
} }
type countingDB struct { type countingDB struct {
Database DatabaseReader
gets map[string]int gets map[string]int
} }
func (db *countingDB) Get(key []byte) ([]byte, error) { func (db *countingDB) Get(key []byte) ([]byte, error) {
db.gets[string(key)]++ db.gets[string(key)]++
return db.Database.Get(key) return db.DatabaseReader.Get(key)
} }
// TestCacheUnload checks that decoded nodes are unloaded after a // TestCacheUnload checks that decoded nodes are unloaded after a
@ -337,19 +337,20 @@ func TestCacheUnload(t *testing.T) {
key2 := "---some other branch" key2 := "---some other branch"
updateString(trie, key1, "this is the branch of key1.") updateString(trie, key1, "this is the branch of key1.")
updateString(trie, key2, "this is the branch of key2.") updateString(trie, key2, "this is the branch of key2.")
root, _ := trie.Commit()
root, _ := trie.Commit(nil)
trie.db.Commit(root, trie.db.diskdb.(DatabaseWriter))
// Commit the trie repeatedly and access key1. // Commit the trie repeatedly and access key1.
// The branch containing it is loaded from DB exactly two times: // The branch containing it is loaded from DB exactly two times:
// in the 0th and 6th iteration. // in the 0th and 6th iteration.
db := &countingDB{Database: trie.db, gets: make(map[string]int)} db := &countingDB{DatabaseReader: trie.db.diskdb, gets: make(map[string]int)}
trie, _ = New(root, db, trie.pool) trie, _ = New(root, NewDatabase(db))
trie.SetCacheLimit(5) trie.SetCacheLimit(5)
for i := 0; i < 12; i++ { for i := 0; i < 12; i++ {
getString(trie, key1) getString(trie, key1)
trie.Commit() trie.Commit(nil)
} }
// Check that it got loaded two times. // Check that it got loaded two times.
for dbkey, count := range db.gets { for dbkey, count := range db.gets {
if count != 2 { if count != 2 {
@ -412,10 +413,10 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value {
} }
func runRandTest(rt randTest) bool { func runRandTest(rt randTest) bool {
db, _ := ethdb.NewMemDatabase() diskdb, _ := ethdb.NewMemDatabase()
mp := NewNodePool() triedb := NewDatabase(diskdb)
tr, _ := New(common.Hash{}, db, mp) tr, _ := New(common.Hash{}, triedb)
values := make(map[string]string) // tracks content of the trie values := make(map[string]string) // tracks content of the trie
for i, step := range rt { for i, step := range rt {
@ -433,23 +434,23 @@ func runRandTest(rt randTest) bool {
rt[i].err = fmt.Errorf("mismatch for key 0x%x, got 0x%x want 0x%x", step.key, v, want) rt[i].err = fmt.Errorf("mismatch for key 0x%x, got 0x%x want 0x%x", step.key, v, want)
} }
case opCommit: case opCommit:
_, rt[i].err = tr.Commit() _, rt[i].err = tr.Commit(nil)
case opHash: case opHash:
tr.Hash() tr.Hash()
case opReset: case opReset:
hash, err := tr.Commit() hash, err := tr.Commit(nil)
if err != nil { if err != nil {
rt[i].err = err rt[i].err = err
return false return false
} }
newtr, err := New(hash, db, mp) newtr, err := New(hash, triedb)
if err != nil { if err != nil {
rt[i].err = err rt[i].err = err
return false return false
} }
tr = newtr tr = newtr
case opItercheckhash: case opItercheckhash:
checktr, _ := New(common.Hash{}, nil, nil) checktr, _ := New(common.Hash{}, triedb)
it := NewIterator(tr.NodeIterator(nil)) it := NewIterator(tr.NodeIterator(nil))
for it.Next() { for it.Next() {
checktr.Update(it.Key, it.Value) checktr.Update(it.Key, it.Value)
@ -522,7 +523,7 @@ func benchGet(b *testing.B, commit bool) {
trie := new(Trie) trie := new(Trie)
if commit { if commit {
_, tmpdb := tempDB() _, tmpdb := tempDB()
trie, _ = New(common.Hash{}, tmpdb, nil) trie, _ = New(common.Hash{}, tmpdb)
} }
k := make([]byte, 32) k := make([]byte, 32)
for i := 0; i < benchElemCount; i++ { for i := 0; i < benchElemCount; i++ {
@ -531,7 +532,7 @@ func benchGet(b *testing.B, commit bool) {
} }
binary.LittleEndian.PutUint64(k, benchElemCount/2) binary.LittleEndian.PutUint64(k, benchElemCount/2)
if commit { if commit {
trie.Commit() trie.Commit(nil)
} }
b.ResetTimer() b.ResetTimer()
@ -541,7 +542,7 @@ func benchGet(b *testing.B, commit bool) {
b.StopTimer() b.StopTimer()
if commit { if commit {
ldb := trie.db.(*ethdb.LDBDatabase) ldb := trie.db.diskdb.(*ethdb.LDBDatabase)
ldb.Close() ldb.Close()
os.RemoveAll(ldb.Path()) os.RemoveAll(ldb.Path())
} }
@ -592,16 +593,16 @@ func BenchmarkHash(b *testing.B) {
trie.Hash() trie.Hash()
} }
func tempDB() (string, Database) { func tempDB() (string, *Database) {
dir, err := ioutil.TempDir("", "trie-bench") dir, err := ioutil.TempDir("", "trie-bench")
if err != nil { if err != nil {
panic(fmt.Sprintf("can't create temporary directory: %v", err)) panic(fmt.Sprintf("can't create temporary directory: %v", err))
} }
db, err := ethdb.NewLDBDatabase(dir, 256, 0) diskdb, err := ethdb.NewLDBDatabase(dir, 256, 0)
if err != nil { if err != nil {
panic(fmt.Sprintf("can't create temporary database: %v", err)) panic(fmt.Sprintf("can't create temporary database: %v", err))
} }
return dir, db return dir, NewDatabase(diskdb)
} }
func getString(trie *Trie, k string) []byte { func getString(trie *Trie, k string) []byte {