diff --git a/trie/bintrie/hashed_node.go b/trie/bintrie/hashed_node.go index 9d349beaf8..1bc4a8be78 100644 --- a/trie/bintrie/hashed_node.go +++ b/trie/bintrie/hashed_node.go @@ -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 } diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go index 51beecefd5..866db88c52 100644 --- a/trie/bintrie/internal_node.go +++ b/trie/bintrie/internal_node.go @@ -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 } diff --git a/trie/bintrie/iterator.go b/trie/bintrie/iterator.go index 30bbb56935..c25bb6abb2 100644 --- a/trie/bintrie/iterator.go +++ b/trie/bintrie/iterator.go @@ -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 } diff --git a/trie/bintrie/node_ref.go b/trie/bintrie/node_ref.go index 5ee6ec9e79..5341d33714 100644 --- a/trie/bintrie/node_ref.go +++ b/trie/bintrie/node_ref.go @@ -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) } -// 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 } diff --git a/trie/bintrie/node_store.go b/trie/bintrie/node_store.go index 4aa3b0739c..388fb9c7d4 100644 --- a/trie/bintrie/node_store.go +++ b/trie/bintrie/node_store.go @@ -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) diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go index e44d25638f..f268b6be74 100644 --- a/trie/bintrie/stem_node.go +++ b/trie/bintrie/stem_node.go @@ -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[:]) diff --git a/trie/bintrie/store_commit.go b/trie/bintrie/store_commit.go index ba759fc3f9..42e0b28c17 100644 --- a/trie/bintrie/store_commit.go +++ b/trie/bintrie/store_commit.go @@ -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: diff --git a/trie/bintrie/store_ops.go b/trie/bintrie/store_ops.go index 6bb966a074..09d0425be1 100644 --- a/trie/bintrie/store_ops.go +++ b/trie/bintrie/store_ops.go @@ -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: