mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +00:00
The nodeHeight *fullNode case returned maxHeight+1 once the running max height reached maxHeight via one child: the `if maxH+1 > maxHeight` guard fired on the next hashNode child, reporting a genuine height-3 branch as height 4. With the `height == 3` archival predicate this made dense, branch-heavy tries (notably the account trie, whose height-3 nodes are all multi-child branches) archive nothing, while only sparse, extension-heavy storage tries archived anything. On a jochemnet shadowfork (path scheme, ~379 GB live KV) a 15.5h `archive generate` archived only 122,340 subtrees / 16.6 MB, and the account trie archived nothing (count=0), with zero read/collection failures in the log -- confirming a height computation bug rather than a read-path issue. Replace the running-max guard with a depth-budget guard mirroring the already-correct *shortNode case; keep the post-update `maxH > maxHeight` exceeded check so recursion and raw-DB reads stay bounded. Add trie/archiver_test.go: a height-3 root branch probes as height 3 (it returned 4 before this change). Co-authored-by: CPerezz <claude.ai.monorail916@passmail.net>
81 lines
3 KiB
Go
81 lines
3 KiB
Go
// Copyright 2026 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 trie
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
)
|
|
|
|
// TestProbeHeightFullNodeBranch is a regression test for an over-estimate bug in
|
|
// nodeHeight's *fullNode case. The previous guard
|
|
//
|
|
// if maxH+1 > maxHeight { return maxHeight + 1 }
|
|
//
|
|
// fired once the running max height reached maxHeight via one child, so the next
|
|
// hashNode child made a genuine height-3 branch report as height 4. Combined with
|
|
// the "archive only height == 3" predicate, dense branch-heavy tries (notably the
|
|
// account trie, whose height-3 nodes are all multi-child branches) archived
|
|
// nothing. Pre-fix probeHeight returns 4 here; post-fix it must return 3.
|
|
//
|
|
// The trie built below has a height-3 ROOT branch:
|
|
//
|
|
// root (branch @ nibble0, children at 0 and 1) height 3
|
|
// ├─ child0 (branch @ nibble1) ├─ leafA ├─ leafB height 2
|
|
// └─ child1 (branch @ nibble1) ├─ leafC ├─ leafD height 2
|
|
//
|
|
// Values are 40 bytes so leaves are NOT embedded; root's children are hashNodes,
|
|
// which is exactly what exercises the buggy branch of nodeHeight.
|
|
func TestProbeHeightFullNodeBranch(t *testing.T) {
|
|
big := func(b byte) []byte { return bytes.Repeat([]byte{b}, 40) }
|
|
|
|
tr := NewEmpty(nil)
|
|
keys := [][]byte{
|
|
{0x00, 0x11, 0x11, 0x11}, // nibbles 0,0,...
|
|
{0x01, 0x22, 0x22, 0x22}, // nibbles 0,1,...
|
|
{0x10, 0x33, 0x33, 0x33}, // nibbles 1,0,...
|
|
{0x11, 0x44, 0x44, 0x44}, // nibbles 1,1,...
|
|
}
|
|
for i, k := range keys {
|
|
if err := tr.Update(k, big(byte('A'+i))); err != nil {
|
|
t.Fatalf("update %x: %v", k, err)
|
|
}
|
|
}
|
|
root, nodes := tr.Commit(false)
|
|
if nodes == nil {
|
|
t.Fatal("nil node set after commit")
|
|
}
|
|
|
|
// Persist the committed nodes under account-trie path keys so the archiver's
|
|
// raw-DB reader (readNodeBlob -> ReadAccountTrieNode) can resolve them.
|
|
raw := rawdb.NewMemoryDatabase()
|
|
for path, n := range nodes.Nodes {
|
|
if n == nil || len(n.Blob) == 0 { // skip deletions
|
|
continue
|
|
}
|
|
rawdb.WriteAccountTrieNode(raw, []byte(path), n.Blob)
|
|
}
|
|
a := &Archiver{db: raw}
|
|
|
|
if got := a.probeHeight(common.Hash{}, nil, root, 3); got != 3 {
|
|
t.Fatalf("probeHeight(height-3 root branch) = %d, want 3 "+
|
|
"(a height-3 branch must be detected as height 3, not over-estimated)", got)
|
|
}
|
|
}
|