nautechsystems/nautilus_trader · error · anyhow::Error
Failed to persist replacement hash {transaction_hash}: {e}
Error message
Failed to persist replacement hash {transaction_hash}: {e} What it means
Wrapped sqlx error from the upsert INSERT INTO execution_transaction_hash ... ON CONFLICT (chain_id, transaction_hash) DO UPDATE SET current = TRUE ... WHERE execution_transaction_hash.intent_id = EXCLUDED.intent_id RETURNING ... statement in add_execution_replacement_hash (database.rs:3818-3837). The failure is a database-level fault during the upsert; the intentional conflict-skip case (row owned by another intent) is surfaced separately as error 93.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:3837
"
INSERT INTO execution_transaction_hash (
intent_id, chain_id, transaction_hash, status, current
) VALUES ($1, $2, $3, 'replaced', TRUE)
ON CONFLICT (chain_id, transaction_hash) DO UPDATE
SET current = TRUE, updated_at = NOW()
WHERE execution_transaction_hash.intent_id = EXCLUDED.intent_id
RETURNING
id, intent_id, chain_id, transaction_hash, raw_transaction, status,
block_number, block_hash, receipt_success, gas_used,
effective_gas_price, current
",
)
.bind(intent_id)
.bind(chain_id_db)
.bind(transaction_hash)
.fetch_optional(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to persist replacement hash {transaction_hash}: {e}"))?
.ok_or_else(|| {
anyhow::anyhow!("Replacement hash {transaction_hash} conflicts with another intent")
})?;
sqlx::query(
"UPDATE execution_intent SET status = 'replaced', updated_at = NOW() WHERE id = $1",
)
.bind(intent_id)
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to mark execution intent replaced: {e}"))?;
sqlx::query(
"
INSERT INTO execution_transaction_transition (
intent_id, transaction_hash_id, transition_key, from_status, to_status
) VALUES ($1, $2, $3, $4, 'replaced')
ON CONFLICT (intent_id, transition_key) DO NOTHING
",View on GitHub (pinned to 2114cf6f76)
Solutions
- Downcast to sqlx::Error and inspect as_database_error(): NULL constraint on raw_transaction means the schema must allow NULL raw bytes for replacement rows
- Apply the project migrations that created replacement support (nullable raw_transaction, the (chain_id, transaction_hash) unique index)
- Retry on transient connectivity/timeout classes with backoff
- Verify the intent row still exists if an FK violation is reported
Defensive patterns
Strategy: retry
Type guard
fn is_transient_db_error(err: &anyhow::Error) -> bool {
err.downcast_ref::<sqlx::Error>().map_or(false, |e| {
matches!(e, sqlx::Error::PoolTimedOut | sqlx::Error::PoolClosed | sqlx::Error::Io(_))
|| e.as_database_error().and_then(|d| d.code()).map_or(false, |c| {
matches!(c.as_ref(), "40001" | "40P01" | "55P03" | "57014" | "08000" | "08003" | "08006")
})
})
} Try / catch
match db.add_execution_replacement_hash(intent_id, chain_id, &hash).await {
Ok(row) => Ok(row),
Err(e) if is_transient_db_error(&e) => retry_with_backoff(e),
Err(e) => return Err(e), // NOT NULL/FK causes: fix schema or referential data
} Prevention
- Keep raw_transaction nullable - replacement rows are created without raw bytes by design
- Maintain the (chain_id, transaction_hash) unique index expected by the ON CONFLICT clause
- Test the upsert path in CI against the migrated schema whenever hash-row columns change
When it happens
Trigger: Connection loss or statement timeout while executing the upsert; a NOT NULL violation because raw_transaction is not provided for the new row (it is inserted with only intent_id, chain_id, transaction_hash, status, current); a foreign-key violation if intent_id does not exist in execution_intent; schema drift on the RETURNING column list.
Common situations: The replacement row is deliberately created without raw bytes (standard JSON-RPC block responses cannot expose the signed envelope, per the function's doc comment), so any NOT NULL constraint on raw_transaction breaks this insert; Postgres restart mid-call.
Related errors
- Failed to retire replaced execution hash: {e}
- Replacement hash {transaction_hash} conflicts with another i
- Failed to mark execution intent replaced: {e}
- Failed to commit replacement transaction: {e}
- Failed to update execution hash {transaction_hash}: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/6e99a0e00deaf28f.
Report an issue: GitHub.