trie: cache rlp encoded blobs

This commit is contained in:
MariusVanDerWijden 2026-07-08 15:47:44 +02:00
parent 4d2181aa41
commit f674fdc18f
No known key found for this signature in database
8 changed files with 44 additions and 13 deletions

View file

@ -149,8 +149,18 @@ func (c *committer) store(path []byte, n node) node {
return n return n
} }
// Collect the dirty node to nodeset for return. // Collect the dirty node to nodeset for return.
var blob []byte
switch n := n.(type) {
case *shortNode:
blob = n.flags.blob
case *fullNode:
blob = n.flags.blob
}
if len(blob) == 0 {
blob = nodeToBytes(n)
}
nhash := common.BytesToHash(hash) nhash := common.BytesToHash(hash)
c.nodes.AddNode(path, trienode.NewNodeWithPrev(nhash, nodeToBytes(n), c.tracer.Get(path))) c.nodes.AddNode(path, trienode.NewNodeWithPrev(nhash, blob, c.tracer.Get(path)))
// Collect the corresponding leaf node if it's required. We don't check // 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 // full node since it's impossible to store value in fullNode. The key

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"sync" "sync"
"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"
) )
@ -28,10 +29,11 @@ import (
// hasher is a type used for the trie Hash operation. A hasher has some // hasher is a type used for the trie Hash operation. A hasher has some
// internal preallocated temp space // internal preallocated temp space
type hasher struct { type hasher struct {
sha crypto.KeccakState sha crypto.KeccakState
tmp []byte tmp []byte
encbuf rlp.EncoderBuffer encbuf rlp.EncoderBuffer
parallel bool // Whether to use parallel threads when hashing parallel bool // Whether to use parallel threads when hashing
cacheBlob bool // Whether to stash each node's encoding for the committer
} }
// hasherPool holds pureHashers // hasherPool holds pureHashers
@ -45,9 +47,10 @@ var hasherPool = sync.Pool{
}, },
} }
func newHasher(parallel bool) *hasher { func newHasher(parallel, cacheBlob bool) *hasher {
h := hasherPool.Get().(*hasher) h := hasherPool.Get().(*hasher)
h.parallel = parallel h.parallel = parallel
h.cacheBlob = cacheBlob
return h return h
} }
@ -76,6 +79,9 @@ func (h *hasher) hash(n node, force bool) []byte {
} }
hash := h.hashData(enc) hash := h.hashData(enc)
n.flags.hash = hash n.flags.hash = hash
if h.cacheBlob {
n.flags.blob = common.CopyBytes(enc)
}
return hash return hash
case *fullNode: case *fullNode:
@ -91,6 +97,9 @@ func (h *hasher) hash(n node, force bool) []byte {
} }
hash := h.hashData(enc) hash := h.hashData(enc)
n.flags.hash = hash n.flags.hash = hash
if h.cacheBlob {
n.flags.blob = common.CopyBytes(enc)
}
return hash return hash
case hashNode: case hashNode:
@ -148,9 +157,9 @@ func (h *hasher) encodeFullNode(n *fullNode) []byte {
go func(i int) { go func(i int) {
defer wg.Done() defer wg.Done()
h := newHasher(false) h2 := newHasher(false, h.cacheBlob)
fn.Children[i] = h.hash(n.Children[i], false) fn.Children[i] = h2.hash(n.Children[i], false)
returnHasherToPool(h) returnHasherToPool(h2)
}(i) }(i)
} }
wg.Wait() wg.Wait()

View file

