Skip to content

Commit abb262b

Browse files
Merge pull request #112 from arkade-os/fix/intermittent-e2e-errors
Fix some intermittent e2e errors
2 parents 3397fa3 + cc674fc commit abb262b

File tree

4 files changed

+100
-65
lines changed

4 files changed

+100
-65
lines changed

ark-client/src/lib.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::error::ErrorContext;
2+
use crate::utils::sleep;
23
use crate::utils::timeout_op;
34
use crate::wallet::BoardingWallet;
45
use crate::wallet::OnchainWallet;
@@ -343,6 +344,50 @@ where
343344
server_info,
344345
})
345346
}
347+
348+
/// Connects to the Ark server and retrieves server information.
349+
///
350+
/// If it encounters errors, it will retry `max_retries`.
351+
///
352+
/// # Errors
353+
///
354+
/// Returns an error if the connection fails or times out.
355+
pub async fn connect_with_retries(mut self, max_retries: usize) -> Result<Client<B, W>, Error> {
356+
let mut n_retries = 0;
357+
while n_retries < max_retries {
358+
let res = timeout_op(self.timeout, self.network_client.connect())
359+
.await
360+
.context("Failed to connect to Ark server")?;
361+
362+
match res {
363+
Ok(()) => break,
364+
Err(error) => {
365+
tracing::warn!(?error, "Failed to connect to Ark server, retrying");
366+
367+
sleep(Duration::from_secs(2)).await;
368+
369+
n_retries += 1;
370+
371+
continue;
372+
}
373+
};
374+
}
375+
376+
let server_info = timeout_op(self.timeout, self.network_client.get_info())
377+
.await
378+
.context("Failed to get Ark server info")??;
379+
380+
tracing::debug!(
381+
name = self.name,
382+
ark_server_url = ?self.network_client,
383+
"Connected to Ark server"
384+
);
385+
386+
Ok(Client {
387+
inner: self,
388+
server_info,
389+
})
390+
}
346391
}
347392

348393
impl<B, W> Client<B, W>

ark-rest/src/conversions.rs

Lines changed: 42 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,15 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
3737
.signer_pubkey
3838
.ok_or_else(|| ConversionError("Missing signer_pubkey".to_string()))?;
3939
let pk = signer_pubkey_str.parse::<PublicKey>().map_err(|e| {
40-
ConversionError(format!(
41-
"Invalid signer_pubkey '{}': {}",
42-
signer_pubkey_str, e
43-
))
40+
ConversionError(format!("Invalid signer_pubkey '{signer_pubkey_str}': {e}",))
4441
})?;
4542

4643
let vtxo_tree_expiry_str = response
4744
.vtxo_tree_expiry
4845
.ok_or_else(|| ConversionError("Missing vtxo_tree_expiry".to_string()))?;
4946
let vtxo_tree_expiry_val = vtxo_tree_expiry_str.parse::<i64>().map_err(|e| {
5047
ConversionError(format!(
51-
"Invalid vtxo_tree_expiry '{}': {}",
52-
vtxo_tree_expiry_str, e
48+
"Invalid vtxo_tree_expiry '{vtxo_tree_expiry_str}': {e}",
5349
))
5450
})?;
5551
let vtxo_tree_expiry = parse_sequence_number(vtxo_tree_expiry_val)?;
@@ -59,8 +55,7 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
5955
.ok_or_else(|| ConversionError("Missing unilateral_exit_delay".to_string()))?;
6056
let unilateral_exit_delay_val = unilateral_exit_delay_str.parse::<i64>().map_err(|e| {
6157
ConversionError(format!(
62-
"Invalid unilateral_exit_delay '{}': {}",
63-
unilateral_exit_delay_str, e
58+
"Invalid unilateral_exit_delay '{unilateral_exit_delay_str}': {e}",
6459
))
6560
})?;
6661
let unilateral_exit_delay = parse_sequence_number(unilateral_exit_delay_val)?;
@@ -70,8 +65,7 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
7065
.ok_or_else(|| ConversionError("Missing boarding_exit_delay".to_string()))?;
7166
let boarding_exit_delay_val = boarding_exit_delay_str.parse::<i64>().map_err(|e| {
7267
ConversionError(format!(
73-
"Invalid boarding_exit_delay '{}': {}",
74-
boarding_exit_delay_str, e
68+
"Invalid boarding_exit_delay '{boarding_exit_delay_str}': {e}",
7569
))
7670
})?;
7771
let boarding_exit_delay = parse_sequence_number(boarding_exit_delay_val)?;
@@ -81,8 +75,7 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
8175
.ok_or_else(|| ConversionError("Missing round_interval".to_string()))?;
8276
let round_interval = round_interval_str.parse::<i64>().map_err(|e| {
8377
ConversionError(format!(
84-
"Invalid round_interval '{}': {}",
85-
round_interval_str, e
78+
"Invalid round_interval '{round_interval_str}': {e}",
8679
))
8780
})?;
8881

@@ -91,14 +84,14 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
9184
.ok_or_else(|| ConversionError("Missing network".to_string()))?;
9285
let network = network_str
9386
.parse::<Network>()
94-
.map_err(|e| ConversionError(format!("Invalid network '{}': {}", network_str, e)))?;
87+
.map_err(|e| ConversionError(format!("Invalid network '{network_str}': {e}")))?;
9588

9689
let dust_str = response
9790
.dust
9891
.ok_or_else(|| ConversionError("Missing dust".to_string()))?;
9992
let dust = dust_str
10093
.parse::<u64>()
101-
.map_err(|e| ConversionError(format!("Invalid dust '{}': {}", dust_str, e)))
94+
.map_err(|e| ConversionError(format!("Invalid dust '{dust_str}': {e}")))
10295
.map(Amount::from_sat)?;
10396

10497
let forfeit_address_str = response
@@ -108,15 +101,13 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
108101
.parse::<bitcoin::Address<bitcoin::address::NetworkUnchecked>>()
109102
.map_err(|e| {
110103
ConversionError(format!(
111-
"Invalid forfeit_address '{}': {}",
112-
forfeit_address_str, e
104+
"Invalid forfeit_address '{forfeit_address_str}': {e}",
113105
))
114106
})?
115107
.require_network(network)
116108
.map_err(|e| {
117109
ConversionError(format!(
118-
"Address network mismatch for '{}': {}",
119-
forfeit_address_str, e
110+
"Address network mismatch for '{forfeit_address_str}': {e}",
120111
))
121112
})?;
122113

@@ -126,9 +117,9 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
126117

