nautechsystems/nautilus_trader · error · anyhow::Error
Execution nonce {} does not match canonical nonce {next_nonc
Error message
Execution nonce {} does not match canonical nonce {next_nonce} What it means
The locked ledger row's next_canonical_nonce does not equal the nonce of the assignment being recorded. Canonical nonce assignment must be strictly sequential: each assignment must consume exactly the ledger's next nonce. This guard prevents gaps, duplicates, or out-of-order execution nonces from being committed to the canonical ledger.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:5933
"
SELECT manifest_version, manifest_digest, next_canonical_nonce, revision
FROM execution_verification_nonce
WHERE chain_id = $1 AND wallet_address = $2
FOR UPDATE
",
)
.bind(chain_id)
.bind(assignment.wallet_address)
.fetch_optional(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to lock canonical nonce ledger: {e}"))?
.ok_or_else(|| anyhow::anyhow!("Canonical nonce ledger is not initialized"))?;
anyhow::ensure!(
manifest_version == assignment.manifest_version
&& manifest_digest == assignment.manifest_digest,
"Verified nonce assignment manifest identity changed"
);
anyhow::ensure!(
next_nonce == nonce,
"Execution nonce {} does not match canonical nonce {next_nonce}",
assignment.nonce
);
let (intent_chain_id, intent_wallet, intent_nonce, intent_status, intent_active) =
sqlx::query_as::<_, (i32, String, Option<i64>, String, bool)>(
"
SELECT chain_id, wallet_address, nonce, status, active
FROM execution_intent
WHERE id = $1
FOR UPDATE
",
)
.bind(assignment.intent_id)
.fetch_optional(&mut *transaction)
.await
.map_err(|e| {View on GitHub (pinned to 18893faf8b)
Solutions
- Refresh the assignment's nonce from the canonical ledger: read next_canonical_nonce and rebuild the assignment.
- Serialize assignments per (chain_id, wallet) through a single writer or the ledger's locking path so no races occur.
- Drop/reject stale assignments from before the ledger advanced; do not retry them verbatim.
- Inspect the ledger row (`SELECT next_canonical_nonce ...`) to see how far the incoming nonce has drifted.
- If nonces legitimately diverged, reconcile via the project's recovery/resync procedure rather than forcing an update.
Example fix
// before
let assignment = Assignment { nonce: cached_nonce, .. };
db.assign_verified_nonce(assignment).await?;
// after
let next = db.current_canonical_nonce(chain_id, wallet).await?;
let assignment = Assignment { nonce: next, .. };
db.assign_verified_nonce(assignment).await?; Defensive patterns
Strategy: validation
Validate before calling
let next: i64 = sqlx::query_scalar("SELECT next_canonical_nonce FROM execution_verification_nonce WHERE chain_id = $1 AND wallet_address = $2").bind(chain_id).bind(wallet).fetch_one(pool).await?;
if assignment.nonce != next as u64 {
return Err(anyhow!("stale nonce {}: canonical is {next}; refetch and rebuild", assignment.nonce));
} Type guard
fn nonce_is_current(assignment: &Assignment, next_canonical: i64) -> bool {
assignment.nonce == next_canonical as u64
} Try / catch
match result {
Err(e) if e.to_string().contains("does not match canonical nonce") => {
let next = db.current_canonical_nonce(chain_id, wallet).await?;
let fresh = assignment.with_nonce(next);
db.assign_verified_nonce(&fresh).await
}
other => other,
} Prevention
- Always derive the next nonce from the canonical ledger, never from a long-lived local cache.
- Route all assignments for a wallet through a single serialized writer or queue.
- After any partial failure, discard in-flight assignments and refetch next_canonical_nonce before retrying.
- Avoid running multiple processes against the same wallet without external coordination.
- Monitor nonce drift metrics to catch divergence between client and ledger early.
When it happens
Trigger: Submitting an assignment whose `assignment.nonce` is not equal to the row's `next_canonical_nonce`: replaying an old assignment, two writers racing where one lost (ledger advanced), a skipped nonce due to a partially failed earlier batch, or client-side nonce computation diverging from the canonical ledger.
Common situations: Retrying a batch of assignments after a partial failure without refreshing next_nonce from the DB; running two processes against the same wallet concurrently; manual nonce management drifting from the ledger; restoring an old DB snapshot while in-flight assignments reference newer nonces.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Verified finalized transaction count advanced without an act
- Verified finalized transaction count is outside the owned re
- Execution intent {} is not prepared for canonical nonce {}
- Canonical nonce ledger changed during finality transition
- Canonical nonce advanced without an authenticated signer tra
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/68de2898c6833c65.
Report an issue: GitHub.