mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 09:51:36 +00:00
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.
This commit is contained in:
parent
bcb0efd756
commit
f74037c010
2 changed files with 43 additions and 0 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue