A mined transaction, marked Failed: "Nonce N consumed externally"
Written 6 October 2026
Applies to OpenZeppelin Relayer 1.5.0 through 1.8.0 on EVM networks. Upstream issue: openzeppelin-relayer #817. Reproduction with compose file, proxy, driver and logs: zachsplat/relayer-817-repro.
Symptom
A transaction that mined with status = 0x1 is finalized as failed with the reason above, and your webhook consumer receives a failed transaction update for something that succeeded onchain. In the log, in this order:
transaction has been pending for too long, resubmitting
resubmission got nonce too low - scheduling nonce recovery
nonce recovery hint detected - performing nonce reconciliation hint=NonceTooLow
nonce recovery: nonce consumed externally, marking as Failed tx_nonce=N on_chain_nonce=N+1
It happens on load-balanced RPC endpoints: eth_ is answered by a node that has not seen the block yet (null), while eth_ is answered by a node that has (N+1). The reporter saw 26 consecutive fast transactions on Base finalized this way during a 45-minute provider lag window.
Cause
src/, reconcile_, in 1.8.0:
- It asks for the receipt of the transaction's current hash.
Ok(Some)returns to the normal flow;Errsetshad_rpc_errorsand later defers;Ok(None)is treated as authoritative "not mined" and continues. - Historical-hash recovery runs only if the record has more than one hash, which it does not after a rejected resend (the re-signed hash was discarded; that part is what PR #894 and its replacement #899 address).
- It compares the onchain nonce with the transaction's nonce.
N+1 > Nmeans "consumed externally", and the transaction is finalizedFailed, irreversibly.
The path is entered through the one-shot NonceTooLow hint that resubmit_transaction sets when a resend is rejected. The resend itself happens when a transaction has had no receipt for longer than the speed's resubmit timeout: safeLow 10 minutes, average 5, fast 3, fastest 2 (get_ in src/). The resend is rejected with nonce too low precisely because the original already mined.
So the sequence is: mined, receipt invisible to the relayer for longer than the resubmit timeout, resend rejected, reconciliation reads a stale null receipt and a fresh nonce, Failed.
Reproduction
On 2026-10-06 we ran the unmodified 1.8.0 image against anvil (a block every 2 s) through a small proxy that returns null for eth_ for a fixed window after the receipt first exists, and passes eth_ through. One transfer at speed: fast: mined at t+10 s with status 1; sixteen transaction not yet mined polls; at t+188 s the resend, JSON-RPC error (code -32003): nonce too low, reconciliation, and failed at t+190 s with the reason above; the webhook receiver got sent, submitted, failed. The receipt was never consulted again.
PR #899
#899 (maintainers, opened 2026-10-05, superseding #894) records the re-signed hash before broadcast and stops treating receipt lookup errors as proof of failure. We built it at d4b0574 and ran this reproduction against it four times on 2026-10-08, with stock 1.8.0 as a control on the same setup. Every run still finalized the mined transaction as Failed with the same reason. The new code is reached (the re-signed hash is recorded, and recovery checks both hashes), but the mined transaction's receipt still reads as Ok(None), which is not a lookup error, so the path above is unchanged. The runs are summarized in a comment on the PR.
Workarounds on 1.8.0
- The path is only reachable when the receipt stays invisible for longer than the speed's resubmit timeout. A lag window shorter than that never triggers it. Slower speeds (
average,safeLow) widen the margin;fastestnarrows it to 2 minutes. - Give the relayer one consistent RPC endpoint rather than a load-balanced pool, or a provider with sticky sessions, so the receipt and the nonce are read from the same view of the chain.
- Alert on
nonce recovery: nonce consumed externally. The record keeps the hash it tracked, so a receipt lookup afterwards tells you whether the transaction actually mined, and your downstream can be corrected by hand. - Nothing in the configuration disables reconciliation. The measures above come from reading the code; none of them was part of the reproduction.