{"record":{"id":"c9c42bfef8dab01b","repo":"nautechsystems/nautilus_trader","slug":"replacement-scan-conflicts-with-the-canonical-nonc","errorCode":null,"errorMessage":"Replacement scan conflicts with the canonical nonce or manifest ledger","messagePattern":"Replacement scan conflicts with the canonical nonce or manifest ledger","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":6269,"sourceCode":"            .begin()\n            .await\n            .context(\"failed to start verified replacement scan transition\")?;\n        let (stored_version, stored_digest, stored_nonce, revision) =\n            sqlx::query_as::<_, (String, String, i64, i64)>(\n                \"\n                SELECT manifest_version, manifest_digest, next_canonical_nonce, revision\n                FROM execution_verification_nonce\n                WHERE chain_id = $1 AND wallet_address = $2\n                FOR SHARE\n                \",\n            )\n            .bind(chain_id)\n            .bind(scan.wallet_address)\n            .fetch_optional(&mut *transaction)\n            .await\n            .context(\"failed to read replacement scan nonce ledger\")?\n            .ok_or_else(|| anyhow::anyhow!(\"Canonical nonce ledger is not initialized\"))?;\n        anyhow::ensure!(\n            stored_version == scan.manifest_version\n                && stored_digest == scan.manifest_digest\n                && stored_nonce == nonce,\n            \"Replacement scan conflicts with the canonical nonce or manifest ledger\"\n        );\n        let current_status = sqlx::query_scalar::<_, String>(\n            \"\n            SELECT status\n            FROM execution_intent\n            WHERE id = $1 AND chain_id = $2 AND wallet_address = $3\n              AND nonce = $4 AND active\n            FOR UPDATE\n            \",\n        )\n        .bind(scan.intent_id)\n        .bind(chain_id)\n        .bind(scan.wallet_address)\n        .bind(nonce)","sourceCodeStart":6251,"sourceCodeEnd":6287,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L6251-L6287","documentation":"Raised in `record_execution_replacement_scan` when the caller-supplied replacement scan does not match the canonical verification nonce ledger row (`execution_verification_nonce`). The stored `manifest_version`, `manifest_digest`, and `next_canonical_nonce` are compared against the scan under a `FOR SHARE` lock; any mismatch aborts the transaction. This guards against submitting scan evidence from a stale or different manifest or an out-of-order nonce.","triggerScenarios":"Calling `record_execution_replacement_scan` with a scan whose `manifest_version` or `manifest_digest` differs from the row stored in `execution_verification_nonce`, or whose `nonce` is not exactly `next_canonical_nonce` for the given (chain_id, wallet_address).","commonSituations":"A deployer updated the verification manifest but the in-memory scan builder still carries the old version/digest; two replacement scans raced and one used a nonce already consumed; running against the wrong chain_id/wallet pair so the loaded ledger row belongs to another identity.","solutions":["Regenerate the scan from the current manifest so `manifest_version` and `manifest_digest` match the row in `execution_verification_nonce`.","Re-read `next_canonical_nonce` for the (chain_id, wallet_address) pair and rebuild the scan with that exact nonce.","Ensure only one scan pipeline advances the canonical nonce at a time; serialize scans per wallet to avoid nonce races.","Verify the chain_id and wallet_address passed to the API are the ones the ledger was initialized with."],"exampleFix":"// before: scan built from stale manifest\nlet scan = ExecutionReplacementScan { manifest_version: \"v1\", manifest_digest: old_digest, nonce: guessed_nonce, .. };\ndb.record_execution_replacement_scan(&scan).await?;\n// after: read canonical ledger first and build from it\nlet ledger = db.load_verification_nonce_ledger(chain_id, wallet).await?;\nlet scan = ExecutionReplacementScan { manifest_version: ledger.version, manifest_digest: ledger.digest, nonce: ledger.next_canonical_nonce, .. };\ndb.record_execution_replacement_scan(&scan).await?;","handlingStrategy":"validation","validationCode":"let ledger = sqlx::query_as::<_, (String, String, i64)>(\"SELECT manifest_version, manifest_digest, next_canonical_nonce FROM execution_verification_nonce WHERE chain_id = $1 AND wallet_address = $2\").bind(chain_id).bind(wallet).fetch_one(&pool).await?;\nif ledger.0 != scan.manifest_version || ledger.1 != scan.manifest_digest || ledger.2 != scan.nonce as i64 {\n    return Err(anyhow!(\"scan does not match canonical nonce/manifest ledger\"));\n}","typeGuard":"fn scan_matches_ledger(scan: &ExecutionReplacementScan, version: &str, digest: &str, next_nonce: i64) -> bool {\n    scan.manifest_version == version && scan.manifest_digest == digest && scan.nonce as i64 == next_nonce\n}","tryCatchPattern":null,"preventionTips":["Always derive the scan from a fresh read of the nonce ledger, never from cached manifest metadata.","Single-writer serialization of replacement scans per wallet prevents nonce races.","Bump and propagate manifest version/digest together whenever the verification manifest changes."],"tags":["database","consistency","state-machine"],"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-14T00:17:10.932Z"}