nautechsystems/nautilus_trader · error · anyhow::Error

Canonical nonce ledger is not initialized

Error message

Canonical nonce ledger is not initialized

What it means

The transaction tried to lock the canonical nonce ledger row for (chain_id, wallet_address) but the SELECT returned no rows. The ledger must be pre-initialized (a row must exist) before any verified nonce assignment can proceed. This is an explicit guard against assigning nonces when the on-disk canonical state is absent.

Source

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

            .pool
            .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. Run the ledger initialization/bootstrap step for this (chain_id, wallet_address) before assigning nonces.
  2. Verify the chain_id and wallet_address passed in the assignment exactly match the initialized row (check case and encoding of the address).
  3. Confirm you are connected to the intended database — a fresh/empty DB will not have the row.
  4. Query `SELECT * FROM execution_verification_nonce WHERE chain_id=$1` to see which wallets are initialized.
  5. If the row was deleted, restore it via the manifest bootstrap process rather than inserting ad hoc.

Example fix

// before
 db.assign_verified_nonce(assignment).await?;
// after
 db.initialize_nonce_ledger(chain_id, wallet, manifest).await?; // ensure row exists first
 db.assign_verified_nonce(assignment).await?;
Defensive patterns

Strategy: validation

Validate before calling

async fn ledger_initialized(pool: &PgPool, chain_id: i64, wallet: &str) -> anyhow::Result<bool> {
    Ok(sqlx::query("SELECT 1 FROM execution_verification_nonce WHERE chain_id = $1 AND wallet_address = $2").bind(chain_id).bind(wallet).fetch_optional(pool).await?.is_some())
}

Type guard

fn is_ledger_missing(err: &anyhow::Error) -> bool {
    err.to_string().contains("Canonical nonce ledger is not initialized")
}

Try / catch

match db.assign_verified_nonce(&assignment).await {
    Err(e) if is_ledger_missing(&e) => {
        db.initialize_nonce_ledger(chain_id, wallet, manifest).await?;
        db.assign_verified_nonce(&assignment).await
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling the verified nonce assignment API for a chain/wallet pair whose `execution_verification_nonce` row has never been created by the initialization routine — e.g. a new wallet added without running ledger init, or a chain_id/address encoding mismatch so the WHERE clause misses the existing row.

Common situations: Deploying a new chain/wallet configuration without running the ledger bootstrap; environment pointing at a fresh empty database; chain_id or wallet_address case/encoding mismatch (e.g. EIP-55 checksum vs lowercase); manifest regenerated with a new wallet.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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