nautechsystems/nautilus_trader · error
Replacement scan manifest identity changed
Error message
Replacement scan manifest identity changed
What it means
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.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6210
AND h.wallet_address = s.wallet_address
AND h.number = s.finalized_cursor_number
AND h.hash = s.finalized_cursor_hash
WHERE s.intent_id = $1
AND s.chain_id = $2
AND s.wallet_address = $3
AND s.nonce = $4
",
)
.bind(intent_id)
.bind(chain_id)
.bind(wallet_address)
.bind(nonce)
.fetch_optional(&self.pool)
.await
.context("failed to load replacement scan cursor")?;
row.map(
|(number, hash, parent_hash, timestamp, base_fee, stored_digest)| {
anyhow::ensure!(
stored_digest == manifest_digest,
"Replacement scan manifest identity changed"
);
Ok(ExecutionVerifiedHeader {
number: u64::try_from(number)
.context("Replacement scan cursor number is negative")?,
hash,
parent_hash,
timestamp: u64::try_from(timestamp)
.context("Replacement scan cursor timestamp is negative")?,
base_fee_per_gas: base_fee
.map(|value| {
value.parse::<u128>().map_err(|_| {
anyhow::anyhow!("Replacement scan cursor base fee is invalid")
})
})
.transpose()?,
})View on GitHub (pinned to 18893faf8b)
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
Example fix
// before
let cursor = db.load_execution_replacement_cursor(intent_id, chain, wallet, nonce, &new_manifest_digest).await?;
// after
let stored: Option<String> = sqlx::query_scalar(
"SELECT manifest_digest FROM execution_replacement_scan WHERE intent_id=$1 AND nonce=$2",
).bind(intent_id).bind(nonce as i64).fetch_optional(&db.pool).await?;
let cursor = db.load_execution_replacement_cursor(intent_id, chain, wallet, nonce,
stored.as_deref().expect("scan must exist before loading cursor")).await?; Defensive patterns
Strategy: validation
Validate before calling
let stored: Option<String> = sqlx::query_scalar(
"SELECT manifest_digest FROM execution_replacement_scan WHERE intent_id=$1 AND chain_id=$2 AND wallet_address=$3 AND nonce=$4",
).bind(intent_id).bind(chain_id).bind(wallet).bind(nonce as i64)
.fetch_optional(&pool).await?;
if stored.as_deref() != Some(current_manifest_digest) {
// rescan under the current manifest instead of loading the stale cursor
} Type guard
fn digest_matches(stored: &Option<String>, expected: &str) -> bool {
stored.as_deref() == Some(expected)
} Try / catch
match db.load_execution_replacement_cursor(intent_id, chain, wallet, nonce, digest).await {
Err(e) if e.to_string().contains("manifest identity changed") => {
// invalidate stored scan and re-run record_execution_replacement_scan
}
other => other,
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- Verified action manifest identity changed
- Verified nonce assignment manifest identity changed
- Deployment manifest pool does not use the pinned pool, facto
- Proxy target has no unique deployment manifest identity
- Proxy target role or code hash conflicts with its deployment
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/49717ac11437c216.
Report an issue: GitHub.