Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: error handling with retries when waiting for receipt #9650

Merged
merged 3 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading