nautechsystems/nautilus_trader · error · anyhow::Error

Verified finalized header ledger is not continuous

Error message

Verified finalized header ledger is not continuous

What it means

After deserializing the verified finalized-header ledger, the library verifies the chain is continuous: it must be non-empty and every header must extend the previous one (number incremented by exactly 1 and parent_hash equal to the previous header's hash). This error is thrown when those invariants fail, because a discontinuous ledger cannot be trusted for execution verification.

Source

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

                    );
                    Ok(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")
                                })
                            })
                            .transpose()?,
                    })
                })
                .collect::<anyhow::Result<Vec<_>>>()?;
            anyhow::ensure!(
                !finalized_headers.is_empty()
                    && finalized_headers.windows(2).all(|headers| {
                        headers[1].number == headers[0].number.saturating_add(1)
                            && headers[1].parent_hash == headers[0].hash
                    }),
                "Verified finalized header ledger is not continuous"
            );
            Ok(Some(ExecutionVerificationResume {
                next_canonical_nonce: u64::try_from(nonce)
                    .context("Canonical nonce is negative")?,
                revision: u64::try_from(revision)
                    .context("Canonical nonce revision is negative")?,
                finalized_headers,
            }))
        }
    }

    #[tokio::test]

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-ingest the full finalized-header ledger from the chain under the current manifest so the sequence is rebuilt contiguously
  2. Restore the ledger from a backup taken before the discontinuity was introduced
  3. Identify the gap/mismatch (compare consecutive number/parent_hash values) and backfill the missing headers in order
  4. Never delete individual rows; truncate the whole ledger and repopulate atomically if cleanup is needed

Example fix

// before
DELETE FROM finalized_headers WHERE number = 101; -- creates a gap
// after
TRUNCATE finalized_headers; -- then rebuild the full contiguous ledger from source
Defensive patterns

Strategy: try-catch

Validate before calling

let gaps = sqlx::query("
    SELECT a.number FROM finalized_headers a
    LEFT JOIN finalized_headers b ON b.number = a.number + 1
    WHERE b.number IS NULL")
    .fetch_all(&pool).await?;
if !gaps.is_empty() { eprintln!("ledger discontinuities at {:?}", gaps); }

Type guard

fn ledger_is_continuous(headers: &[ExecutionVerifiedHeader]) -> bool {
    !headers.is_empty()
        && headers.windows(2).all(|w| {
            w[1].number == w[0].number + 1 && w[1].parent_hash == w[0].hash
        })
}

Try / catch

match load_finalized_ledger(digest).await {
    Err(e) if e.to_string().contains("not continuous") => {
        // truncate and rebuild the ledger from the chain
    }
    other => other?,
}

Prevention

When it happens

Trigger: Loading a ledger with zero rows, rows with gaps in numbers (e.g. 100, 102), or rows whose parent_hash does not match the previous row's hash — typically after manual deletes, a partial re-ingestion, or mixing rows from different manifests/chains.

Common situations: Deleting individual header rows to 'clean up' the table; a failed backfill that skipped blocks; rows from a reorged or forked chain interleaved with the canonical ledger; a wiped table being partially repopulated.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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