nautechsystems/nautilus_trader · error · anyhow::Error
Signed transaction {transaction_hash} conflicts with its per
Error message
Signed transaction {transaction_hash} conflicts with its persisted identity What it means
The upsert's conflict-update WHERE clause matched no row, so RETURNING produced nothing: an existing execution_transaction_hash row for (chain_id, transaction_hash) exists but belongs to a different intent, lacks payload_expected, or stores a different payload than the one being persisted. The library rejects this identity conflict rather than overwriting the persisted record.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6709
RETURNING
id, intent_id, chain_id, transaction_hash, payload_expected,
raw_transaction, sealed_transaction, status,
block_number, block_hash, receipt_success, gas_used,
effective_gas_price, current
",
)
.bind(intent_id)
.bind(chain_id_db)
.bind(transaction_hash)
.bind(raw_transaction)
.bind(sealed_transaction)
.fetch_optional(&mut *transaction)
.await
.map_err(|e| {
anyhow::anyhow!("Failed to persist signed transaction {transaction_hash}: {e}")
})?
.ok_or_else(|| {
anyhow::anyhow!(
"Signed transaction {transaction_hash} conflicts with its persisted identity"
)
})?;
sqlx::query(
"UPDATE execution_intent SET status = 'signed', updated_at = NOW() WHERE id = $1",
)
.bind(intent_id)
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to mark execution intent signed: {e}"))?;
sqlx::query(
"
INSERT INTO execution_transaction_transition (
intent_id, transaction_hash_id, transition_key, from_status, to_status
) VALUES ($1, $2, $3, $4, 'signed')
ON CONFLICT (intent_id, transition_key) DO NOTHING
",View on GitHub (pinned to 18893faf8b)
Solutions
- Query the existing row: SELECT intent_id, payload_expected FROM execution_transaction_hash WHERE chain_id=$1 AND transaction_hash=$2 and reconcile which intent actually owns the hash.
- Fix nonce allocation so two intents never produce the same transaction hash; re-issue the losing intent with a new nonce.
- Do not retry with the same payload expecting success — the conflict is deterministic; investigate the differing payload bytes instead.
- If the row was created by an aborted/incorrect flow, correct it through the supported recovery path, never by direct UPDATE.
Example fix
// before
let existing: Option<_> = sqlx::query_scalar(
"SELECT intent_id FROM execution_transaction_hash WHERE chain_id=$1 AND transaction_hash=$2")
.bind(chain_id).bind(hash).fetch_optional(&pool).await?;
// after
if let Some(owner) = existing {
anyhow::ensure!(owner == intent_id, "hash {hash} owned by intent {owner}; allocate a fresh nonce");
} Defensive patterns
Strategy: validation
Validate before calling
let owner: Option<i64> = sqlx::query_scalar(
"SELECT intent_id FROM execution_transaction_hash WHERE chain_id=$1 AND transaction_hash=$2")
.bind(chain_id as i32).bind(transaction_hash).fetch_optional(&pool).await?;
anyhow::ensure!(owner.map_or(true, |o| o == intent_id), "hash {transaction_hash} already persisted under intent {owner:?}"); Try / catch
match db.add_execution_transaction(intent_id, chain_id, hash, sealed).await {
Err(e) if e.to_string().contains("conflicts with its persisted identity") => {
// deterministic conflict: reconcile ownership / reissue intent, do not blind-retry
alert_identity_conflict(intent_id, hash);
}
r => r?,
} Prevention
- Guarantee unique nonce allocation per chain so hashes never collide across intents
- Look up existing (chain_id, transaction_hash) rows before signing
- Never reuse signed payloads across intents or environments
- Treat this error as an integrity alarm, not a transient failure
When it happens
Trigger: Persisting a signed transaction whose (chain_id, transaction_hash) already exists under another intent_id (reused nonce producing the same hash), or re-persisting the same hash with different raw/sealed bytes than stored, or the existing row has payload_expected = FALSE.
Common situations: Duplicate nonce assignment causing two intents to produce the identical transaction hash; a corrupted or partially-migrated row; replaying a signing job against a database where the hash row was created by a different intent; mixed deployments producing different sealed bytes for the same transaction.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Order {client_order_id} is already claimed by execution clie
- Execution verification manifest identity changed
- Finalized header manifest identity changed
- Signed transaction envelope does not use the database active
- Plaintext signed transaction persistence is disabled after p
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/f0880570e4ea526c.
Report an issue: GitHub.