Skip to content

fix(nonce_manager): skip out-of-order-confirmed nonces on acquire#118

Merged
abhicris merged 1 commit into
kcolbchain:mainfrom
kite-builds:fix/nonce-skip-ooo-confirmed
Jun 7, 2026
Merged

fix(nonce_manager): skip out-of-order-confirmed nonces on acquire#118
abhicris merged 1 commit into
kcolbchain:mainfrom
kite-builds:fix/nonce-skip-ooo-confirmed

Conversation

@kite-builds

Copy link
Copy Markdown
Contributor

Problem

NonceManager supports out-of-order confirmations: confirm_nonce(n) for an n greater than confirmed_nonce drops n from pending_nonces and stashes it in out_of_order_confirmations to be rolled into confirmed_nonce once the gap fills.

But _reserve_next_nonce (the gap-reuse walk introduced for the release_nonce/gap case in #93) only consulted pending_nonces. An out-of-order-confirmed nonce is not pending (it was dropped) and not below confirmed_nonce, so the walk happily re-hands it out — even though it is already mined on-chain. The chain then rejects the new transaction with "nonce too low", and because Ethereum requires gapless nonces, every higher-nonce pending tx stalls behind it.

Reproduction (current main)

nm.acquire_nonce(addr)        # 0
nm.acquire_nonce(addr)        # 1
nm.acquire_nonce(addr)        # 2
nm.confirm_nonce(addr, 2)     # mined out of order; pending = {0, 1}
nm.acquire_nonce(addr)        # -> 2  ❌ already confirmed -> "nonce too low"

Expected: 3.

Fix

_reserve_next_nonce now skips nonces present in out_of_order_confirmations in addition to pending_nonces. Behaviour in the gapless common case (and the #93 release/gap-reuse case) is unchanged.

next_nonce = state.confirmed_nonce
while (
    next_nonce in state.pending_nonces
    or next_nonce in state.out_of_order_confirmations
):
    next_nonce += 1

Tests

  • New regression test test_acquire_skips_out_of_order_confirmed_noncefails on main (2 != 3), passes with the fix.
  • Full suite: 219 passed, 62 skipped. ruff check switchboard/nonce_manager.py clean.

Follow-up to the same gap-reuse logic as #93.

A nonce confirmed ahead of confirmed_nonce (out-of-order) is already mined
on-chain but is dropped from pending_nonces on confirmation, so the gap-reuse
walk in _reserve_next_nonce — which only consulted pending_nonces — re-handed
it out. The chain then rejects the new tx with "nonce too low", stalling the
whole pending queue (Ethereum requires gapless nonces).

Fix: the walk now skips nonces present in out_of_order_confirmations as well as
pending_nonces. +1 regression test (test_acquire_skips_out_of_order_confirmed_nonce).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@abhicris

abhicris commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Audit verdict: safe

Legitimate bugfix to nonce management with clear defensive intent and comprehensive regression test; no malicious payloads, supply-chain risk, credential leakage, or dangerous logic errors.

Audited by the kcolbchain PR pipeline. See pipeline docs.

@abhicris abhicris merged commit 8310064 into kcolbchain:main Jun 7, 2026
2 checks passed
@abhicris

abhicris commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Merged — thanks, @kite-builds. That's 6 merged PRs to kcolbchain now.

You're a Fellow — when paid research, partner-org work, or grant milestones come up, you're in the first invite pool.

Next-up issues across the org: https://kcolbchain.com/invitations/

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants