{"record":{"id":"6522a6add00196bf","repo":"unionlabs/union","slug":"wait-for-packet-ack-failed","errorCode":null,"errorMessage":"wait_for_packet_ack failed: {:?}","messagePattern":"wait_for_packet_ack failed: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"tools/union-test/src/lib.rs","lineNumber":843,"sourceCode":"        };\n        println!(\n            \"Packet sent from {} to {} with hash: {}\",\n            source_chain.chain_id(),\n            destination_chain.chain_id(),\n            packet_hash\n        );\n\n        let withdraw_rewards = match destination_chain\n            .wait_for_withdraw_rewards(validator, timeout)\n            .await\n        {\n            Ok(evt) => Ok(evt),\n            Err(e) => anyhow::bail!(\"wait_for_packet_recv failed: {:?}\", e),\n        };\n\n        match source_chain.wait_for_packet_ack(packet_hash, timeout).await {\n            Ok(evt) => evt,\n            Err(e) => anyhow::bail!(\"wait_for_packet_ack failed: {:?}\", e),\n        };\n\n        withdraw_rewards\n    }\n\n    pub async fn send_and_recv_refund<Src: ChainEndpoint, Dst: ChainEndpoint>(\n        &self,\n        source_chain: &Src,\n        contract: Src::Contract,\n        msg: Src::Msg,\n        destination_chain: &Dst,\n        timeout: Duration,\n        signer: &Src::ProviderType,\n    ) -> anyhow::Result<helpers::PacketRecv> {\n        let (packet_hash, _height) = match source_chain\n            .send_ibc_transaction(contract.clone(), msg.clone(), signer)\n            .await\n        {","sourceCodeStart":825,"sourceCodeEnd":861,"githubUrl":"https://github.com/unionlabs/union/blob/031785bb6dc6b957c624e62bc64c184409c97d7b/tools/union-test/src/lib.rs#L825-L861","documentation":"In `send_and_recv_withdraw_rewards`, after the withdraw-rewards event is seen the code waits for the source chain to observe the packet acknowledgement via `wait_for_packet_ack(packet_hash, timeout)`; failure means no ack event for that hash within the timeout.","triggerScenarios":"The relayer didn't relay the ack back to the source chain in time; the source chain stalled; or the packet timed out on the destination (timeout packet instead of an ack) so no acknowledgement is ever written.","commonSituations":"Slow relayers on public testnets; timeouts configured below round-trip relay latency; destination-side errors producing timeout packets rather than acks.","solutions":["Increase `timeout`","Verify relayer health on the destination-to-source direction","Check destination tx logs to confirm an acknowledgement was written rather than a timeout packet"],"exampleFix":"// before\n... .await?; // uses Duration::from_secs(120) for the ack wait \n// after: size the ack timeout to relayer round-trip latency\nmatch source_chain.wait_for_packet_ack(packet_hash, Duration::from_secs(900)).await {\n    Ok(evt) => evt,\n    Err(e) => anyhow::bail!(\"wait_for_packet_ack failed: {:?}\", e),\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"let mut n = 0;\nloop {\n    match source_chain.wait_for_packet_ack(packet_hash, timeout).await {\n        Ok(evt) => break Ok(evt),\n        Err(e) if n < 2 => { n += 1; source_chain.wait_for_packet_ack(packet_hash, timeout * 2).await.ok().map(|e| break Ok(e)); continue; }\n        Err(e) => break Err(anyhow::anyhow!(\"wait_for_packet_ack failed: {:?}\", e)),\n    }\n}","preventionTips":["Budget ack timeouts for the full round trip (relay + destination execution + relay back)","Monitor relayer health in both directions for ack-dependent flows","Check destination receipts to distinguish acks from timeout packets when debugging"],"tags":["rust","ibc","ack","relayer","timeout"],"backgroundTag":null,"analyzedSha":"031785bb6dc6b957c624e62bc64c184409c97d7b","analyzedAt":"2026-08-16T06:24:09.996Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}