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

  1. Retry the transaction preparation; the fence will pass if the header is stable on the second attempt.
  2. Anchor decisions to finalized (not just latest) blocks so a reorg cannot swap the header before signing.
  3. Pin the client to a single consistent RPC node rather than a load-balanced set.
  4. 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

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


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