nautechsystems/nautilus_trader · error · anyhow::Error

Verified finalized header ledger is empty

Error message

Verified finalized header ledger is empty

What it means

Raised in database.rs:3730 when the execution_verification_nonce row exists for the chain/wallet but the execution_verified_finalized_header ledger contains no rows. The nonce position cannot be loaded without a finalized-header tip to anchor it, so the library treats the missing ledger as an inconsistent state rather than returning an empty position.

Source

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

        anyhow::ensure!(
            stored_version == manifest_version && stored_digest == manifest_digest,
            "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>()

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-run verification bootstrap to rebuild the finalized-header ledger for this chain/wallet.
  2. Restore the ledger from backup if rows were deleted accidentally.
  3. Check for a cleanup/retention job that removes execution_verified_finalized_header rows and exclude the active wallet/chain.
  4. If bootstrapping, ensure it completes atomically (nonce row and first header committed together) before resuming verification.

Example fix

-- before: inconsistent state
SELECT COUNT(*) FROM execution_verified_finalized_header WHERE chain_id=$1 AND wallet_address=$2; -- 0

-- after: re-bootstrap so the ledger holds at least one verified header
SELECT COUNT(*) FROM execution_verified_finalized_header WHERE chain_id=$1 AND wallet_address=$2; -- >= 1
Defensive patterns

Strategy: validation

Validate before calling

let headers: i64 = sqlx::query_scalar(
    "SELECT COUNT(*) FROM execution_verified_finalized_header \
     WHERE chain_id = $1 AND wallet_address = $2",
).bind(chain_id).bind(wallet).fetch_one(&pool).await?;
if headers == 0 {
    anyhow::bail!("header ledger empty; bootstrap verification first");
}

Try / catch

match load_position(...).await {
    Err(e) if e.to_string().contains("Verified finalized header ledger is empty") => {
        // re-run bootstrap to rebuild the ledger before resuming
    },
    other => other?,
}

Prevention

When it happens

Trigger: Calling load_execution_verification_position after the nonce row was written but before any finalized header was recorded, or after the header ledger was truncated/deleted while the nonce row survived.

Common situations: Partial/corrupted bootstrap that persisted the nonce but crashed before writing the first verified header; manual deletion of execution_verified_finalized_header rows; ledger retention/cleanup job removing all headers for the wallet.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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