nautechsystems/nautilus_trader · critical · anyhow::Error
Verified transaction fields differ from the authenticated si
Error message
Verified transaction fields differ from the authenticated signed payload
What it means
Before submitting a raw transaction, the library decodes the locally signed payload and compares every field of the RPC-returned transaction (hash, from, nonce, chain_id, type=2 EIP-1559, to, etc.) against the decoded signature. This error means the transaction the node reports differs from what was actually signed — a safety check against RPC tampering, field mutation, or decoding mismatches.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:4287
) -> anyhow::Result<()> {
anyhow::ensure!(
profiler_block <= latest_block,
"Pool state at block {profiler_block} is ahead of the latest block {latest_block}; the execution RPC endpoint lags the data feed"
);
let quote_age = latest_block - profiler_block;
anyhow::ensure!(
quote_age <= max_age_blocks,
"Stale quote: pool state at block {profiler_block}, latest block {latest_block}, exceeds `max_quote_age_blocks` {max_age_blocks}"
);
Ok(())
}
fn validate_rpc_transaction_matches_payload(
transaction: &RpcTransaction,
raw_transaction: &[u8],
) -> anyhow::Result<()> {
let signed = decode_signed_transaction(raw_transaction)?;
anyhow::ensure!(
transaction.hash == signed.hash
&& transaction.from == signed.signer
&& transaction.nonce == signed.nonce
&& transaction.chain_id == Some(signed.chain_id)
&& transaction.transaction_type == Some(2)
&& transaction.to == Some(signed.to)
&& transaction.input == signed.input
&& transaction.value == signed.value
&& transaction.gas == Some(signed.gas_limit)
&& transaction.max_fee_per_gas == Some(U256::from(signed.max_fee_per_gas))
&& transaction.max_priority_fee_per_gas
== Some(U256::from(signed.max_priority_fee_per_gas)),
"Verified transaction fields differ from the authenticated signed payload"
);
Ok(())
}
async fn verify_finalized_transaction(View on GitHub (pinned to 18893faf8b)
Solutions
- Re-sign and re-send with a consistent signer/encoder version; verify chain_id and type=2 (EIP-1559) are set at signing time.
- Inspect any RPC proxy/middleware between client and node for field mutation; bypass or fix it.
- Confirm the raw bytes passed to validation are exactly the signed payload (not re-encoded).
- Upgrade the signing and transaction decode libraries to matched versions so field encoding agrees.
Example fix
// before: raw bytes re-encoded before validation let raw = re_encode_for_rpc(&signed_tx); validate_rpc_transaction_matches_payload(&fetched, &raw)?; // after: validate against the exact signed payload let raw = signed_tx.encoded_2718(); validate_rpc_transaction_matches_payload(&fetched, raw)?;
Defensive patterns
Strategy: validation
Validate before calling
let signed = decode_signed_transaction(&raw)?; assert_eq!(fetched.hash, signed.hash); assert_eq!(fetched.from, signed.signer); assert_eq!(fetched.nonce, signed.nonce); assert_eq!(fetched.chain_id, Some(signed.chain_id)); assert_eq!(fetched.transaction_type, Some(2));
Type guard
fn tx_matches_payload(tx: &RpcTransaction, signed: &SignedTx) -> bool {
tx.hash == signed.hash
&& tx.from == signed.signer
&& tx.nonce == signed.nonce
&& tx.chain_id == Some(signed.chain_id)
&& tx.transaction_type == Some(2)
&& tx.to == Some(signed.to)
} Try / catch
match client.send_raw_transaction(&raw).await {
Ok(h) => h,
Err(e) if e.to_string().contains("differ from the authenticated signed payload") => {
// treat as potentially tampered: re-sign locally and abort this submission
resign_and_abort(e)
}
Err(e) => return Err(e),
} Prevention
- Validate against the exact encoded_2718 signed bytes, never re-encoded payloads
- Audit RPC proxies/middleware for transaction field mutation
- Keep signer and transaction libraries on matched versions
- Always sign with explicit chain_id and EIP-1559 type
When it happens
Trigger: Calling validate_rpc_transaction_matches_payload when the fetched RpcTransaction differs from decode_signed_transaction(raw_transaction) on hash, signer, nonce, chain_id, transaction_type != 2, or to address.
Common situations: RPC proxy/middleware rewriting transaction fields (e.g., adding gas or changing type); signing and sending through different library versions (legacy vs EIP-1559 encoding mismatch); decoding the wrong raw bytes; malicious or buggy relayer modifying the payload.
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Failed to start replacement transaction persistence: {e}
- Failed to retire replaced execution hash: {e}
- Failed to mark execution intent replaced: {e}
- Failed to record replacement transition: {e}
- Finalized block {} does not contain transaction {}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/cc37c844e3fc1dc2.
Report an issue: GitHub.