nautechsystems/nautilus_trader · error · anyhow::Error
Execution transaction hash {transaction_hash} was not found
Error message
Execution transaction hash {transaction_hash} was not found for intent {intent_id} What it means
Guard inside record_execution_status (database.rs:3662-3665): the UPDATE ... WHERE intent_id = $1 AND transaction_hash = $2 affected zero rows. The WHERE clause has no status or current filter, so even a retired/replaced hash row would match; this fires only when the (intent_id, transaction_hash) pair does not exist in execution_transaction_hash at all. The whole transaction then rolls back, leaving the intent status untouched.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:3662
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 2114cf6f76)
Solutions
- Confirm the pair exists: call get_execution_transaction_hashes(intent_id) or run SELECT id, status FROM execution_transaction_hash WHERE intent_id = $1 AND transaction_hash = $2
- If the hash is absent, persist it first through the normal signing/replacement recording path before replaying the receipt observation
- Normalize the hash (0x-prefixed lowercase hex) on both the write and read paths and compare byte-for-byte with the stored value
- If intent_id came from a restarted or legacy process, re-resolve it via get_active_execution_intent instead of caching it across restarts
Example fix
// before: assume the pair exists, discover the mismatch at UPDATE time
let _ = db.record_execution_status(intent_id, &hash, status, block, block_hash, ok, gas, price).await?;
// after: validate the (intent_id, hash) pair first
let hashes = db.get_execution_transaction_hashes(intent_id).await?;
let normalized = hash.to_lowercase();
if !hashes.iter().any(|r| r.transaction_hash == normalized) {
anyhow::bail!("hash {normalized} not persisted for intent {intent_id}; record it before applying a receipt");
}
let _ = db.record_execution_status(intent_id, &normalized, status, block, block_hash, ok, gas, price).await?; Defensive patterns
Strategy: validation
Validate before calling
let hashes = db.get_execution_transaction_hashes(intent_id).await?;
let normalized = hash.to_lowercase();
anyhow::ensure!(
hashes.iter().any(|r| r.transaction_hash == normalized),
"hash {normalized} not persisted for intent {intent_id}; record it before applying a receipt"
); Try / catch
match db.record_execution_status(...).await {
Ok(()) => Ok(()),
Err(e) if e.to_string().contains("was not found for intent") => {
// data mismatch: reconcile hash/intent pairing, never retry blindly
log_and_alert(e)
}
Err(e) => Err(e),
} Prevention
- Normalize transaction hashes (0x-prefixed lowercase hex) at every ingestion boundary
- Never cache intent_id across process restarts; re-resolve via get_active_execution_intent
- Ensure a hash is always persisted (signed/replacement path) before receipt observations reference it
- Add a foreign-key-style invariant test: every (intent_id, hash) passed to record_execution_status exists after the preceding add/record calls
When it happens
Trigger: Calling record_execution_status with a hash that was never persisted under that intent (a receipt observed for a broadcast hash recorded against a different intent_id), an intent_id belonging to another chain/instance, or a hash string that differs byte-for-byte from the stored value (missing 0x prefix, different casing, wrong length).
Common situations: A replacement transaction consumed the nonce but its hash was attached under another intent record; intent_id cached in a long-lived process across a database rebuild; hashes copied from logs or RPC responses with non-normalized casing; partial outage where intent rows survived but hash rows were pruned.
Related errors
- Active execution intent {intent_id} was not found
- Replacement hash {transaction_hash} conflicts with another i
- Failed to update execution hash {transaction_hash}: {e}
- Failed to update execution intent {intent_id}: {e}
- Failed to record execution transition: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/1d3ec55b385f117b.
Report an issue: GitHub.