nautechsystems/nautilus_trader · critical · anyhow::Error
Verified call-trace root differs from the authenticated tran
Error message
Verified call-trace root differs from the authenticated transaction
What it means
This error is raised by `validate_call_trace` when the root of the verified debug call trace for a finalized transaction does not match the authenticated signed transaction. The check requires the root call to be a plain `Call`, sent from the signer, to the signed recipient, with the signed value, an input whose keccak256 digest equals the signed calldata, and a success flag matching the receipt status. Any divergence means the trace RPC returned a trace that does not correspond to the transaction being verified, so the execution cannot be attested.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:4404
Some(included.block_number),
Some(included.block_number),
),
verification_decision(
&deployment_verification,
Some(included.block_number),
Some(included.block_number),
),
])
}
fn validate_call_trace(
trace: &VerifiedCallTrace,
signed: &crate::execution::transaction::DecodedSignedTransaction,
receipt_success: bool,
purpose: &str,
manifest: &BlockchainDeploymentManifest,
) -> anyhow::Result<()> {
anyhow::ensure!(
trace.call_type == RpcCallType::Call
&& trace.from == signed.signer
&& trace.to == Some(signed.to)
&& trace.value == signed.value
&& trace.input_digest == keccak256(&signed.input)
&& trace.success == receipt_success,
"Verified call-trace root differs from the authenticated transaction"
);
validate_internal_calls(&trace.calls, signed.to, purpose, manifest)
}
fn validate_internal_calls(
calls: &[VerifiedCallTrace],
caller_context: Address,
purpose: &str,
manifest: &BlockchainDeploymentManifest,
) -> anyhow::Result<()> {
for call in calls {View on GitHub (pinned to 18893faf8b)
Solutions
- Re-fetch the call trace from the same node that served the transaction/receipt and confirm the trace root's hash, from, to, value, and input actually correspond to the signed transaction.
- Verify the tracing node has debug/trace APIs enabled and is fully synced at the inclusion block (archive node for historical blocks).
- Confirm the raw signed transaction bytes used for `decode_signed_transaction` are the exact bytes submitted for `included.tx_hash`; stale bytes from a replaced transaction will not match.
- If receipt status and trace success disagree, wait for finality/reorg resolution and re-verify, or switch to a node without indexer lag.
- Compare the RPC's trace format (e.g. callTracer vs opcode tracer) against what the verification coordinator expects; a wrong tracer setting yields differently shaped roots.
Example fix
// before: tracing via a proxy without debug API enabled, returning a mismatched root let trace = proxy.debug_trace_transaction(tx_hash, CallTraceTracer).await?; validate_call_trace(&trace, &signed, receipt.status, purpose, manifest)?; // after: verify the trace belongs to this transaction and from an archive-capable node anyhow::ensure!(trace.tx_hash == tx_hash, "trace is for a different transaction"); let trace = archive_node.debug_trace_transaction(tx_hash, CallTraceTracer).await?; validate_call_trace(&trace, &signed, receipt.status, purpose, manifest)?;
Defensive patterns
Strategy: validation
Validate before calling
// Pre-validate the trace root against the signed transaction before deeper checks.
fn trace_root_matches(trace: &VerifiedCallTrace, signed: &DecodedSignedTransaction, receipt_success: bool) -> bool {
trace.call_type == RpcCallType::Call
&& trace.from == signed.signer
&& trace.to == Some(signed.to)
&& trace.value == signed.value
&& trace.input_digest == keccak256(&signed.input)
&& trace.success == receipt_success
} Type guard
fn is_matching_root_trace(trace: &VerifiedCallTrace, signed: &DecodedSignedTransaction, success: bool) -> bool {
trace.call_type == RpcCallType::Call && trace.from == signed.signer && trace.to == Some(signed.to)
} Try / catch
match validate_call_trace(&trace, &signed, receipt.status, purpose, manifest) {
Err(e) if e.to_string().contains("root differs from the authenticated transaction") => {
// re-fetch trace from a trusted archive node once, then fail for review
let fresh = trusted_node.verify_call_trace(&tx_hash).await?;
validate_call_trace(&fresh, &signed, receipt.status, purpose, manifest)
}
r => r,
} Prevention
- Use a fully synced archive node with debug/trace APIs enabled for trace verification.
- Keep the raw signed transaction bytes used for submission available for post-inclusion checks.
- Resolve receipt status only after finality/reorg windows to avoid success-flag mismatches.
- Configure the same tracer type (e.g. callTracer) the verification coordinator expects.
- Log the trace root fields (from/to/value/input_digest) on mismatch to speed triage.
When it happens
Trigger: Fails when: the trace root `call_type` is not `RpcCallType::Call` (e.g. the entry was recorded as delegatecall/staticcall/create); `trace.from` is not the signed transaction's signer; `trace.to` is not `Some(signed.to)`; `trace.value != signed.value`; `trace.input_digest != keccak256(&signed.input)`; or `trace.success` disagrees with `included.receipt.status`. Triggered whenever `verify_call_trace` (a debug_traceTransaction-style RPC) returns a root frame inconsistent with the signed payload.
Common situations: The node's debug/trace API is disabled or a proxy returns traces from a different transaction or in an unexpected shape; tracing is done against a non-archive or misconfigured node that rewrites/reformats traces; comparing against a cached signed transaction from a previous submission attempt while the trace is for a replacement transaction; receipt status vs trace success mismatch due to eventually-consistent indexing after a reorg.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Finalized transaction does not match the authenticated signe
- Finalized block {} does not contain transaction {}
- Finalized execution transaction {tx_hash} no longer has a re
- Canonical head changed during signer-nonce replacement scan
- Finalized block {} changed from {} to {} before intent valid
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/50ad9a232aae0178.
Report an issue: GitHub.