nautechsystems/nautilus_trader · critical · anyhow::Error

Finalized header manifest identity changed

Error message

Finalized header manifest identity changed

What it means

Raised in database.rs:3732 when the manifest_digest stored on the latest verified finalized header row does not equal the caller's manifest_digest. The tip header was recorded under a different verification manifest, so its data cannot be trusted as a continuation point for the current manifest's verification.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:3732

            "Execution verification manifest identity changed"
        );
        let row = sqlx::query_as::<_, (i64, String, String, i64, Option<String>, String)>(
            "
            SELECT number, hash, parent_hash, timestamp, base_fee_per_gas, manifest_digest
            FROM execution_verified_finalized_header
            WHERE chain_id = $1 AND wallet_address = $2
            ORDER BY number DESC
            LIMIT 1
            ",
        )
        .bind(chain_id)
        .bind(wallet_address)
        .fetch_optional(&self.pool)
        .await
        .context("failed to load verified finalized header tip")?
        .ok_or_else(|| anyhow::anyhow!("Verified finalized header ledger is empty"))?;
        let (number, hash, parent_hash, timestamp, base_fee, digest) = row;
        anyhow::ensure!(
            digest == manifest_digest,
            "Finalized header manifest identity changed"
        );
        Ok(Some(ExecutionVerificationPosition {
            next_canonical_nonce: u64::try_from(nonce).context("Canonical nonce is negative")?,
            revision: u64::try_from(revision).context("Canonical nonce revision is negative")?,
            finalized_tip: ExecutionVerifiedHeader {
                number: u64::try_from(number).context("Finalized header number is negative")?,
                hash,
                parent_hash,
                timestamp: u64::try_from(timestamp)
                    .context("Finalized header timestamp is negative")?,
                base_fee_per_gas: base_fee
                    .map(|value| {
                        value
                            .parse::<u128>()
                            .map_err(|_| anyhow::anyhow!("Finalized header base fee is invalid"))
                    })

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-bootstrap verification state under the current manifest so headers are re-recorded with the active digest.
  2. Ensure the manifest_digest passed to load_execution_verification_position is identical to the one used when headers were written.
  3. Revert the manifest configuration change if it was unintended (compare digests against the stored rows).
  4. Do not delete or rewrite the digest column manually; rotate state through the bootstrap path instead.

Example fix

// before: digest mismatch on tip header
let pos = db.load_execution_verification_position(chain, wallet, ver, &new_digest).await?;
// Err: Finalized header manifest identity changed

// after: use the digest under which the ledger was written, or re-bootstrap under new_digest
let pos = db.load_execution_verification_position(chain, wallet, ver, &ledger_digest).await?;
Defensive patterns

Strategy: validation

Validate before calling

let tip: Option<String> = sqlx::query_scalar(
    "SELECT manifest_digest FROM execution_verified_finalized_header \
     WHERE chain_id = $1 AND wallet_address = $2 ORDER BY number DESC LIMIT 1",
).bind(chain_id).bind(wallet).fetch_optional(&pool).await?;
if tip.as_deref() != Some(manifest_digest) {
    anyhow::bail!("ledger digest {:?} != active manifest digest", tip);
}

Try / catch

match load_position(...).await {
    Err(e) if e.to_string().contains("Finalized header manifest identity changed") => {
        // stop; re-bootstrap or revert manifest config before continuing
    },
    other => other?,
}

Prevention

When it happens

Trigger: Loading the verified finalized header tip in load_execution_verification_position when the digest column of the newest execution_verified_finalized_header row differs from the manifest_digest parameter passed by the caller.

Common situations: Verification manifest changed (contract set or rules updated) after headers were already recorded; restoring a mixed database where headers came from a different deployment; passing the wrong manifest digest in code/config.

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/4cdf5f396f03f521. Report an issue: GitHub.