diff --git a/core/state/database.go b/core/state/database.go
index ce5d8d731715..b8927162cd56 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -88,9 +88,11 @@ type Trie interface {
// can be used even if the trie doesn't have one.
Hash() common.Hash
- // Commit writes all nodes to the trie's memory database, tracking the internal
- // and external (for account tries) references.
- Commit(onleaf trie.LeafCallback) (common.Hash, int, error)
+ // Commit collects all dirty nodes in the trie and replace them with the
+ // corresponding node hash. All collected nodes(including dirty leaves if
+ // collectLeaf is true) will be encapsulated into a nodeset for return.
+ // The returned nodeset can be nil if the trie is clean(nothing to commit).
+ Commit(collectLeaf bool) (common.Hash, *trie.NodeSet, error)
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key.
diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go
index 36055856e1c7..bf714db4c2d0 100644
--- a/core/state/snapshot/generate.go
+++ b/core/state/snapshot/generate.go
@@ -367,7 +367,10 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, owner common.Hash, roo
for i, key := range result.keys {
snapTrie.Update(key, result.vals[i])
}
- root, _, _ := snapTrie.Commit(nil)
+ root, nodes, _ := snapTrie.Commit(false)
+ if nodes != nil {
+ snapTrieDb.Update(trie.NewWithNodeSet(nodes))
+ }
snapTrieDb.Commit(root, false, nil)
}
// Construct the trie for state iteration, reuse the trie
diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go
index fe81993e9d2f..911a211f7ce6 100644
--- a/core/state/snapshot/generate_test.go
+++ b/core/state/snapshot/generate_test.go
@@ -143,6 +143,7 @@ type testHelper struct {
diskdb ethdb.Database
triedb *trie.Database
accTrie *trie.SecureTrie
+ nodes *trie.MergedNodeSet
}
func newHelper() *testHelper {
@@ -153,6 +154,7 @@ func newHelper() *testHelper {
diskdb: diskdb,
triedb: triedb,
accTrie: accTrie,
+ nodes: trie.NewMergedNodeSet(),
}
}
@@ -184,17 +186,22 @@ func (t *testHelper) makeStorageTrie(stateRoot, owner common.Hash, keys []string
for i, k := range keys {
stTrie.Update([]byte(k), []byte(vals[i]))
}
- var root common.Hash
if !commit {
- root = stTrie.Hash()
- } else {
- root, _, _ = stTrie.Commit(nil)
+ return stTrie.Hash().Bytes()
+ }
+ root, nodes, _ := stTrie.Commit(false)
+ if nodes != nil {
+ t.nodes.Merge(nodes)
}
return root.Bytes()
}
func (t *testHelper) Commit() common.Hash {
- root, _, _ := t.accTrie.Commit(nil)
+ root, nodes, _ := t.accTrie.Commit(false)
+ if nodes != nil {
+ t.nodes.Merge(nodes)
+ }
+ t.triedb.Update(t.nodes)
t.triedb.Commit(root, false, nil)
return root
}
@@ -378,7 +385,7 @@ func TestGenerateCorruptAccountTrie(t *testing.T) {
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4
- root, _, _ := helper.accTrie.Commit(nil) // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
+ root, _, _ := helper.accTrie.Commit(false) // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
// Delete an account trie leaf and ensure the generator chokes
helper.triedb.Commit(root, false, nil)
@@ -413,7 +420,7 @@ func TestGenerateMissingStorageTrie(t *testing.T) {
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
stRoot = helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
- root, _, _ := helper.accTrie.Commit(nil)
+ root, _, _ := helper.accTrie.Commit(false)
// We can only corrupt the disk database, so flush the tries out
helper.triedb.Reference(
@@ -458,7 +465,7 @@ func TestGenerateCorruptStorageTrie(t *testing.T) {
stRoot = helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
- root, _, _ := helper.accTrie.Commit(nil)
+ root, _, _ := helper.accTrie.Commit(false)
// We can only corrupt the disk database, so flush the tries out
helper.triedb.Reference(
@@ -825,10 +832,12 @@ func populateDangling(disk ethdb.KeyValueStore) {
// This test will populate some dangling storages to see if they can be cleaned up.
func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
var helper = newHelper()
- stRoot := helper.makeStorageTrie(common.Hash{}, common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
+ stRoot := helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
+
+ helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
@@ -858,10 +867,12 @@ func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
// This test will populate some dangling storages to see if they can be cleaned up.
func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) {
var helper = newHelper()
- stRoot := helper.makeStorageTrie(common.Hash{}, common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
+ stRoot := helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
+
+ helper.makeStorageTrie(common.Hash{}, hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()})
populateDangling(helper.diskdb)
diff --git a/core/state/state_object.go b/core/state/state_object.go
index bc1ca1f40eaf..a23df895458c 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/trie"
)
var emptyCodeHash = crypto.Keccak256(nil)
@@ -375,23 +376,23 @@ func (s *stateObject) updateRoot(db Database) {
// CommitTrie the storage trie of the object to db.
// This updates the trie root.
-func (s *stateObject) CommitTrie(db Database) (int, error) {
+func (s *stateObject) CommitTrie(db Database) (*trie.NodeSet, error) {
// If nothing changed, don't bother with hashing anything
if s.updateTrie(db) == nil {
- return 0, nil
+ return nil, nil
}
if s.dbErr != nil {
- return 0, s.dbErr
+ return nil, s.dbErr
}
// Track the amount of time wasted on committing the storage trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
}
- root, committed, err := s.trie.Commit(nil)
+ root, nodes, err := s.trie.Commit(false)
if err == nil {
s.data.Root = root
}
- return committed, err
+ return nodes, err
}
// AddBalance adds amount to s's balance.
diff --git a/core/state/statedb.go b/core/state/statedb.go
index e945ab595013..9d5e8ef08acc 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -907,7 +907,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
s.IntermediateRoot(deleteEmptyObjects)
// Commit objects to the trie, measuring the elapsed time
- var storageCommitted int
+ var (
+ accounts int
+ storages int
+ nodes = trie.NewMergedNodeSet()
+ )
codeWriter := s.db.TrieDB().DiskDB().NewBatch()
for addr := range s.stateObjectsDirty {
if obj := s.stateObjects[addr]; !obj.deleted {
@@ -917,11 +921,17 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
obj.dirtyCode = false
}
// Write any storage changes in the state object to its storage trie
- committed, err := obj.CommitTrie(s.db)
+ set, err := obj.CommitTrie(s.db)
if err != nil {
return common.Hash{}, err
}
- storageCommitted += committed
+ // Merge the dirty nodes of storage trie into global set
+ if set != nil {
+ if err := nodes.Merge(set); err != nil {
+ return common.Hash{}, err
+ }
+ storages += set.Len()
+ }
}
}
if len(s.stateObjectsDirty) > 0 {
@@ -937,21 +947,17 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
if metrics.EnabledExpensive {
start = time.Now()
}
- // The onleaf func is called _serially_, so we can reuse the same account
- // for unmarshalling every time.
- var account types.StateAccount
- root, accountCommitted, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash, _ []byte) error {
- if err := rlp.DecodeBytes(leaf, &account); err != nil {
- return nil
- }
- if account.Root != emptyRoot {
- s.db.TrieDB().Reference(account.Root, parent)
- }
- return nil
- })
+ root, set, err := s.trie.Commit(true)
if err != nil {
return common.Hash{}, err
}
+ // Merge the dirty nodes of account trie into global set
+ if set != nil {
+ if err := nodes.Merge(set); err != nil {
+ return common.Hash{}, err
+ }
+ accounts = set.Len()
+ }
if metrics.EnabledExpensive {
s.AccountCommits += time.Since(start)
@@ -959,8 +965,8 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
storageUpdatedMeter.Mark(int64(s.StorageUpdated))
accountDeletedMeter.Mark(int64(s.AccountDeleted))
storageDeletedMeter.Mark(int64(s.StorageDeleted))
- accountCommittedMeter.Mark(int64(accountCommitted))
- storageCommittedMeter.Mark(int64(storageCommitted))
+ accountCommittedMeter.Mark(int64(accounts))
+ storageCommittedMeter.Mark(int64(storages))
s.AccountUpdated, s.AccountDeleted = 0, 0
s.StorageUpdated, s.StorageDeleted = 0, 0
}
@@ -984,6 +990,9 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
}
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
}
+ if err := s.db.TrieDB().Update(nodes); err != nil {
+ return common.Hash{}, err
+ }
s.originalRoot = root
return root, err
}
diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go
index 85e4dc5e4f83..0654faf79511 100644
--- a/eth/protocols/snap/sync_test.go
+++ b/eth/protocols/snap/sync_test.go
@@ -1364,7 +1364,7 @@ func makeAccountTrieNoStorage(n int) (*trie.Trie, entrySlice) {
entries = append(entries, elem)
}
sort.Sort(entries)
- accTrie.Commit(nil)
+ accTrie.Commit(false)
return accTrie, entries
}
@@ -1420,7 +1420,7 @@ func makeBoundaryAccountTrie(n int) (*trie.Trie, entrySlice) {
entries = append(entries, elem)
}
sort.Sort(entries)
- trie.Commit(nil)
+ trie.Commit(false)
return trie, entries
}
@@ -1444,7 +1444,7 @@ func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool)
// Create a storage trie
stTrie, stEntries := makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), i, db)
stRoot := stTrie.Hash()
- stTrie.Commit(nil)
+ stTrie.Commit(false)
value, _ := rlp.EncodeToBytes(&types.StateAccount{
Nonce: i,
Balance: big.NewInt(int64(i)),
@@ -1460,7 +1460,7 @@ func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool)
}
sort.Sort(entries)
- accTrie.Commit(nil)
+ accTrie.Commit(false)
return accTrie, entries, storageTries, storageEntries
}
@@ -1491,7 +1491,7 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (*trie
stTrie, stEntries = makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), 0, db)
}
stRoot := stTrie.Hash()
- stTrie.Commit(nil)
+ stTrie.Commit(false)
value, _ := rlp.EncodeToBytes(&types.StateAccount{
Nonce: i,
@@ -1507,7 +1507,7 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (*trie
storageEntries[common.BytesToHash(key)] = stEntries
}
sort.Sort(entries)
- accTrie.Commit(nil)
+ accTrie.Commit(false)
return accTrie, entries, storageTries, storageEntries
}
@@ -1530,7 +1530,7 @@ func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *trie.Databas
entries = append(entries, elem)
}
sort.Sort(entries)
- trie.Commit(nil)
+ trie.Commit(false)
return trie, entries
}
@@ -1581,7 +1581,7 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (*trie
entries = append(entries, elem)
}
sort.Sort(entries)
- trie.Commit(nil)
+ trie.Commit(false)
return trie, entries
}
diff --git a/light/postprocess.go b/light/postprocess.go
index c09b00e71c81..1ce781a50f2b 100644
--- a/light/postprocess.go
+++ b/light/postprocess.go
@@ -217,10 +217,15 @@ func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) e
// Commit implements core.ChainIndexerBackend
func (c *ChtIndexerBackend) Commit() error {
- root, _, err := c.trie.Commit(nil)
+ root, nodes, err := c.trie.Commit(false)
if err != nil {
return err
}
+ if nodes != nil {
+ if err := c.triedb.Update(trie.NewWithNodeSet(nodes)); err != nil {
+ return err
+ }
+ }
// Pruning historical trie nodes if necessary.
if !c.disablePruning {
// Flush the triedb and track the latest trie nodes.
@@ -453,10 +458,15 @@ func (b *BloomTrieIndexerBackend) Commit() error {
b.trie.Delete(encKey[:])
}
}
- root, _, err := b.trie.Commit(nil)
+ root, nodes, err := b.trie.Commit(false)
if err != nil {
return err
}
+ if nodes != nil {
+ if err := b.triedb.Update(trie.NewWithNodeSet(nodes)); err != nil {
+ return err
+ }
+ }
// Pruning historical trie nodes if necessary.
if !b.disablePruning {
// Flush the triedb and track the latest trie nodes.
diff --git a/light/trie.go b/light/trie.go
index 931ba30cb40a..a2ef8ebff3d3 100644
--- a/light/trie.go
+++ b/light/trie.go
@@ -137,11 +137,11 @@ func (t *odrTrie) TryDelete(key []byte) error {
})
}
-func (t *odrTrie) Commit(onleaf trie.LeafCallback) (common.Hash, int, error) {
+func (t *odrTrie) Commit(collectLeaf bool) (common.Hash, *trie.NodeSet, error) {
if t.trie == nil {
- return t.id.Root, 0, nil
+ return t.id.Root, nil, nil
}
- return t.trie.Commit(onleaf)
+ return t.trie.Commit(collectLeaf)
}
func (t *odrTrie) Hash() common.Hash {
diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go
index 17d67a8758c2..e6165df08c15 100644
--- a/tests/fuzzers/stacktrie/trie_fuzzer.go
+++ b/tests/fuzzers/stacktrie/trie_fuzzer.go
@@ -173,10 +173,13 @@ func (f *fuzzer) fuzz() int {
return 0
}
// Flush trie -> database
- rootA, _, err := trieA.Commit(nil)
+ rootA, nodes, err := trieA.Commit(false)
if err != nil {
panic(err)
}
+ if nodes != nil {
+ dbA.Update(trie.NewWithNodeSet(nodes))
+ }
// Flush memdb -> disk (sponge)
dbA.Commit(rootA, false, nil)
diff --git a/tests/fuzzers/trie/trie-fuzzer.go b/tests/fuzzers/trie/trie-fuzzer.go
index ca1509085b12..96674d9a4c4e 100644
--- a/tests/fuzzers/trie/trie-fuzzer.go
+++ b/tests/fuzzers/trie/trie-fuzzer.go
@@ -158,14 +158,27 @@ func runRandTest(rt randTest) error {
rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want)
}
case opCommit:
- _, _, rt[i].err = tr.Commit(nil)
+ _, nodes, err := tr.Commit(false)
+ if err != nil {
+ rt[i].err = err
+ }
+ if nodes != nil {
+ if err := triedb.Update(trie.NewWithNodeSet(nodes)); err != nil {
+ return err
+ }
+ }
case opHash:
tr.Hash()
case opReset:
- hash, _, err := tr.Commit(nil)
+ hash, nodes, err := tr.Commit(false)
if err != nil {
return err
}
+ if nodes != nil {
+ if err := triedb.Update(trie.NewWithNodeSet(nodes)); err != nil {
+ return err
+ }
+ }
newtr, err := trie.New(common.Hash{}, hash, triedb)
if err != nil {
return err
diff --git a/trie/committer.go b/trie/committer.go
index 7a392abab7f4..efc645a7329f 100644
--- a/trie/committer.go
+++ b/trie/committer.go
@@ -17,34 +17,25 @@
package trie
import (
- "errors"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/common"
)
-// leafChanSize is the size of the leafCh. It's a pretty arbitrary number, to allow
-// some parallelism but not incur too much memory overhead.
-const leafChanSize = 200
-
-// leaf represents a trie leaf value
+// leaf represents a trie leaf node
type leaf struct {
- size int // size of the rlp data (estimate)
- hash common.Hash // hash of rlp data
- node node // the node to commit
- path []byte // the path from the root node
+ blob []byte // raw blob of leaf
+ parent common.Hash // the hash of parent node
}
-// committer is a type used for the trie Commit operation. A committer has some
-// internal preallocated temp space, and also a callback that is invoked when
-// leaves are committed. The leafs are passed through the `leafCh`, to allow
-// some level of parallelism.
-// By 'some level' of parallelism, it's still the case that all leaves will be
-// processed sequentially - onleaf will never be called in parallel or out of order.
+// committer is a type used for the trie Commit operation. The committer will
+// capture all dirty nodes during the commit process and keep them cached in
+// insertion order.
type committer struct {
- onleaf LeafCallback
- leafCh chan *leaf
+ owner common.Hash
+ nodes *NodeSet
+ collectLeaf bool
}
// committers live in a global sync.Pool
@@ -55,34 +46,36 @@ var committerPool = sync.Pool{
}
// newCommitter creates a new committer or picks one from the pool.
-func newCommitter() *committer {
- return committerPool.Get().(*committer)
+func newCommitter(owner common.Hash, collectLeaf bool) *committer {
+ ret := committerPool.Get().(*committer)
+ ret.owner = owner
+ ret.nodes = NewNodeSet(owner)
+ ret.collectLeaf = collectLeaf
+ return ret
}
func returnCommitterToPool(h *committer) {
- h.onleaf = nil
- h.leafCh = nil
+ h.owner = common.Hash{}
+ h.nodes = nil
+ h.collectLeaf = false
committerPool.Put(h)
}
// Commit collapses a node down into a hash node and inserts it into the database
-func (c *committer) Commit(n node, db *Database) (hashNode, int, error) {
- if db == nil {
- return nil, 0, errors.New("no db provided")
- }
- h, committed, err := c.commit(nil, n, db)
+func (c *committer) Commit(n node, db *nodeStore) (hashNode, *NodeSet, error) {
+ h, err := c.commit(nil, n, db)
if err != nil {
- return nil, 0, err
+ return nil, nil, err
}
- return h.(hashNode), committed, nil
+ return h.(hashNode), c.nodes, nil
}
// commit collapses a node down into a hash node and inserts it into the database
-func (c *committer) commit(path []byte, n node, db *Database) (node, int, error) {
+func (c *committer) commit(path []byte, n node, db *nodeStore) (node, error) {
// if this path is clean, use available cached data
hash, dirty := n.cache()
if hash != nil && !dirty {
- return hash, 0, nil
+ return hash, nil
}
// Commit children, then parent, and remove the dirty flag.
switch cn := n.(type) {
@@ -92,36 +85,35 @@ func (c *committer) commit(path []byte, n node, db *Database) (node, int, error)
// If the child is fullNode, recursively commit,
// otherwise it can only be hashNode or valueNode.
- var childCommitted int
if _, ok := cn.Val.(*fullNode); ok {
- childV, committed, err := c.commit(append(path, cn.Key...), cn.Val, db)
+ childV, err := c.commit(append(path, cn.Key...), cn.Val, db)
if err != nil {
- return nil, 0, err
+ return nil, err
}
- collapsed.Val, childCommitted = childV, committed
+ collapsed.Val = childV
}
// The key needs to be copied, since we're delivering it to database
collapsed.Key = hexToCompact(cn.Key)
hashedNode := c.store(path, collapsed, db)
if hn, ok := hashedNode.(hashNode); ok {
- return hn, childCommitted + 1, nil
+ return hn, nil
}
- return collapsed, childCommitted, nil
+ return collapsed, nil
case *fullNode:
- hashedKids, childCommitted, err := c.commitChildren(path, cn, db)
+ hashedKids, err := c.commitChildren(path, cn, db)
if err != nil {
- return nil, 0, err
+ return nil, err
}
collapsed := cn.copy()
collapsed.Children = hashedKids
hashedNode := c.store(path, collapsed, db)
if hn, ok := hashedNode.(hashNode); ok {
- return hn, childCommitted + 1, nil
+ return hn, nil
}
- return collapsed, childCommitted, nil
+ return collapsed, nil
case hashNode:
- return cn, 0, nil
+ return cn, nil
default:
// nil, valuenode shouldn't be committed
panic(fmt.Sprintf("%T: invalid node: %v", n, n))
@@ -129,11 +121,8 @@ func (c *committer) commit(path []byte, n node, db *Database) (node, int, error)
}
// commitChildren commits the children of the given fullnode
-func (c *committer) commitChildren(path []byte, n *fullNode, db *Database) ([17]node, int, error) {
- var (
- committed int
- children [17]node
- )
+func (c *committer) commitChildren(path []byte, n *fullNode, db *nodeStore) ([17]node, error) {
+ var children [17]node
for i := 0; i < 16; i++ {
child := n.Children[i]
if child == nil {
@@ -149,83 +138,62 @@ func (c *committer) commitChildren(path []byte, n *fullNode, db *Database) ([17]
// Commit the child recursively and store the "hashed" value.
// Note the returned node can be some embedded nodes, so it's
// possible the type is not hashNode.
- hashed, childCommitted, err := c.commit(append(path, byte(i)), child, db)
+ hashed, err := c.commit(append(path, byte(i)), child, db)
if err != nil {
- return children, 0, err
+ return children, err
}
children[i] = hashed
- committed += childCommitted
}
// For the 17th child, it's possible the type is valuenode.
if n.Children[16] != nil {
children[16] = n.Children[16]
}
- return children, committed, nil
+ return children, nil
}
// 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
// node->external trie references.
-func (c *committer) store(path []byte, n node, db *Database) node {
+func (c *committer) store(path []byte, n node, db *nodeStore) node {
// Larger nodes are replaced by their hash and stored in the database.
- var (
- hash, _ = n.cache()
- size int
- )
+ var hash, _ = n.cache()
+
+ // This was not generated - must be a small node stored in the parent.
+ // In theory, we should check if the node is leaf here (embedded node
+ // usually is leaf node). But small value(less than 32bytes) is not
+ // our target(leaves in account trie only).
if hash == nil {
- // This was not generated - must be a small node stored in the parent.
- // In theory, we should apply the leafCall here if it's not nil(embedded
- // node usually contains value). But small value(less than 32bytes) is
- // not our target.
return n
- } else {
- // We have the hash already, estimate the RLP encoding-size of the node.
- // The size is used for mem tracking, does not need to be exact
- size = estimateSize(n)
}
- // If we're using channel-based leaf-reporting, send to channel.
- // The leaf channel will be active only when there an active leaf-callback
- if c.leafCh != nil {
- c.leafCh <- &leaf{
- size: size,
- hash: common.BytesToHash(hash),
- node: n,
- path: path,
+ // We have the hash already, estimate the RLP encoding-size of the node.
+ // The size is used for mem tracking, does not need to be exact
+ var (
+ size = estimateSize(n)
+ nhash = common.BytesToHash(hash)
+ mnode = &memoryNode{
+ hash: nhash,
+ node: simplifyNode(n),
+ size: uint16(size),
}
- } else if db != nil {
- // No leaf-callback used, but there's still a database. Do serial
- // insertion
- db.insert(common.BytesToHash(hash), size, n)
- }
- return hash
-}
-
-// commitLoop does the actual insert + leaf callback for nodes.
-func (c *committer) commitLoop(db *Database) {
- for item := range c.leafCh {
- var (
- hash = item.hash
- size = item.size
- n = item.node
- )
- // We are pooling the trie nodes into an intermediate memory cache
- db.insert(hash, size, n)
-
- if c.onleaf != nil {
- switch n := n.(type) {
- case *shortNode:
- if child, ok := n.Val.(valueNode); ok {
- c.onleaf(nil, nil, child, hash, nil)
- }
- case *fullNode:
- // For children in range [0, 15], it's impossible
- // to contain valueNode. Only check the 17th child.
- if n.Children[16] != nil {
- c.onleaf(nil, nil, n.Children[16].(valueNode), hash, nil)
- }
+ spath = string(path)
+ )
+ // Insert the dirty nodes into internal store for accessing later.
+ db.write(spath, mnode)
+
+ // Collect the dirty node to nodeset.
+ c.nodes.add(spath, mnode)
+
+ // Collect the corresponding leaf node if it's required. We don't check
+ // full node since it's impossible to store value in fullNode. The key
+ // length of leaves should be exactly same.
+ if c.collectLeaf {
+ if sn, ok := n.(*shortNode); ok {
+ if val, ok := sn.Val.(valueNode); ok {
+ c.nodes.addLeaf(&leaf{blob: val, parent: nhash})
}
}
}
+ return hash
}
// estimateSize estimates the size of an rlp-encoded node, without actually
diff --git a/trie/database.go b/trie/database.go
index 8e1788a21239..2418ce73ac50 100644
--- a/trie/database.go
+++ b/trie/database.go
@@ -28,6 +28,7 @@ import (
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
@@ -305,14 +306,10 @@ func (db *Database) DiskDB() ethdb.KeyValueStore {
return db.diskdb
}
-// insert inserts a collapsed trie node into the memory database.
-// The blob size must be specified to allow proper size tracking.
+// insert inserts a simplified trie node into the memory database.
// All nodes inserted by this function will be reference tracked
// and in theory should only used for **trie nodes** insertion.
func (db *Database) insert(hash common.Hash, size int, node node) {
- db.lock.Lock()
- defer db.lock.Unlock()
-
// If the node's already cached, skip
if _, ok := db.dirties[hash]; ok {
return
@@ -321,7 +318,7 @@ func (db *Database) insert(hash common.Hash, size int, node node) {
// Create the cached entry for this node
entry := &cachedNode{
- node: simplifyNode(node),
+ node: node,
size: uint16(size),
flushPrev: db.newest,
}
@@ -763,6 +760,41 @@ func (c *cleaner) Delete(key []byte) error {
panic("not implemented")
}
+// Update inserts the dirty nodes in provided nodeset into database and
+// link the account trie with multiple storage tries if necessary.
+func (db *Database) Update(nodes *MergedNodeSet) error {
+ db.lock.Lock()
+ defer db.lock.Unlock()
+
+ // Insert dirty nodes into the database. In the same tree, it must be
+ // ensured that children are inserted first, then parent so that children
+ // can be linked with their parent correctly. The order of writing between
+ // different tries(account trie, storage tries) is not required.
+ for owner, subset := range nodes.nodes {
+ for _, path := range subset.paths {
+ n, ok := subset.nodes[path]
+ if !ok {
+ return fmt.Errorf("missing node %x %v", owner, path)
+ }
+ db.insert(n.hash, int(n.size), n.node)
+ }
+ }
+ // Link up the account trie and storage trie if the node points
+ // to an account trie leaf.
+ if set, present := nodes.nodes[common.Hash{}]; present {
+ for _, n := range set.leafs {
+ var account types.StateAccount
+ if err := rlp.DecodeBytes(n.blob, &account); err != nil {
+ return err
+ }
+ if account.Root != emptyRoot {
+ db.reference(account.Root, n.parent)
+ }
+ }
+ }
+ 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, common.StorageSize) {
diff --git a/trie/iterator.go b/trie/iterator.go
index e0006ee05e3b..e0ab350772cd 100644
--- a/trie/iterator.go
+++ b/trie/iterator.go
@@ -375,8 +375,7 @@ func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) {
}
}
}
- resolved, err := it.trie.resolveHash(hash, path)
- return resolved, err
+ return it.trie.nodes.readNode(it.trie.owner, common.BytesToHash(hash), path)
}
func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error) {
@@ -385,7 +384,7 @@ func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error)
return blob, nil
}
}
- return it.trie.resolveBlob(hash, path)
+ return it.trie.nodes.readBlob(it.trie.owner, common.BytesToHash(hash), path)
}
func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
diff --git a/trie/iterator_test.go b/trie/iterator_test.go
index e3e6d0e3a8fa..de90eb922326 100644
--- a/trie/iterator_test.go
+++ b/trie/iterator_test.go
@@ -59,7 +59,7 @@ func TestIterator(t *testing.T) {
all[val.k] = val.v
trie.Update([]byte(val.k), []byte(val.v))
}
- trie.Commit(nil)
+ trie.Commit(false)
found := make(map[string]string)
it := NewIterator(trie.NodeIterator(nil))
@@ -218,13 +218,13 @@ func TestDifferenceIterator(t *testing.T) {
for _, val := range testdata1 {
triea.Update([]byte(val.k), []byte(val.v))
}
- triea.Commit(nil)
+ triea.Commit(false)
trieb := newEmpty()
for _, val := range testdata2 {
trieb.Update([]byte(val.k), []byte(val.v))
}
- trieb.Commit(nil)
+ trieb.Commit(false)
found := make(map[string]string)
di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil))
@@ -254,13 +254,13 @@ func TestUnionIterator(t *testing.T) {
for _, val := range testdata1 {
triea.Update([]byte(val.k), []byte(val.v))
}
- triea.Commit(nil)
+ triea.Commit(false)
trieb := newEmpty()
for _, val := range testdata2 {
trieb.Update([]byte(val.k), []byte(val.v))
}
- trieb.Commit(nil)
+ trieb.Commit(false)
di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)})
it := NewIterator(di)
@@ -316,7 +316,8 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) {
for _, val := range testdata1 {
tr.Update([]byte(val.k), []byte(val.v))
}
- tr.Commit(nil)
+ _, nodes, _ := tr.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
if !memonly {
triedb.Commit(tr.Hash(), true, nil)
}
@@ -407,7 +408,8 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) {
for _, val := range testdata1 {
ctr.Update([]byte(val.k), []byte(val.v))
}
- root, _, _ := ctr.Commit(nil)
+ root, nodes, _ := ctr.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
if !memonly {
triedb.Commit(root, true, nil)
}
@@ -525,13 +527,16 @@ func makeLargeTestTrie() (*Database, *SecureTrie, *loggingDb) {
val = crypto.Keccak256(val)
trie.Update(key, val)
}
- trie.Commit(nil)
+ _, nodes, _ := trie.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
// Return the generated trie
return triedb, trie, logDb
}
// Tests that the node iterator indeed walks over the entire database contents.
func TestNodeIteratorLargeTrie(t *testing.T) {
+ t.SkipNow()
+
// Create some arbitrary test trie to iterate
db, trie, logDb := makeLargeTestTrie()
db.Cap(0) // flush everything
@@ -564,7 +569,8 @@ func TestIteratorNodeBlob(t *testing.T) {
all[val.k] = val.v
trie.Update([]byte(val.k), []byte(val.v))
}
- trie.Commit(nil)
+ _, nodes, _ := trie.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
triedb.Cap(0)
found := make(map[common.Hash][]byte)
diff --git a/trie/nodeset.go b/trie/nodeset.go
new file mode 100644
index 000000000000..90735ad28914
--- /dev/null
+++ b/trie/nodeset.go
@@ -0,0 +1,84 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package trie
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// NodeSet contains all dirty nodes collected during the commit operation.
+// Each node is keyed by path. It's not thread-safe to use.
+type NodeSet struct {
+ owner common.Hash // the identifier of the trie
+ paths []string // the path of dirty nodes, sort by insertion order
+ nodes map[string]*memoryNode // the map of dirty nodes, keyed by node path
+ leafs []*leaf // the list of dirty leafs
+}
+
+// NewNodeSet initializes an empty dirty node set.
+func NewNodeSet(owner common.Hash) *NodeSet {
+ return &NodeSet{
+ owner: owner,
+ nodes: make(map[string]*memoryNode),
+ }
+}
+
+// add caches node with provided path and node object.
+func (set *NodeSet) add(path string, node *memoryNode) {
+ set.paths = append(set.paths, path)
+ set.nodes[path] = node
+}
+
+// addLeaf caches the provided leaf node.
+func (set *NodeSet) addLeaf(node *leaf) {
+ set.leafs = append(set.leafs, node)
+}
+
+// Len returns the number of dirty nodes contained in the set.
+func (set *NodeSet) Len() int {
+ return len(set.nodes)
+}
+
+// MergedNodeSet represents a merged dirty node set for a group of tries.
+type MergedNodeSet struct {
+ nodes map[common.Hash]*NodeSet
+}
+
+// NewMergedNodeSet initializes an empty merged set.
+func NewMergedNodeSet() *MergedNodeSet {
+ return &MergedNodeSet{nodes: make(map[common.Hash]*NodeSet)}
+}
+
+// NewWithNodeSet constructs a merged nodeset with the provided single set.
+func NewWithNodeSet(set *NodeSet) *MergedNodeSet {
+ merged := NewMergedNodeSet()
+ merged.Merge(set)
+ return merged
+}
+
+// Merge merges the provided dirty nodes of a trie into the set. The assumption
+// is held that no duplicated set belonging to the same trie will be merged twice.
+func (set *MergedNodeSet) Merge(other *NodeSet) error {
+ _, present := set.nodes[other.owner]
+ if present {
+ return fmt.Errorf("duplicated trie %x", other.owner)
+ }
+ set.nodes[other.owner] = other
+ return nil
+}
diff --git a/trie/nodestore.go b/trie/nodestore.go
new file mode 100644
index 000000000000..40557d871fa7
--- /dev/null
+++ b/trie/nodestore.go
@@ -0,0 +1,170 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package trie
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// errUnexpectedNode is returned if the requested node with specified path is
+// not hash matched or marked as deleted.
+var errUnexpectedNode = errors.New("unexpected node")
+
+// memoryNode is all the information we know about a single cached trie node
+// in the memory.
+type memoryNode struct {
+ hash common.Hash // Node hash, computed by hashing rlp value
+ size uint16 // Byte size of the useful cached data
+ node node // Cached collapsed trie node, or raw rlp data
+}
+
+// memoryNodeSize is the raw size of a memoryNode data structure without any
+// node data included. It's an approximate size, but should be a lot better
+// than not counting them.
+var memoryNodeSize = int(reflect.TypeOf(memoryNode{}).Size())
+
+// rlp returns the raw rlp encoded blob of the cached trie node, either directly
+// from the cache, or by regenerating it from the collapsed node.
+func (n *memoryNode) rlp() []byte {
+ if n.node == nil {
+ return nil
+ }
+ if node, ok := n.node.(rawNode); ok {
+ return node
+ }
+ return nodeToBytes(n.node)
+}
+
+// obj returns the decoded and expanded trie node, either directly from the cache,
+// or by regenerating it from the rlp encoded blob.
+func (n *memoryNode) obj() node {
+ if n.node == nil {
+ return nil
+ }
+ if node, ok := n.node.(rawNode); ok {
+ return mustDecodeNode(n.hash[:], node)
+ }
+ return expandNode(n.hash[:], n.node)
+}
+
+// memorySize returns the total memory size used by this node.
+func (n *memoryNode) memorySize(key int) int {
+ return int(n.size) + memoryNodeSize + key
+}
+
+// nodeStore is built on the underlying node database with an additional
+// node cache. The dirty nodes will be cached here whenever trie commit
+// is performed to make them accessible. Nodes are keyed by node path
+// which is unique in the trie.
+//
+// nodeStore is not safe for concurrent use.
+type nodeStore struct {
+ db *Database
+ nodes map[string]*memoryNode
+}
+
+// readNode retrieves the node in canonical representation.
+// Returns an MissingNodeError error if the node is not found.
+func (s *nodeStore) readNode(owner common.Hash, hash common.Hash, path []byte) (node, error) {
+ // Load the node from the local cache first.
+ mn, ok := s.nodes[string(path)]
+ if ok {
+ if mn.hash == hash {
+ return mn.obj(), nil
+ }
+ // Bubble up an error if the trie node is not hash matched.
+ // It shouldn't happen at all.
+ return nil, fmt.Errorf("%w %x!=%x(%x %v)", errUnexpectedNode, mn.hash, hash, owner, path)
+ }
+ // Load the node from the underlying database then
+ if s.db == nil {
+ return nil, &MissingNodeError{Owner: owner, NodeHash: hash, Path: path}
+ }
+ n := s.db.node(hash)
+ if n != nil {
+ return n, nil
+ }
+ return nil, &MissingNodeError{Owner: owner, NodeHash: hash, Path: path}
+}
+
+// readBlob retrieves the node in rlp-encoded representation.
+// Returns an MissingNodeError error if the node is not found.
+func (s *nodeStore) readBlob(owner common.Hash, hash common.Hash, path []byte) ([]byte, error) {
+ // Load the node from the local cache first
+ mn, ok := s.nodes[string(path)]
+ if ok {
+ if mn.hash == hash {
+ return mn.rlp(), nil
+ }
+ // Bubble up an error if the trie node is not hash matched.
+ // It shouldn't happen at all.
+ return nil, fmt.Errorf("%w %x!=%x(%x %v)", errUnexpectedNode, mn.hash, hash, owner, path)
+ }
+ // Load the node from the underlying database then
+ if s.db == nil {
+ return nil, &MissingNodeError{Owner: owner, NodeHash: hash, Path: path}
+ }
+ blob, err := s.db.Node(hash)
+ if err == nil {
+ return blob, nil
+ }
+ return nil, &MissingNodeError{Owner: owner, NodeHash: hash, Path: path, err: err}
+}
+
+// write inserts a dirty node into the store. It happens in trie commit procedure.
+func (s *nodeStore) write(path string, node *memoryNode) {
+ s.nodes[path] = node
+}
+
+// copy deep copies the nodeStore and returns an independent handler but with
+// same content cached inside.
+func (s *nodeStore) copy() *nodeStore {
+ nodes := make(map[string]*memoryNode)
+ for k, n := range s.nodes {
+ nodes[k] = n
+ }
+ return &nodeStore{
+ db: s.db, // safe to copy directly.
+ nodes: nodes,
+ }
+}
+
+// size returns the total memory usage used by caching nodes internally.
+func (s *nodeStore) size() common.StorageSize {
+ var size common.StorageSize
+ for k, n := range s.nodes {
+ size += common.StorageSize(n.memorySize(len(k)))
+ }
+ return size
+}
+
+// newNodeStore initializes the nodeStore with the given node reader.
+func newNodeStore(db *Database) (*nodeStore, error) {
+ return &nodeStore{
+ db: db,
+ nodes: make(map[string]*memoryNode),
+ }, nil
+}
+
+// newMemoryStore initializes the pure in-memory store.
+func newMemoryStore() *nodeStore {
+ return &nodeStore{nodes: make(map[string]*memoryNode)}
+}
diff --git a/trie/nodestore_test.go b/trie/nodestore_test.go
new file mode 100644
index 000000000000..7bf532c242b5
--- /dev/null
+++ b/trie/nodestore_test.go
@@ -0,0 +1,115 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package trie
+
+import (
+ "bytes"
+ "math/rand"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+func TestNodeStoreCopy(t *testing.T) {
+ // Insert a batch of entries into trie
+ triedb := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(triedb)
+ vals := []struct{ k, v string }{
+ {"do", "verb"},
+ {"ether", "wookiedoo"},
+ {"horse", "stallion"},
+ {"shaman", "horse"},
+ {"doge", "coin"},
+ {"dog", "puppy"},
+ {"somethingveryoddindeedthis is", "myothernodedata"},
+ }
+ for _, val := range vals {
+ trie.Update([]byte(val.k), []byte(val.v))
+ }
+ trie.Commit(false) // all nodes should be committed into store
+
+ seen := make(map[string][]byte)
+ iter := trie.NodeIterator(nil)
+ for iter.Next(true) {
+ if iter.Hash() != (common.Hash{}) {
+ seen[string(iter.Path())] = common.CopyBytes(iter.NodeBlob())
+ }
+ }
+
+ // Create the node store copy, ensure all nodes can be retrieved back.
+ store := trie.nodes
+ storeCopy := store.copy()
+
+ for path, blob := range seen {
+ blob1, err1 := store.readBlob(common.Hash{}, crypto.Keccak256Hash(blob), []byte(path))
+ blob2, err2 := storeCopy.readBlob(common.Hash{}, crypto.Keccak256Hash(blob), []byte(path))
+ if err1 != nil || err2 != nil {
+ t.Fatalf("Failed to read node, %v, %v", err1, err2)
+ }
+ if !bytes.Equal(blob1, blob) || !bytes.Equal(blob2, blob) {
+ t.Fatal("Node is mismatched")
+ }
+ }
+ // Flush items into the origin reader, it shouldn't affect the copy
+ var (
+ node = randomNode()
+ path = randomHash()
+ )
+ store.write(string(path.Bytes()), node)
+ blob, err := store.readBlob(common.Hash{}, node.hash, path.Bytes())
+ if err != nil {
+ t.Fatalf("Failed to read blob %v", err)
+ }
+ if !bytes.Equal(blob, node.rlp()) {
+ t.Fatal("Unexpected node")
+ }
+ _, err = storeCopy.readBlob(common.Hash{}, node.hash, path.Bytes())
+ missing, ok := err.(*MissingNodeError)
+ if !ok || missing.NodeHash != node.hash {
+ t.Fatal("didn't hit missing node, got", err)
+ }
+
+ // Create a new copy, it should retrieve the node correctly
+ copyTwo := store.copy()
+ blob, err = copyTwo.readBlob(common.Hash{}, node.hash, path.Bytes())
+ if err != nil {
+ t.Fatalf("Failed to read blob %v", err)
+ }
+ if !bytes.Equal(blob, node.rlp()) {
+ t.Fatal("Unexpected node")
+ }
+}
+
+// randomHash generates a random blob of data and returns it as a hash.
+func randomHash() common.Hash {
+ var hash common.Hash
+ if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil {
+ panic(err)
+ }
+ return hash
+}
+
+func randomNode() *memoryNode {
+ val := randBytes(100)
+ return &memoryNode{
+ hash: crypto.Keccak256Hash(val),
+ node: rawNode(val),
+ size: 100,
+ }
+}
diff --git a/trie/proof.go b/trie/proof.go
index 9bf9107562fa..d1f16caf429a 100644
--- a/trie/proof.go
+++ b/trie/proof.go
@@ -35,9 +35,12 @@ import (
// with the node that proves the absence of the key.
func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
// Collect all nodes on the path to key.
+ var (
+ prefix []byte
+ nodes []node
+ tn = t.root
+ )
key = keybytesToHex(key)
- var nodes []node
- tn := t.root
for len(key) > 0 && tn != nil {
switch n := tn.(type) {
case *shortNode:
@@ -46,16 +49,18 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e
tn = nil
} else {
tn = n.Val
+ prefix = append(prefix, n.Key...)
key = key[len(n.Key):]
}
nodes = append(nodes, n)
case *fullNode:
tn = n.Children[key[0]]
+ prefix = append(prefix, key[0])
key = key[1:]
nodes = append(nodes, n)
case hashNode:
var err error
- tn, err = t.resolveHash(n, nil)
+ tn, err = t.resolveHash(n, prefix)
if err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
return err
@@ -553,7 +558,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key
}
// Rebuild the trie with the leaf stream, the shape of trie
// should be same with the original one.
- tr := newWithRootNode(root)
+ tr := &Trie{root: root, nodes: newMemoryStore()}
if empty {
tr.root = nil
}
diff --git a/trie/secure_trie.go b/trie/secure_trie.go
index 967194df9628..0ea533732536 100644
--- a/trie/secure_trie.go
+++ b/trie/secure_trie.go
@@ -160,12 +160,12 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte {
return t.preimages.preimage(common.BytesToHash(shaKey))
}
-// Commit writes all nodes and the secure hash pre-images to the trie's database.
-// Nodes are stored with their sha3 hash as the key.
-//
-// Committing flushes nodes from memory. Subsequent Get calls will load nodes
-// from the database.
-func (t *SecureTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) {
+// Commit collects all dirty nodes in the trie and replace them with the
+// corresponding node hash. All collected nodes(including dirty leaves if
+// collectLeaf is true) will be encapsulated into a nodeset for return.
+// The returned nodeset can be nil if the trie is clean(nothing to commit).
+// All cached preimages will be also flushed if preimages recording is enabled.
+func (t *SecureTrie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) {
// Write all the pre-images to the actual disk database
if len(t.getSecKeyCache()) > 0 {
if t.preimages != nil {
@@ -178,7 +178,7 @@ func (t *SecureTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) {
t.secKeyCache = make(map[string][]byte)
}
// Commit the trie to its intermediate node database
- return t.trie.Commit(onleaf)
+ return t.trie.Commit(collectLeaf)
}
// Hash returns the root hash of SecureTrie. It does not write to the
diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go
index beea5845ad0d..c18d39954349 100644
--- a/trie/secure_trie_test.go
+++ b/trie/secure_trie_test.go
@@ -57,7 +57,7 @@ func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) {
trie.Update(key, val)
}
}
- trie.Commit(nil)
+ trie.Commit(false)
// Return the generated trie
return triedb, trie, content
@@ -135,7 +135,7 @@ func TestSecureTrieConcurrency(t *testing.T) {
tries[index].Update(key, val)
}
}
- tries[index].Commit(nil)
+ tries[index].Commit(false)
}(i)
}
// Wait for all threads to finish
diff --git a/trie/sync_test.go b/trie/sync_test.go
index 472c31a63b9b..afc202ef3b8e 100644
--- a/trie/sync_test.go
+++ b/trie/sync_test.go
@@ -18,6 +18,7 @@ package trie
import (
"bytes"
+ "fmt"
"testing"
"github.com/ethereum/go-ethereum/common"
@@ -50,8 +51,13 @@ func makeTestTrie() (*Database, *SecureTrie, map[string][]byte) {
trie.Update(key, val)
}
}
- trie.Commit(nil)
-
+ _, nodes, err := trie.Commit(false)
+ if err != nil {
+ panic(fmt.Errorf("failed to commit trie %v", err))
+ }
+ if err := triedb.Update(NewWithNodeSet(nodes)); err != nil {
+ panic(fmt.Errorf("failed to commit db %v", err))
+ }
// Return the generated trie
return triedb, trie, content
}
diff --git a/trie/trie.go b/trie/trie.go
index 1e168402ad95..3189602b23ca 100644
--- a/trie/trie.go
+++ b/trie/trie.go
@@ -21,10 +21,8 @@ import (
"bytes"
"errors"
"fmt"
- "sync"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
@@ -55,23 +53,27 @@ var (
// for extracting the raw states(leaf nodes) with corresponding paths.
type LeafCallback func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error
-// Trie is a Merkle Patricia Trie.
-// The zero value is an empty trie with no database.
-// Use New to create a trie that sits on top of a database.
+// Trie is a Merkle Patricia Trie. Use New to create a trie that sits on
+// top of a database. Whenever trie performs a commit operation, the generated
+// dirty nodes will be cached in the internal store. It's users' responsibility
+// to manage the memory usage and re-create trie if necessary in order to avoid
+// out-of-memory issue.
//
// Trie is not safe for concurrent use.
type Trie struct {
- db *Database
root node
owner common.Hash
// Keep track of the number leaves which have been inserted since the last
// hashing operation. This number will not directly map to the number of
- // actually unhashed nodes
+ // actually unhashed nodes.
unhashed int
- // tracer is the state diff tracer can be used to track newly added/deleted
- // trie node. It will be reset after each commit operation.
+ // nodes is the place to cache dirty nodes and access trie node from.
+ nodes *nodeStore
+
+ // tracer is the tool to track the trie changes.
+ // It will be reset after each commit operation.
tracer *tracer
}
@@ -83,10 +85,10 @@ func (t *Trie) newFlag() nodeFlag {
// Copy returns a copy of Trie.
func (t *Trie) Copy() *Trie {
return &Trie{
- db: t.db,
root: t.root,
owner: t.owner,
unhashed: t.unhashed,
+ nodes: t.nodes.copy(),
tracer: t.tracer.copy(),
}
}
@@ -99,33 +101,13 @@ func (t *Trie) Copy() *Trie {
// New will panic if db is nil and returns a MissingNodeError if root does
// not exist in the database. Accessing the trie loads nodes from db on demand.
func New(owner common.Hash, root common.Hash, db *Database) (*Trie, error) {
- return newTrie(owner, root, db)
-}
-
-// NewEmpty is a shortcut to create empty tree. It's mostly used in tests.
-func NewEmpty(db *Database) *Trie {
- tr, _ := newTrie(common.Hash{}, common.Hash{}, db)
- return tr
-}
-
-// newWithRootNode initializes the trie with the given root node.
-// It's only used by range prover.
-func newWithRootNode(root node) *Trie {
- return &Trie{
- root: root,
- //tracer: newTracer(),
- db: NewDatabase(rawdb.NewMemoryDatabase()),
- }
-}
-
-// newTrie is the internal function used to construct the trie with given parameters.
-func newTrie(owner common.Hash, root common.Hash, db *Database) (*Trie, error) {
- if db == nil {
- panic("trie.New called without a database")
+ store, err := newNodeStore(db)
+ if err != nil {
+ return nil, err
}
trie := &Trie{
- db: db,
owner: owner,
+ nodes: store,
//tracer: newTracer(),
}
if root != (common.Hash{}) && root != emptyRoot {
@@ -138,6 +120,12 @@ func newTrie(owner common.Hash, root common.Hash, db *Database) (*Trie, error) {
return trie, nil
}
+// NewEmpty is a shortcut to create empty tree. It's mostly used in tests.
+func NewEmpty(db *Database) *Trie {
+ tr, _ := New(common.Hash{}, common.Hash{}, db)
+ return tr
+}
+
// NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at
// the key after the given start key.
func (t *Trie) NodeIterator(start []byte) NodeIterator {
@@ -236,7 +224,7 @@ func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, new
if hash == nil {
return nil, origNode, 0, errors.New("non-consensus node")
}
- blob, err := t.db.Node(common.BytesToHash(hash))
+ blob, err := t.nodes.readBlob(t.owner, common.BytesToHash(hash), path)
return blob, origNode, 1, err
}
// Path still needs to be traversed, descend into children
@@ -512,7 +500,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
// shortNode{..., shortNode{...}}. Since the entry
// might not be loaded yet, resolve it just for this
// check.
- cnode, err := t.resolve(n.Children[pos], prefix)
+ cnode, err := t.resolve(n.Children[pos], append(prefix, byte(pos)))
if err != nil {
return false, nil, err
}
@@ -572,21 +560,10 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) {
return n, nil
}
+// resolveHash loads node from the underlying store with the given
+// node hash and path prefix.
func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
- hash := common.BytesToHash(n)
- if node := t.db.node(hash); node != nil {
- return node, nil
- }
- return nil, &MissingNodeError{Owner: t.owner, NodeHash: hash, Path: prefix}
-}
-
-func (t *Trie) resolveBlob(n hashNode, prefix []byte) ([]byte, error) {
- hash := common.BytesToHash(n)
- blob, _ := t.db.Node(hash)
- if len(blob) != 0 {
- return blob, nil
- }
- return nil, &MissingNodeError{Owner: t.owner, NodeHash: hash, Path: prefix}
+ return t.nodes.readNode(t.owner, common.BytesToHash(n), prefix)
}
// Hash returns the root hash of the trie. It does not write to the
@@ -597,56 +574,39 @@ func (t *Trie) Hash() common.Hash {
return common.BytesToHash(hash.(hashNode))
}
-// Commit writes all nodes to the trie's memory database, tracking the internal
-// and external (for account tries) references.
-func (t *Trie) Commit(onleaf LeafCallback) (common.Hash, int, error) {
- if t.db == nil {
- panic("commit called on trie with nil database")
- }
+// Commit collects all dirty nodes in the trie and replace them with the
+// corresponding node hash. All collected nodes(including dirty leaves if
+// collectLeaf is true) will be encapsulated into a nodeset for return.
+// The returned nodeset can be nil if the trie is clean(nothing to commit).
+// Note that all dirty nodes will also be cached in the nodestore inside
+// the trie to ensure these nodes can still be accessed after the commit.
+func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) {
defer t.tracer.reset()
if t.root == nil {
- return emptyRoot, 0, nil
+ return emptyRoot, nil, nil
}
// Derive the hash for all dirty nodes first. We hold the assumption
// in the following procedure that all nodes are hashed.
rootHash := t.Hash()
- h := newCommitter()
+
+ h := newCommitter(t.owner, collectLeaf)
defer returnCommitterToPool(h)
- // Do a quick check if we really need to commit, before we spin
- // up goroutines. This can happen e.g. if we load a trie for reading storage
- // values, but don't write to it.
+ // Do a quick check if we really need to commit. This can happen e.g.
+ // if we load a trie for reading storage values, but don't write to it.
if hashedNode, dirty := t.root.cache(); !dirty {
// Replace the root node with the origin hash in order to
// ensure all resolved nodes are dropped after the commit.
t.root = hashedNode
- return rootHash, 0, nil
- }
- var wg sync.WaitGroup
- if onleaf != nil {
- h.onleaf = onleaf
- h.leafCh = make(chan *leaf, leafChanSize)
- wg.Add(1)
- go func() {
- defer wg.Done()
- h.commitLoop(t.db)
- }()
- }
- newRoot, committed, err := h.Commit(t.root, t.db)
- if onleaf != nil {
- // The leafch is created in newCommitter if there was an onleaf callback
- // provided. The commitLoop only _reads_ from it, and the commit
- // operation was the sole writer. Therefore, it's safe to close this
- // channel here.
- close(h.leafCh)
- wg.Wait()
+ return rootHash, nil, nil
}
+ newRoot, nodes, err := h.Commit(t.root, t.nodes)
if err != nil {
- return common.Hash{}, 0, err
+ return common.Hash{}, nil, err
}
t.root = newRoot
- return rootHash, committed, nil
+ return rootHash, nodes, nil
}
// hashRoot calculates the root hash of the given trie
@@ -668,9 +628,10 @@ func (t *Trie) Reset() {
t.owner = common.Hash{}
t.unhashed = 0
t.tracer.reset()
+ t.nodes = nil
}
-// Owner returns the associated trie owner.
-func (t *Trie) Owner() common.Hash {
- return t.owner
+// Size returns the total memory usage used by caching nodes internally.
+func (t *Trie) Size() common.StorageSize {
+ return t.nodes.size()
}
diff --git a/trie/trie_test.go b/trie/trie_test.go
index 135e94e3d0a3..1959cd78cc4a 100644
--- a/trie/trie_test.go
+++ b/trie/trie_test.go
@@ -91,7 +91,8 @@ func testMissingNode(t *testing.T, memonly bool) {
trie := NewEmpty(triedb)
updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer")
updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf")
- root, _, _ := trie.Commit(nil)
+ root, nodes, _ := trie.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
if !memonly {
triedb.Commit(root, true, nil)
}
@@ -173,7 +174,7 @@ func TestInsert(t *testing.T) {
updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab")
- root, _, err := trie.Commit(nil)
+ root, _, err := trie.Commit(false)
if err != nil {
t.Fatalf("commit error: %v", err)
}
@@ -202,7 +203,7 @@ func TestGet(t *testing.T) {
if i == 1 {
return
}
- trie.Commit(nil)
+ trie.Commit(false)
}
}
@@ -258,7 +259,8 @@ func TestEmptyValues(t *testing.T) {
}
func TestReplication(t *testing.T) {
- trie := newEmpty()
+ triedb := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(triedb)
vals := []struct{ k, v string }{
{"do", "verb"},
{"ether", "wookiedoo"},
@@ -271,13 +273,14 @@ func TestReplication(t *testing.T) {
for _, val := range vals {
updateString(trie, val.k, val.v)
}
- exp, _, err := trie.Commit(nil)
+ exp, nodes, err := trie.Commit(false)
if err != nil {
t.Fatalf("commit error: %v", err)
}
+ triedb.Update(NewWithNodeSet(nodes))
// create a new trie on top of the database and check that lookups work.
- trie2, err := New(common.Hash{}, exp, trie.db)
+ trie2, err := New(common.Hash{}, exp, triedb)
if err != nil {
t.Fatalf("can't recreate trie at %x: %v", exp, err)
}
@@ -286,7 +289,7 @@ func TestReplication(t *testing.T) {
t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v)
}
}
- hash, _, err := trie2.Commit(nil)
+ hash, _, err := trie2.Commit(false)
if err != nil {
t.Fatalf("commit error: %v", err)
}
@@ -434,16 +437,26 @@ func runRandTest(rt randTest) bool {
rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want)
}
case opCommit:
- _, _, rt[i].err = tr.Commit(nil)
+ _, nodes, err := tr.Commit(false)
+ if err != nil {
+ rt[i].err = err
+ return false
+ }
+ if nodes != nil {
+ triedb.Update(NewWithNodeSet(nodes))
+ }
origTrie = tr.Copy()
case opHash:
tr.Hash()
case opReset:
- hash, _, err := tr.Commit(nil)
+ hash, nodes, err := tr.Commit(false)
if err != nil {
rt[i].err = err
return false
}
+ if nodes != nil {
+ triedb.Update(NewWithNodeSet(nodes))
+ }
newtr, err := New(common.Hash{}, hash, triedb)
if err != nil {
rt[i].err = err
@@ -541,10 +554,11 @@ func BenchmarkUpdateLE(b *testing.B) { benchUpdate(b, binary.LittleEndian) }
const benchElemCount = 20000
func benchGet(b *testing.B, commit bool) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
+ triedb := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(triedb)
if commit {
- tmpdb := tempDB(b)
- trie = NewEmpty(tmpdb)
+ triedb = tempDB(b)
+ trie = NewEmpty(triedb)
}
k := make([]byte, 32)
for i := 0; i < benchElemCount; i++ {
@@ -553,7 +567,7 @@ func benchGet(b *testing.B, commit bool) {
}
binary.LittleEndian.PutUint64(k, benchElemCount/2)
if commit {
- trie.Commit(nil)
+ trie.Commit(false)
}
b.ResetTimer()
@@ -563,7 +577,7 @@ func benchGet(b *testing.B, commit bool) {
b.StopTimer()
if commit {
- ldb := trie.db.diskdb.(*leveldb.Database)
+ ldb := triedb.diskdb.(*leveldb.Database)
ldb.Close()
os.RemoveAll(ldb.Path())
}
@@ -621,19 +635,14 @@ func BenchmarkHash(b *testing.B) {
// insert into the trie before measuring the hashing.
func BenchmarkCommitAfterHash(b *testing.B) {
b.Run("no-onleaf", func(b *testing.B) {
- benchmarkCommitAfterHash(b, nil)
+ benchmarkCommitAfterHash(b, false)
})
- var a types.StateAccount
- onleaf := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash, parentPath []byte) error {
- rlp.DecodeBytes(leaf, &a)
- return nil
- }
b.Run("with-onleaf", func(b *testing.B) {
- benchmarkCommitAfterHash(b, onleaf)
+ benchmarkCommitAfterHash(b, true)
})
}
-func benchmarkCommitAfterHash(b *testing.B, onleaf LeafCallback) {
+func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) {
// Make the random benchmark deterministic
addresses, accounts := makeAccounts(b.N)
trie := newEmpty()
@@ -644,7 +653,7 @@ func benchmarkCommitAfterHash(b *testing.B, onleaf LeafCallback) {
trie.Hash()
b.ResetTimer()
b.ReportAllocs()
- trie.Commit(onleaf)
+ trie.Commit(collectLeaf)
}
func TestTinyTrie(t *testing.T) {
@@ -663,7 +672,7 @@ func TestTinyTrie(t *testing.T) {
if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root {
t.Errorf("3: got %x, exp %x", root, exp)
}
- checktr := NewEmpty(trie.db)
+ checktr := newEmpty()
it := NewIterator(trie.NodeIterator(nil))
for it.Next() {
checktr.Update(it.Key, it.Value)
@@ -682,13 +691,13 @@ func TestCommitAfterHash(t *testing.T) {
}
// Insert the accounts into the trie and hash it
trie.Hash()
- trie.Commit(nil)
+ trie.Commit(false)
root := trie.Hash()
exp := common.HexToHash("72f9d3f3fe1e1dd7b8936442e7642aef76371472d94319900790053c493f3fe6")
if exp != root {
t.Errorf("got %x, exp %x", root, exp)
}
- root, _, _ = trie.Commit(nil)
+ root, _, _ = trie.Commit(false)
if exp != root {
t.Errorf("got %x, exp %x", root, exp)
}
@@ -797,7 +806,8 @@ func TestCommitSequence(t *testing.T) {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
}
// Flush trie -> database
- root, _, _ := trie.Commit(nil)
+ root, nodes, _ := trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
// Flush memdb -> disk (sponge)
db.Commit(root, false, func(c common.Hash) {
// And spongify the callback-order
@@ -849,7 +859,8 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
trie.Update(key, val)
}
// Flush trie -> database
- root, _, _ := trie.Commit(nil)
+ root, nodes, _ := trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
// Flush memdb -> disk (sponge)
db.Commit(root, false, func(c common.Hash) {
// And spongify the callback-order
@@ -875,7 +886,7 @@ func TestCommitSequenceStackTrie(t *testing.T) {
stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"}
stTrie := NewStackTrie(stackTrieSponge)
// Fill the trie with elements
- for i := 1; i < count; i++ {
+ for i := 0; i < count; i++ {
// For the stack trie, we need to do inserts in proper order
key := make([]byte, 32)
binary.BigEndian.PutUint64(key, uint64(i))
@@ -891,8 +902,9 @@ func TestCommitSequenceStackTrie(t *testing.T) {
stTrie.TryUpdate(key, val)
}
// Flush trie -> database
- root, _, _ := trie.Commit(nil)
+ root, nodes, _ := trie.Commit(false)
// Flush memdb -> disk (sponge)
+ db.Update(NewWithNodeSet(nodes))
db.Commit(root, false, nil)
// And flush stacktrie -> disk
stRoot, err := stTrie.Commit()
@@ -936,8 +948,9 @@ func TestCommitSequenceSmallRoot(t *testing.T) {
trie.TryUpdate(key, []byte{0x1})
stTrie.TryUpdate(key, []byte{0x1})
// Flush trie -> database
- root, _, _ := trie.Commit(nil)
+ root, nodes, _ := trie.Commit(false)
// Flush memdb -> disk (sponge)
+ db.Update(NewWithNodeSet(nodes))
db.Commit(root, false, nil)
// And flush stacktrie -> disk
stRoot, err := stTrie.Commit()
@@ -1057,7 +1070,7 @@ func benchmarkCommitAfterHashFixedSize(b *testing.B, addresses [][20]byte, accou
// Insert the accounts into the trie and hash it
trie.Hash()
b.StartTimer()
- trie.Commit(nil)
+ trie.Commit(false)
b.StopTimer()
}
@@ -1102,14 +1115,16 @@ func BenchmarkDerefRootFixedSize(b *testing.B) {
func benchmarkDerefRootFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
b.ReportAllocs()
- trie := newEmpty()
+ triedb := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(triedb)
for i := 0; i < len(addresses); i++ {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
}
h := trie.Hash()
- trie.Commit(nil)
+ _, nodes, _ := trie.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
b.StartTimer()
- trie.db.Dereference(h)
+ triedb.Dereference(h)
b.StopTimer()
}
diff --git a/trie/util_test.go b/trie/util_test.go
index 589eca62423a..cefc4ac767d5 100644
--- a/trie/util_test.go
+++ b/trie/util_test.go
@@ -66,7 +66,7 @@ func TestTrieTracer(t *testing.T) {
}
// Commit the changes
- trie.Commit(nil)
+ trie.Commit(false)
// Delete all the elements, check deletion set
for _, val := range vals {