unionlabs/union · error · anyhow::Error
failed to send transaction
Error message
failed to send transaction
What it means
`send_ibc_transaction` calls `send_cosmwasm_transaction_with_retry`, which retries up to 5 attempts but only on account-sequence-mismatch errors; it returns None only when every attempt failed with a sequence mismatch. This `ok_or_else` turns that None into 'failed to send transaction', so the error specifically means the signer's sequence stayed out of sync with the node for the full retry window (~25s).
Source
Thrown at tools/union-test/src/cosmos.rs:779
/// Helper to detect the ABCI “account sequence mismatch” error.
fn is_sequence_mismatch(&self, err: &BroadcastTxCommitError) -> bool {
match err {
BroadcastTxCommitError::Query(grpc_err) => {
grpc_err.log.contains("account sequence mismatch")
}
_ => false,
}
}
pub async fn send_ibc_transaction(
&self,
contract: Addr,
msg: (Vec<u8>, Vec<Coin>),
signer: &LocalSigner,
) -> anyhow::Result<(H256, u64)> {
let result = self.send_cosmwasm_transaction(contract, msg, signer).await;
let tx_result = result.ok_or_else(|| anyhow!("failed to send transaction"))??;
let height = tx_result
.height
.ok_or_else(|| anyhow!("transaction height not found"))?;
let send_event = tx_result
.tx_result
.events
.into_iter()
.find_map(|e| {
if e.ty == "wasm-packet_send" {
CosmosSdkEvent::<ModuleEvent>::new(e).ok().map(|e| e.event)
} else {
None
}
})
.ok_or_else(|| anyhow!("wasm-packet_send event not found"))?;
Ok(match send_event {View on GitHub (pinned to 031785bb6d)
Solutions
- Serialize sends from the same signer: await each send before broadcasting the next
- Wait a few seconds for in-flight txs to commit, then retry the whole send
- Reconnect to / restart a healthy RPC node if sequences are stuck
- Give each concurrent test flow its own funded account
Example fix
// before: two sends from one signer in parallel
tokio::join!(
client.send_ibc_transaction(c.clone(), m1, &signer),
client.send_ibc_transaction(c, m2, &signer),
);
// after: serialize sends from the same account
client.send_ibc_transaction(c.clone(), m1, &signer).await?;
client.send_ibc_transaction(c, m2, &signer).await?; Defensive patterns
Strategy: retry
Validate before calling
// pre-flight: no other in-flight txs for this signer let status = client.rpc.status().await?; // and account_info query for the sequence // ensure prior broadcasts have committed before sending the next one
Try / catch
let mut attempts = 0;
loop {
match client.send_ibc_transaction(contract.clone(), msg.clone(), &signer).await {
Ok(r) => break Ok(r),
Err(e) if e.to_string().contains("failed to send transaction") && attempts < 3 => {
attempts += 1;
tokio::time::sleep(Duration::from_secs(15)).await; // let in-flight txs commit
continue;
}
Err(e) => break Err(e),
}
} Prevention
- Serialize all sends from one signer; never broadcast concurrently with the same account
- Give each parallel test flow its own funded account
- After node restarts, wait for the sequence to settle before resuming
When it happens
Trigger: Concurrent transactions broadcast from the same LocalSigner while earlier ones are still uncommitted; an RPC node lagging on account sequence after restart; a previously broadcast tx not yet committed when each retry re-derives the sequence.
Common situations: Tests firing multiple sends in parallel with one signer; slow single-validator devnets; stale sequence state after node crash/restart; the same test key reused by another process.
Related errors
- timed out after {:?}
- transaction height not found
- NOT IMPLEMENTED
- waiting for transaction failed
- simulation result not found
AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16).
Data as JSON: /api/errors/fd5cd39b739bba2d.
Report an issue: GitHub.