Skip to content

Commit 52b8c09

Browse files
authored
triedb/pathdb: skip duplicate-root layer insertion (#34642)
PathDB keys diff layers by state root, not by block hash. That means a side-chain block can legitimately collide with an existing canonical diff layer when both blocks produce the same post-state (for example same parent, same coinbase, no txs). Today `layerTree.add` blindly inserts that second layer. If the root already exists, this overwrites `tree.layers[root]` and appends the same root to the mutation lookup again. Later account/storage lookups resolve that root to the wrong diff layer, which can corrupt reads for descendant canonical states. At runtime, the corruption is silent: no error is logged and no invariant check fires. State reads against affected descendants simply return stale data from the wrong diff layer (for example, an account balance that reflects one fewer block reward), which can propagate into RPC responses and block validation. This change makes duplicate-root inserts idempotent. A second layer with the same state root does not add any new retrievable state to a tree that is already keyed by root; keeping the original layer preserves the existing parent chain and avoids polluting the lookup history with duplicate roots. The regression test imports a canonical chain of two layers followed by a fork layer at height 1 with the same state root but a different block hash. Before the fix, account and storage lookups at the head resolve the fork layer instead of the canonical one. After the fix, the duplicate insert is skipped and lookups remain correct.
1 parent b5d3220 commit 52b8c09

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

triedb/pathdb/layertree.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,15 @@ func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint6
151151
if root == parentRoot {
152152
return errors.New("layer cycle")
153153
}
154+
// If a layer with this root already exists, skip the insertion. Fork blocks
155+
// can produce the same state root as the canonical block (same parent, same
156+
// coinbase, zero txs); overwriting tree.layers[root] would corrupt the parent
157+
// chain for any child layers already built on top of the existing one, and
158+
// appending a duplicate root to the lookup indices causes accountTip/storageTip
159+
// to resolve the wrong layer.
160+
if tree.get(root) != nil {
161+
return nil
162+
}
154163
parent := tree.get(parentRoot)
155164
if parent == nil {
156165
return fmt.Errorf("triedb parent [%#x] layer missing", parentRoot)

triedb/pathdb/layertree_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,40 @@ func TestDescendant(t *testing.T) {
575575
}
576576
}
577577

578+
func TestDuplicateRootLookup(t *testing.T) {
579+
// Chain:
580+
// C1->C2->C3 (HEAD)
581+
tr := newTestLayerTree() // base = 0x1
582+
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil),
583+
NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false))
584+
tr.add(common.Hash{0x3}, common.Hash{0x2}, 2, NewNodeSetWithOrigin(nil, nil),
585+
NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false))
586+
587+
// A fork block with the same state root as C2; inserting it must not
588+
// pollute the lookup history for the canonical descendant C3.
589+
tr.add(common.Hash{0x2}, common.Hash{0x1}, 1, NewNodeSetWithOrigin(nil, nil),
590+
NewStateSetWithOrigin(randomAccountSet("0xa"), randomStorageSet([]string{"0xa"}, [][]string{{"0x1"}}, nil), nil, nil, false))
591+
if n := tr.len(); n != 3 {
592+
t.Fatalf("duplicate root insert changed layer count, got %d, want 3", n)
593+
}
594+
595+
l, err := tr.lookupAccount(common.HexToHash("0xa"), common.Hash{0x3})
596+
if err != nil {
597+
t.Fatalf("account lookup failed: %v", err)
598+
}
599+
if l.rootHash() != (common.Hash{0x3}) {
600+
t.Errorf("unexpected account tip, want %x, got %x", common.Hash{0x3}, l.rootHash())
601+
}
602+
603+
l, err = tr.lookupStorage(common.HexToHash("0xa"), common.HexToHash("0x1"), common.Hash{0x3})
604+
if err != nil {
605+
t.Fatalf("storage lookup failed: %v", err)
606+
}
607+
if l.rootHash() != (common.Hash{0x3}) {
608+
t.Errorf("unexpected storage tip, want %x, got %x", common.Hash{0x3}, l.rootHash())
609+
}
610+
}
611+
578612
func TestAccountLookup(t *testing.T) {
579613
// Chain:
580614
// C1->C2->C3->C4 (HEAD)

0 commit comments

Comments
 (0)