From f74037c0103d6dd465b4a96a0e982ef7d3dde18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Lo=CC=81pez=20Leo=CC=81n?= Date: Thu, 2 Apr 2026 19:39:33 -0300 Subject: [PATCH] triedb/pathdb: skip duplicate-root layer insertion PathDB keys diff layers by state root, not by block hash. A side-chain block can legitimately collide with an existing canonical diff layer when both blocks produce the same post-state (same parent, same coinbase, no txs). layerTree.add blindly overwrites tree.layers[root] in this case, corrupting the parent chain for any child layers already built on top of the existing one and appending a duplicate root to the lookup indices so that accountTip/storageTip resolve the wrong diff layer. Make duplicate-root inserts idempotent: a second layer with an already- present state root carries no new retrievable state, so keeping the original layer preserves the existing parent chain and avoids polluting the lookup history. --- triedb/pathdb/layertree.go | 9 +++++++++ triedb/pathdb/layertree_test.go | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/triedb/pathdb/layertree.go b/triedb/pathdb/layertree.go index ec45257db5..0d7ca5a5b4 100644 --- a/triedb/pathdb/layertree.go +++ b/triedb/pathdb/layertree.go @@ -151,6 +151,15 @@ func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint6 if root == parentRoot { return errors.New("layer cycle") } + // If a layer with this root already exists, skip the insertion. Fork blocks + // can produce the same state root as the canonical block (same parent, same + // coinbase, zero txs); overwriting tree.layers[root] would corrupt the parent + // chain for any child layers already built on top of the existing one, and + // appending a duplicate root to the lookup indices causes accountTip/storageTip + // to resolve the wrong layer. + if tree.get(root) != nil { + return nil + } parent := tree.get(parentRoot) if parent == nil { return fmt.Errorf("triedb parent [%#x] layer missing", parentRoot) diff --git a/triedb/pathdb/layertree_test.go b/triedb/pathdb/layertree_test.go index a74c6eb045..285ca67b6c 100644 --- a/triedb/pathdb/layertree_test.go +++ b/triedb/pathdb/layertree_test.go @@ -575,6 +575,40 @@ func TestDescendant(t *testing.T) { } } +func TestDuplicateRootLookup(t *testing.T) { + // Chain: + // C1->C2->C3 (HEAD) + tr := newTestLayerTree() // base = 0x1 + tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), + NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false)) + tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil), + NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false)) + + // A fork block with the same state root as C2; inserting it must not + // pollute the lookup history for the canonical descendant C3. + tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil), + NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false)) + if n := tr.len(); n != 3 { + t.Fatalf("duplicate root insert changed layer count, got %d, want 3", n) + } + + l, err := tr.lookupAccount(common.HexToHash("0xa"), common.Hash{0x3}) + if err != nil { + t.Fatalf("account lookup failed: %v", err) + } + if l.rootHash() != (common.Hash{0x3}) { + t.Errorf("unexpected account tip, want %x, got %x", common.Hash{0x3}, l.rootHash()) + } + + l, err = tr.lookupStorage(common.HexToHash("0xa"), common.HexToHash("0x1"), common.Hash{0x3}) + if err != nil { + t.Fatalf("storage lookup failed: %v", err) + } + if l.rootHash() != (common.Hash{0x3}) { + t.Errorf("unexpected storage tip, want %x, got %x", common.Hash{0x3}, l.rootHash()) + } +} + func TestAccountLookup(t *testing.T) { // Chain: // C1->C2->C3->C4 (HEAD)