@@ -336,143 +336,179 @@ impl ProofAggregator {
336336
337337 let max_retries = self . config . max_bump_retries ;
338338 let retry_interval = Duration :: from_secs ( self . config . bump_retry_interval_seconds ) ;
339- let base_bump_percentage = self . config . base_bump_percentage ; // e.g., 10
340- let retry_attempt_percentage = self . config . retry_attempt_percentage ; // e.g., 5
339+ let base_bump_percentage = self . config . base_bump_percentage ;
340+ let retry_attempt_percentage = self . config . retry_attempt_percentage ;
341341
342- for attempt in 0 ..max_retries {
343- let mut tx_req = match aggregated_proof {
344- AlignedProof :: SP1 ( proof) => self
345- . proof_aggregation_service
346- . verifyAggregationSP1 (
347- blob_versioned_hash. into ( ) ,
348- proof. proof_with_pub_values . public_values . to_vec ( ) . into ( ) ,
349- proof. proof_with_pub_values . bytes ( ) . into ( ) ,
350- self . sp1_chunk_aggregator_vk_hash_bytes . into ( ) ,
351- )
352- . sidecar ( blob. clone ( ) )
353- . into_transaction_request ( ) ,
354- AlignedProof :: Risc0 ( proof) => {
355- let encoded_seal = encode_seal ( & proof. receipt )
356- . map_err ( |e| {
357- AggregatedProofSubmissionError :: Risc0EncodingSeal ( e. to_string ( ) )
358- } )
359- . map_err ( RetryError :: Permanent ) ?;
360- self . proof_aggregation_service
361- . verifyAggregationRisc0 (
362- blob_versioned_hash. into ( ) ,
363- encoded_seal. into ( ) ,
364- proof. receipt . journal . bytes . clone ( ) . into ( ) ,
365- self . risc0_chunk_aggregator_image_id_bytes . into ( ) ,
366- )
367- . sidecar ( blob. clone ( ) )
368- . into_transaction_request ( )
369- }
370- } ;
371-
372- // Increase gas price/fees for retries before filling
373- if attempt > 0 {
374- tx_req = self
375- . update_gas_fees (
376- base_bump_percentage,
377- retry_attempt_percentage,
378- attempt as u64 ,
379- tx_req,
380- )
381- . await ?;
382- }
383-
384- let provider = self . proof_aggregation_service . provider ( ) ;
342+ let mut last_error: Option < AggregatedProofSubmissionError > = None ;
385343
386- let envelope = provider
387- . fill ( tx_req)
388- . await
389- . map_err ( |err| {
390- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
391- err. to_string ( ) ,
392- )
393- } )
394- . map_err ( RetryError :: Transient ) ?
395- . try_into_envelope ( )
396- . map_err ( |err| {
397- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
398- err. to_string ( ) ,
399- )
400- } )
401- . map_err ( RetryError :: Transient ) ?;
402-
403- let tx: EthereumTxEnvelope < TxEip4844WithSidecar < BlobTransactionSidecarEip7594 > > =
404- envelope
405- . try_into_pooled ( )
406- . map_err ( |err| {
407- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
408- err. to_string ( ) ,
409- )
410- } )
411- . map_err ( RetryError :: Transient ) ?
412- . try_map_eip4844 ( |tx| {
413- tx. try_map_sidecar ( |sidecar| {
414- sidecar. try_into_7594 ( EnvKzgSettings :: Default . get ( ) )
415- } )
416- } )
417- . map_err ( |err| {
418- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
419- err. to_string ( ) ,
420- )
421- } )
422- . map_err ( RetryError :: Transient ) ?;
423-
424- let encoded_tx = tx. encoded_2718 ( ) ;
425- let pending_tx = provider
426- . send_raw_transaction ( & encoded_tx)
427- . await
428- . map_err ( |err| {
429- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
430- err. to_string ( ) ,
431- )
432- } )
433- . map_err ( RetryError :: Transient ) ?;
344+ for attempt in 0 ..max_retries {
345+ info ! ( "Transaction attempt {} of {}" , attempt + 1 , max_retries) ;
434346
435- // Wait for the receipt with timeout
436- let receipt_result =
437- tokio:: time:: timeout ( retry_interval, pending_tx. get_receipt ( ) ) . await ;
347+ // Wrap the entire transaction submission in a result to catch all errors
348+ let attempt_result = self
349+ . try_submit_transaction (
350+ & blob,
351+ blob_versioned_hash,
352+ aggregated_proof,
353+ base_bump_percentage,
354+ retry_attempt_percentage,
355+ attempt as u64 ,
356+ retry_interval,
357+ )
358+ . await ;
438359
439- match receipt_result {
440- Ok ( Ok ( receipt) ) => {
360+ match attempt_result {
361+ Ok ( receipt) => {
441362 info ! (
442363 "Transaction confirmed successfully on attempt {}" ,
443364 attempt + 1
444365 ) ;
445366 return Ok ( receipt) ;
446367 }
447- Ok ( Err ( err) ) => {
448- warn ! ( "Error getting receipt on attempt {}: {}" , attempt + 1 , err) ;
449- if attempt == max_retries - 1 {
450- return Err ( RetryError :: Transient (
451- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
452- err. to_string ( ) ,
453- ) ,
454- ) ) ;
455- }
456- }
457- Err ( _) => {
458- warn ! ( "Transaction not confirmed after {} seconds on attempt {}, retrying with higher fee..." ,
459- retry_interval. as_secs( ) , attempt + 1 ) ;
460- if attempt == max_retries - 1 {
461- return Err ( RetryError :: Transient (
462- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
463- "Transaction timeout after all retries" . to_string ( ) ,
464- ) ,
465- ) ) ;
368+ Err ( err) => {
369+ warn ! ( "Attempt {} failed: {:?}" , attempt + 1 , err) ;
370+ last_error = Some ( err) ;
371+
372+ if attempt < max_retries - 1 {
373+ info ! ( "Retrying with bumped gas fees..." ) ;
374+
375+ tokio:: time:: sleep ( Duration :: from_millis ( 500 ) ) . await ;
376+ } else {
377+ warn ! ( "Max retries ({}) exceeded" , max_retries) ;
466378 }
467379 }
468380 }
469381 }
470382
471- Err ( RetryError :: Transient (
383+ // If we exhausted all retries, return the last error
384+ Err ( RetryError :: Transient ( last_error. unwrap_or_else ( || {
472385 AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
473- "Max retries exceeded" . to_string ( ) ,
386+ "Max retries exceeded with no error details" . to_string ( ) ,
387+ )
388+ } ) ) )
389+ }
390+
391+ async fn try_submit_transaction (
392+ & self ,
393+ blob : & BlobTransactionSidecar ,
394+ blob_versioned_hash : [ u8 ; 32 ] ,
395+ aggregated_proof : & AlignedProof ,
396+ base_bump_percentage : u64 ,
397+ retry_attempt_percentage : u64 ,
398+ attempt : u64 ,
399+ retry_interval : Duration ,
400+ ) -> Result < TransactionReceipt , AggregatedProofSubmissionError > {
401+ // Build the transaction request
402+ let mut tx_req = match aggregated_proof {
403+ AlignedProof :: SP1 ( proof) => self
404+ . proof_aggregation_service
405+ . verifyAggregationSP1 (
406+ blob_versioned_hash. into ( ) ,
407+ proof. proof_with_pub_values . public_values . to_vec ( ) . into ( ) ,
408+ proof. proof_with_pub_values . bytes ( ) . into ( ) ,
409+ self . sp1_chunk_aggregator_vk_hash_bytes . into ( ) ,
410+ )
411+ . sidecar ( blob. clone ( ) )
412+ . into_transaction_request ( ) ,
413+ AlignedProof :: Risc0 ( proof) => {
414+ let encoded_seal = encode_seal ( & proof. receipt ) . map_err ( |e| {
415+ AggregatedProofSubmissionError :: Risc0EncodingSeal ( e. to_string ( ) )
416+ } ) ?;
417+ self . proof_aggregation_service
418+ . verifyAggregationRisc0 (
419+ blob_versioned_hash. into ( ) ,
420+ encoded_seal. into ( ) ,
421+ proof. receipt . journal . bytes . clone ( ) . into ( ) ,
422+ self . risc0_chunk_aggregator_image_id_bytes . into ( ) ,
423+ )
424+ . sidecar ( blob. clone ( ) )
425+ . into_transaction_request ( )
426+ }
427+ } ;
428+
429+ // Apply gas fee bump for retries
430+ if attempt > 0 {
431+ tx_req = self
432+ . apply_gas_fee_bump (
433+ base_bump_percentage,
434+ retry_attempt_percentage,
435+ attempt,
436+ tx_req,
437+ )
438+ . await ?;
439+ }
440+
441+ let provider = self . proof_aggregation_service . provider ( ) ;
442+
443+ // Fill the transaction
444+ let envelope = provider
445+ . fill ( tx_req)
446+ . await
447+ . map_err ( |err| {
448+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
449+ "Failed to fill transaction: {}" ,
450+ err
451+ ) )
452+ } ) ?
453+ . try_into_envelope ( )
454+ . map_err ( |err| {
455+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
456+ "Failed to convert to envelope: {}" ,
457+ err
458+ ) )
459+ } ) ?;
460+
461+ // Convert to EIP-4844 transaction
462+ let tx: EthereumTxEnvelope < TxEip4844WithSidecar < BlobTransactionSidecarEip7594 > > = envelope
463+ . try_into_pooled ( )
464+ . map_err ( |err| {
465+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
466+ "Failed to pool transaction: {}" ,
467+ err
468+ ) )
469+ } ) ?
470+ . try_map_eip4844 ( |tx| {
471+ tx. try_map_sidecar ( |sidecar| sidecar. try_into_7594 ( EnvKzgSettings :: Default . get ( ) ) )
472+ } )
473+ . map_err ( |err| {
474+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
475+ "Failed to convert to EIP-7594: {}" ,
476+ err
477+ ) )
478+ } ) ?;
479+
480+ // Send the transaction
481+ let encoded_tx = tx. encoded_2718 ( ) ;
482+ let pending_tx = provider
483+ . send_raw_transaction ( & encoded_tx)
484+ . await
485+ . map_err ( |err| {
486+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
487+ "Failed to send raw transaction: {}" ,
488+ err
489+ ) )
490+ } ) ?;
491+
492+ info ! ( "Transaction sent, waiting for confirmation..." ) ;
493+
494+ // Wait for the receipt with timeout
495+ let receipt_result = tokio:: time:: timeout ( retry_interval, pending_tx. get_receipt ( ) ) . await ;
496+
497+ match receipt_result {
498+ Ok ( Ok ( receipt) ) => Ok ( receipt) ,
499+ Ok ( Err ( err) ) => Err (
500+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
501+ "Error getting receipt: {}" ,
502+ err
503+ ) ) ,
504+ ) ,
505+ Err ( _) => Err (
506+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
507+ "Transaction timeout after {} seconds" ,
508+ retry_interval. as_secs( )
509+ ) ) ,
474510 ) ,
475- ) )
511+ }
476512 }
477513
478514 // Updates the gas fees of a `TransactionRequest` for retry attempts by applying a linear fee
@@ -487,30 +523,36 @@ impl ProofAggregator {
487523 // - `attempt = 3` → 10% + (3 * 5%) = 25% bump
488524 //
489525 // The bumped price is: current_gas_price * (1 + total_bump_percentage / 100)
490- async fn update_gas_fees (
526+ async fn apply_gas_fee_bump (
491527 & self ,
492528 base_bump_percentage : u64 ,
493529 retry_attempt_percentage : u64 ,
494530 attempt : u64 ,
495531 tx_req : alloy:: rpc:: types:: TransactionRequest ,
496- ) -> Result < alloy:: rpc:: types:: TransactionRequest , RetryError < AggregatedProofSubmissionError > >
497- {
532+ ) -> Result < alloy:: rpc:: types:: TransactionRequest , AggregatedProofSubmissionError > {
498533 let provider = self . proof_aggregation_service . provider ( ) ;
499534
500535 // Calculate total bump percentage: base + (retry_count * retry_attempt)
501536 let incremental_retry_percentage = retry_attempt_percentage * attempt;
502537 let total_bump_percentage = base_bump_percentage + incremental_retry_percentage;
503538
539+ info ! (
540+ "Applying {}% gas fee bump for attempt {}" ,
541+ total_bump_percentage,
542+ attempt + 1
543+ ) ;
544+
504545 let mut current_tx_req = tx_req. clone ( ) ;
505546
506547 if current_tx_req. max_fee_per_gas . is_none ( ) {
507- let current_gas_price = provider. get_gas_price ( ) . await . map_err ( |e| {
508- RetryError :: Transient ( AggregatedProofSubmissionError :: GasPriceError ( e. to_string ( ) ) )
509- } ) ?;
548+ let current_gas_price = provider
549+ . get_gas_price ( )
550+ . await
551+ . map_err ( |e| AggregatedProofSubmissionError :: GasPriceError ( e. to_string ( ) ) ) ?;
510552
511553 let new_max_fee =
512554 Self :: calculate_bumped_price ( current_gas_price, total_bump_percentage) ;
513- let new_priority_fee = new_max_fee / 10 ; // 10% of max fee
555+ let new_priority_fee = new_max_fee / 10 ;
514556
515557 current_tx_req = current_tx_req
516558 . with_max_fee_per_gas ( new_max_fee)
0 commit comments