Skip to content

Commit ce697ca

Browse files
Save the tx hash if the tx is pending and check pending ones after all attempts
1 parent 72e586c commit ce697ca

1 file changed

Lines changed: 62 additions & 24 deletions

File tree

  • aggregation_mode/proof_aggregator/src/backend

aggregation_mode/proof_aggregator/src/backend/mod.rs

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use alloy::{
2323
eips::{eip4844::BYTES_PER_BLOB, eip7594::BlobTransactionSidecarEip7594, Encodable2718},
2424
hex,
2525
network::{EthereumWallet, TransactionBuilder},
26-
primitives::{utils::parse_ether, Address, U256},
26+
primitives::{utils::parse_ether, Address, TxHash, U256},
2727
providers::{PendingTransactionError, Provider, ProviderBuilder},
2828
rpc::types::{TransactionReceipt, TransactionRequest},
2929
signers::local::LocalSigner,
@@ -54,6 +54,11 @@ pub enum AggregatedProofSubmissionError {
5454
GasPriceError(String),
5555
}
5656

57+
enum SubmitOutcome {
58+
Confirmed(TransactionReceipt),
59+
Pending(TxHash),
60+
}
61+
5762
pub struct ProofAggregator {
5863
engine: ZKVMEngine,
5964
proof_aggregation_service: AlignedProofAggregationServiceContract,
@@ -342,6 +347,8 @@ impl ProofAggregator {
342347

343348
let mut last_error: Option<AggregatedProofSubmissionError> = None;
344349

350+
let mut pending_hashes: Vec<TxHash> = Vec::with_capacity(max_retries as usize);
351+
345352
// Get the nonce once at the beginning and reuse it for all retries
346353
let nonce = self
347354
.proof_aggregation_service
@@ -364,23 +371,35 @@ impl ProofAggregator {
364371
// Wrap the entire transaction submission in a result to catch all errors, passing
365372
// the same nonce to all attempts
366373
let attempt_result = self
367-
.try_submit_transaction(
368-
&blob,
369-
blob_versioned_hash,
370-
aggregated_proof,
371-
attempt as u64,
372-
nonce,
373-
)
374+
.try_submit_transaction(&blob, blob_versioned_hash, aggregated_proof, nonce)
374375
.await;
375376

376377
match attempt_result {
377-
Ok(receipt) => {
378+
Ok(SubmitOutcome::Confirmed(receipt)) => {
378379
info!(
379380
"Transaction confirmed successfully on attempt {}",
380381
attempt + 1
381382
);
382383
return Ok(receipt);
383384
}
385+
Ok(SubmitOutcome::Pending(tx_hash)) => {
386+
warn!(
387+
"Attempt {} timed out waiting for receipt; storing pending tx and continuing",
388+
attempt + 1
389+
);
390+
pending_hashes.push(tx_hash);
391+
392+
last_error = Some(
393+
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(
394+
"Timed out waiting for receipt".to_string(),
395+
),
396+
);
397+
398+
if attempt < max_retries - 1 {
399+
info!("Retrying with bumped gas fees and same nonce {}...", nonce);
400+
tokio::time::sleep(Duration::from_millis(500)).await;
401+
}
402+
}
384403
Err(err) => {
385404
warn!("Attempt {} failed: {:?}", attempt + 1, err);
386405
last_error = Some(err);
@@ -396,7 +415,36 @@ impl ProofAggregator {
396415
}
397416
}
398417

399-
// If we exhausted all retries, return the last error
418+
// After exhausting all retry attempts, we iterate over every pending transaction hash
419+
// that was previously submitted with the same nonce but different gas parameters.
420+
// One of these transactions may have been included in a block while we were still
421+
// retrying and waiting on others. By explicitly checking the receipt for each hash,
422+
// we ensure we don't "lose" a transaction that was actually mined but whose receipt
423+
// we never observed due to timeouts during earlier attempts.
424+
for (i, tx_hash) in pending_hashes.into_iter().enumerate() {
425+
match self
426+
.proof_aggregation_service
427+
.provider()
428+
.get_transaction_receipt(tx_hash)
429+
.await
430+
{
431+
Ok(Some(receipt)) => {
432+
info!("Pending tx #{} confirmed; returning receipt", i + 1);
433+
return Ok(receipt);
434+
}
435+
Ok(None) => {
436+
warn!(
437+
"Pending tx #{} still no receipt yet (hash {})",
438+
i + 1,
439+
tx_hash
440+
);
441+
}
442+
Err(err) => {
443+
warn!("Pending tx #{} receipt query failed: {:?}", i + 1, err);
444+
}
445+
}
446+
}
447+
400448
Err(RetryError::Transient(last_error.unwrap_or_else(|| {
401449
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(
402450
"Max retries exceeded with no error details".to_string(),
@@ -409,9 +457,8 @@ impl ProofAggregator {
409457
blob: &BlobTransactionSidecar,
410458
blob_versioned_hash: [u8; 32],
411459
aggregated_proof: &AlignedProof,
412-
_attempt: u64, // Check if this param is useful
413460
nonce: u64,
414-
) -> Result<TransactionReceipt, AggregatedProofSubmissionError> {
461+
) -> Result<SubmitOutcome, AggregatedProofSubmissionError> {
415462
let retry_interval = Duration::from_secs(self.config.bump_retry_interval_seconds);
416463
let base_bump_percentage = self.config.base_bump_percentage;
417464
let max_fee_bump_percentage = self.config.max_fee_bump_percentage;
@@ -504,27 +551,18 @@ impl ProofAggregator {
504551
))
505552
})?;
506553

507-
info!(
508-
"Transaction sent with nonce {}, waiting for confirmation...",
509-
nonce
510-
);
554+
let tx_hash = *pending_tx.tx_hash();
511555

512-
// Wait for the receipt with timeout
513556
let receipt_result = tokio::time::timeout(retry_interval, pending_tx.get_receipt()).await;
514557

515558
match receipt_result {
516-
Ok(Ok(receipt)) => Ok(receipt),
559+
Ok(Ok(receipt)) => Ok(SubmitOutcome::Confirmed(receipt)),
517560
Ok(Err(err)) => Err(
518561
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(format!(
519562
"Error getting receipt: {err}"
520563
)),
521564
),
522-
Err(_) => Err(
523-
AggregatedProofSubmissionError::SendVerifyAggregatedProofTransaction(format!(
524-
"Transaction timeout after {} seconds",
525-
retry_interval.as_secs()
526-
)),
527-
),
565+
Err(_) => Ok(SubmitOutcome::Pending(tx_hash)),
528566
}
529567
}
530568

0 commit comments

Comments
 (0)