diff --git a/crates/common/src/retry.rs b/crates/common/src/retry.rs index b79e095ceb83..8673c2306e9a 100644 --- a/crates/common/src/retry.rs +++ b/crates/common/src/retry.rs @@ -6,6 +6,8 @@ use std::{future::Future, time::Duration}; /// Error type for Retry. #[derive(Debug, thiserror::Error)] pub enum RetryError { + /// Continues operation without decrementing retries. + Continue(E), /// Keeps retrying operation. Retry(E), /// Stops retrying operation immediately. @@ -74,6 +76,12 @@ impl Retry { { loop { match callback().await { + Err(RetryError::Continue(e)) => { + self.handle_continue(e); + if !self.delay.is_zero() { + tokio::time::sleep(self.delay).await; + } + } Err(RetryError::Retry(e)) if self.retries > 0 => { self.handle_err(e); if !self.delay.is_zero() { @@ -89,6 +97,10 @@ impl Retry { fn handle_err(&mut self, err: Error) { debug_assert!(self.retries > 0); self.retries -= 1; + self.handle_continue(err); + } + + fn handle_continue(&mut self, err: Error) { let _ = sh_warn!( "{msg}{delay} ({retries} tries remaining)", msg = crate::errors::display_chain(&err), diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index ee1c7b46c8c3..605cdf9ddb0d 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -2,8 +2,8 @@ use alloy_chains::Chain; use alloy_network::AnyTransactionReceipt; use alloy_primitives::{utils::format_units, TxHash, U256}; use alloy_provider::{PendingTransactionBuilder, PendingTransactionError, Provider, WatchTxError}; -use eyre::Result; -use foundry_common::{provider::RetryProvider, shell}; +use eyre::{eyre, Result}; +use foundry_common::{provider::RetryProvider, retry, retry::RetryError, shell}; use std::time::Duration; /// Convenience enum for internal signalling of transaction status @@ -30,39 +30,28 @@ pub async fn check_tx_status( hash: TxHash, timeout: u64, ) -> (TxHash, Result) { - // We use the inner future so that we can use ? operator in the future, but - // still neatly return the tuple - let result = async move { - // First check if there's a receipt - let receipt_opt = provider.get_transaction_receipt(hash).await?; - if let Some(receipt) = receipt_opt { - return Ok(receipt.into()); - } - - loop { + let result = retry::Retry::new_no_delay(3) + .run_async_until_break(|| async { match PendingTransactionBuilder::new(provider.clone(), hash) .with_timeout(Some(Duration::from_secs(timeout))) .get_receipt() .await { - Ok(receipt) => return Ok(receipt.into()), - // do nothing on timeout, we will check whether tx is dropped below - Err(PendingTransactionError::TxWatcher(WatchTxError::Timeout)) => {} - // treat other errors as fatal - Err(e) => return Err(e.into()), - } - - if provider.get_transaction_by_hash(hash).await?.is_some() { - trace!("tx is still known to the node, waiting for receipt"); - } else { - trace!("eth_getTransactionByHash returned null, assuming dropped"); - break + Ok(receipt) => Ok(receipt.into()), + Err(e) => match provider.get_transaction_by_hash(hash).await { + Ok(_) => match e { + PendingTransactionError::TxWatcher(WatchTxError::Timeout) => { + Err(RetryError::Continue(eyre!( + "tx is still known to the node, waiting for receipt" + ))) + } + _ => Err(RetryError::Retry(e.into())), + }, + Err(_) => Ok(TxStatus::Dropped), + }, } - } - - Ok(TxStatus::Dropped) - } - .await; + }) + .await; (hash, result) }