diff --git a/trie/bintrie/trie_test.go b/trie/bintrie/trie_test.go index 0ef0c865b4..46fb3cdb94 100644 --- a/trie/bintrie/trie_test.go +++ b/trie/bintrie/trie_test.go @@ -555,6 +555,48 @@ func TestDeleteAccountPreservesHeaderStorage(t *testing.T) { } } +// TestDeleteAccountHashIsDeterministic verifies that two independent tries +// running the same UpdateAccount → DeleteAccount sequence produce identical +// root hashes. This is the consensus-critical property of the tombstone +// model: deletion may not produce a pristine-empty root (zero blobs hash to +// non-zero leaves), but it MUST be deterministic across independent runs. +// +// A regression that introduced any non-determinism into the tombstone write +// path (e.g. relying on map iteration order, randomized offsets) would +// silently fork the network on the first self-destruct after enabling flat +// state. +func TestDeleteAccountHashIsDeterministic(t *testing.T) { + addr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + codeHash := common.HexToHash("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + acc := makeAccount(42, 1000, codeHash) + + run := func() common.Hash { + tr := newTestTrie(t) + if err := tr.UpdateAccount(addr, acc, 0); err != nil { + t.Fatalf("UpdateAccount: %v", err) + } + if err := tr.DeleteAccount(addr); err != nil { + t.Fatalf("DeleteAccount: %v", err) + } + return tr.Hash() + } + + first := run() + second := run() + if first != second { + t.Fatalf("non-deterministic root after Update+Delete: first=%x second=%x", first, second) + } + + // Sanity check the tombstone model itself: the post-delete root is NOT + // the empty-trie root, because zero blobs hash to non-zero leaves. If + // this assertion ever flips, the tombstone semantics have silently + // changed and the deletion-detection branch in GetAccount needs review. + empty := newTestTrie(t).Hash() + if first == empty { + t.Fatalf("post-delete root unexpectedly equals empty-trie root %x; tombstone semantics changed", empty) + } +} + func TestBinaryTrieWitness(t *testing.T) { tracer := trie.NewPrevalueTracer()