127118
let utxo_min_amount = match response.utxo_min_amount {
128119
Some(s) => {
129-
let val = s.parse::<i64>().map_err(|e| {
130-
ConversionError(format!("Invalid utxo_min_amount '{}': {}", s, e))
131-
})?;
120+
let val = s
121+
.parse::<i64>()
122+
.map_err(|e| ConversionError(format!("Invalid utxo_min_amount '{s}': {e}")))?;
132123
if val < 0 {
133124
None
134125
} else {
@@ -140,9 +131,9 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
140131

141132
let utxo_max_amount = match response.utxo_max_amount {
142133
Some(s) => {
143-
let val = s.parse::<i64>().map_err(|e| {
144-
ConversionError(format!("Invalid utxo_max_amount '{}': {}", s, e))
145-
})?;
134+
let val = s
135+
.parse::<i64>()
136+
.map_err(|e| ConversionError(format!("Invalid utxo_max_amount '{s}': {e}")))?;
146137
if val < 0 {
147138
None
148139
} else {
@@ -154,9 +145,9 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
154145

155146
let vtxo_min_amount = match response.vtxo_min_amount {
156147
Some(s) => {
157-
let val = s.parse::<i64>().map_err(|e| {
158-
ConversionError(format!("Invalid vtxo_min_amount '{}': {}", s, e))
159-
})?;
148+
let val = s
149+
.parse::<i64>()
150+
.map_err(|e| ConversionError(format!("Invalid vtxo_min_amount '{s}': {e}")))?;
160151
if val < 0 {
161152
None
162153
} else {
@@ -168,9 +159,9 @@ impl TryFrom<V1GetInfoResponse> for ark_core::server::Info {
168159

169160
let vtxo_max_amount = match response.vtxo_max_amount {
170161
Some(s) => {
171-
let val = s.parse::<i64>().map_err(|e| {
172-
ConversionError(format!("Invalid vtxo_max_amount '{}': {}", s, e))
173-
})?;
162+
let val = s
163+
.parse::<i64>()
164+
.map_err(|e| ConversionError(format!("Invalid vtxo_max_amount '{s}': {e}")))?;
174165
if val < 0 {
175166
None
176167
} else {
@@ -212,7 +203,7 @@ impl TryFrom<V1IndexerVtxo> for ark_core::server::VirtualTxOutPoint {
212203
.ok_or_else(|| ConversionError("Missing outpoint txid".to_string()))?;
213204
let txid = txid_str
214205
.parse::<Txid>()
215-
.map_err(|e| ConversionError(format!("Invalid outpoint txid '{}': {}", txid_str, e)))?;
206+
.map_err(|e| ConversionError(format!("Invalid outpoint txid '{txid_str}': {e}")))?;
216207

217208
let vout = outpoint_data
218209
.vout
@@ -225,40 +216,40 @@ impl TryFrom<V1IndexerVtxo> for ark_core::server::VirtualTxOutPoint {
225216
let created_at_str = value
226217
.created_at
227218
.ok_or_else(|| ConversionError("Missing created_at".to_string()))?;
228-
let created_at = created_at_str.parse::<i64>().map_err(|e| {
229-
ConversionError(format!("Invalid created_at '{}': {}", created_at_str, e))
230-
})?;
219+
let created_at = created_at_str
220+
.parse::<i64>()
221+
.map_err(|e| ConversionError(format!("Invalid created_at '{created_at_str}': {e}")))?;
231222

232223
let expires_at_str = value
233224
.expires_at
234225
.ok_or_else(|| ConversionError("Missing expires_at".to_string()))?;
235-
let expires_at = expires_at_str.parse::<i64>().map_err(|e| {
236-
ConversionError(format!("Invalid expires_at '{}': {}", expires_at_str, e))
237-
})?;
226+
let expires_at = expires_at_str
227+
.parse::<i64>()
228+
.map_err(|e| ConversionError(format!("Invalid expires_at '{expires_at_str}': {e}")))?;
238229

239230
// Parse amount
240231
let amount_str = value
241232
.amount
242233
.ok_or_else(|| ConversionError("Missing amount".to_string()))?;
243234
let amount_val = amount_str
244235
.parse::<u64>()
245-
.map_err(|e| ConversionError(format!("Invalid amount '{}': {}", amount_str, e)))?;
236+
.map_err(|e| ConversionError(format!("Invalid amount '{amount_str}': {e}")))?;
246237
let amount = Amount::from_sat(amount_val);
247238

248239
// Parse script
249240
let script_str = value
250241
.script
251242
.ok_or_else(|| ConversionError("Missing script".to_string()))?;
252243
let script = ScriptBuf::from_hex(&script_str)
253-
.map_err(|e| ConversionError(format!("Invalid script hex '{}': {}", script_str, e)))?;
244+
.map_err(|e| ConversionError(format!("Invalid script hex '{script_str}': {e}")))?;
254245

255246
// Parse optional spent_by
256247
let spent_by = value
257248
.spent_by
258249
.filter(|s| !s.is_empty())
259250
.map(|s| s.parse::<Txid>())
260251
.transpose()
261-
.map_err(|e| ConversionError(format!("Invalid spent_by txid: {}", e)))?;
252+
.map_err(|e| ConversionError(format!("Invalid spent_by txid: {e}")))?;
262253

263254
// Parse commitment_txids
264255
let commitment_txids = value
@@ -267,23 +258,23 @@ impl TryFrom<V1IndexerVtxo> for ark_core::server::VirtualTxOutPoint {
267258
.into_iter()
268259
.map(|s| s.parse::<Txid>())
269260
.collect::<Result<Vec<_>, _>>()
270-
.map_err(|e| ConversionError(format!("Invalid commitment_txid: {}", e)))?;
261+
.map_err(|e| ConversionError(format!("Invalid commitment_txid: {e}")))?;
271262

272263
// Parse optional settled_by
273264
let settled_by = value
274265
.settled_by
275266
.filter(|s| !s.is_empty())
276267
.map(|s| s.parse::<Txid>())
277268
.transpose()
278-
.map_err(|e| ConversionError(format!("Invalid settled_by txid: {}", e)))?;
269+
.map_err(|e| ConversionError(format!("Invalid settled_by txid: {e}")))?;
279270

280271
// Parse optional ark_txid
281272
let ark_txid = value
282273
.ark_txid
283274
.filter(|s| !s.is_empty())
284275
.map(|s| s.parse::<Txid>())
285276
.transpose()
286-
.map_err(|e| ConversionError(format!("Invalid ark_txid: {}", e)))?;
277+
.map_err(|e| ConversionError(format!("Invalid ark_txid: {e}")))?;
287278

288279
Ok(ark_core::server::VirtualTxOutPoint {
289280
outpoint,
@@ -331,23 +322,23 @@ impl TryFrom<V1GetSubscriptionResponse> for ark_core::server::SubscriptionRespon
331322
.txid
332323
.ok_or_else(|| ConversionError("Missing txid".to_string()))?
333324
.parse()
334-
.map_err(|e| ConversionError(format!("Invalid txid: {}", e)))?;
325+
.map_err(|e| ConversionError(format!("Invalid txid: {e}")))?;
335326

336327
let new_vtxos = value
337328
.new_vtxos
338329
.unwrap_or_default()
339330
.into_iter()
340331
.map(ark_core::server::VirtualTxOutPoint::try_from)
341332
.collect::<Result<Vec<_>, _>>()
342-
.map_err(|e| ConversionError(format!("Invalid new_vtxos: {}", e)))?;
333+
.map_err(|e| ConversionError(format!("Invalid new_vtxos: {e}")))?;
343334

344335
let spent_vtxos = value
345336
.spent_vtxos
346337
.unwrap_or_default()
347338
.into_iter()
348339
.map(ark_core::server::VirtualTxOutPoint::try_from)
349340
.collect::<Result<Vec<_>, _>>()
350-
.map_err(|e| ConversionError(format!("Invalid spent_vtxos: {}", e)))?;
341+
.map_err(|e| ConversionError(format!("Invalid spent_vtxos: {e}")))?;
351342

352343
let tx = if let Some(tx_str) = value.tx.filter(|s| !s.is_empty()) {
353344
let base64 = base64::engine::GeneralPurpose::new(
@@ -356,10 +347,10 @@ impl TryFrom<V1GetSubscriptionResponse> for ark_core::server::SubscriptionRespon
356347
);
357348
let bytes = base64
358349
.decode(&tx_str)
359-
.map_err(|e| ConversionError(format!("Invalid tx base64: {}", e)))?;
350+
.map_err(|e| ConversionError(format!("Invalid tx base64: {e}")))?;
360351
Some(
361352
Psbt::deserialize(&bytes)
362-
.map_err(|e| ConversionError(format!("Invalid tx psbt: {}", e)))?,
353+
.map_err(|e| ConversionError(format!("Invalid tx psbt: {e}")))?,
363354
)
364355
} else {
365356
None
@@ -371,12 +362,12 @@ impl TryFrom<V1GetSubscriptionResponse> for ark_core::server::SubscriptionRespon
371362
.into_iter()
372363
.map(|(k, v)| {
373364
let out_point = OutPoint::from_str(&k)
374-
.map_err(|e| ConversionError(format!("Invalid checkpoint outpoint: {}", e)))?;
365+
.map_err(|e| ConversionError(format!("Invalid checkpoint outpoint: {e}")))?;
375366
let txid = v
376367
.txid
377368
.ok_or_else(|| ConversionError("Missing checkpoint txid".to_string()))?
378369
.parse()
379-
.map_err(|e| ConversionError(format!("Invalid checkpoint txid: {}", e)))?;
370+
.map_err(|e| ConversionError(format!("Invalid checkpoint txid: {e}")))?;
380371
Ok((out_point, txid))
381372
})
382373
.collect::<Result<HashMap<_, _>, ConversionError>>()?;
@@ -387,7 +378,7 @@ impl TryFrom<V1GetSubscriptionResponse> for ark_core::server::SubscriptionRespon
387378
.iter()
388379
.map(|h| {
389380
ScriptBuf::from_hex(h)
390-
.map_err(|e| ConversionError(format!("Invalid script hex: {}", e)))
381+
.map_err(|e| ConversionError(format!("Invalid script hex: {e}")))
391382
})
392383
.collect::<Result<Vec<_>, _>>()?;
393384

0 commit comments

Comments
 (0)