Skip to content

Commit 3bac894

Browse files
authored
Revert "feat(aggregation-mode): Bump fee when proof verification times out" (#2267)
1 parent 7559f9b commit 3bac894

File tree

4 files changed

+47
-260
lines changed

4 files changed

+47
-260
lines changed

aggregation_mode/proof_aggregator/src/backend/config.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ pub struct Config {
2121
pub sp1_chunk_aggregator_vk_hash: String,
2222
pub monthly_budget_eth: f64,
2323
pub db_connection_urls: Vec<String>,
24-
pub max_bump_retries: u16,
25-
pub bump_retry_interval_seconds: u64,
26-
pub max_fee_bump_percentage: u64,
27-
pub max_priority_fee_upper_limit: u128,
2824
}
2925

3026
impl Config {

aggregation_mode/proof_aggregator/src/backend/mod.rs

Lines changed: 45 additions & 242 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,12 @@ use crate::{
2020

2121
use 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
};
3431
use 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

7057
pub 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

8167
impl 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(

config-files/config-proof-aggregator-ethereum-package.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@ monthly_budget_eth: 15.0
2727
sp1_chunk_aggregator_vk_hash: "00d6e32a34f68ea643362b96615591c94ee0bf99ee871740ab2337966a4f77af"
2828
risc0_chunk_aggregator_image_id: "8908f01022827e80a5de71908c16ee44f4a467236df20f62e7c994491629d74c"
2929

30-
# These values modify the bumping behavior after the aggregated proof on-chain submission times out.
31-
max_bump_retries: 5
32-
bump_retry_interval_seconds: 120
33-
max_fee_bump_percentage: 100
34-
max_priority_fee_upper_limit: 3000000000 # 3 Gwei
35-
3630
ecdsa:
3731
private_key_store_path: "config-files/anvil.proof-aggregator.ecdsa.key.json"
3832
private_key_store_password: ""

0 commit comments

Comments
 (0)