An account can be deleted and created again in the same block: for example, a contract runs SELFDESTRUCT in one transaction, and the next transaction deploys new code at the same address with CREATE2. After that block, the new account must not see any storage of the old one. Until 2026-11-15 xOneRS refused such a block and stopped at it. ADR-011 proposed a fix, and PR #1084, merged the same day, implements it. The ADR's status line still says Proposed: no owner acceptance is recorded in it.

The ADR also says that the geth-XDC behaviour it describes is to be checked against geth-XDC at the pinned commit before acceptance. This post marks that rule as unverified in the same way.

What must happen to the old storage?

The ADR states the rule as follows, to be verified at the pin:

At the end of a block the state of the account is: the new account record, with storage equal to the slots written after the deletion and nothing from before it. Slots of the old account that the new one does not write are absent, not kept.

So if the old contract had slots 1, 2 and 3, and the new contract writes only slot 2, then after the block slot 2 has the new value and slots 1 and 3 are empty. A client that keeps slots 1 and 3 computes a different state root and leaves the chain.

What did xOneRS do with such a block before PR #1084?

It refused it. Overlay::set in crates/xone-executor/src/overlay.rs (line 123) returns OverlayError::Recreated, so the block cannot be imported and the node stops at it. The chain does not stop: the ADR notes that geth-XDC accepts the block.

The cause is the shape of the state diff (StateDiff). An entry can say "delete the account and all its storage" or "set the account and write these slots", but not "wipe the storage, then write these slots". The state changeset spec records the same limit.

The issue was #443 (GAP-0120). PR #1084 closed it.

What does PR #1084 change?

From its description: a block that deletes an account and creates it again is no longer refused with OverlayError::Recreated. StateDiff carries a wipe marker for such accounts; apply clears the old storage before it writes the new slots; the changeset revert restores the original account and values; and the overlay compares the recreated account's storage with zero, not with the parent. This is design A below. The PR description lists no gate results, and none of the ADR's oracle vectors (a geth-XDC devnet block that recreates an account). The v0.1.0 release still refuses such a block.

Later on 2026-11-15: the net2040 replay of blocks 0 to 20,000 on main, with #1084, passed at 0 mismatches in 180.6 s (posted on #443). That range has no account recreation, so it does not exercise the wipe marker. A review of #1084 found it incomplete below the overlay: the MDBX write path, the diff codecs, the flat commit and the gap reseed ignore the wipe marker, so old slots would stay on disk. PR #1161 fixed that: the store write path, the diff codec, the flat commit, the side overlays and the gap reseed now honour the wipe marker, with property tests on four layers and the replay at 0 mismatches. ADR-011 still says Proposed, with no owner acceptance recorded.

What does ADR-011 propose?

A wipe marker in the diff:

  1. StateDiff and HashedDiff get one more field, wiped: the accounts whose storage is wiped before this diff's slot writes. An account that ends as set and is also in wiped is a recreation.
  2. The executor already records deletions in a private wiped set. It hands that set over, compares the new storage with zero (not with the old values), and stops returning Recreated.
  3. The flat state tables run the wipe step they already have (find the account's hashed slots, record their previous values, zero them), then set the account, then write the new slots.
  4. The trie drops the old storage trie as for a deletion, then continues with a new empty storage trie. The flush already writes "delete prefix", then "delete", then "put", so new rows replace the deleted prefix in the same transaction.
  5. Unwind needs no change: the changeset already holds the previous value of every slot it touches, including the slots the wipe zeroed.
  6. No on-disk migration: changesets keep their shape. The marker exists only in memory, in the diff handed to apply.

No preimages are needed, because the wipe works on hashed slot keys.

Which alternatives does the ADR reject, and why?

REFERENCE TABLEScroll to explore all columns →
Option What it does Cost given in the ADR
A. Wipe marker (proposed) Mark the account as wiped in the diff Several hundred lines in the consensus-critical state path
B. Net diff with explicit zeros The executor lists a zero for every old slot Needs raw slot keys, which a snap-synced or minimal datadir does not have without preimages; the diff grows with the deleted storage
C. Keep refusing No change The node halts at the first such block on any network, and a halted validator is penalised
D. Execute the block in two parts and merge No diff change Moves block semantics out of the executor, with no oracle to check it

Which gates did the ADR ask for?

The ADR lists these gates before merge. The PR description does not report them:

  • The net2040 replay of blocks 0 to 20,000 at 0 mismatches. No block in that range recreates an account, so this shows only that nothing else broke. See the net2040 replay how-to.
  • New vectors: a block that deletes and recreates one account, with and without surviving old slots, checked against geth-XDC state roots from a devnet block produced by the oracle.
  • The unwind and restart property tests over such a block, and the persisted-trie flush test.

The ADR names the trie path as the part most likely to be wrong, because it has no real-data test today.

Frequently asked questions

Will an xOne node stop if a block recreates an account?

v0.1.0 and builds before PR #1084, yes: they refuse the block with OverlayError::Recreated. Builds from main after PR #1084 import it, and since PR #1161 the old storage is also wiped on disk.

Does the new account keep the old contract's storage?

No. By the rule ADR-011 states, which is still to be verified against geth-XDC, only the slots written after the deletion remain.

Does ADR-011 change the datadir format?

No. Changesets keep their shape, and the wipe marker exists only in memory.

Is ADR-011 accepted?

Its status line still says Proposed, although PR #1084 implements it. Only the owner can change that line, and the ADR asks for the geth-XDC rule to be checked at the pinned commit first.

Sources