Skip to content

Commit

Permalink
fix: error handling with retries when waiting for receipt (#9650)
Browse files Browse the repository at this point in the history
* fix: error handling with retries when waiting for receipt

* Add RetryError::Continue variant, rework receipts tx check
  • Loading branch information
grandizzy authored Jan 13, 2025
1 parent be34f5b commit b6c094c
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 29 deletions.
12 changes: 12 additions & 0 deletions crates/common/src/retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::{future::Future, time::Duration};
/// Error type for Retry.
#[derive(Debug, thiserror::Error)]
pub enum RetryError<E = Report> {
/// Continues operation without decrementing retries.
Continue(E),
/// Keeps retrying operation.
Retry(E),
/// Stops retrying operation immediately.
Expand Down Expand Up @@ -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() {
Expand All @@ -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),
Expand Down
47 changes: 18 additions & 29 deletions crates/script/src/receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,39 +30,28 @@ pub async fn check_tx_status(
hash: TxHash,
timeout: u64,
) -> (TxHash, Result<TxStatus, eyre::Report>) {
// 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)
}
Expand Down

0 comments on commit b6c094c

Please sign in to comment.