Skip to content

Commit bca9ee1

Browse files
Move the helpers and retry logic to separate files
1 parent aa9ddc5 commit bca9ee1

File tree

3 files changed

+105
-97
lines changed

3 files changed

+105
-97
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use alloy::primitives::{utils::parse_ether, U256};
2+
use std::time::Duration;
3+
4+
/// Decides whether to send the aggregated proof to be verified on-chain based on
5+
/// time elapsed since last submission and monthly ETH budget.
6+
/// We make a linear function with the eth to spend this month and the time elapsed since last submission.
7+
/// If eth to spend / elapsed time is over the linear function, we skip the submission.
8+
pub fn should_send_proof_to_verify_on_chain(
9+
time_elapsed: Duration,
10+
monthly_eth_budget: f64,
11+
network_gas_price: U256,
12+
) -> bool {
13+
// We assume a fixed gas cost of 300,000 for each of the 2 transactions
14+
const ON_CHAIN_COST_IN_GAS_UNITS: u64 = 600_000u64;
15+
16+
let on_chain_cost_in_gas: U256 = U256::from(ON_CHAIN_COST_IN_GAS_UNITS);
17+
let max_to_spend_in_wei = max_to_spend_in_wei(time_elapsed, monthly_eth_budget);
18+
19+
let expected_cost_in_wei = network_gas_price * on_chain_cost_in_gas;
20+
21+
expected_cost_in_wei <= max_to_spend_in_wei
22+
}
23+
24+
fn max_to_spend_in_wei(time_elapsed: Duration, monthly_eth_budget: f64) -> U256 {
25+
const SECONDS_PER_MONTH: u64 = 30 * 24 * 60 * 60;
26+
27+
// Note: this expect is safe because in case it was invalid, should have been caught at startup
28+
let monthly_budget_in_wei = parse_ether(&monthly_eth_budget.to_string())
29+
.expect("The monthly budget should be a non-negative value");
30+
31+
let elapsed_seconds = U256::from(time_elapsed.as_secs());
32+
33+
let budget_available_per_second_in_wei = monthly_budget_in_wei / U256::from(SECONDS_PER_MONTH);
34+
35+
budget_available_per_second_in_wei * elapsed_seconds
36+
}

aggregation_mode/proof_aggregator/src/backend/mod.rs

Lines changed: 13 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
pub mod config;
22
mod db;
33
pub mod fetcher;
4+
mod helpers;
45
mod merkle_tree;
6+
mod retry;
57
mod types;
68

79
use crate::{
810
aggregators::{AlignedProof, ProofAggregationError, ZKVMEngine},
9-
backend::db::{Db, DbError},
11+
backend::{
12+
db::{Db, DbError},
13+
retry::{retry_function, RetryError},
14+
},
1015
};
1116

17+
use aligned_sdk::common::constants::{
18+
ETHEREUM_CALL_BACKOFF_FACTOR, ETHEREUM_CALL_MAX_RETRIES, ETHEREUM_CALL_MAX_RETRY_DELAY,
19+
ETHEREUM_CALL_MIN_RETRY_DELAY,
20+
};
1221
use alloy::{
1322
consensus::{BlobTransactionSidecar, EnvKzgSettings, EthereumTxEnvelope, TxEip4844WithSidecar},
1423
eips::{eip4844::BYTES_PER_BLOB, eip7594::BlobTransactionSidecarEip7594, Encodable2718},
@@ -334,7 +343,7 @@ async fn bump_and_send_proof_to_verify_on_chain(
334343
RetryError::Transient(AggregatedProofSubmissionError::GasPriceError(e.to_string()))
335344
})?;
336345

