nautechsystems/nautilus_trader · error · anyhow::Error

Empty profiler ancestry does not end at the decision header

Error message

Empty profiler ancestry does not end at the decision header

What it means

When verify_header_window returns an empty ancestry, the library falls back to requiring that the canonical block itself equals the decision head — meaning the profiler watermark block is the decision block. If neither the ancestry's last element nor the canonical block matches the head, the decision header cannot be justified from the profiler anchor, so execution aborts.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:4004

            verification,
        )
        .await?;
    }

    let ancestry = verified_value(
        verification
            .verify_header_window(canonical_block, head.number)
            .await,
        "profiler-to-decision ancestry",
    )?;

    if let Some(last) = ancestry.last() {
        anyhow::ensure!(
            *last == head,
            "Swap decision header conflicts with profiler ancestry"
        );
    } else {
        anyhow::ensure!(
            canonical_block == head,
            "Empty profiler ancestry does not end at the decision header"
        );
    }
    verified_value(
        verification
            .verify_deployment_manifest(manifest, head.number)
            .await,
        "swap deployment manifest",
    )?;
    let quote_contract = validate_manifest_pool(plan, manifest)?;
    let quote_kind = match plan.order.order_side() {
        OrderSide::Sell => SwapQuoteKind::ExactInput(quantity_to_raw_amount(
            plan.order.quantity(),
            plan.pool.get_base_token().decimals,
        )?),
        OrderSide::Buy => SwapQuoteKind::ExactOutput(quantity_to_raw_amount(
            plan.order.quantity(),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Refresh the profiler so the watermark block is close to the current head, then revalidate.
  2. Fix verifier configuration so verify_header_window returns the actual header window instead of an empty list.
  3. Ensure canonical block and head come from the same verified source in the same attempt.
  4. Retry after transient RPC issues; verify the endpoint serves consistent header data.
Defensive patterns

Strategy: validation

Validate before calling

let ancestry = verification.verify_header_window(canonical_block, head.number).await?;
let ok = ancestry.last().map_or(false, |l| *l == head) || canonical_block == head;
if !ok {
    anyhow::bail!("decision header not justified by profiler anchor");
}

Type guard

fn ancestry_justifies_head(ancestry: &[Block], canonical: &Block, head: &Block) -> bool {
    ancestry.last().map_or(false, |l| l == head) || canonical == head
}

Prevention

When it happens

Trigger: verify_header_window(canonical_block, head.number) returns an empty Vec while canonical_block != head — e.g. the verifier declined to produce a window for the requested range, leaving no chain of headers connecting the profiler block to the decision head.

Common situations: Verifier misconfiguration (window too small, unsupported range) returning empty windows; canonical/head fetched from different sources or times; head advanced past profiler block with the verifier failing to return ancestry; profiler watermark equal to head but canonical data refreshed in between.

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/11a4f19b27fb7811. Report an issue: GitHub.