nautechsystems/nautilus_trader · error
Decision header changed before signing
Error message
Decision header changed before signing
What it means
As a final fence before signing, `verify_pre_sign_header_fence` re-verifies the block header at the decision block's height and requires it to equal the header verified earlier in the flow. If the header at that height changed (reorg or a provider returning different data), the decision state is no longer trustworthy and the client refuses to sign.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:2496
tx_hash,
raw_tx,
payload_lease: Some(payload_lease),
})
}
async fn verify_pre_sign_header_fence(
&self,
target: VerifiedBlockHeader,
) -> anyhow::Result<Vec<ExecutionVerificationDecision>> {
let checkpoint = required_verification(
self.verification.verify_checkpoint().await,
"pre-sign checkpoint reread",
)?;
let header = required_verification(
self.verification.verify_block(target.number).await,
"pre-sign decision header reread",
)?;
anyhow::ensure!(
header.value == target,
"Decision header changed before signing"
);
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(View on GitHub (pinned to 18893faf8b)
Solutions
- Retry the transaction preparation; the fence will pass if the header is stable on the second attempt.
- Anchor decisions to finalized (not just latest) blocks so a reorg cannot swap the header before signing.
- Pin the client to a single consistent RPC node rather than a load-balanced set.
- Investigate chain health if this recurs — repeated mismatches indicate unstable finality or an unreliable verification source.
Example fix
// before: decision header taken from latest (unfinalized) let header = verification.verify_block(latest_number).await; // after: use a finalized header for the decision let header = verification.verify_block(finalized_number).await;
Defensive patterns
Strategy: retry
Validate before calling
pub async fn header_stable(ver: &Verification, number: u64) -> anyhow::Result<bool> {
let a = ver.verify_block(number).await?;
tokio::time::sleep(Duration::from_millis(250)).await;
let b = ver.verify_block(number).await?;
Ok(a.value == b.value)
} Try / catch
match prepare().await {
Err(e) if e.to_string().contains("Decision header changed before signing") => {
backoff_retry(prepare, 3).await?
}
other => other?,
} Prevention
- Anchor decisions to finalized blocks rather than latest.
- Pin a single consistent RPC node instead of a load-balanced pool.
- Alert on repeated fence failures — they indicate reorgs or provider inconsistency.
When it happens
Trigger: Calling the pre-sign path and, between the initial `verify_decision_header`/`verify_block` call and the fence re-read, the block at `target.number` is replaced by a different block (reorg) or the verification provider returns a differing header for the same height.
Common situations: Operating on a chain with short finality and fast block production where reorgs occur during the verification window; RPC load balancer serving inconsistent nodes; deep reorg after an upstream client issue.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- Verified swap decision header changed before signing
- Durable finalized header tip conflicts with independent sour
- Pool state block {} changed from {} to {}; refresh the profi
- Swap decision header conflicts with profiler ancestry
- Verified inclusion header does not match the finalized recei
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/1e38a5c75d271879.
Report an issue: GitHub.