@ -234,7 +234,7 @@ func (it *nodeIterator) LeafBlob() []byte {
func (it *nodeIterator) LeafProof() [][]byte { func (it *nodeIterator) LeafProof() [][]byte {
if len(it.stack) > 0 { if len(it.stack) > 0 {
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
hasher := newHasher(false) hasher := newHasher(false, false)
defer returnHasherToPool(hasher) defer returnHasherToPool(hasher)
proofs := make([][]byte, 0, len(it.stack)) proofs := make([][]byte, 0, len(it.stack))

View file

@ -80,12 +80,14 @@ func (n *fullNode) EncodeRLP(w io.Writer) error {
// nodeFlag contains caching-related metadata about a node. // nodeFlag contains caching-related metadata about a node.
type nodeFlag struct { type nodeFlag struct {
hash hashNode // cached hash of the node (may be nil) hash hashNode // cached hash of the node (may be nil)
blob []byte // cached rlp encoding of the node, set along with the hash (may be nil)
dirty bool // whether the node has changes that must be written to the database dirty bool // whether the node has changes that must be written to the database
} }
func (n nodeFlag) copy() nodeFlag { func (n nodeFlag) copy() nodeFlag {
return nodeFlag{ return nodeFlag{
hash: common.CopyBytes(n.hash), hash: common.CopyBytes(n.hash),
blob: common.CopyBytes(n.blob),
dirty: n.dirty, dirty: n.dirty,
} }
} }

View file

@ -82,7 +82,7 @@ func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) panic(fmt.Sprintf("%T: invalid node: %v", tn, tn))
} }
} }
hasher := newHasher(false) hasher := newHasher(false, false)
defer returnHasherToPool(hasher) defer returnHasherToPool(hasher)
for i, n := range nodes { for i, n := range nodes {

View file

@ -83,6 +83,10 @@ func NewStateTrie(id *ID, db database.NodeDatabase) (*StateTrie, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// State tries always follow a hash-then-commit lifecycle, so cache the node
// encodings produced during hashing for reuse by the subsequent commit.
trie.cacheBlob = true
tr := &StateTrie{ tr := &StateTrie{
trie: *trie, trie: *trie,
db: db, db: db,

View file

@ -58,7 +58,7 @@ type StackTrie struct {
func NewStackTrie(onTrieNode OnTrieNode) *StackTrie { func NewStackTrie(onTrieNode OnTrieNode) *StackTrie {
return &StackTrie{ return &StackTrie{
root: stPool.Get().(*stNode), root: stPool.Get().(*stNode),
h: newHasher(false), h: newHasher(false, false),
onTrieNode: onTrieNode, onTrieNode: onTrieNode,
kBuf: make([]byte, 64), kBuf: make([]byte, 64),
pBuf: make([]byte, 64), pBuf: make([]byte, 64),

View file

@ -54,6 +54,11 @@ type Trie struct {
// uncommitted is the number of updates since last commit. // uncommitted is the number of updates since last commit.
uncommitted int uncommitted int
// cacheBlob makes the hasher cache encoded trie nodes.
// Saves re-encoding on commit. Can be turned off for
// Trie's that are never committed (e.g. for snap sync).
cacheBlob bool
// reader is the handler trie can retrieve nodes from. // reader is the handler trie can retrieve nodes from.
reader *Reader reader *Reader
@ -75,6 +80,7 @@ func (t *Trie) Copy() *Trie {
committed: t.committed, committed: t.committed,
unhashed: t.unhashed, unhashed: t.unhashed,
uncommitted: t.uncommitted, uncommitted: t.uncommitted,
cacheBlob: t.cacheBlob,
reader: t.reader, reader: t.reader,
opTracer: t.opTracer.copy(), opTracer: t.opTracer.copy(),
prevalueTracer: t.prevalueTracer.Copy(), prevalueTracer: t.prevalueTracer.Copy(),
@ -860,7 +866,7 @@ func (t *Trie) hashRoot() []byte {
return types.EmptyRootHash.Bytes() return types.EmptyRootHash.Bytes()
} }
// If the number of changes is below 100, we let one thread handle it // If the number of changes is below 100, we let one thread handle it
h := newHasher(t.unhashed >= 100) h := newHasher(t.unhashed >= 100, t.cacheBlob)
defer func() { defer func() {
returnHasherToPool(h) returnHasherToPool(h)
t.unhashed = 0 t.unhashed = 0