{"record":{"id":"49717ac11437c216","repo":"nautechsystems/nautilus_trader","slug":"replacement-scan-manifest-identity-changed","errorCode":null,"errorMessage":"Replacement scan manifest identity changed","messagePattern":"Replacement scan manifest identity changed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":6210,"sourceCode":"             AND h.wallet_address = s.wallet_address\n             AND h.number = s.finalized_cursor_number\n             AND h.hash = s.finalized_cursor_hash\n            WHERE s.intent_id = $1\n              AND s.chain_id = $2\n              AND s.wallet_address = $3\n              AND s.nonce = $4\n            \",\n        )\n        .bind(intent_id)\n        .bind(chain_id)\n        .bind(wallet_address)\n        .bind(nonce)\n        .fetch_optional(&self.pool)\n        .await\n        .context(\"failed to load replacement scan cursor\")?;\n        row.map(\n            |(number, hash, parent_hash, timestamp, base_fee, stored_digest)| {\n                anyhow::ensure!(\n                    stored_digest == manifest_digest,\n                    \"Replacement scan manifest identity changed\"\n                );\n                Ok(ExecutionVerifiedHeader {\n                    number: u64::try_from(number)\n                        .context(\"Replacement scan cursor number is negative\")?,\n                    hash,\n                    parent_hash,\n                    timestamp: u64::try_from(timestamp)\n                        .context(\"Replacement scan cursor timestamp is negative\")?,\n                    base_fee_per_gas: base_fee\n                        .map(|value| {\n                            value.parse::<u128>().map_err(|_| {\n                                anyhow::anyhow!(\"Replacement scan cursor base fee is invalid\")\n                            })\n                        })\n                        .transpose()?,\n                })","sourceCodeStart":6192,"sourceCodeEnd":6228,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L6192-L6228","documentation":"load_execution_replacement_cursor joins the stored replacement scan row with its verified finalized header and compares the stored manifest_digest against the caller-supplied manifest_digest. If they differ, the persisted scan cursor belongs to a different verification manifest than the current one, so the code refuses to return it.","triggerScenarios":"Calling load_execution_replacement_cursor with a manifest_digest that differs from the digest stored on the execution_replacement_scan row for that (intent_id, chain_id, wallet_address, nonce).","commonSituations":"The verification manifest was regenerated (code/schema change) after the scan was recorded; pointing at the wrong wallet/nonce so the digest lookup matches a different scan; upgrading the binary between recording and loading the cursor.","solutions":["Regenerate the replacement scan under the current manifest so the stored digest matches","Pass the manifest_digest that was used when the scan was recorded (check execution_replacement_scan.manifest_digest)","Confirm the (intent_id, chain_id, wallet_address, nonce) tuple identifies the intended scan row","Clear stale scan rows after a manifest change and rescan"],"exampleFix":"// before\nlet cursor = db.load_execution_replacement_cursor(intent_id, chain, wallet, nonce, &new_manifest_digest).await?;\n// after\nlet stored: Option<String> = sqlx::query_scalar(\n    \"SELECT manifest_digest FROM execution_replacement_scan WHERE intent_id=$1 AND nonce=$2\",\n).bind(intent_id).bind(nonce as i64).fetch_optional(&db.pool).await?;\nlet cursor = db.load_execution_replacement_cursor(intent_id, chain, wallet, nonce,\n    stored.as_deref().expect(\"scan must exist before loading cursor\")).await?;","handlingStrategy":"validation","validationCode":"let stored: Option<String> = sqlx::query_scalar(\n    \"SELECT manifest_digest FROM execution_replacement_scan WHERE intent_id=$1 AND chain_id=$2 AND wallet_address=$3 AND nonce=$4\",\n).bind(intent_id).bind(chain_id).bind(wallet).bind(nonce as i64)\n.fetch_optional(&pool).await?;\nif stored.as_deref() != Some(current_manifest_digest) {\n    // rescan under the current manifest instead of loading the stale cursor\n}","typeGuard":"fn digest_matches(stored: &Option<String>, expected: &str) -> bool {\n    stored.as_deref() == Some(expected)\n}","tryCatchPattern":"match db.load_execution_replacement_cursor(intent_id, chain, wallet, nonce, digest).await {\n    Err(e) if e.to_string().contains(\"manifest identity changed\") => {\n        // invalidate stored scan and re-run record_execution_replacement_scan\n    }\n    other => other,\n}","preventionTips":["Bump and propagate manifest_version/digest whenever verification logic changes","After upgrading, delete or re-record scans from old manifests","Pass the digest from the same source that recorded the scan","Key lookups by the full (intent, chain, wallet, nonce, digest) tuple"],"tags":["digest","manifest","cache","consistency"],"backgroundTag":"checksum-mismatch","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}