trie/bintrie: test DeleteAccount produces a deterministic root

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.
This commit is contained in:
CPerezz 2026-04-08 11:52:30 +02:00
parent 2a7effb6ca
commit d38689e4ed
No known key found for this signature in database
GPG key ID: 62045F34B97177DD

View file

@ -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()