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

  1. Serialize sends from the same signer: await each send before broadcasting the next
  2. Wait a few seconds for in-flight txs to commit, then retry the whole send
  3. Reconnect to / restart a healthy RPC node if sequences are stuck
  4. 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

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


AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16). Data as JSON: /api/errors/fd5cd39b739bba2d. Report an issue: GitHub.