partner_chains_cardano_offchain/
await_tx.rs1use anyhow::anyhow;
2use ogmios_client::query_ledger_state::QueryUtxoByUtxoId;
3use sidechain_domain::UtxoId;
4use std::time::Duration;
5use tokio_retry::{Retry, strategy::FixedInterval};
6
7pub trait AwaitTx {
9 #[allow(async_fn_in_trait)]
10 async fn await_tx_output<C: QueryUtxoByUtxoId>(
13 &self,
14 client: &C,
15 utxo_id: UtxoId,
16 ) -> anyhow::Result<()>;
17}
18
19pub struct FixedDelayRetries {
21 delay: Duration,
22 retries: usize,
23}
24
25impl FixedDelayRetries {
26 pub fn new(delay: Duration, retries: usize) -> Self {
28 Self { delay, retries }
29 }
30
31 pub fn five_minutes() -> Self {
33 Self { delay: Duration::from_secs(5), retries: 59 }
34 }
35}
36
37impl AwaitTx for FixedDelayRetries {
38 async fn await_tx_output<C: QueryUtxoByUtxoId>(
39 &self,
40 client: &C,
41 utxo_id: UtxoId,
42 ) -> anyhow::Result<()> {
43 let strategy = FixedInterval::new(self.delay).take(self.retries);
44 let _ = Retry::spawn(strategy, || async {
45 log::info!("Probing for transaction output '{}'", utxo_id);
46 let utxo = client.query_utxo_by_id(utxo_id).await.map_err(|_| ())?;
47 utxo.ok_or(())
48 })
49 .await
50 .map_err(|_| {
51 anyhow!(
52 "Retries for confirmation of transaction '{}' exceeded the limit",
53 hex::encode(utxo_id.tx_hash.0)
54 )
55 })?;
56 log::info!("Transaction output '{}'", hex::encode(utxo_id.tx_hash.0));
57 Ok(())
58 }
59}
60
61#[cfg(test)]
62pub(crate) mod mock {
63 use super::AwaitTx;
64 use ogmios_client::query_ledger_state::QueryUtxoByUtxoId;
65
66 pub(crate) struct ImmediateSuccess;
67
68 impl AwaitTx for ImmediateSuccess {
69 async fn await_tx_output<Q: QueryUtxoByUtxoId>(
70 &self,
71 _query: &Q,
72 _utxo_id: sidechain_domain::UtxoId,
73 ) -> anyhow::Result<()> {
74 Ok(())
75 }
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::{AwaitTx, FixedDelayRetries};
82 use ogmios_client::{
83 OgmiosClientError,
84 query_ledger_state::QueryUtxoByUtxoId,
85 types::{OgmiosTx, OgmiosUtxo},
86 };
87 use sidechain_domain::{McTxHash, UtxoId, UtxoIndex};
88 use std::{cell::RefCell, time::Duration};
89
90 #[tokio::test]
91 async fn immediate_success() {
92 let mock =
93 MockQueryUtxoByUtxoId { responses: RefCell::new(vec![Ok(Some(awaited_utxo()))]) };
94 FixedDelayRetries::new(Duration::from_millis(1), 3)
95 .await_tx_output(&mock, awaited_utxo_id())
96 .await
97 .unwrap();
98 }
99
100 #[tokio::test]
101 async fn success_in_2nd_attempt() {
102 let mock = MockQueryUtxoByUtxoId {
103 responses: RefCell::new(vec![Ok(None), Ok(Some(awaited_utxo()))]),
104 };
105 FixedDelayRetries::new(Duration::from_millis(1), 3)
106 .await_tx_output(&mock, awaited_utxo_id())
107 .await
108 .unwrap();
109 }
110
111 #[tokio::test]
112 async fn all_attempts_result_not_found() {
113 let mock =
114 MockQueryUtxoByUtxoId { responses: RefCell::new(vec![Ok(None), Ok(None), Ok(None)]) };
115 let result = FixedDelayRetries::new(Duration::from_millis(1), 2)
116 .await_tx_output(&mock, awaited_utxo_id())
117 .await;
118 assert!(result.is_err())
119 }
120
121 #[tokio::test]
122 async fn all_attempts_failed() {
123 let mock = MockQueryUtxoByUtxoId {
124 responses: RefCell::new(vec![
125 Err(OgmiosClientError::RequestError("test error1".to_string())),
126 Err(OgmiosClientError::RequestError("test error2".to_string())),
127 Err(OgmiosClientError::RequestError("test error3".to_string())),
128 ]),
129 };
130 let result = FixedDelayRetries::new(Duration::from_millis(1), 2)
131 .await_tx_output(&mock, awaited_utxo_id())
132 .await;
133 assert!(result.is_err())
134 }
135
136 struct MockQueryUtxoByUtxoId {
137 responses: RefCell<Vec<Result<Option<OgmiosUtxo>, OgmiosClientError>>>,
138 }
139
140 impl QueryUtxoByUtxoId for MockQueryUtxoByUtxoId {
141 async fn query_utxo_by_id(
142 &self,
143 utxo: sidechain_domain::UtxoId,
144 ) -> Result<Option<OgmiosUtxo>, OgmiosClientError> {
145 if utxo == awaited_utxo_id() {
146 self.responses.borrow_mut().pop().unwrap()
147 } else {
148 Ok(None)
149 }
150 }
151 }
152
153 fn awaited_utxo_id() -> UtxoId {
154 UtxoId { tx_hash: McTxHash([7u8; 32]), index: UtxoIndex(1) }
155 }
156
157 fn awaited_utxo() -> OgmiosUtxo {
158 OgmiosUtxo { transaction: OgmiosTx { id: [7u8; 32] }, index: 1, ..Default::default() }
159 }
160}