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
- Re-verify/refresh the decision header (or swap anchors) so it targets a block at or after the current checkpoint.
- Discard stale intents whose anchors predate the checkpoint and re-quote them.
- Check for clock skew or stalled consumers that cause very old headers to be used as decision blocks.
- 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
- Re-anchor or re-verify decision headers instead of replaying old ones.
- Drop queued intents whose anchors have been passed by the checkpoint.
- Monitor consumer lag so decision headers stay near chain head.
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
- Pre-sign decision header does not extend the durable finaliz
- Canonical nonce ledger changed during verification bootstrap
- Finalized checkpoint ledger conflicts with the trusted chain
- Verified finalized headers do not start at the trusted check
- Verified finalized header conflicts with its ancestry window
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/542075abff9f3f41.
Report an issue: GitHub.