Skip to content

Commit 380b0df

Browse files
authored
fix: handle empty receipt when transactions are discarded by RPCs (#10457)
* Update progress.rs * Update receipts.rs * Update receipts.rs * Update progress.rs * Update receipts.rs * Update progress.rs * Update receipts.rs * Update progress.rs * Update Cargo.toml * Update progress.rs * Update coverage.rs * Update vanity.rs * Update progress.rs * Update receipts.rs
1 parent 047d28d commit 380b0df

File tree

5 files changed

+73
-14
lines changed

5 files changed

+73
-14
lines changed

crates/cast/src/cmd/wallet/vanity.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ impl VanityArgs {
149149
"Successfully found vanity address in {:.3} seconds.{}{}\nAddress: {}\nPrivate Key: 0x{}",
150150
timer.elapsed().as_secs_f64(),
151151
if nonce.is_some() { "\nContract address: " } else { "" },
152-
if let Some(nonce) = nonce {
153-
wallet.address().create(nonce).to_checksum(None)
152+
if let Some(nonce_val) = nonce {
153+
wallet.address().create(nonce_val).to_checksum(None)
154154
} else {
155155
String::new()
156156
},

crates/forge/src/coverage.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ impl CoverageReporter for LcovReporter {
166166
}
167167
}
168168
CoverageItemKind::Branch { branch_id, path_id, .. } => {
169-
let hits = if hits == 0 { "-" } else { &hits.to_string() };
170-
writeln!(out, "BRDA:{line},{branch_id},{path_id},{hits}")?;
169+
let hits_str = if hits == 0 { "-" } else { &hits.to_string() };
170+
writeln!(out, "BRDA:{line},{branch_id},{path_id},{hits_str}")?;
171171
}
172172
}
173173
}

crates/script/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ alloy-dyn-abi.workspace = true
5454
alloy-primitives.workspace = true
5555
alloy-eips.workspace = true
5656
alloy-consensus.workspace = true
57+
thiserror.workspace = true
5758

5859
[dev-dependencies]
5960
tempfile.workspace = true

crates/script/src/progress.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::receipts::{TxStatus, check_tx_status, format_receipt};
1+
use crate::receipts::{PendingReceiptError, TxStatus, check_tx_status, format_receipt};
22
use alloy_chains::Chain;
33
use alloy_primitives::{
44
B256,
@@ -207,20 +207,37 @@ impl ScriptProgress {
207207
let mut tasks = futures::stream::iter(futs).buffer_unordered(10);
208208

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

211212
while let Some((tx_hash, result)) = tasks.next().await {
212213
match result {
213214
Err(err) => {
214-
errors.push(format!("Failure on receiving a receipt for {tx_hash:?}:\n{err}"));
215-
216-
seq_progress.inner.write().finish_tx_spinner(tx_hash);
215+
// Check if this is a retry error for pending receipts
216+
if err.downcast_ref::<PendingReceiptError>().is_some() {
217+
// We've already retried several times with sleep, but the receipt is still
218+
// pending
219+
discarded_transactions = true;
220+
deployment_sequence.remove_pending(tx_hash);
221+
seq_progress
222+
.inner
223+
.write()
224+
.finish_tx_spinner_with_msg(tx_hash, &err.to_string())?;
225+
} else {
226+
errors.push(format!(
227+
"Failure on receiving a receipt for {tx_hash:?}:\n{err}"
228+
));
229+
seq_progress.inner.write().finish_tx_spinner(tx_hash);
230+
}
217231
}
218232
Ok(TxStatus::Dropped) => {
219233
// We want to remove it from pending so it will be re-broadcast.
220234
deployment_sequence.remove_pending(tx_hash);
221-
errors.push(format!("Transaction dropped from the mempool: {tx_hash:?}"));
235+
discarded_transactions = true;
222236

223-
seq_progress.inner.write().finish_tx_spinner(tx_hash);
237+
let msg = format!(
238+
"Transaction {tx_hash:?} dropped from the mempool. It will be retried when using --resume."
239+
);
240+
seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?;
224241
}
225242
Ok(TxStatus::Success(receipt)) => {
226243
trace!(tx_hash=?tx_hash, "received tx receipt");
@@ -249,11 +266,20 @@ impl ScriptProgress {
249266
// print any errors
250267
if !errors.is_empty() {
251268
let mut error_msg = errors.join("\n");
252-
if !deployment_sequence.pending.is_empty() {
253-
error_msg += "\n\n Add `--resume` to your command to try and continue broadcasting
254-
the transactions."
269+
270+
// Add information about using --resume if necessary
271+
if !deployment_sequence.pending.is_empty() || discarded_transactions {
272+
error_msg += r#"
273+
274+
Add `--resume` to your command to try and continue broadcasting the transactions. This will attempt to resend transactions that were discarded by the RPC."#;
255275
}
276+
256277
eyre::bail!(error_msg);
278+
} else if discarded_transactions {
279+
// If we have discarded transactions but no errors, still inform the user
280+
sh_warn!(
281+
"Some transactions were discarded by the RPC node. Use `--resume` to retry these transactions."
282+
)?;
257283
}
258284

259285
Ok(())

crates/script/src/receipts.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ use eyre::{Result, eyre};
66
use foundry_common::{provider::RetryProvider, retry, retry::RetryError, shell};
77
use std::time::Duration;
88

9+
/// Marker error type for pending receipts
10+
#[derive(Debug, thiserror::Error)]
11+
#[error(
12+
"Received a pending receipt for {tx_hash}, but transaction is still known to the node, retrying"
13+
)]
14+
pub struct PendingReceiptError {
15+
pub tx_hash: TxHash,
16+
}
17+
918
/// Convenience enum for internal signalling of transaction status
1019
pub enum TxStatus {
1120
Dropped,
@@ -37,7 +46,30 @@ pub async fn check_tx_status(
3746
.get_receipt()
3847
.await
3948
{
40-
Ok(receipt) => Ok(receipt.into()),
49+
Ok(receipt) => {
50+
// Check if the receipt is pending (missing block information)
51+
let is_pending = receipt.block_number.is_none()
52+
|| receipt.block_hash.is_none()
53+
|| receipt.transaction_index.is_none();
54+
55+
if !is_pending {
56+
return Ok(receipt.into());
57+
}
58+
59+
// Receipt is pending, try to sleep and retry a few times
60+
match provider.get_transaction_by_hash(hash).await {
61+
Ok(_) => {
62+
// Sleep for a short time to allow the transaction to be mined
63+
tokio::time::sleep(Duration::from_millis(500)).await;
64+
// Transaction is still known to the node, retry
65+
Err(RetryError::Retry(PendingReceiptError { tx_hash: hash }.into()))
66+
}
67+
Err(_) => {
68+
// Transaction is not known to the node, mark it as dropped
69+
Ok(TxStatus::Dropped)
70+
}
71+
}
72+
}
4173
Err(e) => match provider.get_transaction_by_hash(hash).await {
4274
Ok(_) => match e {
4375
PendingTransactionError::TxWatcher(WatchTxError::Timeout) => {

0 commit comments

Comments
 (0)