mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 12:46:44 +00:00
trie/bintrie: skip resolver for zero-hash children
This commit is contained in:
parent
6452b7ad05
commit
926ded3385
2 changed files with 203 additions and 42 deletions
|
|
@ -51,6 +51,31 @@ func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([
|
||||||
bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
||||||
if bit == 0 {
|
if bit == 0 {
|
||||||
if hn, ok := bt.left.(HashedNode); ok {
|
if hn, ok := bt.left.(HashedNode); ok {
|
||||||
|
if common.Hash(hn) == (common.Hash{}) {
|
||||||
|
bt.left = Empty{}
|
||||||
|
} else {
|
||||||
|
path, err := keyToPath(bt.depth, stem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
|
||||||
|
}
|
||||||
|
data, err := resolver(path, common.Hash(hn))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
|
||||||
|
}
|
||||||
|
node, err := DeserializeNode(data, bt.depth+1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetValuesAtStem node deserialization error: %w", err)
|
||||||
|
}
|
||||||
|
bt.left = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bt.left.GetValuesAtStem(stem, resolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hn, ok := bt.right.(HashedNode); ok {
|
||||||
|
if common.Hash(hn) == (common.Hash{}) {
|
||||||
|
bt.right = Empty{}
|
||||||
|
} else {
|
||||||
path, err := keyToPath(bt.depth, stem)
|
path, err := keyToPath(bt.depth, stem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
|
return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
|
||||||
|
|
@ -63,25 +88,8 @@ func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetValuesAtStem node deserialization error: %w", err)
|
return nil, fmt.Errorf("GetValuesAtStem node deserialization error: %w", err)
|
||||||
}
|
}
|
||||||
bt.left = node
|
bt.right = node
|
||||||
}
|
}
|
||||||
return bt.left.GetValuesAtStem(stem, resolver)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hn, ok := bt.right.(HashedNode); ok {
|
|
||||||
path, err := keyToPath(bt.depth, stem)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
|
|
||||||
}
|
|
||||||
data, err := resolver(path, common.Hash(hn))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
|
|
||||||
}
|
|
||||||
node, err := DeserializeNode(data, bt.depth+1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetValuesAtStem node deserialization error: %w", err)
|
|
||||||
}
|
|
||||||
bt.right = node
|
|
||||||
}
|
}
|
||||||
return bt.right.GetValuesAtStem(stem, resolver)
|
return bt.right.GetValuesAtStem(stem, resolver)
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +149,37 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
|
||||||
}
|
}
|
||||||
|
|
||||||
if hn, ok := bt.left.(HashedNode); ok {
|
if hn, ok := bt.left.(HashedNode); ok {
|
||||||
|
if common.Hash(hn) == (common.Hash{}) {
|
||||||
|
bt.left = Empty{}
|
||||||
|
} else {
|
||||||
|
path, err := keyToPath(bt.depth, stem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err)
|
||||||
|
}
|
||||||
|
data, err := resolver(path, common.Hash(hn))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err)
|
||||||
|
}
|
||||||
|
node, err := DeserializeNode(data, bt.depth+1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("InsertValuesAtStem node deserialization error: %w", err)
|
||||||
|
}
|
||||||
|
bt.left = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bt.left, err = bt.left.InsertValuesAtStem(stem, values, resolver, depth+1)
|
||||||
|
return bt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bt.right == nil {
|
||||||
|
bt.right = Empty{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hn, ok := bt.right.(HashedNode); ok {
|
||||||
|
if common.Hash(hn) == (common.Hash{}) {
|
||||||
|
bt.right = Empty{}
|
||||||
|
} else {
|
||||||
path, err := keyToPath(bt.depth, stem)
|
path, err := keyToPath(bt.depth, stem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err)
|
return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err)
|
||||||
|
|
@ -153,31 +192,8 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("InsertValuesAtStem node deserialization error: %w", err)
|
return nil, fmt.Errorf("InsertValuesAtStem node deserialization error: %w", err)
|
||||||
}
|
}
|
||||||
bt.left = node
|
bt.right = node
|
||||||
}
|
}
|
||||||
|
|
||||||
bt.left, err = bt.left.InsertValuesAtStem(stem, values, resolver, depth+1)
|
|
||||||
return bt, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if bt.right == nil {
|
|
||||||
bt.right = Empty{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hn, ok := bt.right.(HashedNode); ok {
|
|
||||||
path, err := keyToPath(bt.depth, stem)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err)
|
|
||||||
}
|
|
||||||
data, err := resolver(path, common.Hash(hn))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err)
|
|
||||||
}
|
|
||||||
node, err := DeserializeNode(data, bt.depth+1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("InsertValuesAtStem node deserialization error: %w", err)
|
|
||||||
}
|
|
||||||
bt.right = node
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bt.right, err = bt.right.InsertValuesAtStem(stem, values, resolver, depth+1)
|
bt.right, err = bt.right.InsertValuesAtStem(stem, values, resolver, depth+1)
|
||||||
|
|
|
||||||
145
trie/bintrie/zero_hash_fix_test.go
Normal file
145
trie/bintrie/zero_hash_fix_test.go
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
// Copyright 2025 go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bintrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestZeroHashSkipsResolver tests that zero-hash HashedNodes don't trigger resolver calls
|
||||||
|
func TestZeroHashSkipsResolver(t *testing.T) {
|
||||||
|
// Create an InternalNode with one real child and one Empty child
|
||||||
|
realHash := common.HexToHash("0x1234")
|
||||||
|
|
||||||
|
node := &InternalNode{
|
||||||
|
depth: 0,
|
||||||
|
left: HashedNode(realHash),
|
||||||
|
right: Empty{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize and deserialize to create zero-hash HashedNode
|
||||||
|
serialized := SerializeNode(node)
|
||||||
|
deserialized, err := DeserializeNode(serialized, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to deserialize: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializedInternal := deserialized.(*InternalNode)
|
||||||
|
|
||||||
|
// Verify that right child is a zero-hash HashedNode after deserialization
|
||||||
|
if hn, ok := deserializedInternal.right.(HashedNode); ok {
|
||||||
|
if common.Hash(hn) != (common.Hash{}) {
|
||||||
|
t.Fatal("Expected right child to be zero-hash HashedNode")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Expected right child to be HashedNode, got %T", deserializedInternal.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track resolver calls
|
||||||
|
resolverCalls := 0
|
||||||
|
resolver := func(path []byte, hash common.Hash) ([]byte, error) {
|
||||||
|
resolverCalls++
|
||||||
|
|
||||||
|
// Zero-hash should never reach resolver
|
||||||
|
if hash == (common.Hash{}) {
|
||||||
|
t.Error("BUG: Resolver called for zero hash")
|
||||||
|
return nil, errors.New("zero hash should not be resolved")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return valid data for real hash
|
||||||
|
if hash == realHash {
|
||||||
|
stem := make([]byte, 31)
|
||||||
|
var values [256][]byte
|
||||||
|
values[5] = common.HexToHash("0xabcd").Bytes()
|
||||||
|
return SerializeNode(&StemNode{Stem: stem, Values: values[:], depth: 1}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access right child (zero-hash) - should not call resolver
|
||||||
|
rightStem := make([]byte, 31)
|
||||||
|
rightStem[0] = 0x80 // First bit is 1, routes to right child
|
||||||
|
|
||||||
|
values, err := deserializedInternal.GetValuesAtStem(rightStem, resolver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetValuesAtStem failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All values should be nil for empty node
|
||||||
|
for i, v := range values {
|
||||||
|
if v != nil {
|
||||||
|
t.Errorf("Expected nil value at index %d, got %x", i, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify resolver was not called for zero-hash
|
||||||
|
if resolverCalls > 0 {
|
||||||
|
t.Errorf("Resolver should not have been called for zero-hash child, but was called %d times", resolverCalls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now test left child (real hash) - should call resolver
|
||||||
|
leftStem := make([]byte, 31)
|
||||||
|
_, err = deserializedInternal.GetValuesAtStem(leftStem, resolver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetValuesAtStem failed for left child: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resolverCalls != 1 {
|
||||||
|
t.Errorf("Expected resolver to be called once for real hash, called %d times", resolverCalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestZeroHashSkipsResolverOnInsert tests that InsertValuesAtStem also skips zero-hash resolver calls
|
||||||
|
func TestZeroHashSkipsResolverOnInsert(t *testing.T) {
|
||||||
|
// Create node after deserialization with zero-hash children
|
||||||
|
node := &InternalNode{
|
||||||
|
depth: 0,
|
||||||
|
left: HashedNode(common.Hash{}), // Zero-hash
|
||||||
|
right: HashedNode(common.Hash{}), // Zero-hash
|
||||||
|
}
|
||||||
|
|
||||||
|
resolverCalls := 0
|
||||||
|
resolver := func(path []byte, hash common.Hash) ([]byte, error) {
|
||||||
|
resolverCalls++
|
||||||
|
|
||||||
|
if hash == (common.Hash{}) {
|
||||||
|
t.Error("BUG: Resolver called for zero hash in InsertValuesAtStem")
|
||||||
|
return nil, errors.New("zero hash should not be resolved")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert values into left subtree (zero-hash child)
|
||||||
|
leftStem := make([]byte, 31)
|
||||||
|
var values [256][]byte
|
||||||
|
values[10] = common.HexToHash("0x5678").Bytes()
|
||||||
|
|
||||||
|
_, err := node.InsertValuesAtStem(leftStem, values[:], resolver, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("InsertValuesAtStem failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify resolver was not called
|
||||||
|
if resolverCalls > 0 {
|
||||||
|
t.Errorf("Resolver should not have been called for zero-hash child, but was called %d times", resolverCalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue