{"record":{"id":"87f65eec508d3389","repo":"nautechsystems/nautilus_trader","slug":"replacement-scan-cursor-is-not-durably-finalize","errorCode":null,"errorMessage":"Replacement scan cursor {} is not durably finalized","messagePattern":"Replacement scan cursor (.+?) is not durably finalized","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":6310,"sourceCode":"\n        if let Some(cursor) = scan.finalized_cursor {\n            let number = i64::try_from(cursor.number)\n                .context(\"Replacement scan cursor exceeds PostgreSQL BIGINT\")?;\n            let durable_hash = sqlx::query_scalar::<_, String>(\n                \"\n                SELECT hash\n                FROM execution_verified_finalized_header\n                WHERE chain_id = $1 AND wallet_address = $2 AND number = $3\n                \",\n            )\n            .bind(chain_id)\n            .bind(scan.wallet_address)\n            .bind(number)\n            .fetch_optional(&mut *transaction)\n            .await\n            .context(\"failed to validate replacement cursor against finalized headers\")?\n            .ok_or_else(|| {\n                anyhow::anyhow!(\n                    \"Replacement scan cursor {} is not durably finalized\",\n                    cursor.number\n                )\n            })?;\n            anyhow::ensure!(\n                durable_hash == cursor.hash,\n                \"Replacement scan cursor conflicts with the finalized header ledger\"\n            );\n            let existing = sqlx::query_as::<_, (i64, String)>(\n                \"\n                SELECT finalized_cursor_number, finalized_cursor_hash\n                FROM execution_replacement_scan\n                WHERE intent_id = $1\n                FOR UPDATE\n                \",\n            )\n            .bind(scan.intent_id)\n            .fetch_optional(&mut *transaction)","sourceCodeStart":6292,"sourceCodeEnd":6328,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L6292-L6328","documentation":"When a replacement scan carries a `finalized_cursor`, the function checks that a header with that (chain_id, wallet_address, number) exists in `execution_verified_finalized_header`. If the lookup returns no row, the cursor was never durably recorded as a verified finalized header, so the scan cannot anchor to it and the transaction aborts.","triggerScenarios":"Passing `scan.finalized_cursor` pointing at a block number that has no row in `execution_verified_finalized_header` — e.g. the header verification pipeline has not yet persisted that height, or the cursor was fabricated from a raw RPC response without prior verification.","commonSituations":"The finalized-header writer lagged behind the scanner (crash or backlog); the cursor came from an unverified RPC source; the wallet_address differs from the one used when headers were recorded.","solutions":["Run the verified-finalized-header pipeline up to the cursor's block number before recording the replacement scan.","Use the newest cursor already present in `execution_verified_finalized_header` for this wallet instead of a newer one.","Confirm the scan's wallet_address matches the address used when the headers were persisted."],"exampleFix":"// before: cursor taken straight from the latest RPC head\nlet cursor = ExecutionVerifiedHeader { number: latest_head_number, .. };\n// after: clamp to the last durably verified header\nlet cursor = db.latest_verified_finalized_header(chain_id, wallet).await?;","handlingStrategy":"validation","validationCode":"let durable = sqlx::query_scalar::<_, String>(\"SELECT hash FROM execution_verified_finalized_header WHERE chain_id=$1 AND wallet_address=$2 AND number=$3\").bind(chain_id).bind(wallet).bind(cursor.number as i64).fetch_optional(&pool).await?;\nif durable.is_none() { return Err(anyhow!(\"cursor {} not durably finalized yet\", cursor.number)); }","typeGuard":"async fn cursor_is_durable(pool: &PgPool, chain_id: i32, wallet: &str, number: u64) -> anyhow::Result<bool> {\n    Ok(sqlx::query_scalar::<_, String>(\"SELECT hash FROM execution_verified_finalized_header WHERE chain_id=$1 AND wallet_address=$2 AND number=$3\")\n        .bind(chain_id).bind(wallet).bind(number as i64).fetch_optional(pool).await?.is_some())\n}","tryCatchPattern":null,"preventionTips":["Clamp scan cursors to the highest durably verified header instead of the raw RPC head.","Ensure the finalized-header writer runs before the replacement scanner consumes new heights.","Use a single wallet_address consistently between header verification and scan submission."],"tags":["database","blockchain","consistency"],"backgroundTag":"record-not-found","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}