Skip to content

faultproof_withdrawals: Skip withdrawal events whose proof was deleted - #199

Open
ajsutton wants to merge 4 commits into
mainfrom
aj/fix/faultproof-deleted-proof
Open

faultproof_withdrawals: Skip withdrawal events whose proof was deleted#199
ajsutton wants to merge 4 commits into
mainfrom
aj/fix/faultproof-deleted-proof

Conversation

@ajsutton

@ajsutton ajsutton commented Sep 1, 2026

Copy link
Copy Markdown

The forgery detector reads the proof record from state, because neither prove event carries the
game address. If the record is gone, provenWithdrawals(hash, submitter) returns the zero
address, the monitor binds a dispute game at 0x0, and RootClaim fails on an address with no
code. Monitor.Run then logs the error and returns without advancing m.state.nextL1Height, so
every later tick re-scans the same range and fails the same way. The monitor stops permanently.

ethereum-optimism/optimism#22669 adds OptimismPortal.deleteProvenWithdrawal, which lets anyone
delete a proof record once its game resolved CHALLENGER_WINS or was blacklisted. The prove
events stay in the logs forever, and a fresh monitor start backfills 14 days, so one deletion
inside that window would brick the monitor at its next restart. This change must ship and deploy
before that contract change.

An empty record on its own does not prove a deletion. The record is read at the head of the
chain, so a lagging node, a failover between nodes, or a reorg of the prove transaction can all
return an empty record for a proof that is still live. Skipping on the empty read alone would
turn a stall into a silent miss of a live proof. So an empty record is only treated as a deletion
when the portal also emitted a matching WithdrawalProofDeleted event at or after the prove
block. Then the validator returns ErrWithdrawalProofDeleted, both enrichment loops log the
event and skip it, and the monitor advances its cursor.

An empty record with no deletion event returns ErrWithdrawalProofMissing, which fails the block
range as before. That case is transient: the range is retried on the next tick and succeeds once
the node catches up, or once the reorged prove event stops being returned.

The deletion topic is computed from the event signature, because the checked-in binding predates
the event.

Tests use a fake JSON-RPC L1, so they pin the behaviour without a devnet. They cover the skip
with a deletion event, the retry without one, and a control proving live proofs are still
enriched. The skip tests fail without the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant