nautechsystems/nautilus_trader · critical · anyhow::Error
Verified call trace child has an invalid caller context
Error message
Verified call trace child has an invalid caller context
What it means
This error comes from `validate_internal_calls`, which recursively walks the internal (child) calls of a verified transaction trace. Each child call's `from` must equal the `caller_context` passed in: the parent's target for `call`/`staticcall`, or the parent's caller for `callcode`/`delegatecall` (which preserve the caller context). A mismatch means the trace contains a frame whose sender does not follow EVM call semantics expected at that depth — i.e. the trace is malformed, was tampered with, or does not actually belong to this execution, so the check aborts.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:4423
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 {
anyhow::ensure!(
call.from == caller_context,
"Verified call trace child has an invalid caller context"
);
let target = call.to.ok_or_else(|| {
anyhow::anyhow!("Verified call trace contains an operation without a target")
})?;
let call_type = match call.call_type {
RpcCallType::Call => "call",
RpcCallType::Callcode => "callcode",
RpcCallType::Delegatecall => "delegatecall",
RpcCallType::Staticcall => "staticcall",
RpcCallType::Create | RpcCallType::Create2 | RpcCallType::Selfdestruct => {
anyhow::bail!("Verified call trace contains a forbidden state-changing operation")
}
};
let permitted = manifest.call_edges.iter().any(|edge| {
edge.purpose == purpose
&& edge.call_type.eq_ignore_ascii_case(call_type)View on GitHub (pinned to 18893faf8b)
Solutions
- Re-fetch the trace from a trusted node and dump the full call tree; check whether the offending frame's `from` actually equals the expected parent target (CALL) or parent caller (DELEGATECALL/CALLCODE).
- Confirm the node's tracer type and version produce standard callTracer frames; upgrade or reconfigure the tracer (e.g. use geth's built-in callTracer) if frames are malformed.
- Ensure the trace is for the same `tx_hash` being verified; a trace from a different transaction will not match the expected caller contexts.
- Check that address casing/checksum handling in the tracer output is consistent (lowercase vs checksummed) so address comparison is not spuriously failing upstream.
- If a specific contract legitimately produces unexpected frames, verify its bytecode/behavior (e.g. Metamorphic/proxy patterns) and update expectations rather than bypassing the check.
Example fix
// before: trusting proxy node traces with nonstandard frames let trace = proxy.debug_trace_transaction(tx_hash, tracer).await?; validate_internal_calls(&trace.root.calls, signed.to, purpose, manifest)?; // after: use a full node's standard callTracer and validate the root first let trace = full_node.debug_trace_transaction(tx_hash, GethCallTracer).await?; validate_call_trace(&trace.root, &signed, receipt.status, purpose, manifest)?; // validates root, then children with correct contexts
Defensive patterns
Strategy: validation
Validate before calling
// Sanity-check child frame senders before full verification.
fn children_have_valid_contexts(calls: &[VerifiedCallTrace], ctx: Address) -> bool {
calls.iter().all(|c| c.from == ctx)
} Type guard
fn has_valid_caller_context(call: &VerifiedCallTrace, caller_context: Address) -> bool {
call.from == caller_context
} Try / catch
if let Err(e) = validate_internal_calls(&trace.calls, signed.to, purpose, manifest) {
if e.to_string().contains("invalid caller context") {
dump_trace_tree(&trace); // capture malformed frames for node/tracer diagnosis
return Err(e.context("tracer produced frames violating EVM call semantics"));
}
return Err(e);
} Prevention
- Use a mainstream tracer (geth/erigon callTracer) rather than proxies or custom tracers that reshape frames.
- Verify traces only from nodes whose version produces standard frame fields.
- Always confirm the trace's transaction hash matches the tx being verified before walking children.
- Keep address comparisons case-normalized upstream so failures reflect real semantic mismatches.
- Log the offending frame's from/expected context on failure for fast root-cause analysis.
When it happens
Trigger: Fails for any child in `calls` where `call.from != caller_context`. E.g. a root-level internal call whose `from` is not the signed transaction's `to` contract (for CALL/STATICCALL), or a nested frame under a DELEGATECALL whose `from` is not the delegating contract's address. Triggered whenever the debug trace returns internal frames that violate standard EVM sender semantics for their position in the tree.
Common situations: The tracing node returns traces in a nonstandard or partially-populated shape (proxy nodes, custom tracers, or older geth/erigon versions with differing frame fields); traces are compared against the wrong transaction so the tree does not line up; a malicious or buggy tracer injects frames with fabricated `from` addresses; contract using unusual opcodes (callcode) confused a custom parser that misassigns caller context.
Related errors
- Verified call trace contains an operation without a target
- Verified call trace contains a forbidden state-changing oper
- Verified call trace contains an unreviewed {call_type} edge
- Finalized block {} does not contain transaction {}
- No deployed bytecode at {description} address {address}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/63f930eba5699787.
Report an issue: GitHub.