nautechsystems/nautilus_trader · error · anyhow::Error

Verified nonce assignment manifest identity changed

Error message

Verified nonce assignment manifest identity changed

What it means

After locking the ledger row, the code asserts that the stored manifest_version and manifest_digest match those carried by the incoming assignment. A mismatch means the execution manifest changed underneath the canonical nonce ledger — the ledger's identity is bound to a specific manifest, and assigning nonces against a different manifest would be unsafe. The transaction is aborted via `anyhow::ensure!`.

Source

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

            .begin()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to start verified nonce assignment: {e}"))?;
        let (manifest_version, manifest_digest, next_nonce, revision) =
            sqlx::query_as::<_, (String, String, i64, i64)>(
                "
                SELECT manifest_version, manifest_digest, next_canonical_nonce, revision
                FROM execution_verification_nonce
                WHERE chain_id = $1 AND wallet_address = $2
                FOR UPDATE
                ",
            )
            .bind(chain_id)
            .bind(assignment.wallet_address)
            .fetch_optional(&mut *transaction)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to lock canonical nonce ledger: {e}"))?
            .ok_or_else(|| anyhow::anyhow!("Canonical nonce ledger is not initialized"))?;
        anyhow::ensure!(
            manifest_version == assignment.manifest_version
                && manifest_digest == assignment.manifest_digest,
            "Verified nonce assignment manifest identity changed"
        );
        anyhow::ensure!(
            next_nonce == nonce,
            "Execution nonce {} does not match canonical nonce {next_nonce}",
            assignment.nonce
        );

        let (intent_chain_id, intent_wallet, intent_nonce, intent_status, intent_active) =
            sqlx::query_as::<_, (i32, String, Option<i64>, String, bool)>(
                "
            SELECT chain_id, wallet_address, nonce, status, active
            FROM execution_intent
            WHERE id = $1
            FOR UPDATE
            ",

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-initialize or migrate the nonce ledger to the new manifest identity through the sanctioned bootstrap path (do not hand-edit rows).
  2. Ensure all replicas run the same manifest version before processing assignments.
  3. Purge stale queued assignments produced under the old manifest digest.
  4. Compare the DB row's manifest_version/manifest_digest against the assignment to confirm which side drifted.
  5. If the mismatch is unintentional, fix the runtime configuration so the manifest matches the ledger's recorded identity.

Example fix

// before
 // manifest bumped but ledger still on old digest
 let assignment = Assignment::build(new_manifest, ...)?;
// after
 db.reinitialize_nonce_ledger(chain_id, wallet, &new_manifest).await?; // rebind identity first
 let assignment = Assignment::build(new_manifest, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

let stored: Option<(String, String)> = sqlx::query_as("SELECT manifest_version, manifest_digest FROM execution_verification_nonce WHERE chain_id = $1 AND wallet_address = $2").bind(chain_id).bind(wallet).fetch_optional(pool).await?;
match stored {
    Some((v, d)) if v == assignment.manifest_version && d == assignment.manifest_digest => Ok(()),
    _ => Err(anyhow!("manifest identity drift; reinitialize ledger before assigning")),
}

Type guard

fn manifest_matches(stored: &ManifestIdentity, assignment: &Assignment) -> bool {
    stored.version == assignment.manifest_version && stored.digest == assignment.manifest_digest
}

Try / catch

match result {
    Err(e) if e.to_string().contains("manifest identity changed") => {
        // halt assignments; require operator-approved re-initialization
        halt_and_reinit_ledger(manifest)?
    }
    other => other,
}

Prevention

When it happens

Trigger: An assignment built from a different manifest_version or manifest_digest than the one recorded when the ledger row was initialized — e.g. the service was redeployed with an updated execution manifest while the database still holds the old manifest identity, or stale in-process state from before a manifest reload is persisted.

Common situations: Rolling deployment where one replica runs the old manifest and writes assignments while the DB reflects the new one; manifest edited without re-initializing the nonce ledger; replaying old queued assignments after a manifest bump; copying a database between environments with different manifests.

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/57b5ca2750f7a152. Report an issue: GitHub.