nautechsystems/nautilus_trader · error · anyhow::Error

Finality transaction hash was not found

Error message

Finality transaction hash was not found

What it means

An assertion that the UPDATE of `execution_transaction_hash` (status, block_number, block_hash) affected exactly one row. Zero affected rows means no transaction-hash record exists for this finality's transaction hash, so the receipt cannot be attached to a known transaction and the finality commit aborts. It is a referential-integrity check against the transaction-hash ledger.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:7131

            UPDATE execution_transaction_hash
            SET status = $3, block_number = $4, block_hash = $5,
                receipt_success = $6, gas_used = $7, effective_gas_price = $8,
                updated_at = NOW()
            WHERE intent_id = $1 AND transaction_hash = $2
            ",
        )
        .bind(finality.intent_id)
        .bind(finality.transaction_hash)
        .bind(finality.status.as_str())
        .bind(block_number)
        .bind(finality.block_hash)
        .bind(finality.receipt_success)
        .bind(gas_used)
        .bind(finality.effective_gas_price)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to record verified finality receipt: {e}"))?;
        anyhow::ensure!(
            hash_result.rows_affected() == 1,
            "Finality transaction hash was not found"
        );

        let active = !fill_emitted && !terminal_emitted;
        sqlx::query(
            "UPDATE execution_intent SET status = $2, active = $3, updated_at = NOW() WHERE id = $1",
        )
        .bind(finality.intent_id)
        .bind(finality.status.as_str())
        .bind(active)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to record verified finality intent: {e}"))?;

        let transition_key = format!(
            "{}:{}:{}:{}",
            finality.status.as_str(),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Query `execution_transaction_hash` by the finality transaction hash to confirm the row exists and with which chain_id/wallet values
  2. Ensure ordering: the transaction-hash row must be committed before finality is applied
  3. Normalize the transaction hash (lowercase hex, consistent 0x handling) on both write and lookup paths
  4. Check retention/cleanup jobs are not deleting rows pending finality
  5. Verify the UPDATE's WHERE clause scoping matches how the row was originally written

Example fix

// before
anyhow::ensure!(
    hash_result.rows_affected() == 1,
    "Finality transaction hash was not found"
);
// after
anyhow::ensure!(
    hash_result.rows_affected() == 1,
    "Finality transaction hash was not found: hash={} chain={} intent={}",
    finality.transaction_hash, chain_id, finality.intent_id
);
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the transaction hash row exists before applying finality
let exists = sqlx::query_scalar::<_, i64>(
    "SELECT COUNT(*) FROM execution_transaction_hash \
     WHERE transaction_hash = $1 AND chain_id = $2 AND wallet_address = $3"
)
.bind(normalize_hash(&finality.transaction_hash))
.bind(chain_id)
.bind(finality.wallet_address)
.fetch_one(&mut *conn).await?;
if exists == 0 { /* defer finality until the hash row is committed */ }

Type guard

fn tx_hash_is_known(finality: &VerifiedFinality, known: &HashSet<String>) -> bool {
    known.contains(&normalize_hash(&finality.transaction_hash))
}

Try / catch

match apply_verified_finality(...).await {
    Err(e) if e.to_string().contains("transaction hash was not found") => {
        warn!("unknown tx hash {} for finality — deferring", finality.transaction_hash);
        requeue(finality);
    }
    other => other,
}

Prevention

When it happens

Trigger: Finality arriving for a transaction hash never recorded in `execution_transaction_hash`; the row was scoped to a different chain_id/wallet in the UPDATE's WHERE clause; a truncated or differently-encoded transaction hash (case/0x-prefix) than stored; the row was deleted by cleanup before finality was processed.

Common situations: Out-of-order processing where finality is applied before the transaction-hash row is written; environment/DB mismatch between the submitter and finality verifier; hash normalization differences (checksummed hex vs lowercase, 0x prefix) after a refactor; retention jobs pruning rows too aggressively.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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