{"record":{"id":"05bfa156b987ae28","repo":"nautechsystems/nautilus_trader","slug":"invalid-execution-transition-for-intent-intent-id-05bfa1","errorCode":null,"errorMessage":"Invalid execution transition for intent {intent_id}: {current_status} -> replaced","messagePattern":"Invalid execution transition for intent (.+?): (.+?) -> replaced","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":3801,"sourceCode":"        &self,\n        intent_id: i64,\n        chain_id: u32,\n        transaction_hash: &str,\n    ) -> anyhow::Result<ExecutionTransactionHashRow> {\n        let chain_id_db = i32::try_from(chain_id)\n            .with_context(|| format!(\"Chain ID {chain_id} exceeds PostgreSQL INTEGER\"))?;\n        let mut transaction = self.pool.begin().await.map_err(|e| {\n            anyhow::anyhow!(\"Failed to start replacement transaction persistence: {e}\")\n        })?;\n        let current_status = sqlx::query_scalar::<_, String>(\n            \"SELECT status FROM execution_intent WHERE id = $1 AND active FOR UPDATE\",\n        )\n        .bind(intent_id)\n        .fetch_optional(&mut *transaction)\n        .await\n        .map_err(|e| anyhow::anyhow!(\"Failed to lock active execution intent {intent_id}: {e}\"))?\n        .ok_or_else(|| anyhow::anyhow!(\"Active execution intent {intent_id} was not found\"))?;\n        anyhow::ensure!(\n            execution_transition_allowed(&current_status, TransactionStatus::Replaced),\n            \"Invalid execution transition for intent {intent_id}: {current_status} -> replaced\"\n        );\n\n        sqlx::query(\n            \"\n            UPDATE execution_transaction_hash\n            SET current = FALSE, status = 'replaced', updated_at = NOW()\n            WHERE intent_id = $1 AND current\n            \",\n        )\n        .bind(intent_id)\n        .execute(&mut *transaction)\n        .await\n        .map_err(|e| anyhow::anyhow!(\"Failed to retire replaced execution hash: {e}\"))?;\n\n        let row = sqlx::query_as::<_, ExecutionTransactionHashRow>(\n            \"","sourceCodeStart":3783,"sourceCodeEnd":3819,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/cache/database.rs#L3783-L3819","documentation":"State-machine guard in add_execution_replacement_hash (database.rs:3801-3804) backed by execution_transition_allowed (database.rs:3990-4028): a transition to 'replaced' is only legal from 'signed', 'broadcast', 'included', 'replaced', 'dropped', or 'reorged'. It is rejected from terminal states ('finalized', 'reverted'), from 'recoverable', from 'prepared', and from any unrecognized status string. The error rejects the replacement recording atomically before any writes.","triggerScenarios":"Calling add_execution_replacement_hash when the intent is already finalized or reverted (the replacement was observed after finality - a late or reordered chain event); the intent is still 'prepared' (never signed, so a replacement hash indicates the hash was matched to the wrong intent); the intent is 'recoverable' and must be recovered, not replaced.","commonSituations":"RPC event reordering where the replacement notification lands after the finalize notification; a reorg causing both a finalize and a replacement observation for the same nonce; hash-to-intent matching logic attributing another wallet's replacement to this intent.","solutions":["Inspect the intent's current status (SELECT status FROM execution_intent WHERE id = $1) and compare against the allowed-from set above","If the intent is finalized/reverted, drop the replacement event - the nonce was consumed by the canonical transaction and the state is terminal","If the intent is 'prepared', the replacement hash points at the wrong intent; re-check the hash-to-intent attribution (nonce, wallet, chain) in the watcher","If the intent is 'recoverable', run the recovery flow first - it re-enters a replaceable state and replacement can then be recorded","Never bypass the guard by writing status directly; the transition table exists to keep the audit trail consistent"],"exampleFix":"// before: unconditional replacement recording fails on terminal intents\nlet row = db.add_execution_replacement_hash(intent_id, chain_id, &hash).await?;\n\n// after: check the transition is legal first, skip stale replacements\nlet hashes = db.get_execution_transaction_hashes(intent_id).await?;\nlet current = /* load intent status via your cached row or a small SELECT */ intent.status.as_str();\nlet replaceable = matches!(current, \"signed\" | \"broadcast\" | \"included\" | \"replaced\" | \"dropped\" | \"reorged\");\nlet row = if replaceable {\n    Some(db.add_execution_replacement_hash(intent_id, chain_id, &hash).await?)\n} else {\n    None // already terminal/recoverable: stale replacement event\n};","handlingStrategy":"validation","validationCode":"// mirror of execution_transition_allowed for the 'replaced' target\nfn replacement_allowed(current_status: &str) -> bool {\n    matches!(\n        current_status,\n        \"signed\" | \"broadcast\" | \"included\" | \"replaced\" | \"dropped\" | \"reorged\"\n    )\n}","typeGuard":null,"tryCatchPattern":"match db.add_execution_replacement_hash(intent_id, chain_id, &hash).await {\n    Ok(row) => Ok(Some(row)),\n    Err(e) if e.to_string().contains(\"Invalid execution transition\") => {\n        // stale or misattributed replacement: log with current status and skip\n        Ok(None)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Check the intent status before recording replacements; terminal intents must reject them","Verify hash-to-intent attribution (wallet + nonce + chain) before calling with a replacement hash","Never write intent status directly to bypass the guard; keep the audit trail consistent","Handle reorgs where finalize and replacement race: exactly one observer should win"],"tags":["rust","postgres","blockchain","state-machine","transition-guard","reorg"],"backgroundTag":"invalid-state-transition","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}