mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 09:51:36 +00:00
trie/bintrie: trim verbose doc comments to essentials
This commit is contained in:
parent
ad64f4ec04
commit
b4a7118d06
8 changed files with 30 additions and 133 deletions
|
|
@ -18,7 +18,7 @@ package bintrie
|
|||
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
// HashedNode represents an unresolved node that only stores its hash.
|
||||
// HashedNode is an unresolved node (hash only).
|
||||
type HashedNode struct {
|
||||
hash common.Hash
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func keyToPath(depth int, key []byte) ([]byte, error) {
|
|||
return path, nil
|
||||
}
|
||||
|
||||
// makeKeyPath is a simplified version of keyToPath that doesn't return an error.
|
||||
// makeKeyPath is like keyToPath but panics on invalid depth.
|
||||
func makeKeyPath(depth int, key []byte) []byte {
|
||||
path := make([]byte, 0, depth+1)
|
||||
for i := range depth + 1 {
|
||||
|
|
@ -44,10 +44,9 @@ func makeKeyPath(depth int, key []byte) []byte {
|
|||
return path
|
||||
}
|
||||
|
||||
// InternalNode is a binary trie internal node.
|
||||
type InternalNode struct {
|
||||
left, right NodeRef
|
||||
depth uint8
|
||||
mustRecompute bool // true if the hash needs to be recomputed
|
||||
hash common.Hash // cached hash when mustRecompute == false
|
||||
mustRecompute bool
|
||||
hash common.Hash
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ func newBinaryNodeIterator(t *BinaryTrie, _ []byte) (trie.NodeIterator, error) {
|
|||
return it, nil
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next node.
|
||||
func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||
if it.lastErr == errIteratorEnd {
|
||||
return false
|
||||
|
|
@ -160,7 +159,6 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// Error returns the error status of the iterator.
|
||||
func (it *binaryNodeIterator) Error() error {
|
||||
if it.lastErr == errIteratorEnd {
|
||||
return nil
|
||||
|
|
@ -168,12 +166,10 @@ func (it *binaryNodeIterator) Error() error {
|
|||
return it.lastErr
|
||||
}
|
||||
|
||||
// Hash returns the hash of the current node.
|
||||
func (it *binaryNodeIterator) Hash() common.Hash {
|
||||
return it.store.ComputeHash(it.current)
|
||||
}
|
||||
|
||||
// Parent returns the hash of the parent of the current node.
|
||||
func (it *binaryNodeIterator) Parent() common.Hash {
|
||||
if len(it.stack) < 2 {
|
||||
return common.Hash{}
|
||||
|
|
@ -181,7 +177,7 @@ func (it *binaryNodeIterator) Parent() common.Hash {
|
|||
return it.store.ComputeHash(it.stack[len(it.stack)-2].Node)
|
||||
}
|
||||
|
||||
// Path returns the hex-encoded path to the current node.
|
||||
// Path returns the bit-path to the current node.
|
||||
func (it *binaryNodeIterator) Path() []byte {
|
||||
if it.Leaf() {
|
||||
return it.LeafKey()
|
||||
|
|
@ -196,12 +192,10 @@ func (it *binaryNodeIterator) Path() []byte {
|
|||
return path
|
||||
}
|
||||
|
||||
// NodeBlob returns the serialized bytes of the current node.
|
||||
func (it *binaryNodeIterator) NodeBlob() []byte {
|
||||
return it.store.SerializeNode(it.current)
|
||||
}
|
||||
|
||||
// Leaf returns true iff the current node is a leaf node.
|
||||
func (it *binaryNodeIterator) Leaf() bool {
|
||||
if it.current.Kind() != KindStem {
|
||||
return false
|
||||
|
|
@ -221,7 +215,6 @@ func (it *binaryNodeIterator) Leaf() bool {
|
|||
return sn.hasValue(byte(currentValueIndex))
|
||||
}
|
||||
|
||||
// LeafKey returns the key of the leaf.
|
||||
func (it *binaryNodeIterator) LeafKey() []byte {
|
||||
if it.current.Kind() != KindStem {
|
||||
panic("Leaf() called on an binary node iterator not at a leaf location")
|
||||
|
|
@ -230,7 +223,6 @@ func (it *binaryNodeIterator) LeafKey() []byte {
|
|||
return sn.Key(it.stack[len(it.stack)-1].Index - 1)
|
||||
}
|
||||
|
||||
// LeafBlob returns the content of the leaf.
|
||||
func (it *binaryNodeIterator) LeafBlob() []byte {
|
||||
if it.current.Kind() != KindStem {
|
||||
panic("LeafBlob() called on an binary node iterator not at a leaf location")
|
||||
|
|
@ -239,7 +231,6 @@ func (it *binaryNodeIterator) LeafBlob() []byte {
|
|||
return sn.getValue(byte(it.stack[len(it.stack)-1].Index - 1))
|
||||
}
|
||||
|
||||
// LeafProof returns the Merkle proof of the leaf.
|
||||
func (it *binaryNodeIterator) LeafProof() [][]byte {
|
||||
if it.current.Kind() != KindStem {
|
||||
panic("LeafProof() called on an binary node iterator not at a leaf location")
|
||||
|
|
@ -274,8 +265,7 @@ func (it *binaryNodeIterator) LeafProof() [][]byte {
|
|||
return proof
|
||||
}
|
||||
|
||||
// AddResolver sets an intermediate database to use for looking up trie nodes
|
||||
// before reaching into the real persistent layer.
|
||||
// AddResolver is a no-op (satisfies the NodeIterator interface).
|
||||
func (it *binaryNodeIterator) AddResolver(trie.NodeResolver) {
|
||||
// Not implemented, but should not panic
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ package bintrie
|
|||
type NodeKind uint8
|
||||
|
||||
const (
|
||||
KindEmpty NodeKind = iota // no node
|
||||
KindInternal // internal binary branching node
|
||||
KindStem // leaf group containing up to 256 values
|
||||
KindHashed // unresolved node (hash only)
|
||||
KindEmpty NodeKind = iota
|
||||
KindInternal
|
||||
KindStem // up to 256 values per stem
|
||||
KindHashed
|
||||
)
|
||||
|
||||
// NodeRef is a compact, GC-invisible reference to a node in a NodeStore.
|
||||
|
|
@ -37,20 +37,17 @@ const (
|
|||
kindShift uint32 = 30
|
||||
indexMask uint32 = (1 << kindShift) - 1
|
||||
|
||||
// EmptyRef is the zero-value NodeRef, representing an empty node.
|
||||
// EmptyRef represents an empty node.
|
||||
EmptyRef NodeRef = 0
|
||||
)
|
||||
|
||||
// MakeRef creates a NodeRef from a kind and pool index.
|
||||
func MakeRef(kind NodeKind, idx uint32) NodeRef {
|
||||
return NodeRef(uint32(kind)<<kindShift | (idx & indexMask))
|
||||
}
|
||||
|
||||
// Kind returns the node type tag.
|
||||
func (r NodeRef) Kind() NodeKind { return NodeKind(uint32(r) >> kindShift) }
|
||||
|
||||
// Index returns the pool index within the node's typed pool.
|
||||
// Index within the typed pool.
|
||||
func (r NodeRef) Index() uint32 { return uint32(r) & indexMask }
|
||||
|
||||
// IsEmpty returns true if this ref represents an empty node.
|
||||
func (r NodeRef) IsEmpty() bool { return r == EmptyRef }
|
||||
|
|
|
|||
|
|
@ -24,31 +24,16 @@ import "github.com/ethereum/go-ethereum/common"
|
|||
// of the backing data, only the outer pointer slice grows).
|
||||
const storeChunkSize = 4096
|
||||
|
||||
// NodeStore is a GC-friendly arena for binary trie nodes.
|
||||
//
|
||||
// Instead of allocating each node as a separate heap object with interface
|
||||
// pointers (which the GC must scan), NodeStore packs nodes into typed chunked
|
||||
// pools. InternalNode and HashedNode contain ZERO Go pointers, so their pool
|
||||
// backing arrays are allocated in noscan spans — the GC skips them entirely.
|
||||
// StemNode has one pointer (valueData []byte) per node.
|
||||
//
|
||||
// For a trie with 25K InternalNodes, this reduces GC-scanned pointer-words
|
||||
// from ~125K (with interface-based nodes) to ~25K (just StemNode valueData),
|
||||
// an ~80% reduction. At mainnet scale (millions of nodes), this prevents
|
||||
// multi-second GC pauses.
|
||||
// NodeStore is a GC-friendly arena for binary trie nodes. Nodes are packed
|
||||
// into typed chunked pools so pointer-free types (InternalNode, HashedNode)
|
||||
// land in noscan spans the GC skips entirely.
|
||||
type NodeStore struct {
|
||||
// InternalNode pool — NOSCAN: InternalNode contains zero Go pointers.
|
||||
// Children are NodeRef (uint32), hash is [32]byte.
|
||||
internalChunks []*[storeChunkSize]InternalNode
|
||||
internalCount uint32
|
||||
|
||||
// StemNode pool — each StemNode has one pointer (valueData []byte).
|
||||
// Still much better than the old design where each InternalNode had
|
||||
// two BinaryNode interface pointers (4 pointer-words each).
|
||||
stemChunks []*[storeChunkSize]StemNode
|
||||
stemCount uint32
|
||||
|
||||
// HashedNode pool — NOSCAN: HashedNode is just [32]byte.
|
||||
hashedChunks []*[storeChunkSize]HashedNode
|
||||
hashedCount uint32
|
||||
|
||||
|
|
@ -60,19 +45,14 @@ type NodeStore struct {
|
|||
freeHashed []uint32
|
||||
}
|
||||
|
||||
// NewNodeStore creates a new empty NodeStore.
|
||||
func NewNodeStore() *NodeStore {
|
||||
return &NodeStore{root: EmptyRef}
|
||||
}
|
||||
|
||||
// Root returns the root NodeRef.
|
||||
func (s *NodeStore) Root() NodeRef { return s.root }
|
||||
|
||||
// SetRoot sets the root NodeRef.
|
||||
func (s *NodeStore) SetRoot(ref NodeRef) { s.root = ref }
|
||||
|
||||
// --- InternalNode allocation ---
|
||||
|
||||
func (s *NodeStore) allocInternal() uint32 {
|
||||
if n := len(s.freeInternals); n > 0 {
|
||||
idx := s.freeInternals[n-1]
|
||||
|
|
@ -96,7 +76,6 @@ func (s *NodeStore) getInternal(idx uint32) *InternalNode {
|
|||
return &s.internalChunks[idx/storeChunkSize][idx%storeChunkSize]
|
||||
}
|
||||
|
||||
// newInternalRef allocates an InternalNode and returns its NodeRef.
|
||||
func (s *NodeStore) newInternalRef(depth int) NodeRef {
|
||||
if depth > 248 {
|
||||
panic("node depth exceeds maximum binary trie depth")
|
||||
|
|
@ -108,8 +87,6 @@ func (s *NodeStore) newInternalRef(depth int) NodeRef {
|
|||
return MakeRef(KindInternal, idx)
|
||||
}
|
||||
|
||||
// --- StemNode allocation ---
|
||||
|
||||
func (s *NodeStore) allocStem() uint32 {
|
||||
if n := len(s.freeStems); n > 0 {
|
||||
idx := s.freeStems[n-1]
|
||||
|
|
@ -133,7 +110,6 @@ func (s *NodeStore) getStem(idx uint32) *StemNode {
|
|||
return &s.stemChunks[idx/storeChunkSize][idx%storeChunkSize]
|
||||
}
|
||||
|
||||
// newStemRef allocates a StemNode with the given stem/depth and returns its NodeRef.
|
||||
func (s *NodeStore) newStemRef(stem []byte, depth int) NodeRef {
|
||||
if depth > 248 {
|
||||
panic("node depth exceeds maximum binary trie depth")
|
||||
|
|
@ -146,8 +122,6 @@ func (s *NodeStore) newStemRef(stem []byte, depth int) NodeRef {
|
|||
return MakeRef(KindStem, idx)
|
||||
}
|
||||
|
||||
// --- HashedNode allocation ---
|
||||
|
||||
func (s *NodeStore) allocHashed() uint32 {
|
||||
if n := len(s.freeHashed); n > 0 {
|
||||
idx := s.freeHashed[n-1]
|
||||
|
|
@ -175,14 +149,12 @@ func (s *NodeStore) freeHashedNode(idx uint32) {
|
|||
s.freeHashed = append(s.freeHashed, idx)
|
||||
}
|
||||
|
||||
// newHashedRef allocates a HashedNode and returns its NodeRef.
|
||||
func (s *NodeStore) newHashedRef(hash common.Hash) NodeRef {
|
||||
idx := s.allocHashed()
|
||||
*s.getHashed(idx) = HashedNode{hash: hash}
|
||||
return MakeRef(KindHashed, idx)
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the NodeStore and all its nodes.
|
||||
func (s *NodeStore) Copy() *NodeStore {
|
||||
ns := &NodeStore{
|
||||
root: s.root,
|
||||
|
|
@ -190,21 +162,16 @@ func (s *NodeStore) Copy() *NodeStore {
|
|||
stemCount: s.stemCount,
|
||||
hashedCount: s.hashedCount,
|
||||
}
|
||||
|
||||
// Deep copy internal chunks
|
||||
ns.internalChunks = make([]*[storeChunkSize]InternalNode, len(s.internalChunks))
|
||||
for i, chunk := range s.internalChunks {
|
||||
cp := *chunk
|
||||
ns.internalChunks[i] = &cp
|
||||
}
|
||||
|
||||
// Deep copy stem chunks (need to deep copy valueData)
|
||||
ns.stemChunks = make([]*[storeChunkSize]StemNode, len(s.stemChunks))
|
||||
for i, chunk := range s.stemChunks {
|
||||
cp := *chunk
|
||||
ns.stemChunks[i] = &cp
|
||||
}
|
||||
// Deep copy pointer fields for each active stem
|
||||
for i := uint32(0); i < s.stemCount; i++ {
|
||||
src := s.getStem(i)
|
||||
dst := ns.getStem(i)
|
||||
|
|
@ -213,15 +180,11 @@ func (s *NodeStore) Copy() *NodeStore {
|
|||
copy(dst.valueData, src.valueData)
|
||||
}
|
||||
}
|
||||
|
||||
// Deep copy hashed chunks
|
||||
ns.hashedChunks = make([]*[storeChunkSize]HashedNode, len(s.hashedChunks))
|
||||
for i, chunk := range s.hashedChunks {
|
||||
cp := *chunk
|
||||
ns.hashedChunks[i] = &cp
|
||||
}
|
||||
|
||||
// Copy free lists
|
||||
if len(s.freeInternals) > 0 {
|
||||
ns.freeInternals = make([]uint32, len(s.freeInternals))
|
||||
copy(ns.freeInternals, s.freeInternals)
|
||||
|
|
|
|||
|
|
@ -22,23 +22,20 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// StemNode represents a group of `StemNodeWidth` values sharing the same stem.
|
||||
// It uses a packed representation: bitmap indicates which of the 256 positions
|
||||
// have values, and valueData stores the values contiguously in bitmap order.
|
||||
// StemNode holds up to 256 values sharing a 31-byte stem, packed via bitmap.
|
||||
type StemNode struct {
|
||||
Stem [StemSize]byte // Stem path to get to StemNodeWidth values
|
||||
bitmap [StemBitmapSize]byte // bitmap indicating which positions have values
|
||||
valueData []byte // packed value data (count * HashSize bytes)
|
||||
count uint16 // number of values present
|
||||
depth uint8 // Depth of the node
|
||||
shared bool // true if valueData is shared with serialized input
|
||||
Stem [StemSize]byte
|
||||
bitmap [StemBitmapSize]byte
|
||||
valueData []byte
|
||||
count uint16
|
||||
depth uint8
|
||||
shared bool // true if valueData is shared with serialized input
|
||||
|
||||
mustRecompute bool // true if the hash needs to be recomputed
|
||||
hash common.Hash // cached hash when mustRecompute == false
|
||||
}
|
||||
|
||||
// posInData returns the index within valueData for the given suffix.
|
||||
// Returns -1 if the suffix is not present.
|
||||
// posInData returns the offset within valueData, or -1 if absent.
|
||||
func (sn *StemNode) posInData(suffix byte) int {
|
||||
idx := int(suffix)
|
||||
if sn.bitmap[idx/8]>>(7-(idx%8))&1 == 0 {
|
||||
|
|
@ -56,7 +53,6 @@ func (sn *StemNode) posInData(suffix byte) int {
|
|||
return pos
|
||||
}
|
||||
|
||||
// getValue returns the value at the given suffix, or nil if not present.
|
||||
func (sn *StemNode) getValue(suffix byte) []byte {
|
||||
pos := sn.posInData(suffix)
|
||||
if pos < 0 {
|
||||
|
|
@ -66,7 +62,6 @@ func (sn *StemNode) getValue(suffix byte) []byte {
|
|||
return sn.valueData[start : start+HashSize]
|
||||
}
|
||||
|
||||
// hasValue returns true if the given suffix has a value.
|
||||
func (sn *StemNode) hasValue(suffix byte) bool {
|
||||
idx := int(suffix)
|
||||
return sn.bitmap[idx/8]>>(7-(idx%8))&1 == 1
|
||||
|
|
@ -85,7 +80,7 @@ func (sn *StemNode) allValues() [][]byte {
|
|||
return values
|
||||
}
|
||||
|
||||
// ensureWritable makes the valueData writable (copies if shared with serialized input).
|
||||
// ensureWritable copies valueData if shared (copy-on-write).
|
||||
func (sn *StemNode) ensureWritable() {
|
||||
if sn.shared || cap(sn.valueData)-len(sn.valueData) < HashSize {
|
||||
newData := make([]byte, len(sn.valueData), len(sn.valueData)+HashSize*4)
|
||||
|
|
@ -95,21 +90,17 @@ func (sn *StemNode) ensureWritable() {
|
|||
}
|
||||
}
|
||||
|
||||
// setValue sets or inserts a value at the given suffix.
|
||||
func (sn *StemNode) setValue(suffix byte, value []byte) {
|
||||
sn.ensureWritable()
|
||||
idx := int(suffix)
|
||||
pos := sn.posInData(suffix)
|
||||
if pos >= 0 {
|
||||
// Overwrite existing value
|
||||
copy(sn.valueData[pos*HashSize:], value[:HashSize])
|
||||
return
|
||||
}
|
||||
// New value: insert into bitmap and valueData at the correct position.
|
||||
sn.bitmap[idx/8] |= 1 << (7 - (idx % 8))
|
||||
sn.count++
|
||||
|
||||
// Find the correct position in valueData (count bits before this position).
|
||||
insertPos := 0
|
||||
byteIdx := idx / 8
|
||||
for i := 0; i < byteIdx; i++ {
|
||||
|
|
@ -118,17 +109,12 @@ func (sn *StemNode) setValue(suffix byte, value []byte) {
|
|||
mask := byte(0xFF) << (8 - (idx % 8))
|
||||
insertPos += bits.OnesCount8(sn.bitmap[byteIdx] & mask)
|
||||
|
||||
// Insert value at the correct position in valueData.
|
||||
insertOffset := insertPos * HashSize
|
||||
// Grow the slice
|
||||
sn.valueData = append(sn.valueData, make([]byte, HashSize)...)
|
||||
// Shift data after insertion point
|
||||
copy(sn.valueData[insertOffset+HashSize:], sn.valueData[insertOffset:len(sn.valueData)-HashSize])
|
||||
// Copy the new value
|
||||
copy(sn.valueData[insertOffset:], value[:HashSize])
|
||||
}
|
||||
|
||||
// Hash returns the hash of the node.
|
||||
func (sn *StemNode) Hash() common.Hash {
|
||||
if !sn.mustRecompute {
|
||||
return sn.hash
|
||||
|
|
@ -175,7 +161,6 @@ func (sn *StemNode) Hash() common.Hash {
|
|||
return sn.hash
|
||||
}
|
||||
|
||||
// Key returns the full key for the given index.
|
||||
func (sn *StemNode) Key(i int) []byte {
|
||||
var ret [HashSize]byte
|
||||
copy(ret[:], sn.Stem[:])
|
||||
|
|
|
|||
|
|
@ -25,15 +25,12 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// NodeFlushFn is called during commit to flush serialized nodes.
|
||||
type NodeFlushFn func(path []byte, hash common.Hash, serialized []byte)
|
||||
|
||||
// Hash computes and returns the root hash.
|
||||
func (s *NodeStore) Hash() common.Hash {
|
||||
return s.ComputeHash(s.root)
|
||||
}
|
||||
|
||||
// ComputeHash computes the hash of the node referenced by ref.
|
||||
func (s *NodeStore) ComputeHash(ref NodeRef) common.Hash {
|
||||
switch ref.Kind() {
|
||||
case KindInternal:
|
||||
|
|
@ -49,11 +46,7 @@ func (s *NodeStore) ComputeHash(ref NodeRef) common.Hash {
|
|||
}
|
||||
}
|
||||
|
||||
// hashInternal computes the hash of an InternalNode. At shallow depths
|
||||
// (< parallelHashDepth), the left subtree is hashed in a goroutine while
|
||||
// the right subtree is hashed inline. This is safe because left and right
|
||||
// subtrees are disjoint in a well-formed tree — no node appears in both.
|
||||
// ComputeHash must not be called concurrently with mutations to the NodeStore.
|
||||
// hashInternal hashes an InternalNode, parallelising at shallow depths.
|
||||
func (s *NodeStore) hashInternal(idx uint32) common.Hash {
|
||||
node := s.getInternal(idx)
|
||||
if !node.mustRecompute {
|
||||
|
|
@ -96,11 +89,7 @@ func (s *NodeStore) hashInternal(idx uint32) common.Hash {
|
|||
return node.hash
|
||||
}
|
||||
|
||||
// --- Serialization ---
|
||||
|
||||
// SerializeNode serializes a node referenced by ref into the flat format:
|
||||
// - InternalNode: [nodeTypeInternal(1)][leftHash(32)][rightHash(32)] = 65 bytes
|
||||
// - StemNode: [nodeTypeStem(1)][stem(31)][bitmap(32)][valueData(variable)]
|
||||
// SerializeNode serializes a node into the flat on-disk format.
|
||||
func (s *NodeStore) SerializeNode(ref NodeRef) []byte {
|
||||
switch ref.Kind() {
|
||||
case KindInternal:
|
||||
|
|
@ -128,18 +117,14 @@ func (s *NodeStore) SerializeNode(ref NodeRef) []byte {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Deserialization ---
|
||||
|
||||
var errInvalidSerializedLength = errors.New("invalid serialized node length")
|
||||
|
||||
// DeserializeNode deserializes a node from bytes, recomputing its hash.
|
||||
// The serialized buffer must not be modified after this call.
|
||||
func (s *NodeStore) DeserializeNode(serialized []byte, depth int) (NodeRef, error) {
|
||||
return s.deserializeNode(serialized, depth, common.Hash{}, true)
|
||||
}
|
||||
|
||||
// DeserializeNodeWithHash deserializes a node, using the provided hash.
|
||||
// The serialized buffer must not be modified after this call.
|
||||
func (s *NodeStore) DeserializeNodeWithHash(serialized []byte, depth int, hn common.Hash) (NodeRef, error) {
|
||||
return s.deserializeNode(serialized, depth, hn, false)
|
||||
}
|
||||
|
|
@ -195,9 +180,7 @@ func (s *NodeStore) deserializeNode(serialized []byte, depth int, hn common.Hash
|
|||
if len(serialized) < dataEnd {
|
||||
return EmptyRef, errInvalidSerializedLength
|
||||
}
|
||||
// Zero-copy: valueData aliases the serialized buffer. The shared
|
||||
// flag triggers copy-on-write via ensureWritable() before mutation.
|
||||
// Callers must not modify serialized after this call.
|
||||
// Zero-copy: aliases the serialized buffer; ensureWritable() COWs before mutation.
|
||||
sn.valueData = serialized[dataStart:dataEnd]
|
||||
sn.shared = true
|
||||
sn.depth = uint8(depth)
|
||||
|
|
@ -210,10 +193,7 @@ func (s *NodeStore) deserializeNode(serialized []byte, depth int, hn common.Hash
|
|||
}
|
||||
}
|
||||
|
||||
// --- CollectNodes (Commit) ---
|
||||
|
||||
// CollectNodes traverses the trie, serializing and flushing each node via flushfn.
|
||||
// Children are flushed before their parents (post-order traversal).
|
||||
// CollectNodes flushes every node via flushfn in post-order.
|
||||
func (s *NodeStore) CollectNodes(ref NodeRef, path []byte, flushfn NodeFlushFn) error {
|
||||
switch ref.Kind() {
|
||||
case KindEmpty:
|
||||
|
|
@ -244,7 +224,6 @@ func (s *NodeStore) CollectNodes(ref NodeRef, path []byte, flushfn NodeFlushFn)
|
|||
}
|
||||
}
|
||||
|
||||
// ToDot generates a DOT representation for debugging.
|
||||
func (s *NodeStore) ToDot(ref NodeRef, parent, path string) string {
|
||||
switch ref.Kind() {
|
||||
case KindInternal:
|
||||
|
|
|
|||
|
|
@ -26,12 +26,10 @@ import (
|
|||
// NodeResolverFn resolves a hashed node from the database.
|
||||
type NodeResolverFn func([]byte, common.Hash) ([]byte, error)
|
||||
|
||||
// GetSingle retrieves a single value at stem[suffix] from the trie root.
|
||||
func (s *NodeStore) GetSingle(stem []byte, suffix byte, resolver NodeResolverFn) ([]byte, error) {
|
||||
return s.getSingle(s.root, stem, suffix, resolver)
|
||||
}
|
||||
|
||||
// getSingle retrieves a single value using iterative traversal.
|
||||
func (s *NodeStore) getSingle(ref NodeRef, stem []byte, suffix byte, resolver NodeResolverFn) ([]byte, error) {
|
||||
cur := ref
|
||||
// Track parent for HashedNode resolution (update parent's child ref).
|
||||
|
|
@ -100,12 +98,10 @@ func (s *NodeStore) getSingle(ref NodeRef, stem []byte, suffix byte, resolver No
|
|||
}
|
||||
}
|
||||
|
||||
// GetValuesAtStem retrieves all 256 values at a stem.
|
||||
func (s *NodeStore) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([][]byte, error) {
|
||||
return s.getValuesAtStem(s.root, stem, resolver)
|
||||
}
|
||||
|
||||
// getValuesAtStem uses iterative traversal to find the StemNode.
|
||||
func (s *NodeStore) getValuesAtStem(ref NodeRef, stem []byte, resolver NodeResolverFn) ([][]byte, error) {
|
||||
cur := ref
|
||||
var parentIdx uint32
|
||||
|
|
@ -173,13 +169,11 @@ func (s *NodeStore) getValuesAtStem(ref NodeRef, stem []byte, resolver NodeResol
|
|||
}
|
||||
}
|
||||
|
||||
// InsertSingle inserts a single value at stem[suffix] into the trie.
|
||||
func (s *NodeStore) InsertSingle(stem []byte, suffix byte, value []byte, resolver NodeResolverFn) error {
|
||||
if len(value) != HashSize {
|
||||
return errors.New("invalid insertion: value length")
|
||||
}
|
||||
|
||||
// Handle root-is-empty case
|
||||
if s.root.IsEmpty() {
|
||||
ref := s.newStemRef(stem, 0)
|
||||
sn := s.getStem(ref.Index())
|
||||
|
|
@ -188,7 +182,6 @@ func (s *NodeStore) InsertSingle(stem []byte, suffix byte, value []byte, resolve
|
|||
return nil
|
||||
}
|
||||
|
||||
// Handle root-is-stem case
|
||||
if s.root.Kind() == KindStem {
|
||||
sn := s.getStem(s.root.Index())
|
||||
if sn.Stem == [StemSize]byte(stem[:StemSize]) {
|
||||
|
|
@ -196,17 +189,14 @@ func (s *NodeStore) InsertSingle(stem []byte, suffix byte, value []byte, resolve
|
|||
sn.mustRecompute = true
|
||||
return nil
|
||||
}
|
||||
// Different stem — promote root to internal node via split
|
||||
newRoot := s.splitStemInsert(s.root, stem, suffix, value, int(sn.depth))
|
||||
s.root = newRoot
|
||||
return nil
|
||||
}
|
||||
|
||||
// Root is an InternalNode — iterative descent
|
||||
return s.insertSingleInternal(stem, suffix, value, resolver)
|
||||
}
|
||||
|
||||
// insertSingleInternal handles insertion when root is an InternalNode.
|
||||
func (s *NodeStore) insertSingleInternal(stem []byte, suffix byte, value []byte, resolver NodeResolverFn) error {
|
||||
type pathEntry struct {
|
||||
internalIdx uint32
|
||||
|
|
@ -297,8 +287,7 @@ func (s *NodeStore) insertSingleInternal(stem []byte, suffix byte, value []byte,
|
|||
}
|
||||
}
|
||||
|
||||
// splitStemInsert handles the case where we need to split a StemNode
|
||||
// into a chain of InternalNodes because the new key has a different stem.
|
||||
// splitStemInsert splits a StemNode into InternalNodes for a divergent stem.
|
||||
func (s *NodeStore) splitStemInsert(existingRef NodeRef, newStem []byte, suffix byte, value []byte, depth int) NodeRef {
|
||||
existing := s.getStem(existingRef.Index())
|
||||
existingDepth := depth
|
||||
|
|
@ -360,7 +349,6 @@ func (s *NodeStore) splitStemInsert(existingRef NodeRef, newStem []byte, suffix
|
|||
}
|
||||
}
|
||||
|
||||
// InsertValuesAtStem inserts a full group of values at the given stem.
|
||||
func (s *NodeStore) InsertValuesAtStem(stem []byte, values [][]byte, resolver NodeResolverFn) error {
|
||||
newRoot, err := s.insertValuesAtStem(s.root, stem, values, resolver, 0)
|
||||
if err != nil {
|
||||
|
|
@ -370,7 +358,6 @@ func (s *NodeStore) InsertValuesAtStem(stem []byte, values [][]byte, resolver No
|
|||
return nil
|
||||
}
|
||||
|
||||
// insertValuesAtStem recursively inserts values at a stem.
|
||||
func (s *NodeStore) insertValuesAtStem(ref NodeRef, stem []byte, values [][]byte, resolver NodeResolverFn, depth int) (NodeRef, error) {
|
||||
switch ref.Kind() {
|
||||
case KindInternal:
|
||||
|
|
@ -482,7 +469,7 @@ func (s *NodeStore) insertValuesAtStem(ref NodeRef, stem []byte, values [][]byte
|
|||
}
|
||||
}
|
||||
|
||||
// splitStemValuesInsert handles splitting a StemNode when inserting values with a different stem.
|
||||
// splitStemValuesInsert splits a StemNode when the new stem diverges.
|
||||
func (s *NodeStore) splitStemValuesInsert(existingRef NodeRef, newStem []byte, values [][]byte, resolver NodeResolverFn, depth int) (NodeRef, error) {
|
||||
existing := s.getStem(existingRef.Index())
|
||||
|
||||
|
|
@ -542,17 +529,14 @@ func (s *NodeStore) splitStemValuesInsert(existingRef NodeRef, newStem []byte, v
|
|||
return nRef, nil
|
||||
}
|
||||
|
||||
// Insert inserts a key-value pair using the full 32-byte key.
|
||||
func (s *NodeStore) Insert(key []byte, value []byte, resolver NodeResolverFn) error {
|
||||
return s.InsertSingle(key[:StemSize], key[StemSize], value, resolver)
|
||||
}
|
||||
|
||||
// Get retrieves the value for the given 32-byte key.
|
||||
func (s *NodeStore) Get(key []byte, resolver NodeResolverFn) ([]byte, error) {
|
||||
return s.GetSingle(key[:StemSize], key[StemSize], resolver)
|
||||
}
|
||||
|
||||
// GetHeight returns the height of the trie rooted at ref.
|
||||
func (s *NodeStore) GetHeight(ref NodeRef) int {
|
||||
switch ref.Kind() {
|
||||
case KindInternal:
|
||||
|
|
|
|||
Loading…
Reference in a new issue