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 2 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
34 changes: 25 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 the error is about an empty receipt
if err.to_string().contains("Received an empty receipt") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't quite empty, but pending, can we update the wording here?

discarded_transactions = true;
// Special handling for empty receipts - we'll mark these for retrying
deployment_sequence.remove_pending(tx_hash);
let msg = format!("Transaction {tx_hash:?} was discarded by RPC. 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,17 @@ 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 += "\n\n Add `--resume` to your command to try and continue broadcasting ";
error_msg += "the transactions. This will attempt to resend transactions that were discarded by the RPC.";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a single raw string here r#""#

}

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
22 changes: 21 additions & 1 deletion crates/script/src/receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,27 @@ pub async fn check_tx_status(
.get_receipt()
.await
{
Ok(receipt) => Ok(receipt.into()),
Ok(receipt) => {
// Check if the receipt has valid block information
if receipt.block_number.is_none() || receipt.block_hash.is_none() || receipt.transaction_index.is_none() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

receipt usually should only return mined transactions, but I can see that some networks might behave that way.

since these are mostly faster L2s, I wonder if the better approach here would be, just retry a few times here?

// Receipt is empty, possibly due to RPC discarding the transaction
match provider.get_transaction_by_hash(hash).await {
Ok(_) => {
// Transaction is still known to the node, so we should wait
Err(RetryError::Continue(eyre!(
"Received an empty receipt for {hash}, but transaction is still known to the node, waiting for full receipt"
)))
}
Err(_) => {
// Transaction is not known to the node, mark it as dropped
Ok(TxStatus::Dropped)
}
}
} else {
// Receipt has valid block information, proceed normally
Ok(receipt.into())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to lift this up, like

let is_pending = receipt.block_number.is_none() || receipt.block_hash.is_none() || receipt.transaction_index.is_none();

if !is_pnding {return Ok(receipt.into())}

}
}
Err(e) => match provider.get_transaction_by_hash(hash).await {
Ok(_) => match e {
PendingTransactionError::TxWatcher(WatchTxError::Timeout) => {
Expand Down
Loading