Skip to content

Commit 5c7ef3c

Browse files
authored
adjust file submission and retry logic (#403)
* adjust file submission and retry logic
1 parent 1e9bb67 commit 5c7ef3c

File tree

4 files changed

+51
-99
lines changed

4 files changed

+51
-99
lines changed

crates/dev-utils/examples/test_concurrent_calls.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async fn main() -> Result<()> {
7777
.build_mint_call(address, amount)
7878
.unwrap();
7979

80-
let tx = retry_call(mint_call, 5, Some(1), wallet_one.provider.clone(), None)
80+
let tx = retry_call(mint_call, 5, wallet_one.provider.clone(), None)
8181
.await
8282
.unwrap();
8383
println!("Transaction hash I: {:?}", tx);
@@ -90,7 +90,7 @@ async fn main() -> Result<()> {
9090
.ai_token
9191
.build_mint_call(address, amount)
9292
.unwrap();
93-
let tx = retry_call(mint_call_two, 5, None, wallet_two.provider.clone(), None)
93+
let tx = retry_call(mint_call_two, 5, wallet_two.provider.clone(), None)
9494
.await
9595
.unwrap();
9696
println!("Transaction hash II: {:?}", tx);

crates/shared/src/web3/contracts/helpers/utils.rs

Lines changed: 47 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
// This is the correct, minimal, and tested fix.
2+
13
use alloy::contract::CallDecoder;
24
use alloy::providers::Provider;
35
use alloy::{
46
contract::CallBuilder,
57
primitives::{keccak256, FixedBytes, Selector},
8+
providers::Network,
69
};
7-
use alloy_provider::Network;
810
use anyhow::Result;
911
use log::{debug, info, warn};
1012
use tokio::time::{timeout, Duration};
@@ -16,123 +18,73 @@ pub fn get_selector(fn_image: &str) -> Selector {
1618
}
1719

1820
pub type PrimeCallBuilder<'a, D> = alloy::contract::CallBuilder<&'a WalletProvider, D>;
21+
1922
pub async fn retry_call<P, D, N>(
2023
mut call: CallBuilder<&P, D, N>,
2124
max_tries: u32,
22-
initial_gas_price: Option<u128>,
2325
provider: P,
2426
retry_delay: Option<u64>,
2527
) -> Result<FixedBytes<32>>
2628
where
27-
P: Provider<N>,
29+
P: Provider<N> + Clone,
2830
N: Network,
29-
D: CallDecoder,
31+
D: CallDecoder + Clone,
3032
{
3133
let mut tries = 0;
32-
let gas_price = initial_gas_price;
33-
let mut gas_multiplier: Option<f64> = None;
34-
let retry_delay = retry_delay.unwrap_or(5);
35-
if let Some(price) = gas_price {
36-
info!("Setting gas price to: {:?}", price);
37-
call = call.gas_price(price);
38-
}
34+
let retry_delay = retry_delay.unwrap_or(2);
3935

4036
while tries < max_tries {
41-
let mut network_gas_price: Option<u128> = None;
4237
if tries > 0 {
4338
tokio::time::sleep(Duration::from_secs(retry_delay)).await;
44-
match provider.get_gas_price().await {
45-
Ok(gas_price) => {
46-
network_gas_price = Some(gas_price);
47-
}
48-
Err(err) => {
49-
warn!("Failed to get gas price from provider: {:?}", err);
50-
}
51-
}
52-
}
53-
if let (Some(multiplier), Some(nw_gas_price)) = (gas_multiplier, network_gas_price) {
54-
let new_gas = (multiplier * nw_gas_price as f64).round() as u128;
55-
if new_gas < nw_gas_price {
56-
return Err(anyhow::anyhow!("Gas price is too low"));
39+
40+
// On retry, always fetch fresh fee estimates from the provider.
41+
let priority_fee_res = provider.get_max_priority_fee_per_gas().await;
42+
let gas_price_res = provider.get_gas_price().await;
43+
44+
if let (Ok(priority_fee), Ok(gas_price)) = (priority_fee_res, gas_price_res) {
45+
// To replace a transaction, we need to bump both fees.
46+
// A common strategy is to increase by a percentage (e.g., 20%).
47+
let new_priority_fee = (priority_fee as f64 * 1.2).round() as u128;
48+
let new_gas_price = (gas_price as f64 * 1.2).round() as u128;
49+
50+
info!(
51+
"Retrying with bumped fees: max_fee={}, priority_fee={}",
52+
new_gas_price, new_priority_fee
53+
);
54+
55+
call = call
56+
.clone()
57+
.max_fee_per_gas(new_gas_price)
58+
.max_priority_fee_per_gas(new_priority_fee);
59+
} else {
60+
warn!("Could not get new gas fees, retrying with old settings.");
5761
}
58-
call = call.max_fee_per_gas(new_gas);
59-
call = call.gas_price(new_gas);
6062
}
61-
match call.send().await {
63+
64+
match call.clone().send().await {
6265
Ok(result) => {
63-
debug!("Transaction sent, waiting for confirmation");
64-
match timeout(Duration::from_secs(20), result.watch()).await {
65-
Ok(watch_result) => match watch_result {
66-
Ok(hash) => return Ok(hash),
67-
Err(err) => {
68-
tries += 1;
69-
if tries == max_tries {
70-
return Err(anyhow::anyhow!(
71-
"Transaction failed after {} attempts: {:?}",
72-
tries,
73-
err
74-
));
75-
}
76-
}
77-
},
78-
Err(_) => {
79-
warn!("Watch timed out, retrying transaction");
80-
tries += 1;
81-
if tries == max_tries {
82-
return Err(anyhow::anyhow!(
83-
"Max retries reached after watch timeouts"
84-
));
85-
}
86-
gas_multiplier = Some((110.0 + (tries as f64 * 10.0)) / 100.0);
87-
}
66+
debug!("Transaction sent, waiting for confirmation...");
67+
match timeout(Duration::from_secs(30), result.watch()).await {
68+
Ok(Ok(hash)) => return Ok(hash),
69+
Ok(Err(err)) => warn!("Transaction watch failed: {:?}", err),
70+
Err(_) => warn!("Watch timed out, retrying transaction..."),
8871
}
8972
}
9073
Err(err) => {
91-
warn!("Transaction failed: {:?}", err);
92-
93-
let err_str = err.to_string();
94-
let retryable_errors = [
95-
"replacement transaction underpriced",
96-
"nonce too low",
97-
"transaction underpriced",
98-
"insufficient funds for gas",
99-
"already known",
100-
"temporarily unavailable",
101-
"network congested",
102-
"gas price too low",
103-
"transaction pool full",
104-
"max fee per gas less than block base fee",
105-
];
106-
107-
if retryable_errors.iter().any(|e| err_str.contains(e)) {
108-
tries += 1;
109-
if tries == max_tries {
110-
return Err(anyhow::anyhow!(
111-
"Max retries reached after {} attempts. Last error: {:?}",
112-
tries,
113-
err
114-
));
115-
}
116-
117-
if err_str.contains("underpriced")
118-
|| err_str.contains("gas price too low")
119-
|| err_str.contains("max fee per gas less than block base fee")
120-
{
121-
gas_multiplier = Some((110.0 + (tries as f64 * 200.0)) / 100.0);
122-
}
123-
} else {
124-
return Err(anyhow::anyhow!(
125-
"Transaction failed with non-retryable error: {:?}",
126-
err
127-
));
74+
warn!("Transaction send failed: {:?}", err);
75+
let err_str = err.to_string().to_lowercase();
76+
if !err_str.contains("replacement transaction underpriced")
77+
&& !err_str.contains("nonce too low")
78+
&& !err_str.contains("transaction already imported")
79+
{
80+
return Err(anyhow::anyhow!("Non-retryable error: {:?}", err));
12881
}
12982
}
13083
}
84+
tries += 1;
13185
}
132-
13386
Err(anyhow::anyhow!("Max retries reached"))
13487
}
135-
13688
#[cfg(test)]
13789
mod tests {
13890

@@ -176,14 +128,14 @@ mod tests {
176128
let contract_clone_1 = contract.clone();
177129
let handle_1 = tokio::spawn(async move {
178130
let call = contract_clone_1.setNumber(U256::from(100));
179-
retry_call(call, 3, Some(1), provider_clone_1, None).await
131+
retry_call(call, 3, provider_clone_1, None).await
180132
});
181133

182134
let contract_clone_2 = contract.clone();
183135
let provider_clone_2 = provider.clone();
184136
let handle_2 = tokio::spawn(async move {
185137
let call = contract_clone_2.setNumber(U256::from(100));
186-
retry_call(call, 3, None, provider_clone_2, None).await
138+
retry_call(call, 3, provider_clone_2, None).await
187139
});
188140

189141
let tx_base = handle_1.await.unwrap();
@@ -213,7 +165,7 @@ mod tests {
213165
let _ = contract.increment().nonce(tx_count).send().await?;
214166

215167
let call_two = contract.increment().nonce(tx_count);
216-
let tx = retry_call(call_two, 3, None, provider_clone, Some(1)).await;
168+
let tx = retry_call(call_two, 3, provider_clone, Some(1)).await;
217169

218170
assert!(tx.is_ok());
219171
Ok(())

crates/worker/src/api/routes/invite.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub async fn invite_node(
101101
};
102102

103103
let provider = &app_state.provider_wallet.provider;
104-
match retry_call(call, 3, None, provider.clone(), None).await {
104+
match retry_call(call, 3, provider.clone(), None).await {
105105
Ok(result) => {
106106
Console::success(&format!("Successfully joined compute pool: {}", result));
107107
}

crates/worker/src/docker/taskbridge/file_handler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ pub async fn handle_file_validation(
357357
.await
358358
.unwrap();
359359

360-
let tx = retry_call(call, 5, None, provider.clone(), None)
360+
let tx = retry_call(call, 20, provider.clone(), None)
361361
.await
362362
.map_err(|e| anyhow::anyhow!("Failed to submit work: {}", e))?;
363363

0 commit comments

Comments
 (0)