{"record":{"id":"7f1279940c245a30","repo":"nautechsystems/nautilus_trader","slug":"invalid-execution-transition-for-intent-intent-id","errorCode":null,"errorMessage":"Invalid execution transition for intent {intent_id}: {current_status} -> {}","messagePattern":"Invalid execution transition for intent (.+?): (.+?) -> (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":3625,"sourceCode":"                \"Execution gas used {} exceeds PostgreSQL BIGINT\",\n                gas_used.unwrap_or_default()\n            )\n        })?;\n        let mut transaction = self\n            .pool\n            .begin()\n            .await\n            .map_err(|e| anyhow::anyhow!(\"Failed to start execution status transition: {e}\"))?;\n        let (current_status, fill_emitted, terminal_emitted) =\n            sqlx::query_as::<_, (String, bool, bool)>(\n            \"SELECT status, fill_emitted, terminal_emitted FROM execution_intent WHERE id = $1 FOR UPDATE\",\n        )\n        .bind(intent_id)\n        .fetch_optional(&mut *transaction)\n        .await\n        .map_err(|e| anyhow::anyhow!(\"Failed to lock execution intent {intent_id}: {e}\"))?\n        .ok_or_else(|| anyhow::anyhow!(\"Execution intent {intent_id} was not found\"))?;\n        anyhow::ensure!(\n            execution_transition_allowed(&current_status, status),\n            \"Invalid execution transition for intent {intent_id}: {current_status} -> {}\",\n            status.as_str()\n        );\n\n        let active = match status {\n            TransactionStatus::Finalized | TransactionStatus::Reverted => {\n                !fill_emitted && !terminal_emitted\n            }\n            TransactionStatus::Recoverable => false,\n            _ => true,\n        };\n        let hash_result = sqlx::query(\n            \"\n            UPDATE execution_transaction_hash\n            SET status = $3,\n                block_number = COALESCE($4, block_number),\n                block_hash = COALESCE($5, block_hash),","sourceCodeStart":3607,"sourceCodeEnd":3643,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/cache/database.rs#L3607-L3643","documentation":"Thrown when execution_transition_allowed rejects the requested transition in record_execution_status. The state machine permits prepared to signed/recoverable; signed or broadcast to broadcast/included/replaced/dropped/reorged; included to finalized/reverted/reorged/replaced/dropped; replaced/dropped/reorged to included/finalized/reverted/replaced/dropped/reorged; and same-status repeats (idempotent). finalized, reverted, and recoverable are terminal, so this fires only for genuinely out-of-order or post-terminal observations.","triggerScenarios":"A late 'included' receipt arriving after the intent already finalized; attempting to move a finalized intent during a reorg; recording broadcast on an intent still 'prepared' (it was never signed); replaying stale observations after crash recovery.","commonSituations":"RPC or websocket events delivered out of order; restarts replaying old observations; chain reorganizations crossing the finalize boundary; duplicate events with differing block data.","solutions":["Check whether the observation is stale: same-status repeats are already tolerated, so this error means the event is out of order or the intent is terminal","Drop delayed observations whose target is no longer reachable from the current status","Order observations by block_number before recording them","If a real reorg invalidated a finalized execution, escalate and handle it as a new intent rather than forcing the transition"],"exampleFix":"// before: recording whatever arrives first\ndb.record_execution_status(intent_id, &tx_hash, status, ...).await?;\n\n// after: skip observations the state machine cannot accept\nlet current = load_intent_status(&pool, intent_id).await?;\nif current == status.as_str() {\n    return Ok(()); // idempotent repeat\n}\nif !transition_allowed(&current, status) {\n    tracing::warn!(%intent_id, %current, ?status, \"dropping out-of-order observation\");\n    return Ok(());\n}\ndb.record_execution_status(intent_id, &tx_hash, status, ...).await?;","handlingStrategy":"validation","validationCode":"// Mirror the persisted state machine before recording\nlet current: String = sqlx::query_scalar(\"SELECT status FROM execution_intent WHERE id = $1\")\n    .bind(intent_id)\n    .fetch_one(&pool)\n    .await?;\nif current != status.as_str() && !transition_allowed(&current, status) {\n    // stale or out-of-order observation: drop it\n}","typeGuard":"fn transition_allowed(current: &str, next: TransactionStatus) -> bool {\n    if current == next.as_str() {\n        return true;\n    }\n    match current {\n        \"prepared\" => matches!(next, TransactionStatus::Signed | TransactionStatus::Recoverable),\n        \"signed\" | \"broadcast\" => matches!(\n            next,\n            TransactionStatus::Broadcast\n                | TransactionStatus::Included\n                | TransactionStatus::Replaced\n                | TransactionStatus::Dropped\n                | TransactionStatus::Reorged\n        ),\n        \"included\" => matches!(\n            next,\n            TransactionStatus::Finalized\n                | TransactionStatus::Reverted\n                | TransactionStatus::Reorged\n                | TransactionStatus::Replaced\n                | TransactionStatus::Dropped\n        ),\n        \"replaced\" | \"dropped\" | \"reorged\" => !matches!(next, TransactionStatus::Prepared | TransactionStatus::Signed),\n        _ => false,\n    }\n}","tryCatchPattern":"match db.record_execution_status(intent_id, &tx_hash, status, block, hash, success, gas, price).await {\n    Err(e) if e.to_string().contains(\"Invalid execution transition\") => {\n        // log current->target, classify as stale/terminal, and skip or escalate - never force\n    }\n    other => other?,\n}","preventionTips":["Order receipt observations by block_number before recording them","Treat 'Invalid execution transition' on terminal intents as a signal to escalate (e.g. deep reorg), not a retryable error","Exploit same-status idempotency: re-recording the current status never throws this error","Keep a local mirror of the transition table in tests to assert observation orderings"],"tags":["rust","sqlx","postgresql","state-machine","out-of-order-events","idempotency","reorg"],"backgroundTag":"invalid-state-transition","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}