diff --git a/Cargo.lock b/Cargo.lock index 9ac07831d7..20f23b1ab3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3019,7 +3019,9 @@ name = "e3-evm-helpers" version = "0.1.7" dependencies = [ "alloy", + "anyhow", "async-trait", + "e3-utils", "eyre", "futures", "futures-util", diff --git a/crates/evm-helpers/Cargo.toml b/crates/evm-helpers/Cargo.toml index 8470dcf60c..4af5491548 100644 --- a/crates/evm-helpers/Cargo.toml +++ b/crates/evm-helpers/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/gnosisguild/enclave/crates/evm-helpers" [dependencies] alloy.workspace = true +anyhow.workspace = true async-trait.workspace = true eyre.workspace = true futures.workspace = true @@ -15,3 +16,4 @@ futures-util.workspace = true once_cell.workspace = true tokio.workspace = true tracing.workspace = true +e3-utils.workspace = true diff --git a/crates/evm-helpers/src/retry.rs b/crates/evm-helpers/src/retry.rs index c5a1d9157a..f0e5c3e993 100644 --- a/crates/evm-helpers/src/retry.rs +++ b/crates/evm-helpers/src/retry.rs @@ -4,13 +4,12 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use eyre::Result; +use e3_utils::{retry_with_backoff, RetryError}; use std::future::Future; -use tokio::time::{sleep, Duration}; use tracing::info; -const READ_RETRY_MAX_ATTEMPTS: u32 = 3; -const READ_RETRY_INITIAL_DELAY_MS: u64 = 2000; +const RETRY_MAX_ATTEMPTS: u32 = 3; +const RETRY_INITIAL_DELAY_MS: u64 = 2000; fn should_retry_error(error: &str, retry_on_errors: &[&str]) -> bool { if retry_on_errors.is_empty() { @@ -22,39 +21,39 @@ fn should_retry_error(error: &str, retry_on_errors: &[&str]) -> bool { pub async fn call_with_retry( operation_name: &str, retry_on_errors: &[&str], - read_fn: F, -) -> Result + operation_fn: F, +) -> anyhow::Result where F: Fn() -> Fut, - Fut: Future>, + Fut: Future>, { let op_name = operation_name.to_string(); let retry_codes: Vec = retry_on_errors.iter().map(|s| s.to_string()).collect(); - let mut attempts = 0; - let mut delay = READ_RETRY_INITIAL_DELAY_MS; - loop { - attempts += 1; - let result = read_fn().await; - - match result { - Ok(value) => return Ok(value), - Err(e) => { - let error_str = format!("{}", e); - let retry_refs: Vec<&str> = retry_codes.iter().map(|s| s.as_str()).collect(); - - if should_retry_error(&error_str, &retry_refs) && attempts < READ_RETRY_MAX_ATTEMPTS - { - info!( - "{}: error (attempt {}/{}), will retry after {}ms: {}", - op_name, attempts, READ_RETRY_MAX_ATTEMPTS, delay, e - ); - sleep(Duration::from_millis(delay)).await; - delay *= 2; - } else { - return Err(e); + retry_with_backoff( + || { + let op_name = op_name.clone(); + let retry_codes = retry_codes.clone(); + let fut = operation_fn(); + async move { + match fut.await { + Ok(value) => Ok(value), + Err(e) => { + let error_str = format!("{}", e); + let retry_refs: Vec<&str> = + retry_codes.iter().map(|s| s.as_str()).collect(); + if should_retry_error(&error_str, &retry_refs) { + info!("{}: error, will retry: {}", op_name, e); + Err(RetryError::Retry(e)) + } else { + Err(RetryError::Failure(e)) + } + } } } - } - } + }, + RETRY_MAX_ATTEMPTS, + RETRY_INITIAL_DELAY_MS, + ) + .await } diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 6922193698..f864b89f8c 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -22,5 +22,6 @@ pub use enclave_sol::EnclaveSol; pub use enclave_sol_reader::EnclaveSolReader; pub use enclave_sol_writer::EnclaveSolWriter; pub use event_reader::{EnclaveEvmEvent, EvmEventReader, EvmEventReaderState, ExtractorFn}; +pub use helpers::send_tx_with_retry; pub use historical_event_coordinator::{CoordinatorStart, HistoricalEventCoordinator}; pub use repo::*; diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index 67db7860ed..4eaf560a61 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -2,6 +2,31 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "actix" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags 2.10.0", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot 0.12.5", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + [[package]] name = "actix-codec" version = "0.5.2" @@ -200,6 +225,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "actix_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.42", + "syn 2.0.111", +] + [[package]] name = "addr2line" version = "0.25.1" @@ -1091,7 +1127,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1102,7 +1138,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2088,6 +2124,15 @@ dependencies = [ "zk-inputs", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -2381,7 +2426,9 @@ name = "e3-evm-helpers" version = "0.1.7" dependencies = [ "alloy", + "anyhow", "async-trait", + "e3-utils", "eyre", "futures 0.3.31", "futures-util", @@ -2450,6 +2497,21 @@ dependencies = [ "fhe-traits", ] +[[package]] +name = "e3-utils" +version = "0.1.7" +dependencies = [ + "actix", + "alloy", + "anyhow", + "derivative", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "tokio", + "tracing", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -2577,7 +2639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4907,7 +4969,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5507,15 +5569,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.24.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5974,14 +6036,13 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] [[package]] @@ -6594,9 +6655,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] diff --git a/examples/CRISP/server/src/server/indexer.rs b/examples/CRISP/server/src/server/indexer.rs index 40dfbf13ae..5bf84e706a 100644 --- a/examples/CRISP/server/src/server/indexer.rs +++ b/examples/CRISP/server/src/server/indexer.rs @@ -56,9 +56,15 @@ pub async fn register_e3_requested( let e3 = call_with_retry("get_e3", &["0xcd6f4a4f"], || { let contract = contract.clone(); let event_e3_id = event.e3Id; - async move { contract.get_e3(event_e3_id).await } + async move { + contract + .get_e3(event_e3_id) + .await + .map_err(|e| anyhow::anyhow!("{}", e)) + } }) - .await?; + .await + .map_err(|e| eyre::eyre!("{}", e))?; // Convert custom params bytes back to token address and balance threshold. @@ -341,9 +347,15 @@ pub async fn register_committee_published( let e3 = call_with_retry("get_e3", &["0xcd6f4a4f"], || { let contract = contract.clone(); let event_e3_id = event.e3Id; - async move { contract.get_e3(event_e3_id).await } + async move { + contract + .get_e3(event_e3_id) + .await + .map_err(|e| anyhow::anyhow!("{}", e)) + } }) - .await?; + .await + .map_err(|e| eyre::eyre!("{}", e))?; if u64::try_from(e3.expiration)? > 0 { info!("[e3_id={}] E3 already activated", event.e3Id); return Ok(()); @@ -375,7 +387,21 @@ async fn handle_committee_time_expired( ctx: Arc>, ) -> eyre::Result<()> { // If not activated activate - let tx = ctx.contract().activate(event.e3Id).await?; + let tx = call_with_retry("activate", &["0x45ccf3c6"], || { + let value = ctx.clone(); + async move { + info!("[e3_id={}] Calling Enclave.Activate", event.e3Id); + let receipt = value + .contract() + .activate(event.e3Id) + .await + .map_err(|e| anyhow::anyhow!("{:?}", e))?; + anyhow::Ok(receipt) + } + }) + .await + .map_err(|e| eyre::eyre!("{:?}", e))?; + info!( "[e3_id={}] E3 activated with tx: {:?}", event.e3Id, tx.transaction_hash diff --git a/examples/CRISP/server/src/server/repo.rs b/examples/CRISP/server/src/server/repo.rs index de9b79d205..addc2d509a 100644 --- a/examples/CRISP/server/src/server/repo.rs +++ b/examples/CRISP/server/src/server/repo.rs @@ -45,20 +45,23 @@ impl CurrentRoundRepository { } /// Get the current (most recent) round for a specific requester - /// + /// /// # Arguments /// * `requester` - The requester address to find the current round for - /// + /// /// # Returns /// * The CurrentRound object for the most recent round by this requester, or None if not found - pub async fn get_current_round_for_requester(&self, requester: String) -> Result> { + pub async fn get_current_round_for_requester( + &self, + requester: String, + ) -> Result> { // Get the current round count to iterate through all rounds let round_count = self.get_current_round_id().await?; - + // Iterate backwards from the most recent round to find the latest one for this requester for round_id in (0..=round_count).rev() { let crisp_repo = CrispE3Repository::new(self.store.clone(), round_id); - + match crisp_repo.get_e3_state_lite().await { Ok(state) => { if state.requester == requester { @@ -180,7 +183,7 @@ impl CrispE3Repository { token_address, balance_threshold, ciphertext_inputs: vec![], - requester + requester, }) .await } diff --git a/examples/CRISP/server/src/server/routes/rounds.rs b/examples/CRISP/server/src/server/routes/rounds.rs index 976d27d079..1431efba5a 100644 --- a/examples/CRISP/server/src/server/routes/rounds.rs +++ b/examples/CRISP/server/src/server/routes/rounds.rs @@ -7,7 +7,8 @@ use crate::config::CONFIG; use crate::server::app_data::AppData; use crate::server::models::{ - CTRequest, ComputeProviderParams, JsonResponse, PKRequest, RoundRequest, RoundRequestWithRequester + CTRequest, ComputeProviderParams, JsonResponse, PKRequest, RoundRequest, + RoundRequestWithRequester, }; use actix_web::{web, HttpResponse, Responder}; @@ -84,7 +85,10 @@ async fn get_current_round( // .get(0) returns Option<&String>, so we need to handle that let result = if let Some(requester) = incoming.requesters.get(0) { // We have a requester, filter by it - store.current_round().get_current_round_for_requester(requester.clone()).await + store + .current_round() + .get_current_round_for_requester(requester.clone()) + .await } else { // No requester provided (empty array) store.current_round().get_current_round().await diff --git a/examples/CRISP/server/src/server/routes/state.rs b/examples/CRISP/server/src/server/routes/state.rs index 384bfd9209..3c91b6aebb 100644 --- a/examples/CRISP/server/src/server/routes/state.rs +++ b/examples/CRISP/server/src/server/routes/state.rs @@ -7,12 +7,15 @@ use std::str::FromStr; use crate::server::{ - CONFIG, app_data::AppData, models::{ - GetRoundRequest, IsSlotEmptyRequest, IsSlotEmptyResponse, PreviousCiphertextRequest, PreviousCiphertextResponse, RoundRequestWithRequester, WebhookPayload - } + app_data::AppData, + models::{ + GetRoundRequest, IsSlotEmptyRequest, IsSlotEmptyResponse, PreviousCiphertextRequest, + PreviousCiphertextResponse, RoundRequestWithRequester, WebhookPayload, + }, + CONFIG, }; use actix_web::{web, HttpResponse, Responder}; -use alloy::{primitives::{Address, Bytes, U256}}; +use alloy::primitives::{Address, Bytes, U256}; use e3_sdk::evm_helpers::contracts::{ EnclaveContract, EnclaveContractFactory, EnclaveWrite, ReadWrite, }; @@ -219,7 +222,10 @@ async fn get_round_result( /// # Returns /// /// * A JSON response containing the results for all rounds -async fn get_all_round_results(data: web::Json::, store: web::Data) -> impl Responder { +async fn get_all_round_results( + data: web::Json, + store: web::Data, +) -> impl Responder { let incoming = data.into_inner(); let round_count = match store.current_round().get_current_round_id().await { diff --git a/examples/CRISP/server/src/server/token_holders/etherscan.rs b/examples/CRISP/server/src/server/token_holders/etherscan.rs index de5e740305..6bdb074bb1 100644 --- a/examples/CRISP/server/src/server/token_holders/etherscan.rs +++ b/examples/CRISP/server/src/server/token_holders/etherscan.rs @@ -4,18 +4,15 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. +use crate::server::{models::TokenHolder, CONFIG}; use alloy::primitives::{Address, U256}; use alloy::providers::ProviderBuilder; use alloy::sol; use eyre::{eyre, Context, Result}; // Add this import use reqwest; -use serde::{Deserialize}; +use serde::Deserialize; use std::collections::{HashMap, HashSet}; use tokio::time::{sleep, Duration}; -use crate::server::{ - CONFIG, - models::TokenHolder -}; // Define the Votes contract interface for getPastVotes sol! { @@ -449,8 +446,11 @@ impl EtherscanClient { let provider = ProviderBuilder::new().connect_http(url); let token = ERC20Votes::new(token_address, provider); - - match token.getPastVotes(voter_address, U256::from(block_number - 1)).call().await { + match token + .getPastVotes(voter_address, U256::from(block_number - 1)) + .call() + .await + { Ok(votes) => Ok(votes), Err(_) => { // Fallback to balanceOf if getPastVotes fails @@ -460,7 +460,7 @@ impl EtherscanClient { .await .context("Failed to call balanceOf")?; Ok(balance) - }, + } } }