From 2eb1ccc6c40d16722a5ee91dc23aa1255e5c2f06 Mon Sep 17 00:00:00 2001 From: forkfury Date: Tue, 20 Jan 2026 13:36:07 +0100 Subject: [PATCH] core/state: ensure deterministic hook emission order in Finalise (#33644) Fixes #33630 Sort self-destructed addresses before emitting hooks in Finalise() to ensure deterministic ordering and fix flaky test TestHooks_OnCodeChangeV2. --------- Co-authored-by: jwasinger --- core/state/statedb_hooked.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 4ffa69b419..48794a3f41 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -17,7 +17,9 @@ package state import ( + "bytes" "math/big" + "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/stateless" @@ -234,13 +236,24 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { return } - // Iterate all dirty addresses and record self-destructs. + // Collect all self-destructed addresses first, then sort them to ensure + // that state change hooks will be invoked in deterministic + // order when the accounts are deleted below + var selfDestructedAddrs []common.Address for addr := range s.inner.journal.dirties { obj := s.inner.stateObjects[addr] if obj == nil || !obj.selfDestructed { // Not self-destructed, keep searching. continue } + selfDestructedAddrs = append(selfDestructedAddrs, addr) + } + sort.Slice(selfDestructedAddrs, func(i, j int) bool { + return bytes.Compare(selfDestructedAddrs[i][:], selfDestructedAddrs[j][:]) < 0 + }) + + for _, addr := range selfDestructedAddrs { + obj := s.inner.stateObjects[addr] // Bingo: state object was self-destructed, call relevant hooks. // If ether was sent to account post-selfdestruct, record as burnt.