mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-15 19:31:37 +00:00
fix: Strip group-depth from GC
This commit is contained in:
parent
8a5e777fde
commit
c1fb257e2e
10 changed files with 80 additions and 329 deletions
|
|
@ -27,19 +27,8 @@ const (
|
||||||
NodeTypeBytes = 1 // Size of node type prefix in serialization
|
NodeTypeBytes = 1 // Size of node type prefix in serialization
|
||||||
HashSize = 32 // Size of a hash in bytes
|
HashSize = 32 // Size of a hash in bytes
|
||||||
StemBitmapSize = 32 // Size of the bitmap in a stem node (256 values = 32 bytes)
|
StemBitmapSize = 32 // Size of the bitmap in a stem node (256 values = 32 bytes)
|
||||||
|
|
||||||
// MaxGroupDepth is the maximum allowed group depth for InternalNode serialization.
|
|
||||||
MaxGroupDepth = 8
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BitmapSizeForDepth returns the bitmap size in bytes for a given group depth.
|
|
||||||
func BitmapSizeForDepth(groupDepth int) int {
|
|
||||||
if groupDepth <= 3 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 1 << (groupDepth - 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nodeTypeStem = iota + 1
|
nodeTypeStem = iota + 1
|
||||||
nodeTypeInternal
|
nodeTypeInternal
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestSerializeDeserializeInternalNode tests serialization and deserialization of InternalNode
|
// TestSerializeDeserializeInternalNode tests flat 65-byte serialization and
|
||||||
// with the grouped subtree format through NodeStore.
|
// deserialization of InternalNode through NodeStore.
|
||||||
func TestSerializeDeserializeInternalNode(t *testing.T) {
|
func TestSerializeDeserializeInternalNode(t *testing.T) {
|
||||||
leftHash := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
|
leftHash := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
|
||||||
rightHash := common.HexToHash("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321")
|
rightHash := common.HexToHash("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321")
|
||||||
|
|
@ -39,30 +39,25 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
|
||||||
rootNode.right = rightRef
|
rootNode.right = rightRef
|
||||||
s.SetRoot(rootRef)
|
s.SetRoot(rootRef)
|
||||||
|
|
||||||
// Serialize the node with default group depth of 8
|
// Serialize the node — flat 65-byte format
|
||||||
serialized := s.SerializeNode(rootRef, MaxGroupDepth)
|
serialized := s.SerializeNode(rootRef)
|
||||||
|
|
||||||
// Check the serialized format
|
// Check the serialized format: [type(1)][leftHash(32)][rightHash(32)]
|
||||||
if serialized[0] != nodeTypeInternal {
|
if serialized[0] != nodeTypeInternal {
|
||||||
t.Errorf("Expected type byte to be %d, got %d", nodeTypeInternal, serialized[0])
|
t.Errorf("Expected type byte to be %d, got %d", nodeTypeInternal, serialized[0])
|
||||||
}
|
}
|
||||||
if serialized[1] != MaxGroupDepth {
|
|
||||||
t.Errorf("Expected group depth to be %d, got %d", MaxGroupDepth, serialized[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmapSize := BitmapSizeForDepth(MaxGroupDepth)
|
expectedLen := NodeTypeBytes + 2*HashSize // 1 + 64 = 65
|
||||||
expectedLen := 1 + 1 + bitmapSize + 2*HashSize
|
|
||||||
if len(serialized) != expectedLen {
|
if len(serialized) != expectedLen {
|
||||||
t.Errorf("Expected serialized length to be %d, got %d", expectedLen, len(serialized))
|
t.Errorf("Expected serialized length to be %d, got %d", expectedLen, len(serialized))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check bitmap bits
|
// Check that left and right hashes are embedded directly
|
||||||
bitmap := serialized[2 : 2+bitmapSize]
|
if !bytes.Equal(serialized[NodeTypeBytes:NodeTypeBytes+HashSize], leftHash[:]) {
|
||||||
if bitmap[0]&0x80 == 0 {
|
t.Error("Left hash not found at expected position")
|
||||||
t.Error("Expected bit 0 to be set in bitmap (left child)")
|
|
||||||
}
|
}
|
||||||
if bitmap[16]&0x80 == 0 {
|
if !bytes.Equal(serialized[NodeTypeBytes+HashSize:], rightHash[:]) {
|
||||||
t.Error("Expected bit 128 to be set in bitmap (right child)")
|
t.Error("Right hash not found at expected position")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserialize into a new store
|
// Deserialize into a new store
|
||||||
|
|
@ -82,35 +77,21 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
|
||||||
t.Errorf("Expected depth 0, got %d", internalNode.depth)
|
t.Errorf("Expected depth 0, got %d", internalNode.depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to position 0 (8 left turns) to find the left hash
|
// Left child should be a HashedNode with the correct hash
|
||||||
node0 := navigateToLeafRef(ds, deserialized, 0, 8)
|
if internalNode.left.Kind() != KindHashed {
|
||||||
if ds.ComputeHash(node0) != leftHash {
|
t.Fatalf("Expected left child to be KindHashed, got %d", internalNode.left.Kind())
|
||||||
t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, ds.ComputeHash(node0))
|
}
|
||||||
|
if ds.ComputeHash(internalNode.left) != leftHash {
|
||||||
|
t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, ds.ComputeHash(internalNode.left))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to position 128 (right, then 7 lefts) to find the right hash
|
// Right child should be a HashedNode with the correct hash
|
||||||
node128 := navigateToLeafRef(ds, deserialized, 128, 8)
|
if internalNode.right.Kind() != KindHashed {
|
||||||
if ds.ComputeHash(node128) != rightHash {
|
t.Fatalf("Expected right child to be KindHashed, got %d", internalNode.right.Kind())
|
||||||
t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, ds.ComputeHash(node128))
|
|
||||||
}
|
}
|
||||||
}
|
if ds.ComputeHash(internalNode.right) != rightHash {
|
||||||
|
t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, ds.ComputeHash(internalNode.right))
|
||||||
// navigateToLeafRef navigates to a specific position in the tree using NodeStore.
|
|
||||||
func navigateToLeafRef(s *NodeStore, ref NodeRef, position, depth int) NodeRef {
|
|
||||||
cur := ref
|
|
||||||
for d := 0; d < depth; d++ {
|
|
||||||
if cur.Kind() != KindInternal {
|
|
||||||
return cur
|
|
||||||
}
|
|
||||||
in := s.getInternal(cur.Index())
|
|
||||||
bit := (position >> (depth - 1 - d)) & 1
|
|
||||||
if bit == 0 {
|
|
||||||
cur = in.left
|
|
||||||
} else {
|
|
||||||
cur = in.right
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return cur
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSerializeDeserializeStemNode tests serialization and deserialization of StemNode through NodeStore.
|
// TestSerializeDeserializeStemNode tests serialization and deserialization of StemNode through NodeStore.
|
||||||
|
|
@ -135,7 +116,7 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize the node
|
// Serialize the node
|
||||||
serialized := s.SerializeNode(ref, MaxGroupDepth)
|
serialized := s.SerializeNode(ref)
|
||||||
|
|
||||||
// Check the serialized format
|
// Check the serialized format
|
||||||
if serialized[0] != nodeTypeStem {
|
if serialized[0] != nodeTypeStem {
|
||||||
|
|
@ -214,8 +195,8 @@ func TestDeserializeInvalidType(t *testing.T) {
|
||||||
// TestDeserializeInvalidLength tests deserialization with invalid data length.
|
// TestDeserializeInvalidLength tests deserialization with invalid data length.
|
||||||
func TestDeserializeInvalidLength(t *testing.T) {
|
func TestDeserializeInvalidLength(t *testing.T) {
|
||||||
s := NewNodeStore()
|
s := NewNodeStore()
|
||||||
// InternalNode with valid type byte and group depth but too short for bitmap
|
// InternalNode with valid type byte but wrong length (needs exactly 65 bytes)
|
||||||
invalidData := []byte{nodeTypeInternal, 8, 0, 0} // Too short for bitmap (needs 32 bytes)
|
invalidData := []byte{nodeTypeInternal, 0, 0, 0}
|
||||||
|
|
||||||
_, err := s.DeserializeNode(invalidData, 0)
|
_, err := s.DeserializeNode(invalidData, 0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ func TestHashedNodeInsertValuesAtStem(t *testing.T) {
|
||||||
sn.setValue(byte(i), v)
|
sn.setValue(byte(i), v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serialized := rs.SerializeNode(ref, MaxGroupDepth)
|
serialized := rs.SerializeNode(ref)
|
||||||
|
|
||||||
validResolver := func(path []byte, hash common.Hash) ([]byte, error) {
|
validResolver := func(path []byte, hash common.Hash) ([]byte, error) {
|
||||||
return serialized, nil
|
return serialized, nil
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ func TestInternalNodeGetWithResolver(t *testing.T) {
|
||||||
ref := rs.newStemRef(stem, 1)
|
ref := rs.newStemRef(stem, 1)
|
||||||
sn := rs.getStem(ref.Index())
|
sn := rs.getStem(ref.Index())
|
||||||
sn.setValue(5, common.HexToHash("0xabcd").Bytes())
|
sn.setValue(5, common.HexToHash("0xabcd").Bytes())
|
||||||
return rs.SerializeNode(ref, MaxGroupDepth), nil
|
return rs.SerializeNode(ref), nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("node not found")
|
return nil, errors.New("node not found")
|
||||||
}
|
}
|
||||||
|
|
@ -290,7 +290,7 @@ func TestInternalNodeCollectNodes(t *testing.T) {
|
||||||
collectedPaths = append(collectedPaths, pathCopy)
|
collectedPaths = append(collectedPaths, pathCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.CollectNodes(s.Root(), []byte{1}, flushFn, MaxGroupDepth)
|
err := s.CollectNodes(s.Root(), []byte{1}, flushFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to collect nodes: %v", err)
|
t.Fatalf("Failed to collect nodes: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ func (it *binaryNodeIterator) Path() []byte {
|
||||||
|
|
||||||
// NodeBlob returns the serialized bytes of the current node.
|
// NodeBlob returns the serialized bytes of the current node.
|
||||||
func (it *binaryNodeIterator) NodeBlob() []byte {
|
func (it *binaryNodeIterator) NodeBlob() []byte {
|
||||||
return it.store.SerializeNode(it.current, MaxGroupDepth)
|
return it.store.SerializeNode(it.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leaf returns true iff the current node is a leaf node.
|
// Leaf returns true iff the current node is a leaf node.
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ func makeTrie(t *testing.T, entries [][2]common.Hash) *BinaryTrie {
|
||||||
tr := &BinaryTrie{
|
tr := &BinaryTrie{
|
||||||
store: store,
|
store: store,
|
||||||
tracer: trie.NewPrevalueTracer(),
|
tracer: trie.NewPrevalueTracer(),
|
||||||
groupDepth: MaxGroupDepth,
|
|
||||||
}
|
}
|
||||||
for _, kv := range entries {
|
for _, kv := range entries {
|
||||||
if err := store.Insert(kv[0][:], kv[1][:], nil); err != nil {
|
if err := store.Insert(kv[0][:], kv[1][:], nil); err != nil {
|
||||||
|
|
@ -66,7 +65,6 @@ func TestIteratorEmptyTrie(t *testing.T) {
|
||||||
tr := &BinaryTrie{
|
tr := &BinaryTrie{
|
||||||
store: NewNodeStore(),
|
store: NewNodeStore(),
|
||||||
tracer: trie.NewPrevalueTracer(),
|
tracer: trie.NewPrevalueTracer(),
|
||||||
groupDepth: MaxGroupDepth,
|
|
||||||
}
|
}
|
||||||
it, err := newBinaryNodeIterator(tr, nil)
|
it, err := newBinaryNodeIterator(tr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,7 @@ func TestStemNodeCollectNodes(t *testing.T) {
|
||||||
collectedPaths = append(collectedPaths, pathCopy)
|
collectedPaths = append(collectedPaths, pathCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.CollectNodes(s.Root(), []byte{0, 1, 0}, flushFn, MaxGroupDepth)
|
err := s.CollectNodes(s.Root(), []byte{0, 1, 0}, flushFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to collect nodes: %v", err)
|
t.Fatalf("Failed to collect nodes: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,86 +98,20 @@ func (s *NodeStore) hashInternal(idx uint32) common.Hash {
|
||||||
|
|
||||||
// --- Serialization ---
|
// --- Serialization ---
|
||||||
|
|
||||||
// countSubtreeChildren counts non-empty children at the bottom layer of a subtree.
|
// SerializeNode serializes a node referenced by ref into the flat format:
|
||||||
func (s *NodeStore) countSubtreeChildren(ref NodeRef, remainingDepth int) int {
|
// - InternalNode: [nodeTypeInternal(1)][leftHash(32)][rightHash(32)] = 65 bytes
|
||||||
if remainingDepth == 0 {
|
// - StemNode: [nodeTypeStem(1)][stem(31)][bitmap(32)][valueData(variable)]
|
||||||
if ref.IsEmpty() {
|
func (s *NodeStore) SerializeNode(ref NodeRef) []byte {
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if ref.Kind() == KindInternal {
|
|
||||||
node := s.getInternal(ref.Index())
|
|
||||||
return s.countSubtreeChildren(node.left, remainingDepth-1) + s.countSubtreeChildren(node.right, remainingDepth-1)
|
|
||||||
}
|
|
||||||
if ref.IsEmpty() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeSubtreeDirect writes child hashes directly into the serialized buffer.
|
|
||||||
func (s *NodeStore) serializeSubtreeDirect(ref NodeRef, remainingDepth int, position int, absoluteDepth int, bitmap []byte, out []byte, hashOffset *int) {
|
|
||||||
if remainingDepth == 0 {
|
|
||||||
if ref.IsEmpty() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bitmap[position/8] |= 1 << (7 - (position % 8))
|
|
||||||
h := s.ComputeHash(ref)
|
|
||||||
copy(out[*hashOffset:*hashOffset+HashSize], h[:])
|
|
||||||
*hashOffset += HashSize
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref.Kind() == KindInternal {
|
|
||||||
node := s.getInternal(ref.Index())
|
|
||||||
leftPos := position * 2
|
|
||||||
rightPos := position*2 + 1
|
|
||||||
s.serializeSubtreeDirect(node.left, remainingDepth-1, leftPos, absoluteDepth+1, bitmap, out, hashOffset)
|
|
||||||
s.serializeSubtreeDirect(node.right, remainingDepth-1, rightPos, absoluteDepth+1, bitmap, out, hashOffset)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref.IsEmpty() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Leaf (StemNode or HashedNode) at a non-zero remaining depth: compute its position.
|
|
||||||
leafPos := position
|
|
||||||
if ref.Kind() == KindStem {
|
|
||||||
sn := s.getStem(ref.Index())
|
|
||||||
for d := 0; d < remainingDepth; d++ {
|
|
||||||
bit := sn.Stem[(absoluteDepth+d)/8] >> (7 - ((absoluteDepth + d) % 8)) & 1
|
|
||||||
leafPos = leafPos*2 + int(bit)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
leafPos = position << remainingDepth
|
|
||||||
}
|
|
||||||
bitmap[leafPos/8] |= 1 << (7 - (leafPos % 8))
|
|
||||||
h := s.ComputeHash(ref)
|
|
||||||
copy(out[*hashOffset:*hashOffset+HashSize], h[:])
|
|
||||||
*hashOffset += HashSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerializeNode serializes a node referenced by ref.
|
|
||||||
func (s *NodeStore) SerializeNode(ref NodeRef, groupDepth int) []byte {
|
|
||||||
if groupDepth < 1 || groupDepth > MaxGroupDepth {
|
|
||||||
panic("groupDepth must be between 1 and 8")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ref.Kind() {
|
switch ref.Kind() {
|
||||||
case KindInternal:
|
case KindInternal:
|
||||||
node := s.getInternal(ref.Index())
|
node := s.getInternal(ref.Index())
|
||||||
bitmapSize := BitmapSizeForDepth(groupDepth)
|
var serialized [NodeTypeBytes + HashSize + HashSize]byte
|
||||||
hashCount := s.countSubtreeChildren(ref, groupDepth)
|
|
||||||
serializedLen := NodeTypeBytes + 1 + bitmapSize + hashCount*HashSize
|
|
||||||
serialized := make([]byte, serializedLen)
|
|
||||||
serialized[0] = nodeTypeInternal
|
serialized[0] = nodeTypeInternal
|
||||||
serialized[1] = byte(groupDepth)
|
lh := s.ComputeHash(node.left)
|
||||||
bitmap := serialized[2 : 2+bitmapSize]
|
rh := s.ComputeHash(node.right)
|
||||||
hashOffset := 2 + bitmapSize
|
copy(serialized[NodeTypeBytes:NodeTypeBytes+HashSize], lh[:])
|
||||||
s.serializeSubtreeDirect(ref, groupDepth, 0, int(node.depth), bitmap, serialized, &hashOffset)
|
copy(serialized[NodeTypeBytes+HashSize:], rh[:])
|
||||||
return serialized
|
return serialized[:]
|
||||||
|
|
||||||
case KindStem:
|
case KindStem:
|
||||||
sn := s.getStem(ref.Index())
|
sn := s.getStem(ref.Index())
|
||||||
|
|
@ -215,27 +149,23 @@ func (s *NodeStore) deserializeNode(serialized []byte, depth int, hn common.Hash
|
||||||
|
|
||||||
switch serialized[0] {
|
switch serialized[0] {
|
||||||
case nodeTypeInternal:
|
case nodeTypeInternal:
|
||||||
if len(serialized) < NodeTypeBytes+1 {
|
if len(serialized) != NodeTypeBytes+2*HashSize {
|
||||||
return EmptyRef, errInvalidSerializedLength
|
return EmptyRef, errInvalidSerializedLength
|
||||||
}
|
}
|
||||||
groupDepth := int(serialized[1])
|
var leftHash, rightHash common.Hash
|
||||||
if groupDepth < 1 || groupDepth > MaxGroupDepth {
|
copy(leftHash[:], serialized[NodeTypeBytes:NodeTypeBytes+HashSize])
|
||||||
return EmptyRef, errors.New("invalid group depth")
|
copy(rightHash[:], serialized[NodeTypeBytes+HashSize:])
|
||||||
}
|
|
||||||
bitmapSize := BitmapSizeForDepth(groupDepth)
|
|
||||||
if len(serialized) < NodeTypeBytes+1+bitmapSize {
|
|
||||||
return EmptyRef, errInvalidSerializedLength
|
|
||||||
}
|
|
||||||
bitmap := serialized[2 : 2+bitmapSize]
|
|
||||||
hashData := serialized[2+bitmapSize:]
|
|
||||||
|
|
||||||
hashIdx := 0
|
leftRef := s.newHashedRef(leftHash)
|
||||||
ref, err := s.deserializeSubtree(groupDepth, 0, depth, bitmap, hashData, &hashIdx, mustRecompute)
|
rightRef := s.newHashedRef(rightHash)
|
||||||
if err != nil {
|
|
||||||
return EmptyRef, err
|
ref := s.newInternalRef(depth)
|
||||||
}
|
node := s.getInternal(ref.Index())
|
||||||
if ref.Kind() == KindInternal && !mustRecompute {
|
node.left = leftRef
|
||||||
s.getInternal(ref.Index()).hash = hn
|
node.right = rightRef
|
||||||
|
if !mustRecompute {
|
||||||
|
node.hash = hn
|
||||||
|
node.mustRecompute = false
|
||||||
}
|
}
|
||||||
return ref, nil
|
return ref, nil
|
||||||
|
|
||||||
|
|
@ -271,167 +201,34 @@ func (s *NodeStore) deserializeNode(serialized []byte, depth int, hn common.Hash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *NodeStore) deserializeSubtree(remainingDepth int, position int, nodeDepth int, bitmap []byte, hashData []byte, hashIdx *int, mustRecompute bool) (NodeRef, error) {
|
|
||||||
if remainingDepth == 0 {
|
|
||||||
if bitmap[position/8]>>(7-(position%8))&1 == 1 {
|
|
||||||
if len(hashData) < (*hashIdx+1)*HashSize {
|
|
||||||
return EmptyRef, errInvalidSerializedLength
|
|
||||||
}
|
|
||||||
hash := common.BytesToHash(hashData[*hashIdx*HashSize : (*hashIdx+1)*HashSize])
|
|
||||||
*hashIdx++
|
|
||||||
return s.newHashedRef(hash), nil
|
|
||||||
}
|
|
||||||
return EmptyRef, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
leftPos := position * 2
|
|
||||||
rightPos := position*2 + 1
|
|
||||||
|
|
||||||
left, err := s.deserializeSubtree(remainingDepth-1, leftPos, nodeDepth+1, bitmap, hashData, hashIdx, true)
|
|
||||||
if err != nil {
|
|
||||||
return EmptyRef, err
|
|
||||||
}
|
|
||||||
right, err := s.deserializeSubtree(remainingDepth-1, rightPos, nodeDepth+1, bitmap, hashData, hashIdx, true)
|
|
||||||
if err != nil {
|
|
||||||
return EmptyRef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if left.IsEmpty() && right.IsEmpty() {
|
|
||||||
return EmptyRef, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if nodeDepth > 248 {
|
|
||||||
panic("node depth exceeds maximum binary trie depth")
|
|
||||||
}
|
|
||||||
idx := s.allocInternal()
|
|
||||||
n := s.getInternal(idx)
|
|
||||||
n.depth = uint8(nodeDepth)
|
|
||||||
n.left = left
|
|
||||||
n.right = right
|
|
||||||
n.mustRecompute = mustRecompute
|
|
||||||
return MakeRef(KindInternal, idx), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- CollectNodes (Commit) ---
|
// --- CollectNodes (Commit) ---
|
||||||
|
|
||||||
// CollectNodes traverses the trie, flushing nodes at group boundaries.
|
// CollectNodes traverses the trie, serializing and flushing each node via flushfn.
|
||||||
func (s *NodeStore) CollectNodes(ref NodeRef, path []byte, flushfn NodeFlushFn, groupDepth int) error {
|
// Children are flushed before their parents (post-order traversal).
|
||||||
if groupDepth < 1 || groupDepth > MaxGroupDepth {
|
func (s *NodeStore) CollectNodes(ref NodeRef, path []byte, flushfn NodeFlushFn) error {
|
||||||
return errors.New("groupDepth must be between 1 and 8")
|
|
||||||
}
|
|
||||||
buf := make([]byte, len(path), len(path)+MaxGroupDepth+1)
|
|
||||||
copy(buf, path)
|
|
||||||
return s.collectNodesBuf(ref, buf, flushfn, groupDepth)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *NodeStore) collectNodesBuf(ref NodeRef, buf []byte, flushfn NodeFlushFn, groupDepth int) error {
|
|
||||||
switch ref.Kind() {
|
switch ref.Kind() {
|
||||||
|
case KindEmpty:
|
||||||
|
return nil
|
||||||
case KindInternal:
|
case KindInternal:
|
||||||
node := s.getInternal(ref.Index())
|
node := s.getInternal(ref.Index())
|
||||||
if int(node.depth)%groupDepth == 0 {
|
if err := s.CollectNodes(node.left, append(path, 0), flushfn); err != nil {
|
||||||
if err := s.collectChildGroupsBuf(ref, buf, flushfn, groupDepth, groupDepth-1); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
serialized := s.SerializeNode(ref, groupDepth)
|
|
||||||
flushfn(buf, s.ComputeHash(ref), serialized)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return s.collectChildGroupsBuf(ref, buf, flushfn, groupDepth, groupDepth-(int(node.depth)%groupDepth)-1)
|
if err := s.CollectNodes(node.right, append(path, 1), flushfn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
flushfn(path, s.ComputeHash(ref), s.SerializeNode(ref))
|
||||||
|
return nil
|
||||||
case KindStem:
|
case KindStem:
|
||||||
serialized := s.SerializeNode(ref, groupDepth)
|
flushfn(path, s.ComputeHash(ref), s.SerializeNode(ref))
|
||||||
flushfn(buf, s.ComputeHash(ref), serialized)
|
|
||||||
return nil
|
return nil
|
||||||
|
case KindHashed:
|
||||||
case KindHashed, KindEmpty:
|
return nil // Already committed
|
||||||
return nil
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("collectNodesBuf: unexpected kind %d", ref.Kind())
|
return fmt.Errorf("CollectNodes: unexpected kind %d", ref.Kind())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *NodeStore) collectChildGroupsBuf(ref NodeRef, buf []byte, flushfn NodeFlushFn, groupDepth int, remainingLevels int) error {
|
|
||||||
if ref.Kind() != KindInternal {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
node := s.getInternal(ref.Index())
|
|
||||||
saved := len(buf)
|
|
||||||
childDepth := int(node.depth) + 1
|
|
||||||
|
|
||||||
if remainingLevels == 0 {
|
|
||||||
if !node.left.IsEmpty() {
|
|
||||||
buf = append(buf, 0)
|
|
||||||
if err := s.collectNodesBuf(node.left, buf, flushfn, groupDepth); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf = buf[:saved]
|
|
||||||
}
|
|
||||||
if !node.right.IsEmpty() {
|
|
||||||
buf = append(buf, 1)
|
|
||||||
if err := s.collectNodesBuf(node.right, buf, flushfn, groupDepth); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Left child
|
|
||||||
if !node.left.IsEmpty() {
|
|
||||||
if node.left.Kind() == KindInternal {
|
|
||||||
buf = append(buf, 0)
|
|
||||||
if err := s.collectChildGroupsBuf(node.left, buf, flushfn, groupDepth, remainingLevels-1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf = buf[:saved]
|
|
||||||
} else {
|
|
||||||
buf = append(buf, 0)
|
|
||||||
buf = s.extendPathBuf(buf, node.left, remainingLevels, childDepth)
|
|
||||||
if err := s.collectNodesBuf(node.left, buf, flushfn, groupDepth); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf = buf[:saved]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right child
|
|
||||||
if !node.right.IsEmpty() {
|
|
||||||
if node.right.Kind() == KindInternal {
|
|
||||||
buf = append(buf, 1)
|
|
||||||
if err := s.collectChildGroupsBuf(node.right, buf, flushfn, groupDepth, remainingLevels-1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buf = append(buf, 1)
|
|
||||||
buf = s.extendPathBuf(buf, node.right, remainingLevels, childDepth)
|
|
||||||
if err := s.collectNodesBuf(node.right, buf, flushfn, groupDepth); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// extendPathBuf extends the path buffer to the group's leaf boundary.
|
|
||||||
func (s *NodeStore) extendPathBuf(buf []byte, ref NodeRef, remainingLevels int, absoluteDepth int) []byte {
|
|
||||||
if remainingLevels <= 0 {
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
if ref.Kind() == KindStem {
|
|
||||||
sn := s.getStem(ref.Index())
|
|
||||||
for d := 0; d < remainingLevels; d++ {
|
|
||||||
bit := sn.Stem[(absoluteDepth+d)/8] >> (7 - ((absoluteDepth + d) % 8)) & 1
|
|
||||||
buf = append(buf, bit)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for d := 0; d < remainingLevels; d++ {
|
|
||||||
buf = append(buf, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToDot generates a DOT representation for debugging.
|
// ToDot generates a DOT representation for debugging.
|
||||||
func (s *NodeStore) ToDot(ref NodeRef, parent, path string) string {
|
func (s *NodeStore) ToDot(ref NodeRef, parent, path string) string {
|
||||||
switch ref.Kind() {
|
switch ref.Kind() {
|
||||||
|
|
|
||||||
|
|
@ -107,10 +107,9 @@ func ChunkifyCode(code []byte) ChunkedCode {
|
||||||
|
|
||||||
// BinaryTrie is the implementation of https://eips.ethereum.org/EIPS/eip-7864.
|
// BinaryTrie is the implementation of https://eips.ethereum.org/EIPS/eip-7864.
|
||||||
type BinaryTrie struct {
|
type BinaryTrie struct {
|
||||||
store *NodeStore
|
store *NodeStore
|
||||||
reader *trie.Reader
|
reader *trie.Reader
|
||||||
tracer *trie.PrevalueTracer
|
tracer *trie.PrevalueTracer
|
||||||
groupDepth int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
|
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
|
||||||
|
|
@ -126,10 +125,9 @@ func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, err
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
t := &BinaryTrie{
|
t := &BinaryTrie{
|
||||||
store: NewNodeStore(),
|
store: NewNodeStore(),
|
||||||
reader: reader,
|
reader: reader,
|
||||||
tracer: trie.NewPrevalueTracer(),
|
tracer: trie.NewPrevalueTracer(),
|
||||||
groupDepth: MaxGroupDepth,
|
|
||||||
}
|
}
|
||||||
// Parse the root node if it's not empty
|
// Parse the root node if it's not empty
|
||||||
if root != types.EmptyBinaryHash && root != types.EmptyRootHash {
|
if root != types.EmptyBinaryHash && root != types.EmptyRootHash {
|
||||||
|
|
@ -311,14 +309,9 @@ func (t *BinaryTrie) Hash() common.Hash {
|
||||||
func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
|
func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
|
||||||
nodeset := trienode.NewNodeSet(common.Hash{})
|
nodeset := trienode.NewNodeSet(common.Hash{})
|
||||||
|
|
||||||
groupDepth := t.groupDepth
|
|
||||||
if groupDepth == 0 {
|
|
||||||
groupDepth = MaxGroupDepth
|
|
||||||
}
|
|
||||||
|
|
||||||
err := t.store.CollectNodes(t.store.Root(), nil, func(path []byte, hash common.Hash, serialized []byte) {
|
err := t.store.CollectNodes(t.store.Root(), nil, func(path []byte, hash common.Hash, serialized []byte) {
|
||||||
nodeset.AddNode(path, trienode.NewNodeWithPrev(hash, serialized, t.tracer.Get(path)))
|
nodeset.AddNode(path, trienode.NewNodeWithPrev(hash, serialized, t.tracer.Get(path)))
|
||||||
}, groupDepth)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("CollectNodes failed: %v", err))
|
panic(fmt.Errorf("CollectNodes failed: %v", err))
|
||||||
}
|
}
|
||||||
|
|
@ -345,10 +338,9 @@ func (t *BinaryTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
||||||
// Copy creates a deep copy of the trie.
|
// Copy creates a deep copy of the trie.
|
||||||
func (t *BinaryTrie) Copy() *BinaryTrie {
|
func (t *BinaryTrie) Copy() *BinaryTrie {
|
||||||
return &BinaryTrie{
|
return &BinaryTrie{
|
||||||
store: t.store.Copy(),
|
store: t.store.Copy(),
|
||||||
reader: t.reader,
|
reader: t.reader,
|
||||||
tracer: t.tracer.Copy(),
|
tracer: t.tracer.Copy(),
|
||||||
groupDepth: t.groupDepth,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,6 @@ func TestStorageRoundTrip(t *testing.T) {
|
||||||
tr := &BinaryTrie{
|
tr := &BinaryTrie{
|
||||||
store: NewNodeStore(),
|
store: NewNodeStore(),
|
||||||
tracer: tracer,
|
tracer: tracer,
|
||||||
groupDepth: MaxGroupDepth,
|
|
||||||
}
|
}
|
||||||
addr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
|
addr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
|
||||||
|
|
||||||
|
|
@ -259,7 +258,6 @@ func newEmptyTestTrie(t *testing.T) *BinaryTrie {
|
||||||
return &BinaryTrie{
|
return &BinaryTrie{
|
||||||
store: NewNodeStore(),
|
store: NewNodeStore(),
|
||||||
tracer: trie.NewPrevalueTracer(),
|
tracer: trie.NewPrevalueTracer(),
|
||||||
groupDepth: MaxGroupDepth,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -585,7 +583,6 @@ func TestBinaryTrieWitness(t *testing.T) {
|
||||||
tr := &BinaryTrie{
|
tr := &BinaryTrie{
|
||||||
store: NewNodeStore(),
|
store: NewNodeStore(),
|
||||||
tracer: tracer,
|
tracer: tracer,
|
||||||
groupDepth: MaxGroupDepth,
|
|
||||||
}
|
}
|
||||||
if w := tr.Witness(); len(w) != 0 {
|
if w := tr.Witness(); len(w) != 0 {
|
||||||
t.Fatal("expected empty witness for fresh trie")
|
t.Fatal("expected empty witness for fresh trie")
|
||||||
|
|
@ -613,7 +610,6 @@ func testAccount(t *testing.T, addr common.Address, nonce uint64, balance uint64
|
||||||
tr := &BinaryTrie{
|
tr := &BinaryTrie{
|
||||||
store: NewNodeStore(),
|
store: NewNodeStore(),
|
||||||
tracer: trie.NewPrevalueTracer(),
|
tracer: trie.NewPrevalueTracer(),
|
||||||
groupDepth: MaxGroupDepth,
|
|
||||||
}
|
}
|
||||||
acc := &types.StateAccount{
|
acc := &types.StateAccount{
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
|
|
@ -668,7 +664,6 @@ func TestGetAccountNonMembershipInternalRoot(t *testing.T) {
|
||||||
tr := &BinaryTrie{
|
tr := &BinaryTrie{
|
||||||
store: NewNodeStore(),
|
store: NewNodeStore(),
|
||||||
tracer: trie.NewPrevalueTracer(),
|
tracer: trie.NewPrevalueTracer(),
|
||||||
groupDepth: MaxGroupDepth,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert two accounts whose binary tree keys have different first bits
|
// Insert two accounts whose binary tree keys have different first bits
|
||||||
|
|
@ -732,7 +727,6 @@ func TestGetStorageNonMembershipInternalRoot(t *testing.T) {
|
||||||
tr := &BinaryTrie{
|
tr := &BinaryTrie{
|
||||||
store: NewNodeStore(),
|
store: NewNodeStore(),
|
||||||
tracer: trie.NewPrevalueTracer(),
|
tracer: trie.NewPrevalueTracer(),
|
||||||
groupDepth: MaxGroupDepth,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
|
addr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue