nautechsystems/nautilus_trader · error

Finalized block {} does not contain transaction {}

Error message

Finalized block {} does not contain transaction {}

What it means

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.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:2141

    executor: &TransactionExecutor,
) -> anyhow::Result<bool> {
    let block = executor
        .http_rpc_client
        .block_by_number(included.block_number, true)
        .await?;
    anyhow::ensure!(
        block.hash == included.receipt.block_hash,
        "Finalized block {} changed from {} to {} before intent validation",
        included.block_number,
        included.receipt.block_hash,
        block.hash
    );
    let Some(transaction) = block
        .transactions
        .iter()
        .find(|transaction| transaction.hash == included.tx_hash)
    else {
        anyhow::bail!(
            "Finalized block {} does not contain transaction {}",
            included.block_number,
            included.tx_hash
        );
    };
    let (expected_to, expected_input, expected_value) = persisted_call_fields(intent)?;

    Ok(transaction.from == executor.wallet_address
        && transaction.nonce == nonce
        && transaction.to == Some(expected_to)
        && transaction.input == expected_input
        && transaction.value == expected_value)
}

/// Parses the persisted destination, calldata, and value of an execution intent.
fn persisted_call_fields(intent: &ExecutionIntentRow) -> anyhow::Result<(Address, Bytes, U256)> {
    let to = Address::from_str(&intent.transaction_to)
        .with_context(|| "persisted execution destination is invalid")?;

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Retry the validation after a short backoff, ideally pinned to one consistent RPC endpoint — transient inconsistency is the most common cause.
  2. Cross-check the transaction independently with eth_getTransactionByHash / eth_getTransactionReceipt before failing the intent.
  3. Switch to an archive-quality, single-provider endpoint for finality checks.
  4. If reproducible, capture block number, block hash, and tx hash and report it to the RPC provider and the nautilus maintainers.

Example fix

// before: single fetch treated as authoritative
let matches = finalized_transaction_matches(&included, &intent, nonce, &executor).await?;

// after: verify by hash with a retry before giving up on the intent
async fn verify_with_retry(executor: &TransactionExecutor, included: &IncludedTransaction) -> anyhow::Result<bool> {
    for attempt in 0..3 {
        let tx = executor.http_rpc_client.transaction_by_hash(included.tx_hash).await?;
        if tx.is_some() { break; }
        tokio::time::sleep(std::time::Duration::from_secs(2u64 * (attempt + 1))).await;
    }
    finalized_transaction_matches(included, &intent, nonce, executor).await
}
Defensive patterns

Strategy: retry

Validate before calling

let tx = executor.http_rpc_client
    .transaction_by_hash(included.tx_hash)
    .await?;
anyhow::ensure!(tx.is_some(), 'tx {} absent from RPC; data inconsistency', included.tx_hash);

Try / catch

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.

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/133886eda2d7304e. Report an issue: GitHub.