|
1 | 1 | pub mod config; |
2 | 2 | mod db; |
3 | 3 | pub mod fetcher; |
| 4 | +mod helpers; |
4 | 5 | mod merkle_tree; |
| 6 | +mod retry; |
5 | 7 | mod types; |
6 | 8 |
|
7 | 9 | use crate::{ |
8 | 10 | aggregators::{AlignedProof, ProofAggregationError, ZKVMEngine}, |
9 | | - backend::db::{Db, DbError}, |
| 11 | + backend::{ |
| 12 | + db::{Db, DbError}, |
| 13 | + retry::{retry_function, RetryError}, |
| 14 | + }, |
10 | 15 | }; |
11 | 16 |
|
| 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 | +}; |
12 | 21 | use alloy::{ |
13 | 22 | consensus::{BlobTransactionSidecar, EnvKzgSettings, EthereumTxEnvelope, TxEip4844WithSidecar}, |
14 | 23 | eips::{eip4844::BYTES_PER_BLOB, eip7594::BlobTransactionSidecarEip7594, Encodable2718}, |
@@ -334,7 +343,7 @@ async fn bump_and_send_proof_to_verify_on_chain( |
334 | 343 | RetryError::Transient(AggregatedProofSubmissionError::GasPriceError(e.to_string())) |
335 | 344 | })?; |
336 | 345 |
|
337 | | - if should_send_proof_to_verify_on_chain( |
| 346 | + if helpers::should_send_proof_to_verify_on_chain( |
338 | 347 | time_elapsed, |
339 | 348 | monthly_budget_eth, |
340 | 349 | U256::from(gas_price), |
@@ -425,105 +434,12 @@ async fn bump_and_send_proof_to_verify_on_chain( |
425 | 434 | Ok(receipt) |
426 | 435 | } |
427 | 436 |
|
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 | | - |
523 | 437 | #[cfg(test)] |
524 | 438 | mod tests { |
525 | 439 | use super::*; |
526 | 440 |
|
| 441 | + use helpers::should_send_proof_to_verify_on_chain; |
| 442 | + |
527 | 443 | #[test] |
528 | 444 | fn test_should_send_proof_to_verify_on_chain_updated_cases() { |
529 | 445 | // The should_send_proof_to_verify_on_chain function returns true when: |
|
0 commit comments