{"record":{"id":"68de2898c6833c65","repo":"nautechsystems/nautilus_trader","slug":"execution-nonce-does-not-match-canonical-nonce","errorCode":null,"errorMessage":"Execution nonce {} does not match canonical nonce {next_nonce}","messagePattern":"Execution nonce (.+?) does not match canonical nonce (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":5933,"sourceCode":"                \"\n                SELECT manifest_version, manifest_digest, next_canonical_nonce, revision\n                FROM execution_verification_nonce\n                WHERE chain_id = $1 AND wallet_address = $2\n                FOR UPDATE\n                \",\n            )\n            .bind(chain_id)\n            .bind(assignment.wallet_address)\n            .fetch_optional(&mut *transaction)\n            .await\n            .map_err(|e| anyhow::anyhow!(\"Failed to lock canonical nonce ledger: {e}\"))?\n            .ok_or_else(|| anyhow::anyhow!(\"Canonical nonce ledger is not initialized\"))?;\n        anyhow::ensure!(\n            manifest_version == assignment.manifest_version\n                && manifest_digest == assignment.manifest_digest,\n            \"Verified nonce assignment manifest identity changed\"\n        );\n        anyhow::ensure!(\n            next_nonce == nonce,\n            \"Execution nonce {} does not match canonical nonce {next_nonce}\",\n            assignment.nonce\n        );\n\n        let (intent_chain_id, intent_wallet, intent_nonce, intent_status, intent_active) =\n            sqlx::query_as::<_, (i32, String, Option<i64>, String, bool)>(\n                \"\n            SELECT chain_id, wallet_address, nonce, status, active\n            FROM execution_intent\n            WHERE id = $1\n            FOR UPDATE\n            \",\n            )\n            .bind(assignment.intent_id)\n            .fetch_optional(&mut *transaction)\n            .await\n            .map_err(|e| {","sourceCodeStart":5915,"sourceCodeEnd":5951,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L5915-L5951","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n let assignment = Assignment { nonce: cached_nonce, .. };\n db.assign_verified_nonce(assignment).await?;\n// after\n let next = db.current_canonical_nonce(chain_id, wallet).await?;\n let assignment = Assignment { nonce: next, .. };\n db.assign_verified_nonce(assignment).await?;","handlingStrategy":"validation","validationCode":"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?;\nif assignment.nonce != next as u64 {\n    return Err(anyhow!(\"stale nonce {}: canonical is {next}; refetch and rebuild\", assignment.nonce));\n}","typeGuard":"fn nonce_is_current(assignment: &Assignment, next_canonical: i64) -> bool {\n    assignment.nonce == next_canonical as u64\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"does not match canonical nonce\") => {\n        let next = db.current_canonical_nonce(chain_id, wallet).await?;\n        let fresh = assignment.with_nonce(next);\n        db.assign_verified_nonce(&fresh).await\n    }\n    other => other,\n}","preventionTips":["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."],"tags":["nonce","consistency","concurrency","database"],"backgroundTag":"invalid-state-transition","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}