{"record":{"id":"133886eda2d7304e","repo":"nautechsystems/nautilus_trader","slug":"finalized-block-does-not-contain-transaction","errorCode":null,"errorMessage":"Finalized block {} does not contain transaction {}","messagePattern":"Finalized block (.+?) does not contain transaction (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":2141,"sourceCode":"    executor: &TransactionExecutor,\n) -> anyhow::Result<bool> {\n    let block = executor\n        .http_rpc_client\n        .block_by_number(included.block_number, true)\n        .await?;\n    anyhow::ensure!(\n        block.hash == included.receipt.block_hash,\n        \"Finalized block {} changed from {} to {} before intent validation\",\n        included.block_number,\n        included.receipt.block_hash,\n        block.hash\n    );\n    let Some(transaction) = block\n        .transactions\n        .iter()\n        .find(|transaction| transaction.hash == included.tx_hash)\n    else {\n        anyhow::bail!(\n            \"Finalized block {} does not contain transaction {}\",\n            included.block_number,\n            included.tx_hash\n        );\n    };\n    let (expected_to, expected_input, expected_value) = persisted_call_fields(intent)?;\n\n    Ok(transaction.from == executor.wallet_address\n        && transaction.nonce == nonce\n        && transaction.to == Some(expected_to)\n        && transaction.input == expected_input\n        && transaction.value == expected_value)\n}\n\n/// Parses the persisted destination, calldata, and value of an execution intent.\nfn persisted_call_fields(intent: &ExecutionIntentRow) -> anyhow::Result<(Address, Bytes, U256)> {\n    let to = Address::from_str(&intent.transaction_to)\n        .with_context(|| \"persisted execution destination is invalid\")?;","sourceCodeStart":2123,"sourceCodeEnd":2159,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/execution/client.rs#L2123-L2159","documentation":"After a swap finalizes, `finalized_transaction_matches` refetches the block by number and re-verifies the transaction before crediting the fill. The block hash must equal the receipt's (checked immediately above); this bail fires when that same block's transaction list does not contain the recorded tx hash. In practice the RPC returned body data inconsistent with the receipt — a deep reorg edge or a misbehaving/pruned node — rather than a normal reorg, which produces the earlier hash-mismatch message instead.","triggerScenarios":"During finalized-intent validation: `block_by_number` succeeds and `block.hash == receipt.block_hash`, yet no transaction in `block.transactions` matches `included.tx_hash`. Typical with load-balanced RPCs serving mixed block bodies, aggressive caching, or a provider bug.","commonSituations":"Multi-endpoint RPC setups where one node serves partial/pruned block bodies; an RPC provider incident; retry logic landing on a different backend between the receipt fetch and the block fetch.","solutions":["Retry the validation after a short backoff, ideally pinned to one consistent RPC endpoint — transient inconsistency is the most common cause.","Cross-check the transaction independently with eth_getTransactionByHash / eth_getTransactionReceipt before failing the intent.","Switch to an archive-quality, single-provider endpoint for finality checks.","If reproducible, capture block number, block hash, and tx hash and report it to the RPC provider and the nautilus maintainers."],"exampleFix":"// before: single fetch treated as authoritative\nlet matches = finalized_transaction_matches(&included, &intent, nonce, &executor).await?;\n\n// after: verify by hash with a retry before giving up on the intent\nasync fn verify_with_retry(executor: &TransactionExecutor, included: &IncludedTransaction) -> anyhow::Result<bool> {\n    for attempt in 0..3 {\n        let tx = executor.http_rpc_client.transaction_by_hash(included.tx_hash).await?;\n        if tx.is_some() { break; }\n        tokio::time::sleep(std::time::Duration::from_secs(2u64 * (attempt + 1))).await;\n    }\n    finalized_transaction_matches(included, &intent, nonce, executor).await\n}","handlingStrategy":"retry","validationCode":"let tx = executor.http_rpc_client\n    .transaction_by_hash(included.tx_hash)\n    .await?;\nanyhow::ensure!(tx.is_some(), 'tx {} absent from RPC; data inconsistency', included.tx_hash);","typeGuard":null,"tryCatchPattern":"match Err(e) if e.to_string().contains('does not contain transaction') => back off (e.g. 2s, 4s, 8s), retry on the same or a pinned alternate archive RPC; only surface the failure to the operator (and capture block/tx hashes for the provider) after retries are exhausted.","preventionTips":["Pin finality checks to one consistent archive-quality RPC endpoint.","Cross-check by tx hash (receipt lookup) before treating a block body as authoritative.","Monitor RPC provider status; mixed backends behind one URL are a common cause."],"tags":["rust","nautilustrader","evm","rpc","reorg","data-consistency","blockchain"],"backgroundTag":"blockchain-reorg","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}