@@ -20,15 +20,12 @@ use crate::{
2020
2121use alloy:: {
2222 consensus:: { BlobTransactionSidecar , EnvKzgSettings , EthereumTxEnvelope , TxEip4844WithSidecar } ,
23- eips:: {
24- eip4844:: BYTES_PER_BLOB , eip7594:: BlobTransactionSidecarEip7594 , BlockNumberOrTag ,
25- Encodable2718 ,
26- } ,
23+ eips:: { eip4844:: BYTES_PER_BLOB , eip7594:: BlobTransactionSidecarEip7594 , Encodable2718 } ,
2724 hex,
28- network:: { EthereumWallet , TransactionBuilder } ,
29- primitives:: { utils:: parse_ether, Address , TxHash , U256 } ,
25+ network:: EthereumWallet ,
26+ primitives:: { utils:: parse_ether, Address , U256 } ,
3027 providers:: { PendingTransactionError , Provider , ProviderBuilder } ,
31- rpc:: types:: { TransactionReceipt , TransactionRequest } ,
28+ rpc:: types:: TransactionReceipt ,
3229 signers:: local:: LocalSigner ,
3330} ;
3431use config:: Config ;
@@ -55,16 +52,6 @@ pub enum AggregatedProofSubmissionError {
5552 MerkleRootMisMatch ,
5653 StoringMerklePaths ( DbError ) ,
5754 GasPriceError ( String ) ,
58- LatestBlockNotFound ,
59- BaseFeePerGasMissing ,
60- }
61-
62- enum SubmitOutcome {
63- // NOTE: Boxed because enums are sized to their largest variant; without boxing,
64- // every `SubmitOutcome` would reserve space for a full `TransactionReceipt`,
65- // even in the `Pending` case (see clippy::large_enum_variant).
66- Confirmed ( Box < TransactionReceipt > ) ,
67- Pending ( TxHash ) ,
6855}
6956
7057pub struct ProofAggregator {
@@ -75,7 +62,6 @@ pub struct ProofAggregator {
7562 sp1_chunk_aggregator_vk_hash_bytes : [ u8 ; 32 ] ,
7663 risc0_chunk_aggregator_image_id_bytes : [ u8 ; 32 ] ,
7764 db : Db ,
78- signer_address : Address ,
7965}
8066
8167impl ProofAggregator {
@@ -86,9 +72,7 @@ impl ProofAggregator {
8672 config. ecdsa . private_key_store_password . clone ( ) ,
8773 )
8874 . expect ( "Keystore signer should be `cast wallet` compliant" ) ;
89- let wallet = EthereumWallet :: from ( signer. clone ( ) ) ;
90-
91- let signer_address = signer. address ( ) ;
75+ let wallet = EthereumWallet :: from ( signer) ;
9276
9377 // Check if the monthly budget is non-negative to avoid runtime errors later
9478 let _monthly_budget_in_wei = parse_ether ( & config. monthly_budget_eth . to_string ( ) )
@@ -133,7 +117,6 @@ impl ProofAggregator {
133117 sp1_chunk_aggregator_vk_hash_bytes,
134118 risc0_chunk_aggregator_image_id_bytes,
135119 db,
136- signer_address,
137120 }
138121 }
139122
@@ -351,98 +334,7 @@ impl ProofAggregator {
351334
352335 info ! ( "Sending proof to ProofAggregationService contract..." ) ;
353336
354- let max_retries = self . config . max_bump_retries ;
355-
356- let mut last_error: Option < AggregatedProofSubmissionError > = None ;
357-
358- let mut pending_hashes: Vec < TxHash > = Vec :: with_capacity ( max_retries as usize ) ;
359-
360- // Get the nonce once at the beginning and reuse it for all retries
361- let nonce = self
362- . proof_aggregation_service
363- . provider ( )
364- . get_transaction_count ( self . signer_address )
365- . await
366- . map_err ( |e| {
367- RetryError :: Transient (
368- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
369- "Failed to get nonce: {e}"
370- ) ) ,
371- )
372- } ) ?;
373-
374- info ! ( "Using nonce {}" , nonce) ;
375-
376- for attempt in 0 ..max_retries {
377- info ! ( "Transaction attempt {} of {}" , attempt + 1 , max_retries) ;
378-
379- // Wrap the entire transaction submission in a result to catch all errors, passing
380- // the same nonce to all attempts
381- let attempt_result = self
382- . try_submit_transaction (
383- & blob,
384- blob_versioned_hash,
385- aggregated_proof,
386- nonce,
387- attempt,
388- )
389- . await ;
390-
391- match attempt_result {
392- Ok ( SubmitOutcome :: Confirmed ( receipt) ) => {
393- info ! (
394- "Transaction confirmed successfully on attempt {}" ,
395- attempt + 1
396- ) ;
397- return Ok ( * receipt) ;
398- }
399- Ok ( SubmitOutcome :: Pending ( tx_hash) ) => {
400- warn ! (
401- "Attempt {} timed out waiting for receipt; storing pending tx" ,
402- attempt + 1
403- ) ;
404- pending_hashes. push ( tx_hash) ;
405- last_error = Some (
406- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
407- "Timed out waiting for receipt" . to_string ( ) ,
408- ) ,
409- ) ;
410- }
411- Err ( err) => {
412- warn ! ( "Attempt {} failed: {:?}" , attempt + 1 , err) ;
413- last_error = Some ( err) ;
414- }
415- }
416-
417- // Check if any pending tx was confirmed before retrying
418- if let Some ( receipt) = self . check_pending_txs_confirmed ( & pending_hashes) . await {
419- return Ok ( receipt) ;
420- }
421-
422- info ! ( "Retrying with bumped gas fees and same nonce {}..." , nonce) ;
423- tokio:: time:: sleep ( Duration :: from_millis ( 500 ) ) . await ;
424- }
425-
426- warn ! ( "Max retries ({}) exceeded" , max_retries) ;
427- Err ( RetryError :: Transient ( last_error. unwrap_or_else ( || {
428- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
429- "Max retries exceeded with no error details" . to_string ( ) ,
430- )
431- } ) ) )
432- }
433-
434- async fn try_submit_transaction (
435- & self ,
436- blob : & BlobTransactionSidecar ,
437- blob_versioned_hash : [ u8 ; 32 ] ,
438- aggregated_proof : & AlignedProof ,
439- nonce : u64 ,
440- attempt : u16 ,
441- ) -> Result < SubmitOutcome , AggregatedProofSubmissionError > {
442- let retry_interval = Duration :: from_secs ( self . config . bump_retry_interval_seconds ) ;
443-
444- // Build the transaction request
445- let mut tx_req = match aggregated_proof {
337+ let tx_req = match aggregated_proof {
446338 AlignedProof :: SP1 ( proof) => self
447339 . proof_aggregation_service
448340 . verifyAggregationSP1 (
@@ -451,170 +343,81 @@ impl ProofAggregator {
451343 proof. proof_with_pub_values . bytes ( ) . into ( ) ,
452344 self . sp1_chunk_aggregator_vk_hash_bytes . into ( ) ,
453345 )
454- . sidecar ( blob. clone ( ) )
346+ . sidecar ( blob)
455347 . into_transaction_request ( ) ,
456348 AlignedProof :: Risc0 ( proof) => {
457- let encoded_seal = encode_seal ( & proof. receipt ) . map_err ( |e| {
458- AggregatedProofSubmissionError :: Risc0EncodingSeal ( e. to_string ( ) )
459- } ) ?;
349+ let encoded_seal = encode_seal ( & proof. receipt )
350+ . map_err ( |e| AggregatedProofSubmissionError :: Risc0EncodingSeal ( e. to_string ( ) ) )
351+ . map_err ( RetryError :: Permanent ) ?;
460352 self . proof_aggregation_service
461353 . verifyAggregationRisc0 (
462354 blob_versioned_hash. into ( ) ,
463355 encoded_seal. into ( ) ,
464356 proof. receipt . journal . bytes . clone ( ) . into ( ) ,
465357 self . risc0_chunk_aggregator_image_id_bytes . into ( ) ,
466358 )
467- . sidecar ( blob. clone ( ) )
359+ . sidecar ( blob)
468360 . into_transaction_request ( )
469361 }
470362 } ;
471363
472- // Set the nonce explicitly
473- tx_req = tx_req. with_nonce ( nonce) ;
474-
475- // Apply gas fee bump for retries
476- tx_req = self . apply_gas_fee_bump ( tx_req, attempt) . await ?;
477-
478364 let provider = self . proof_aggregation_service . provider ( ) ;
479-
480- // Fill the transaction
481365 let envelope = provider
482366 . fill ( tx_req)
483367 . await
484368 . map_err ( |err| {
485- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
486- "Failed to fill transaction: {err}"
487- ) )
488- } ) ?
369+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
370+ err. to_string ( ) ,
371+ )
372+ } )
373+ . map_err ( RetryError :: Transient ) ?
489374 . try_into_envelope ( )
490375 . map_err ( |err| {
491- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
492- "Failed to convert to envelope: {err}"
493- ) )
494- } ) ?;
495-
496- // Convert to EIP-4844 transaction
376+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
377+ err. to_string ( ) ,
378+ )
379+ } )
380+ . map_err ( RetryError :: Transient ) ?;
497381 let tx: EthereumTxEnvelope < TxEip4844WithSidecar < BlobTransactionSidecarEip7594 > > = envelope
498382 . try_into_pooled ( )
499383 . map_err ( |err| {
500- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
501- "Failed to pool transaction: {err}"
502- ) )
503- } ) ?
384+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
385+ err. to_string ( ) ,
386+ )
387+ } )
388+ . map_err ( RetryError :: Transient ) ?
504389 . try_map_eip4844 ( |tx| {
505390 tx. try_map_sidecar ( |sidecar| sidecar. try_into_7594 ( EnvKzgSettings :: Default . get ( ) ) )
506391 } )
507392 . map_err ( |err| {
508- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
509- "Failed to convert to EIP-7594: {err}"
510- ) )
511- } ) ?;
393+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
394+ err. to_string ( ) ,
395+ )
396+ } )
397+ . map_err ( RetryError :: Transient ) ?;
512398
513- // Send the transaction
514399 let encoded_tx = tx. encoded_2718 ( ) ;
515400 let pending_tx = provider
516401 . send_raw_transaction ( & encoded_tx)
517402 . await
518403 . map_err ( |err| {
519- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
520- "Failed to send raw transaction: {err}"
521- ) )
522- } ) ?;
523-
524- let tx_hash = * pending_tx. tx_hash ( ) ;
525-
526- let receipt_result = tokio:: time:: timeout ( retry_interval, pending_tx. get_receipt ( ) ) . await ;
527-
528- match receipt_result {
529- Ok ( Ok ( receipt) ) => Ok ( SubmitOutcome :: Confirmed ( Box :: new ( receipt) ) ) ,
530- Ok ( Err ( err) ) => Err (
531- AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction ( format ! (
532- "Error getting receipt: {err}"
533- ) ) ,
534- ) ,
535- Err ( _) => Ok ( SubmitOutcome :: Pending ( tx_hash) ) ,
536- }
537- }
538-
539- // Checks if any of the pending transactions have been confirmed.
540- // Returns the receipt if one is found, otherwise None.
541- async fn check_pending_txs_confirmed (
542- & self ,
543- pending_hashes : & [ TxHash ] ,
544- ) -> Option < TransactionReceipt > {
545- for tx_hash in pending_hashes {
546- if let Ok ( Some ( receipt) ) = self
547- . proof_aggregation_service
548- . provider ( )
549- . get_transaction_receipt ( * tx_hash)
550- . await
551- {
552- info ! ( "Pending tx {} confirmed before retry" , tx_hash) ;
553- return Some ( receipt) ;
554- }
555- }
556- None
557- }
558-
559- // Updates the gas fees of a `TransactionRequest` using EIP-1559 fee parameters.
560- // Intended for retrying an on-chain submission after a timeout.
561- //
562- // Strategy:
563- // - Fetch the current base fee from the latest block.
564- // - Fetch the suggested priority fee from the network (eth_maxPriorityFeePerGas).
565- // - Compute priority fee as: suggested * (1 + (attempt + 1) * 0.1), capped at `max_priority_fee_upper_limit`.
566- // - Compute `max_fee_per_gas` as: (1 + max_fee_bump_percentage/100) * base_fee + priority_fee.
567- //
568- // Fees are recomputed on each retry using the latest base fee.
569-
570- async fn apply_gas_fee_bump (
571- & self ,
572- tx_req : TransactionRequest ,
573- attempt : u16 ,
574- ) -> Result < TransactionRequest , AggregatedProofSubmissionError > {
575- let provider = self . proof_aggregation_service . provider ( ) ;
576-
577- let max_fee_bump_percentage = self . config . max_fee_bump_percentage ;
578- let max_priority_fee_upper_limit = self . config . max_priority_fee_upper_limit ;
404+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
405+ err. to_string ( ) ,
406+ )
407+ } )
408+ . map_err ( RetryError :: Transient ) ?;
579409
580- let latest_block = provider
581- . get_block_by_number ( BlockNumberOrTag :: Latest )
582- . await
583- . map_err ( |e| AggregatedProofSubmissionError :: GasPriceError ( e. to_string ( ) ) ) ?
584- . ok_or ( AggregatedProofSubmissionError :: LatestBlockNotFound ) ?;
585-
586- let current_base_fee = latest_block
587- . header
588- . base_fee_per_gas
589- . ok_or ( AggregatedProofSubmissionError :: BaseFeePerGasMissing ) ?
590- as f64 ;
591-
592- // Fetch suggested priority fee from the network
593- let suggested_priority_fee = provider
594- . get_max_priority_fee_per_gas ( )
410+ let receipt = pending_tx
411+ . get_receipt ( )
595412 . await
596- . map_err ( |e| AggregatedProofSubmissionError :: GasPriceError ( e. to_string ( ) ) ) ?;
597-
598- // Calculate priority fee: suggested * (attempt + 1), capped at max
599- let priority_fee_multiplier = ( attempt + 1 ) as u128 ;
600- let max_priority_fee_per_gas =
601- ( suggested_priority_fee * priority_fee_multiplier) . min ( max_priority_fee_upper_limit) ;
602-
603- // Calculate max fee with cumulative bump per attempt to ensure replacement tx is accepted
604- let max_fee_multiplier = 1.0 + max_fee_bump_percentage as f64 / 100.0 ;
605- let max_fee_per_gas =
606- ( max_fee_multiplier * current_base_fee) as u128 + max_priority_fee_per_gas;
607-
608- info ! (
609- "Base fee: {:.4} Gwei. Applying max_fee_per_gas: {:.4} Gwei and max_priority_fee_per_gas: {:.4} Gwei to tx" ,
610- current_base_fee / 1e9 ,
611- max_fee_per_gas as f64 / 1e9 ,
612- max_priority_fee_per_gas as f64 / 1e9
613- ) ;
413+ . map_err ( |err| {
414+ AggregatedProofSubmissionError :: SendVerifyAggregatedProofTransaction (
415+ err. to_string ( ) ,
416+ )
417+ } )
418+ . map_err ( RetryError :: Transient ) ?;
614419
615- Ok ( tx_req
616- . with_max_fee_per_gas ( max_fee_per_gas)
617- . with_max_priority_fee_per_gas ( max_priority_fee_per_gas) )
420+ Ok ( receipt)
618421 }
619422
620423 async fn wait_until_can_submit_aggregated_proof (
0 commit comments