Commit d926bf2c authored by Péter Szilágyi's avatar Péter Szilágyi Committed by Felix Lange

trie: cache collapsed tries node, not rlp blobs (#16876)

The current trie memory database/cache that we do pruning on stores
trie nodes as binary rlp encoded blobs, and also stores the node
relationships/references for GC purposes. However, most of the trie
nodes (everything apart from a value node) is in essence just a
collection of references.

This PR switches out the RLP encoded trie blobs with the
collapsed-but-not-serialized trie nodes. This permits most of the
references to be recovered from within the node data structure,
avoiding the need to track them a second time (expensive memory wise).
parent 8db8d074
...@@ -672,7 +672,7 @@ func (bc *BlockChain) Stop() { ...@@ -672,7 +672,7 @@ func (bc *BlockChain) Stop() {
} }
} }
for !bc.triegc.Empty() { for !bc.triegc.Empty() {
triedb.Dereference(bc.triegc.PopItem().(common.Hash), common.Hash{}) triedb.Dereference(bc.triegc.PopItem().(common.Hash))
} }
if size, _ := triedb.Size(); size != 0 { if size, _ := triedb.Size(); size != 0 {
log.Error("Dangling trie nodes after full cleanup") log.Error("Dangling trie nodes after full cleanup")
...@@ -947,7 +947,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types. ...@@ -947,7 +947,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
bc.triegc.Push(root, number) bc.triegc.Push(root, number)
break break
} }
triedb.Dereference(root.(common.Hash), common.Hash{}) triedb.Dereference(root.(common.Hash))
} }
} }
} }
......
...@@ -1313,8 +1313,8 @@ func TestTrieForkGC(t *testing.T) { ...@@ -1313,8 +1313,8 @@ func TestTrieForkGC(t *testing.T) {
} }
// Dereference all the recent tries and ensure no past trie is left in // Dereference all the recent tries and ensure no past trie is left in
for i := 0; i < triesInMemory; i++ { for i := 0; i < triesInMemory; i++ {
chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root(), common.Hash{}) chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root())
chain.stateCache.TrieDB().Dereference(forks[len(blocks)-1-i].Root(), common.Hash{}) chain.stateCache.TrieDB().Dereference(forks[len(blocks)-1-i].Root())
} }
if len(chain.stateCache.TrieDB().Nodes()) > 0 { if len(chain.stateCache.TrieDB().Nodes()) > 0 {
t.Fatalf("stale tries still alive after garbase collection") t.Fatalf("stale tries still alive after garbase collection")
......
...@@ -596,7 +596,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) ...@@ -596,7 +596,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error)
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 {
s.db.TrieDB().Insert(common.BytesToHash(stateObject.CodeHash()), stateObject.code) s.db.TrieDB().InsertBlob(common.BytesToHash(stateObject.CodeHash()), stateObject.code)
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.
......
...@@ -297,7 +297,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl ...@@ -297,7 +297,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
database.TrieDB().Reference(root, common.Hash{}) database.TrieDB().Reference(root, common.Hash{})
} }
// Dereference all past tries we ourselves are done working with // Dereference all past tries we ourselves are done working with
database.TrieDB().Dereference(proot, common.Hash{}) database.TrieDB().Dereference(proot)
proot = root proot = root
// TODO(karalabe): Do we need the preimages? Won't they accumulate too much? // TODO(karalabe): Do we need the preimages? Won't they accumulate too much?
...@@ -320,7 +320,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl ...@@ -320,7 +320,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
done[uint64(result.Block)] = result done[uint64(result.Block)] = result
// Dereference any paret tries held in memory by this task // Dereference any paret tries held in memory by this task
database.TrieDB().Dereference(res.rootref, common.Hash{}) database.TrieDB().Dereference(res.rootref)
// Stream completed traces to the user, aborting on the first error // Stream completed traces to the user, aborting on the first error
for result, ok := done[next]; ok; result, ok = done[next] { for result, ok := done[next]; ok; result, ok = done[next] {
...@@ -526,7 +526,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* ...@@ -526,7 +526,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
return nil, err return nil, err
} }
database.TrieDB().Reference(root, common.Hash{}) database.TrieDB().Reference(root, common.Hash{})
database.TrieDB().Dereference(proot, common.Hash{}) database.TrieDB().Dereference(proot)
proot = root proot = root
} }
nodes, imgs := database.TrieDB().Size() nodes, imgs := database.TrieDB().Size()
......
This diff is collapsed.
...@@ -137,9 +137,6 @@ func (h *hasher) hashChildren(original node, db *Database) (node, node, error) { ...@@ -137,9 +137,6 @@ func (h *hasher) hashChildren(original node, db *Database) (node, node, error) {
return original, original, err return original, original, err
} }
} }
if collapsed.Val == nil {
collapsed.Val = valueNode(nil) // Ensure that nil children are encoded as empty strings.
}
return collapsed, cached, nil return collapsed, cached, nil
case *fullNode: case *fullNode:
...@@ -152,14 +149,9 @@ func (h *hasher) hashChildren(original node, db *Database) (node, node, error) { ...@@ -152,14 +149,9 @@ func (h *hasher) hashChildren(original node, db *Database) (node, node, error) {
if err != nil { if err != nil {
return original, original, err return original, original, err
} }
} else {
collapsed.Children[i] = valueNode(nil) // Ensure that nil children are encoded as empty strings.
} }
} }
cached.Children[16] = n.Children[16] cached.Children[16] = n.Children[16]
if collapsed.Children[16] == nil {
collapsed.Children[16] = valueNode(nil)
}
return collapsed, cached, nil return collapsed, cached, nil
default: default:
...@@ -192,34 +184,22 @@ func (h *hasher) store(n node, db *Database, force bool) (node, error) { ...@@ -192,34 +184,22 @@ func (h *hasher) store(n node, db *Database, force bool) (node, error) {
if db != 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
db.lock.Lock()
hash := common.BytesToHash(hash) hash := common.BytesToHash(hash)
db.insert(hash, h.tmp)
// Track all direct parent->child node references db.lock.Lock()
switch n := n.(type) { db.insert(hash, h.tmp, n)
case *shortNode:
if child, ok := n.Val.(hashNode); ok {
db.reference(common.BytesToHash(child), hash)
}
case *fullNode:
for i := 0; i < 16; i++ {
if child, ok := n.Children[i].(hashNode); ok {
db.reference(common.BytesToHash(child), hash)
}
}
}
db.lock.Unlock() db.lock.Unlock()
// Track external references from account->storage trie // Track external references from account->storage trie
if h.onleaf != nil { if h.onleaf != nil {
switch n := n.(type) { switch n := n.(type) {
case *shortNode: case *shortNode:
if child, ok := n.Val.(valueNode); ok && child != nil { if child, ok := n.Val.(valueNode); ok {
h.onleaf(child, hash) h.onleaf(child, hash)
} }
case *fullNode: case *fullNode:
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
if child, ok := n.Children[i].(valueNode); ok && child != nil { if child, ok := n.Children[i].(valueNode); ok {
h.onleaf(child, hash) h.onleaf(child, hash)
} }
} }
......
...@@ -47,9 +47,22 @@ type ( ...@@ -47,9 +47,22 @@ type (
valueNode []byte valueNode []byte
) )
// nilValueNode is used when collapsing internal trie nodes for hashing, since
// unset children need to serialize correctly.
var nilValueNode = valueNode(nil)
// EncodeRLP encodes a full node into the consensus RLP format. // EncodeRLP encodes a full node into the consensus RLP format.
func (n *fullNode) EncodeRLP(w io.Writer) error { func (n *fullNode) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, n.Children) var nodes [17]node
for i, child := range n.Children {
if child != nil {
nodes[i] = child
} else {
nodes[i] = nilValueNode
}
}
return rlp.Encode(w, nodes)
} }
func (n *fullNode) copy() *fullNode { copy := *n; return &copy } func (n *fullNode) copy() *fullNode { copy := *n; return &copy }
......
...@@ -433,12 +433,10 @@ func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { ...@@ -433,12 +433,10 @@ func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
cacheMissCounter.Inc(1) cacheMissCounter.Inc(1)
hash := common.BytesToHash(n) hash := common.BytesToHash(n)
if node := t.db.node(hash, t.cachegen); node != nil {
enc, err := t.db.Node(hash) return node, nil
if err != nil || enc == nil {
return nil, &MissingNodeError{NodeHash: hash, Path: prefix}
} }
return mustDecodeNode(n, enc, t.cachegen), nil return nil, &MissingNodeError{NodeHash: hash, Path: prefix}
} }
// Root returns the root hash of the trie. // Root returns the root hash of the trie.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment