Skip to content

fix: handle empty receipt when transactions are discarded by RPCs #10457

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
35 changes: 26 additions & 9 deletions crates/script/src/progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,20 +207,30 @@ impl ScriptProgress {
let mut tasks = futures::stream::iter(futs).buffer_unordered(10);

let mut errors: Vec<String> = vec![];
let mut discarded_transactions = false;

while let Some((tx_hash, result)) = tasks.next().await {
match result {
Err(err) => {
errors.push(format!("Failure on receiving a receipt for {tx_hash:?}:\n{err}"));

seq_progress.inner.write().finish_tx_spinner(tx_hash);
// Check if this is a retry error for pending receipts
if err.to_string().contains("Received a pending receipt") {
// We've already retried several times with sleep, but the receipt is still pending
discarded_transactions = true;
deployment_sequence.remove_pending(tx_hash);
let msg = format!("Transaction {tx_hash:?} was discarded by RPC despite multiple retry attempts. It will be retried when using --resume.");
seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?;
} else {
errors.push(format!("Failure on receiving a receipt for {tx_hash:?}:\n{err}"));
seq_progress.inner.write().finish_tx_spinner(tx_hash);
}
}
Ok(TxStatus::Dropped) => {
// We want to remove it from pending so it will be re-broadcast.
deployment_sequence.remove_pending(tx_hash);
errors.push(format!("Transaction dropped from the mempool: {tx_hash:?}"));

seq_progress.inner.write().finish_tx_spinner(tx_hash);
discarded_transactions = true;

let msg = format!("Transaction {tx_hash:?} dropped from the mempool. It will be retried when using --resume.");
seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?;
}
Ok(TxStatus::Success(receipt)) => {
trace!(tx_hash=?tx_hash, "received tx receipt");
Expand Down Expand Up @@ -249,11 +259,18 @@ impl ScriptProgress {
// print any errors
if !errors.is_empty() {
let mut error_msg = errors.join("\n");
if !deployment_sequence.pending.is_empty() {
error_msg += "\n\n Add `--resume` to your command to try and continue broadcasting
the transactions."

// Add information about using --resume if necessary
if !deployment_sequence.pending.is_empty() || discarded_transactions {
error_msg += r#"

Add `--resume` to your command to try and continue broadcasting the transactions. This will attempt to resend transactions that were discarded by the RPC."#;
}

eyre::bail!(error_msg);
} else if discarded_transactions {
// If we have discarded transactions but no errors, still inform the user
sh_warn!("Some transactions were discarded by the RPC node. Use `--resume` to retry these transactions.")?;
}

Ok(())
Expand Down
25 changes: 24 additions & 1 deletion crates/script/src/receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,30 @@ pub async fn check_tx_status(
.get_receipt()
.await
{
Ok(receipt) => Ok(receipt.into()),
Ok(receipt) => {
// Check if the receipt is pending (missing block information)
let is_pending = receipt.block_number.is_none() || receipt.block_hash.is_none() || receipt.transaction_index.is_none();

if !is_pending {
return Ok(receipt.into());
}

// Receipt is pending, try to sleep and retry a few times
match provider.get_transaction_by_hash(hash).await {
Ok(_) => {
// Sleep for a short time to allow the transaction to be mined
tokio::time::sleep(Duration::from_millis(500)).await;
// Transaction is still known to the node, retry
Err(RetryError::Retry(eyre!(
"Received a pending receipt for {hash}, but transaction is still known to the node, retrying"
)))
}
Err(_) => {
// Transaction is not known to the node, mark it as dropped
Ok(TxStatus::Dropped)
}
}
}
Err(e) => match provider.get_transaction_by_hash(hash).await {
Ok(_) => match e {
PendingTransactionError::TxWatcher(WatchTxError::Timeout) => {
Expand Down