nautechsystems/nautilus_trader · error

Pre-sign decision header precedes the trusted checkpoint

Error message

Pre-sign decision header precedes the trusted checkpoint

What it means

`verify_decision_ancestry` verifies the trusted checkpoint and requires it to be at or below the decision header's height. If the decision block precedes the checkpoint, the ancestry chain between checkpoint and decision block cannot be established, so verification of the decision state is impossible and the client aborts.

Source

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

        Ok(vec![
            verification_decision(
                &checkpoint,
                Some(checkpoint.value.number),
                Some(checkpoint.value.number),
            ),
            verification_decision(&header, Some(target.number), Some(target.number)),
        ])
    }

    async fn verify_decision_ancestry(
        &self,
        target: VerifiedBlockHeader,
    ) -> anyhow::Result<Vec<ExecutionVerificationDecision>> {
        let checkpoint = required_verification(
            self.verification.verify_checkpoint().await,
            "pre-sign checkpoint",
        )?;
        anyhow::ensure!(
            checkpoint.value.number <= target.number,
            "Pre-sign decision header precedes the trusted checkpoint"
        );
        let mut decisions = vec![verification_decision(
            &checkpoint,
            Some(checkpoint.value.number),
            Some(checkpoint.value.number),
        )];
        let wallet_address = self.wallet_address.to_string();
        let position = self
            .database
            .load_execution_verification_position(
                self.chain_id,
                &wallet_address,
                &self.manifest_version,
                &self.manifest_digest,
            )
            .await?

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-verify/refresh the decision header (or swap anchors) so it targets a block at or after the current checkpoint.
  2. Discard stale intents whose anchors predate the checkpoint and re-quote them.
  3. Check for clock skew or stalled consumers that cause very old headers to be used as decision blocks.
  4. Ensure the checkpoint source is advancing normally; a frozen checkpoint with advancing targets is fine, but old targets are not.

Example fix

// before: reusing an old anchor
let anchors = load_stale_anchors(intent_id)?; // state.number < checkpoint.number
// after: refresh if stale
let anchors = if load_stale_anchors(intent_id)?.state.number < checkpoint.number { quoter.re_anchor(intent_id).await? } else { load_stale_anchors(intent_id)? };
Defensive patterns

Strategy: validation

Validate before calling

pub fn decision_after_checkpoint(decision_number: u64, checkpoint_number: u64) -> bool {
    decision_number >= checkpoint_number
}

Try / catch

if decision_header.number < checkpoint.number {
    return Err(anyhow!("stale decision header; re-verify before signing"));
}

Prevention

When it happens

Trigger: Calling the pre-sign path with a decision header (from stale swap anchors or an explicitly supplied `Verified<VerifiedBlockHeader>`) whose block number is lower than the current verified checkpoint number — typically a very old/stale anchor.

Common situations: Replaying a queued intent with anchors captured long ago while the checkpoint advanced; clock skew or backlog causing old headers to be used as decisions; restoring from a backup of old quote anchors.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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