{"record":{"id":"b61a0a632d2b697d","repo":"nautechsystems/nautilus_trader","slug":"execution-intent-cannot-own-canonical-nonce","errorCode":null,"errorMessage":"Execution intent {} cannot own canonical nonce {}","messagePattern":"Execution intent (.+?) cannot own canonical nonce (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":5957,"sourceCode":"        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| {\n                anyhow::anyhow!(\"Failed to lock execution intent for nonce assignment: {e}\")\n            })?\n            .ok_or_else(|| {\n                anyhow::anyhow!(\"Execution intent {} was not found\", assignment.intent_id)\n            })?;\n        anyhow::ensure!(\n            intent_chain_id == chain_id\n                && intent_wallet == assignment.wallet_address\n                && intent_status == \"prepared\"\n                && intent_active\n                && intent_nonce.is_none_or(|assigned| assigned == nonce),\n            \"Execution intent {} cannot own canonical nonce {}\",\n            assignment.intent_id,\n            assignment.nonce\n        );\n\n        for (index, decision) in assignment.decisions.iter().enumerate() {\n            let height_start = decision\n                .height_start\n                .map(i64::try_from)\n                .transpose()\n                .context(\"Verification height exceeds PostgreSQL BIGINT\")?;\n            let height_end = decision\n                .height_end","sourceCodeStart":5939,"sourceCodeEnd":5975,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L5939-L5975","documentation":"After locking the intent row, an `anyhow::ensure!` verifies the intent's chain_id, wallet_address, status (must be \"prepared\"), active flag, and prior nonce all match the assignment being made (an already-assigned nonce must equal the requested one). If any check fails, this error is thrown. It is an ownership/state-consistency guard: the intent being assigned does not actually belong to this chain/wallet at this point in the state machine, or already owns a different nonce.","triggerScenarios":"Calling `assign_execution_intent_nonce_verified` when: the intent belongs to a different chain_id or wallet than the ledger row selected; the intent status is not \"prepared\" (already assigned, executed, cancelled, or expired); the intent is marked inactive; or the intent already has a nonce recorded that differs from the requested canonical nonce.","commonSituations":"Double-assignment attempts after a first assignment succeeded (status moved past \"prepared\"); a cancelled or superseded intent reused from stale state; caller mixing up wallets or chains when building the assignment; a nonce re-fetch produced a newer canonical nonce than the one already written to the intent.","solutions":["Re-read the intent (chain_id, wallet, status, active, nonce) before assignment and only call this method for intents in `prepared`/active state with no nonce or the matching nonce.","Ensure the assignment is built from the same chain_id and wallet_address used when locking the canonical nonce ledger row.","Handle the already-assigned case idempotently: if intent.nonce equals the requested nonce, treat assignment as already done instead of re-assigning.","Check for concurrent assignment races and cancel/re-create intents that have left the prepared state."],"exampleFix":"// before: blindly assigning\nlet assignment = ExecutionNonceAssignment { intent_id, chain_id, wallet: other_wallet, nonce, .. };\ndb.assign_execution_intent_nonce_verified(&assignment).await?;\n// after: validate intent state first\nlet intent = db.get_execution_intent(intent_id).await?;\nanyhow::ensure!(intent.status == \"prepared\" && intent.active, \"intent {} not assignable\", intent_id);\nanyhow::ensure!(intent.chain_id == assignment.chain_id && intent.wallet_address == assignment.wallet, \"intent ownership mismatch\");\ndb.assign_execution_intent_nonce_verified(&assignment).await?;","handlingStrategy":"validation","validationCode":"let (chain_id, wallet, status, active, nonce): (i32, String, String, bool, Option<i64>) = sqlx::query_as(\n    \"SELECT chain_id, wallet_address, status, active, nonce FROM execution_intent WHERE id = $1\")\n    .bind(&assignment.intent_id).fetch_one(pool).await?;\nanyhow::ensure!(status == \"prepared\" && active, \"intent {} not in prepared+active state\", assignment.intent_id);\nanyhow::ensure!(chain_id == assignment.chain_id && wallet == assignment.wallet_address, \"intent ownership mismatch\");\nanyhow::ensure!(nonce.is_none() || nonce == Some(assignment.nonce), \"intent already owns a different nonce\");","typeGuard":null,"tryCatchPattern":"match db.assign_execution_intent_nonce_verified(&assignment).await {\n    Err(e) if e.to_string().contains(\"cannot own canonical nonce\") => { /* re-fetch intent; handle already-assigned or invalid state */ }\n    other => other?,\n}","preventionTips":["Only assign nonces to intents you can prove are `prepared` and `active`.","Treat assignment as idempotent when intent.nonce already equals the target nonce.","Build assignments from the same (chain_id, wallet) pair used to prepare the intent; validate before calling.","Route every intent mutation through the database API so the state machine cannot be bypassed."],"tags":["invalid-state-transition","database","nonce-assignment","consistency"],"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"}