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
- Refresh the profiler so the watermark block is close to the current head, then revalidate.
- Fix verifier configuration so verify_header_window returns the actual header window instead of an empty list.
- Ensure canonical block and head come from the same verified source in the same attempt.
- 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
- Keep the profiler watermark close to head so a header window always exists.
- Configure the verifier to return non-empty windows for the execution range.
- Fetch canonical block and head in the same verification attempt.
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
- Verified swap decision header changed before signing
- Decision header changed before signing
- Durable finalized header tip conflicts with independent sour
- Verified replacement scan returned no blocks
- Pool state block {} changed from {} to {}; refresh the profi
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/11a4f19b27fb7811.
Report an issue: GitHub.