337-
if should_send_proof_to_verify_on_chain(
346+
if helpers::should_send_proof_to_verify_on_chain(
338347
time_elapsed,
339348
monthly_budget_eth,
340349
U256::from(gas_price),
@@ -425,105 +434,12 @@ async fn bump_and_send_proof_to_verify_on_chain(
425434
Ok(receipt)
426435
}
427436

428-
/// Decides whether to send the aggregated proof to be verified on-chain based on
429-
/// time elapsed since last submission and monthly ETH budget.
430-
/// We make a linear function with the eth to spend this month and the time elapsed since last submission.
431-
/// If eth to spend / elapsed time is over the linear function, we skip the submission.
432-
fn should_send_proof_to_verify_on_chain(
433-
time_elapsed: Duration,
434-
monthly_eth_budget: f64,
435-
network_gas_price: U256,
436-
) -> bool {
437-
// We assume a fixed gas cost of 300,000 for each of the 2 transactions
438-
const ON_CHAIN_COST_IN_GAS_UNITS: u64 = 600_000u64;
439-
440-
let on_chain_cost_in_gas: U256 = U256::from(ON_CHAIN_COST_IN_GAS_UNITS);
441-
let max_to_spend_in_wei = max_to_spend_in_wei(time_elapsed, monthly_eth_budget);
442-
443-
let expected_cost_in_wei = network_gas_price * on_chain_cost_in_gas;
444-
445-
expected_cost_in_wei <= max_to_spend_in_wei
446-
}
447-
448-
fn max_to_spend_in_wei(time_elapsed: Duration, monthly_eth_budget: f64) -> U256 {
449-
const SECONDS_PER_MONTH: u64 = 30 * 24 * 60 * 60;
450-
451-
// Note: this expect is safe because in case it was invalid, should have been caught at startup
452-
let monthly_budget_in_wei = parse_ether(&monthly_eth_budget.to_string())
453-
.expect("The monthly budget should be a non-negative value");
454-
455-
let elapsed_seconds = U256::from(time_elapsed.as_secs());
456-
457-
let budget_available_per_second_in_wei = monthly_budget_in_wei / U256::from(SECONDS_PER_MONTH);
458-
459-
budget_available_per_second_in_wei * elapsed_seconds
460-
}
461-
462-
use backon::ExponentialBuilder;
463-
use backon::Retryable;
464-
use std::future::Future;
465-
466-
#[derive(Debug)]
467-
pub enum RetryError<E> {
468-
Transient(E),
469-
Permanent(E),
470-
}
471-
472-
impl<E: std::fmt::Display> std::fmt::Display for RetryError<E> {
473-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
474-
match self {
475-
RetryError::Transient(e) => write!(f, "{}", e),
476-
RetryError::Permanent(e) => write!(f, "{}", e),
477-
}
478-
}
479-
}
480-
481-
impl<E> RetryError<E> {
482-
pub fn inner(self) -> E {
483-
match self {
484-
RetryError::Transient(e) => e,
485-
RetryError::Permanent(e) => e,
486-
}
487-
}
488-
}
489-
490-
impl<E: std::fmt::Display> std::error::Error for RetryError<E> where E: std::fmt::Debug {}
491-
492-
pub const ETHEREUM_CALL_MIN_RETRY_DELAY: u64 = 500; // milliseconds
493-
pub const ETHEREUM_CALL_MAX_RETRIES: usize = 5;
494-
pub const ETHEREUM_CALL_BACKOFF_FACTOR: f32 = 2.0;
495-
pub const ETHEREUM_CALL_MAX_RETRY_DELAY: u64 = 60; // seconds
496-
497-
/// Supports retries only on async functions. See: https://docs.rs/backon/latest/backon/#retry-an-async-function
498-
/// Runs with `jitter: false`.
499-
pub async fn retry_function<FutureFn, Fut, T, E>(
500-
function: FutureFn,
501-
min_delay: u64,
502-
factor: f32,
503-
max_times: usize,
504-
max_delay: u64,
505-
) -> Result<T, RetryError<E>>
506-
where
507-
Fut: Future<Output = Result<T, RetryError<E>>>,
508-
FutureFn: FnMut() -> Fut,
509-
{
510-
let backoff = ExponentialBuilder::default()
511-
.with_min_delay(Duration::from_millis(min_delay))
512-
.with_max_times(max_times)
513-
.with_factor(factor)
514-
.with_max_delay(Duration::from_secs(max_delay));
515-
516-
function
517-
.retry(backoff)
518-
.sleep(tokio::time::sleep)
519-
.when(|e| matches!(e, RetryError::Transient(_)))
520-
.await
521-
}
522-
523437
#[cfg(test)]
524438
mod tests {
525439
use super::*;
526440

441+
use helpers::should_send_proof_to_verify_on_chain;
442+
527443
#[test]
528444
fn test_should_send_proof_to_verify_on_chain_updated_cases() {
529445
// The should_send_proof_to_verify_on_chain function returns true when:
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use backon::ExponentialBuilder;
2+
use backon::Retryable;
3+
use std::future::Future;
4+
use std::time::Duration;
5+
6+
#[derive(Debug)]
7+
pub enum RetryError<E> {
8+
Transient(E),
9+
// Permanent(E),
10+
}
11+
12+
impl<E: std::fmt::Display> std::fmt::Display for RetryError<E> {
13+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
14+
match self {
15+
RetryError::Transient(e) => write!(f, "{e}"),
16+
//RetryError::Permanent(e) => write!(f, "{e}"),
17+
}
18+
}
19+
}
20+
21+
impl<E> RetryError<E> {
22+
pub fn inner(self) -> E {
23+
match self {
24+
RetryError::Transient(e) => e,
25+
//RetryError::Permanent(e) => e,
26+
}
27+
}
28+
}
29+
30+
impl<E: std::fmt::Display> std::error::Error for RetryError<E> where E: std::fmt::Debug {}
31+
32+
/// Supports retries only on async functions. See: https://docs.rs/backon/latest/backon/#retry-an-async-function
33+
/// Runs with `jitter: false`.
34+
pub async fn retry_function<FutureFn, Fut, T, E>(
35+
function: FutureFn,
36+
min_delay: u64,
37+
factor: f32,
38+
max_times: usize,
39+
max_delay: u64,
40+
) -> Result<T, RetryError<E>>
41+
where
42+
Fut: Future<Output = Result<T, RetryError<E>>>,
43+
FutureFn: FnMut() -> Fut,
44+
{
45+
let backoff = ExponentialBuilder::default()
46+
.with_min_delay(Duration::from_millis(min_delay))
47+
.with_max_times(max_times)
48+
.with_factor(factor)
49+
.with_max_delay(Duration::from_secs(max_delay));
50+
51+
function
52+
.retry(backoff)
53+
.sleep(tokio::time::sleep)
54+
.when(|e| matches!(e, RetryError::Transient(_)))
55+
.await
56+
}

0 commit comments

Comments
 (0)