nautechsystems/nautilus_trader · error · anyhow::Error

Failed to initialize finalized header ledger: {e}

Error message

Failed to initialize finalized header ledger: {e}

What it means

This error wraps a sqlx failure on the INSERT that seeds the `execution_verified_finalized_header` ledger with the trusted checkpoint header (ON CONFLICT DO NOTHING). The bootstrap path writes the checkpoint row inside a transaction before validating it; any database-level failure on that INSERT is re-raised with this message and aborts ledger initialization.

Source

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

            INSERT INTO execution_verified_finalized_header (
                chain_id, wallet_address, number, hash, parent_hash, timestamp,
                base_fee_per_gas, manifest_digest
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
            ON CONFLICT (chain_id, wallet_address, number) DO NOTHING
            ",
        )
        .bind(chain_id)
        .bind(bootstrap.wallet_address)
        .bind(checkpoint_number)
        .bind(bootstrap.checkpoint_hash)
        .bind(bootstrap.checkpoint_parent_hash)
        .bind(checkpoint_timestamp)
        .bind(base_fee)
        .bind(bootstrap.manifest_digest)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to initialize finalized header ledger: {e}"))?;
        let stored_checkpoint = sqlx::query_as::<_, (String, String, i64, Option<String>)>(
            "
            SELECT hash, parent_hash, timestamp, base_fee_per_gas
            FROM execution_verified_finalized_header
            WHERE chain_id = $1 AND wallet_address = $2 AND number = $3
            ",
        )
        .bind(chain_id)
        .bind(bootstrap.wallet_address)
        .bind(checkpoint_number)
        .fetch_one(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to validate finalized checkpoint ledger: {e}"))?;
        anyhow::ensure!(
            stored_checkpoint
                == (
                    bootstrap.checkpoint_hash.to_string(),
                    bootstrap.checkpoint_parent_hash.to_string(),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Apply the crate's schema migrations so `execution_verified_finalized_header` exists with columns (chain_id, wallet_address, number, hash, parent_hash, timestamp, base_fee_per_gas, manifest_digest).
  2. Read the wrapped sqlx error after the colon for the root cause (undefined table, out-of-range value, connection error).
  3. Confirm checkpoint_number and checkpoint_timestamp fit into i64 before bootstrapping.
  4. Check database connectivity, credentials, and pool configuration.
  5. Retry initialization after a transient failure — the ON CONFLICT DO NOTHING makes re-runs idempotent.

Example fix

// before
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to initialize finalized header ledger: {e}"))?;

// after
.execute(&mut *transaction)
.await
.map_err(|e| {
    anyhow::anyhow!(
        "Failed to initialize finalized header ledger at number {checkpoint_number}: {e:#}"
    )
})?;
Defensive patterns

Strategy: retry

Validate before calling

// Verify schema readiness before bootstrap
let ready: bool = sqlx::query_scalar(
    "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='execution_verified_finalized_header')",
).fetch_one(&pool).await?;
anyhow::ensure!(ready, "verified header table missing; run migrations");
anyhow::ensure!(
    bootstrap.checkpoint_number <= i64::MAX as u64,
    "checkpoint number exceeds BIGINT"
);

Try / catch

match bootstrap_result {
    Err(e) if is_transient_db_error(&e) => {
        warn!("transient DB error, retrying bootstrap: {e:#}");
        backoff_retry(|| initialize_ledger(...), 3);
    }
    Err(e) => return Err(e),
    Ok(v) => Ok(v),
}

Prevention

When it happens

Trigger: During finalized-ledger bootstrap when the checkpoint INSERT fails: table `execution_verified_finalized_header` missing or schema-drifted, i64 bind of checkpoint_number/timestamp/base_fee out of range, connection/pool failure, or the transaction already aborted from an earlier step.

Common situations: Deploying a new version of the adapter against an un-migrated database; checkpoint height or base-fee values that exceed BIGINT; transient Postgres restarts or timeouts; wrong database_url pointing at a database without the verified-header tables.

Related errors


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