From d38689e4ed159ad522bcb73c81241ca99260c51d Mon Sep 17 00:00:00 2001 From: CPerezz Date: Wed, 8 Apr 2026 11:52:30 +0200 Subject: [PATCH] trie/bintrie: test DeleteAccount produces a deterministic root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two independent tries running the same UpdateAccount → DeleteAccount sequence must produce identical root hashes. Deletion does not return the trie to a pristine-empty root (zero blobs hash to non-zero leaves under the tombstone model), but the post-delete root must be deterministic across runs — any non-determinism in the tombstone-write path would silently fork the network on the first self-destruct after enabling flat state. Also pin the post-delete root != empty root invariant so a future change to the tombstone semantics surfaces here instead of in GetAccount's deletion-detection branch. --- trie/bintrie/trie_test.go | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) 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()