nautechsystems/nautilus_trader · error · anyhow::Error

Execution transaction hash

Error message

Execution transaction hash {transaction_hash} was not found for intent {intent_id}

What it means

Raised by anyhow::ensure! when the execution-hash UPDATE affected 0 rows, meaning no execution_transaction (or equivalent) row exists with the given transaction_hash for this intent. The status lock succeeded but the receipt references a hash that was never recorded, so the transition is rolled back.

Solutions

  1. Confirm the stored hash: SELECT transaction_hash ... WHERE intent_id = '<id>'; compare exact bytes/casing with the one passed in.
  2. Persist the execution hash before subscribing to/processing its receipt; enforce ordering in the adapter pipeline.
  3. Handle replaced transactions: update the intent's hash when a replacement tx is submitted, before the receipt arrives.
  4. Normalize hash format (consistent lowercase hex) at both write and lookup points.

Example fix

// before
anyhow::ensure!(
    hash_result.rows_affected() == 1,
    "Execution transaction hash {transaction_hash} was not found for intent {intent_id}"
);
// after
if hash_result.rows_affected() == 0 {
    tracing::warn!(%intent_id, %transaction_hash, "unknown execution hash; registering receipt out-of-band");
    anyhow::bail!("Execution transaction hash {transaction_hash} was not found for intent {intent_id}");
}
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the execution hash is registered before processing its receipt
let known = sqlx::query_scalar::<_, bool>(
    "SELECT EXISTS(SELECT 1 FROM execution_transaction WHERE intent_id = $1 AND transaction_hash = $2)")
    .bind(&intent_id)
    .bind(&transaction_hash)
    .fetch_one(&pool)
    .await?;
if !known { register_execution_hash(&intent_id, &transaction_hash).await?; }

Try / catch

if let Err(e) = transition.await {
    if e.to_string().contains("was not found for intent") {
        tracing::warn!(%intent_id, %transaction_hash, "hash not registered; check replacement tx or ordering");
    }
}

Prevention

When it happens

Trigger: Calling the transition with a transaction_hash that differs from the one stored when the intent was created — e.g. a replacement/ Speedup transaction with a different hash, a hash casing/format mismatch, or the tx was never registered before its receipt was processed.

Common situations: Transaction replaced/repriced (speed-up/cancel) onchain so the mined hash differs from the submitted one; processing receipts before the execute call persisted the hash; mixing lowercase/0x-prefixed hash formats between the record and lookup paths; data loss or reset between registration and receipt.

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/1d3ec55b385f117b. Report an issue: GitHub.

Appendix: source

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

                receipt_success = COALESCE($6, receipt_success),
                gas_used = COALESCE($7, gas_used),
                effective_gas_price = COALESCE($8, effective_gas_price),
                updated_at = NOW()
            WHERE intent_id = $1 AND transaction_hash = $2
            ",
        )
        .bind(intent_id)
        .bind(transaction_hash)
        .bind(status.as_str())
        .bind(block_number_db)
        .bind(block_hash)
        .bind(receipt_success)
        .bind(gas_used_db)
        .bind(effective_gas_price)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to update execution hash {transaction_hash}: {e}"))?;
        anyhow::ensure!(
            hash_result.rows_affected() == 1,
            "Execution transaction hash {transaction_hash} was not found for intent {intent_id}"
        );

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

View on GitHub (pinned to 18893faf8b)