fix(delivery): classify the receiver's MessageAlreadyProcessed as idempotent success - #52
Conversation
…mpotent success
Two on-chain guards can reject a delivery as a duplicate, and only one was
matched. The Inbox guard (MessageAlreadyValidated / the SimpleInbox "Already
validated" string) was treated as idempotent success; the receiver-side guard —
MessageReceiverBase.MessageAlreadyProcessed, which fires when the callback
already ran for this messageId — was not, so it fell through to the terminal
revert path.
A restart replaying the delivery checkpoint hit exactly this on usc-devnet
(2026-09-01):
ERROR ❌ delivery reverted; no further retries chain_key=8
message_id=0x635ab2e7… reason=… execution reverted,
data: "0x730ac1e2635ab2e7…"
0x730ac1e2 is MessageAlreadyProcessed(bytes32) carrying our own messageId: the
message was long since delivered and processed, yet the relayer burned gas on
the resend, logged a terminal ERROR and counted status=Reverted — polluting the
one deliver_tx status that should mean a real failure.
Rename revert_already_validated to revert_duplicate_delivery and match the
receiver guard the same three ways as the Inbox guard (string, decoded name,
raw selector). All three call sites — simulate, send error, mined-and-reverted —
flow through this one classifier.
The mirror lives in write_ability::abi as a minimal IMessageReceiver interface
and is asserted against the compiled MessageReceiverBase artifact by the ABI
drift gate; the dialect test additionally pins the selector to 0x730ac1e2 so a
signature edit that silently stops matching real reverts fails in CI.
PR SummaryLow Risk Overview The shared classifier is renamed to Reviewed by Cursor Bugbot for commit 8d07c98. Bugbot is set up for automated code reviews on this repo. Configure here. |
Found via last night's soak on usc-devnet: 144 deliveries, 143 succeeded, and the single
Revertedwas not a failure at all.What happened
A restart replayed the delivery checkpoint and resent a message that had already been fully processed:
0x730ac1e2decodes toMessageAlreadyProcessed(bytes32)—MessageReceiverBase's duplicate guard — carrying our own messageId. The delivery goal was already achieved, but the classifier only knew the Inbox duplicate guard (MessageAlreadyValidated/"Already validated"), so the receiver guard fell through to the terminal path: gas burned on the resend, a terminal ERROR logged, anddeliver_tx{status=Reverted}incremented — polluting the one status that should mean a genuine failure.Change
revert_already_validated→revert_duplicate_delivery, now matching both guards, each three ways (revert string, decoded error name, raw 4-byte selector) since node dialects differ. All three call sites (simulate, send error, mined-and-reverted) flow through this single classifier.IMessageReceivermirror inwrite_ability::abi, asserted against the compiledMessageReceiverBaseartifact by the ABI drift gate.[0x73, 0x0a, 0xc1, 0xe2]so a mirror edit that silently stops matching real reverts fails in CI rather than on devnet.Verification
cargo test --workspace— 150 tests pass, 0 failed.-D warningsclean, fmt clean.