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

  1. 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).
  2. 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.
  3. Ensure the trace is for the same `tx_hash` being verified; a trace from a different transaction will not match the expected caller contexts.
  4. Check that address casing/checksum handling in the tracer output is consistent (lowercase vs checksummed) so address comparison is not spuriously failing upstream.
  5. 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

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


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