{"record":{"id":"d1d62ffabdf72445","repo":"nautechsystems/nautilus_trader","slug":"execution-intent-intent-id-is-not-prepared-for-n","errorCode":null,"errorMessage":"Execution intent {intent_id} is not prepared for nonce {nonce}","messagePattern":"Execution intent (.+?) is not prepared for nonce (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":3417,"sourceCode":"        nonce: u64,\n    ) -> anyhow::Result<()> {\n        let nonce_db = i64::try_from(nonce)\n            .with_context(|| format!(\"Execution nonce {nonce} exceeds PostgreSQL BIGINT\"))?;\n        let result = sqlx::query(\n            \"\n            UPDATE execution_intent\n            SET nonce = $2, updated_at = NOW()\n            WHERE id = $1\n              AND status = 'prepared'\n              AND (nonce IS NULL OR nonce = $2)\n            \",\n        )\n        .bind(intent_id)\n        .bind(nonce_db)\n        .execute(&self.pool)\n        .await\n        .map_err(|e| anyhow::anyhow!(\"Failed to assign nonce {nonce} to execution intent: {e}\"))?;\n        anyhow::ensure!(\n            result.rows_affected() == 1,\n            \"Execution intent {intent_id} is not prepared for nonce {nonce}\"\n        );\n        Ok(())\n    }\n\n    /// Releases an intent when no broadcast attempt can have occurred.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the intent advanced to broadcast or persistence fails.\n    pub async fn mark_execution_intent_recoverable(&self, intent_id: i64) -> anyhow::Result<()> {\n        let mut transaction = self.pool.begin().await.map_err(|e| {\n            anyhow::anyhow!(\"Failed to start recoverable execution transition: {e}\")\n        })?;\n        let current_status = sqlx::query_scalar::<_, String>(\n            \"SELECT status FROM execution_intent WHERE id = $1 FOR UPDATE\",\n        )","sourceCodeStart":3399,"sourceCodeEnd":3435,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/cache/database.rs#L3399-L3435","documentation":"The guarded UPDATE in assign_execution_intent_nonce matched zero rows: the intent is not in status 'prepared', or it already owns a different nonce (the nonce IS NULL OR nonce = $2 predicate failed). This is the API failing closed against binding two different nonces to one intent, which would desynchronize the signer's on-chain nonce sequence.","triggerScenarios":"Calling assign after the intent advanced to 'signed'; a crash-recovery flow re-derives a nonce from the node and assigns it to an intent that already stored one; passing the wrong intent_id.","commonSituations":"Restart between reserve and assign where nonce management computed a fresh value; concurrent executors targeting the same intent; drift between the node's transaction count and the persisted nonce.","solutions":["Load the intent first and verify status is 'prepared' and nonce is NULL or equal before assigning","If a different nonce is already stored, stop and reconcile against the node's transaction count - do not force the UPDATE","If status advanced past 'prepared', resume from the persisted state instead of re-assigning","Pass the identical nonce on retries so the assignment stays idempotent"],"exampleFix":"// before: assign whatever nonce was just fetched\nlet nonce = provider.transaction_count(wallet).await?;\ndb.assign_execution_intent_nonce(intent_id, nonce).await?;\n\n// after: keep the persisted nonce authoritative\nlet nonce = match row.nonce {\n    Some(stored) => stored as u64,\n    None => provider.transaction_count(wallet).await?,\n};\ndb.assign_execution_intent_nonce(row.id, nonce).await?;","handlingStrategy":"validation","validationCode":"// Verify ownership preconditions before assigning\nlet row: Option<(String, Option<i64>)> = sqlx::query_as(\n    \"SELECT status, nonce FROM execution_intent WHERE id = $1\",\n)\n.bind(intent_id)\n.fetch_optional(&pool)\n.await?;\nmatch row {\n    Some((status, nonce)) if status == \"prepared\" && nonce.map_or(true, |n| n == nonce_db) => {\n        // safe to assign\n    }\n    _ => { /* reconcile instead of assigning */ }\n}","typeGuard":"fn intent_accepts_nonce(row: &ExecutionIntentRow, nonce: u64) -> bool {\n    row.status.as_str() == \"prepared\"\n        && row.nonce.map_or(true, |stored| stored == nonce as i64)\n}","tryCatchPattern":"match db.assign_execution_intent_nonce(intent_id, nonce).await {\n    Err(e) if e.to_string().contains(\"is not prepared for nonce\") => {\n        // load the intent and reconcile: resume from its persisted nonce or halt for manual review\n    }\n    other => other?,\n}","preventionTips":["Treat the persisted nonce as authoritative - never re-derive and force a different one after restarts","Keep the ExecutionIntentRow from reserve_execution_intent and use its status/nonce for all subsequent decisions","Halt execution on nonce mismatch and reconcile with the node's transaction count rather than overriding"],"tags":["rust","sqlx","postgresql","nonce","state-machine","optimistic-concurrency","fail-closed"],"backgroundTag":"optimistic-concurrency-conflict","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}