nautechsystems/nautilus_trader · error · anyhow::Error
Failed to persist broadcast attempt for transaction {tx_hash
Error message
Failed to persist broadcast attempt for transaction {tx_hash}: {e}; the in-flight slot stays occupied What it means
Immediately before broadcasting, the client records the 'Broadcast' status attempt via record_execution_status and that Postgres write failed ({e} carries the DB error). Because the write precedes send_raw_transaction, the transaction was not broadcast this attempt; the in-flight slot stays occupied so the intent cannot be double-processed, and reconciliation on the next connect completes the recovery.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:1570
/// Broadcasts the signed transaction and classifies the acceptance outcome.
async fn broadcast(&self, prepared: &PreparedTransaction) -> anyhow::Result<BroadcastOutcome> {
let tx_hash = prepared.tx_hash;
self.database
.record_execution_status(
prepared.intent_id,
&tx_hash.to_string(),
TransactionStatus::Broadcast,
None,
None,
None,
None,
None,
)
.await
.map_err(|e| {
anyhow::anyhow!(
"Failed to persist broadcast attempt for transaction {tx_hash}: {e}; the in-flight slot stays occupied"
)
})?;
match self
.http_rpc_client
.send_raw_transaction(&prepared.raw_tx, &tx_hash)
.await
{
Ok(broadcast_hash) => {
if broadcast_hash != tx_hash {
// The node acknowledged a different hash: acceptance of the signed
// transaction is unverified, so reconcile through the persisted record
// rather than poll a hash that cannot match the chain
return Ok(BroadcastOutcome::Ambiguous(format!(
"Broadcast of transaction {tx_hash} returned a differing hash {broadcast_hash}; the persisted record reconciles instead of rebroadcasting"
)));
}View on GitHub (pinned to 2114cf6f76)
Solutions
- Reconnect the client after restoring database service: reconciliation loads the active intent, sees the signed hash, and safely re-drives broadcast/finality
- Verify the intent row: SELECT status FROM execution_intent WHERE id = <intent_id>; it should still be 'signed', which reconciliation handles explicitly
- Keep the same wallet and Postgres store configured so reconciliation finds the intent
- Fix the underlying DB reliability issue (pool sizing, timeouts, HA) surfaced by {e}
Defensive patterns
Strategy: try-catch
Validate before calling
-- Pre-trade DB liveness check (cheap canary the caller can run) SELECT 1 AS ok; -- Any timeout here means the broadcast-attempt persist will likely fail too; pause trading.
Try / catch
try:
await submit_and_wait(order)
except Exception as e:
if 'Failed to persist broadcast attempt' in str(e):
# tx was NOT broadcast this attempt; slot stays occupied on purpose.
# Restore DB, reconnect with the same wallet/store, and let reconciliation re-drive broadcast.
alert_ops('broadcast persist failure - reconnect to reconcile')
raise Prevention
- Monitor Postgres write latency and pause order flow when it degrades, since persist failures freeze the in-flight slot
- Never manually clear the in-flight state after this error; reconciliation is the safe path
When it happens
Trigger: broadcast() hitting a DB error (connection loss, constraint failure, timeout) at the record_execution_status(TransactionStatus::Broadcast) call, before any RPC send is attempted.
Common situations: Transient Postgres outages at the worst moment; schema drift in the status-history table after a partial migration; connection pool exhaustion under concurrent strategy load.
Related errors
- Failed to persist transaction {tx_hash}: {e}; the in-flight
- Execution intent {} uses unsupported schema version {}
- Execution intent {} has unknown purpose {}
- Active execution intent {} has no nonce
- Execution intent {intent_id} has no current hash
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/25406f5b726a20e7.
Report an issue: GitHub.