From bd62c333e413f533618f7e199510e9d36a2f0fb5 Mon Sep 17 00:00:00 2001 From: kbizikav <132550763+kbizikav@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:14:36 +0700 Subject: [PATCH 1/9] feat: delete mining mode --- src/cli/mod.rs | 14 +--- src/cli/mode_selection.rs | 46 ------------ src/external_api/intmax/event.rs | 0 src/external_api/intmax/mod.rs | 3 +- src/services/mining/deterministic_sleep.rs | 81 ---------------------- src/services/mining/mod.rs | 42 ++--------- src/services/mod.rs | 6 +- 7 files changed, 11 insertions(+), 181 deletions(-) create mode 100644 src/external_api/intmax/event.rs delete mode 100644 src/services/mining/deterministic_sleep.rs diff --git a/src/cli/mod.rs b/src/cli/mod.rs index e21dd36..a54c8d6 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -4,7 +4,7 @@ use ::console::{style, Term}; use alloy::primitives::B256; use configure::recover_withdrawal_private_key; use console::clear_console; -use mode_selection::{legacy_select_mode, select_mode}; +use mode_selection::legacy_select_mode; use term_of_use::make_agreement; use crate::{ @@ -52,11 +52,7 @@ pub async fn run(mode: Option) -> anyhow::Result<()> { } let mut mode = if is_interactive { - if is_legacy() { - legacy_select_mode()? - } else { - select_mode()? - } + legacy_select_mode()? } else { mode.unwrap() }; @@ -136,11 +132,7 @@ async fn mode_loop( // if not in interactive mode, we only run once break; } - *mode = if is_legacy() { - legacy_select_mode()? - } else { - select_mode()? - }; + *mode = legacy_select_mode()?; } Ok(()) } diff --git a/src/cli/mode_selection.rs b/src/cli/mode_selection.rs index eb6a743..9e7e435 100644 --- a/src/cli/mode_selection.rs +++ b/src/cli/mode_selection.rs @@ -3,52 +3,6 @@ use dialoguer::Select; use crate::state::mode::RunMode; -pub fn select_mode() -> anyhow::Result { - let items = [ - format!( - "{} {}", - style("Mining:").bold(), - style("performs mining by repeatedly executing deposits and withdrawals").dim() - ), - format!( - "{} {}", - style("Claim:").bold(), - style("claims available ITX tokens").dim() - ), - format!( - "{} {}", - style("Exit:").bold(), - style("withdraws all balances currently and cancels pending deposits").dim() - ), - format!( - "{} {}", - style("Export:").bold(), - style("export deposit private keys").dim() - ), - format!( - "{} {}", - style("Check Update:").bold(), - style("check for updates of this CLI").dim() - ), - ]; - let term = Term::stdout(); - term.clear_screen()?; - let mode = Select::new() - .with_prompt("Select mode (press ctrl+c to abort)") - .items(&items) - .default(0) - .interact()?; - let mode = match mode { - 0 => RunMode::Mining, - 1 => RunMode::Claim, - 2 => RunMode::Exit, - 3 => RunMode::Export, - 4 => RunMode::CheckUpdate, - _ => unreachable!(), - }; - Ok(mode) -} - pub fn legacy_select_mode() -> anyhow::Result { let items = [ format!( diff --git a/src/external_api/intmax/event.rs b/src/external_api/intmax/event.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/external_api/intmax/mod.rs b/src/external_api/intmax/mod.rs index eba5323..0de0369 100644 --- a/src/external_api/intmax/mod.rs +++ b/src/external_api/intmax/mod.rs @@ -1,6 +1,7 @@ pub mod availability; pub mod circulation; pub mod error; +pub mod event; pub mod gnark; +pub mod header; pub mod withdrawal; -pub mod header; \ No newline at end of file diff --git a/src/services/mining/deterministic_sleep.rs b/src/services/mining/deterministic_sleep.rs deleted file mode 100644 index d810fff..0000000 --- a/src/services/mining/deterministic_sleep.rs +++ /dev/null @@ -1,81 +0,0 @@ -use alloy::primitives::Address; -use log::info; -use rand::{Rng as _, SeedableRng as _}; -use rand_chacha::ChaCha20Rng; - -use crate::{ - cli::console::print_log, - external_api::graph::client::GraphClient, - utils::{config::Settings, encryption::keccak256_hash, time::sleep_for}, -}; - -/// Random sleep before deposit to improve privacy. -pub async fn sleep_before_deposit( - graph_client: &GraphClient, - withdrawal_address: Address, -) -> anyhow::Result<()> { - let last_withdrawal_time = graph_client - .get_latest_withdrawal_timestamp(withdrawal_address) - .await?; - info!("last_withdrawal_time: {:?}", last_withdrawal_time); - if last_withdrawal_time.is_none() { - return Ok(()); // no withdrawal yet - } - let last_withdrawal_time = last_withdrawal_time.unwrap(); - let sleep_time = determine_sleep_time(last_withdrawal_time, withdrawal_address, "deposit"); - let target_time = last_withdrawal_time + sleep_time; - sleep_if_needed(target_time, true).await; - Ok(()) -} - -/// Random sleep before withdrawal to improve privacy. -pub async fn sleep_before_withdrawal( - graph_client: &GraphClient, - deposit_address: Address, -) -> anyhow::Result<()> { - let last_deposit_time: Option = graph_client - .get_latest_deposit_timestamp(deposit_address) - .await?; - info!("last_deposit_time: {:?}", last_deposit_time); - if last_deposit_time.is_none() { - return Ok(()); // no deposit yet - } - let last_deposit_time = last_deposit_time.unwrap(); - let sleep_time = determine_sleep_time(last_deposit_time, deposit_address, "withdrawal"); - let target_time = last_deposit_time + sleep_time; - sleep_if_needed(target_time, false).await; - Ok(()) -} - -async fn sleep_if_needed(target_time: u64, is_deposit: bool) { - log::info!( - "sleep_if_needed: target_time: {}, is_deposit: {}", - target_time, - is_deposit - ); - let now = chrono::Utc::now().timestamp() as u64; - if now >= target_time { - info!("No need to sleep: now={}, target_time={}", now, target_time); - return; // no need to sleep - } - let sleep_from_now = target_time - now; - let sleep_until_chrono = - chrono::Local::now() + chrono::Duration::seconds(sleep_from_now as i64); - let next_step = if is_deposit { "deposit" } else { "withdrawal" }; - print_log(format!( - "Next {} will start at {}.", - next_step, - sleep_until_chrono.format("%Y-%m-%d %H:%M:%S"), - )); - sleep_for(sleep_from_now); -} - -fn determine_sleep_time(last_time: u64, address: Address, random_nonce: &'static str) -> u64 { - let seed_str = format!("{}{}{}", last_time, address, random_nonce); - let seed_hash = keccak256_hash(&seed_str); - let mut rng = ChaCha20Rng::from_seed(seed_hash); - let settings = Settings::load().expect("Failed to load settings"); - rng.gen_range( - settings.service.mining_min_cooldown_in_sec..settings.service.mining_max_cooldown_in_sec, - ) -} diff --git a/src/services/mining/mod.rs b/src/services/mining/mod.rs index 980d626..440af79 100644 --- a/src/services/mining/mod.rs +++ b/src/services/mining/mod.rs @@ -1,31 +1,26 @@ -use alloy::{primitives::U256, providers::Provider}; +use alloy::primitives::U256; use anyhow::Context; -use deterministic_sleep::{sleep_before_deposit, sleep_before_withdrawal}; -use intmax2_zkp::common::deposit::get_pubkey_salt_hash; + use withdrawal::withdrawal_task; use crate::{ cli::console::print_warning, external_api::contracts::convert::convert_u256_to_alloy, state::{key::Key, state::State}, - utils::{ - derive_key::{derive_pubkey_from_private_key, derive_salt_from_private_key_nonce}, - errors::CLIError, - }, + utils::errors::CLIError, }; use super::{assets_status::AssetsStatus, utils::await_until_low_gas_price}; -pub mod deterministic_sleep; pub mod withdrawal; pub async fn mining_task( state: &mut State, key: &Key, assets_status: &AssetsStatus, - new_deposit: bool, + _new_deposit: bool, cancel_pending_deposits: bool, - mining_unit: U256, + _mining_unit: U256, ) -> anyhow::Result<()> { // cancel pending deposits if cancel_pending_deposits { @@ -73,7 +68,6 @@ pub async fn mining_task( // withdrawal if !assets_status.not_withdrawn_indices.is_empty() { - sleep_before_withdrawal(&state.graph_client, key.deposit_address).await?; for &index in assets_status.not_withdrawn_indices.iter() { let event = assets_status.senders_deposits[index].clone(); withdrawal_task(state, key, event) @@ -84,31 +78,5 @@ pub async fn mining_task( return Ok(()); } - // deposit - if new_deposit { - sleep_before_deposit(&state.graph_client, key.withdrawal_address).await?; - let deposit_address = key.deposit_address; - let nonce = state - .provider - .get_transaction_count(deposit_address) - .await?; - let salt = derive_salt_from_private_key_nonce(key.deposit_private_key, nonce); - let pubkey = derive_pubkey_from_private_key(key.deposit_private_key); - let pubkey_salt_hash = get_pubkey_salt_hash(pubkey, salt); - // execute deposit task - await_until_low_gas_price(&state.provider).await?; - state - .int1 - .deposit_native_token(key.deposit_private_key, pubkey_salt_hash, mining_unit) - .await - .with_context(|| { - format!( - "Failed to deposit to address {:?} with nonce {}", - key.deposit_address, nonce - ) - })?; - return Ok(()); - } - Ok(()) } diff --git a/src/services/mod.rs b/src/services/mod.rs index f91bc42..8b0e0a2 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -3,15 +3,11 @@ use crate::{ availability::check_availability, balance_validation::validate_withdrawal_address_balance, console::{print_assets_status, print_log, print_status, print_warning}, - }, - external_api::intmax::circulation::get_circulation, - state::{key::Key, state::State}, - utils::{config::Settings, time::sleep_for}, + }, external_api::intmax::circulation::get_circulation, services::mining::mining_task, state::{key::Key, state::State}, utils::{config::Settings, time::sleep_for} }; use alloy::primitives::{B256, U256}; use chrono::TimeZone as _; use claim::claim_task; -use mining::mining_task; use utils::{await_until_graph_syncs, is_address_used}; const DEPOSIT_CLOSE_TIMESTAMP: u64 = 1751068800; // 2025-06-28 00:00:00 UTC From 18e34d87366bc76e09e2ddef8cebe6956050d1b0 Mon Sep 17 00:00:00 2001 From: kbizikav <132550763+kbizikav@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:30:18 +0700 Subject: [PATCH 2/9] feat: add event server url --- config/config.base.toml | 1 + config/config.mainnet.toml | 1 + src/utils/config.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/config/config.base.toml b/config/config.base.toml index 6fb07a3..da1c83c 100644 --- a/config/config.base.toml +++ b/config/config.base.toml @@ -4,6 +4,7 @@ withdrawal_gnark_prover_url = "https://v1.base-prod.mining-gateway.intmax.xyz/v1 claim_gnark_prover_url = "https://v1.base-prod.mining-gateway.intmax.xyz/v1/gnark-claim-circuit" withdrawal_server_url = "https://v1.base-prod.mining-gateway.intmax.xyz/v1/withdrawal" circulation_server_url = "https://v1.base-prod.mining-gateway.intmax.xyz/v1/mining" +event_server_url = "https://v1.base-prod.mining-gateway.intmax.xyz/v1/deposits" tree_data_repository = "InternetMaximalism/intmax2-mining" tree_data_directory = "base-data" tree_data_branch = "main" diff --git a/config/config.mainnet.toml b/config/config.mainnet.toml index 063b3a0..e9e50de 100644 --- a/config/config.mainnet.toml +++ b/config/config.mainnet.toml @@ -4,6 +4,7 @@ withdrawal_gnark_prover_url = "https://v1.mining-gateway.intmax.xyz/v1/gnark-wit claim_gnark_prover_url = "https://v1.mining-gateway.intmax.xyz/v1/gnark-claim-circuit" withdrawal_server_url = "https://v1.mining-gateway.intmax.xyz/v1/withdrawal" circulation_server_url = "https://v1.mining-gateway.intmax.xyz/v1/mining" +event_server_url = "https://v1.mining-gateway.intmax.xyz/v1/deposits" tree_data_repository = "InternetMaximalism/intmax2-mining" tree_data_directory = "data" tree_data_branch = "main" diff --git a/src/utils/config.rs b/src/utils/config.rs index a099125..41f3499 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -37,6 +37,7 @@ pub struct Api { pub sync_tree_data_interval_in_sec: u64, pub gnark_get_proof_cooldown_in_sec: u64, pub withdrawal_server_url: String, + pub event_server_url: String, } #[derive(Clone, Debug, Deserialize, Serialize)] From c0c19ebf0f8d90360cd884a48d86a5f5d77f065b Mon Sep 17 00:00:00 2001 From: kbizikav <132550763+kbizikav@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:09:50 +0700 Subject: [PATCH 3/9] feat: use event server --- src/external_api/intmax/event.rs | 108 +++++++++++++++++++++++++++++++ src/services/assets_status.rs | 11 ++-- src/services/mod.rs | 13 ++-- src/services/sync.rs | 32 +-------- src/services/utils.rs | 23 +------ src/state/state.rs | 23 ++----- src/test/mod.rs | 20 ++---- 7 files changed, 131 insertions(+), 99 deletions(-) diff --git a/src/external_api/intmax/event.rs b/src/external_api/intmax/event.rs index e69de29..42300d3 100644 --- a/src/external_api/intmax/event.rs +++ b/src/external_api/intmax/event.rs @@ -0,0 +1,108 @@ +use alloy::{ + consensus::Transaction as _, + primitives::{Address, TxHash}, +}; +use intmax2_zkp::ethereum_types::{bytes32::Bytes32, u256::U256}; +use log::info; +use serde::{Deserialize, Serialize}; + +use crate::{ + external_api::{ + contracts::{ + events::Deposited, + utils::{get_batch_transaction, NormalProvider}, + }, + intmax::{ + error::{IntmaxError, IntmaxErrorResponse}, + header::VersionHeader as _, + }, + }, + utils::{config::Settings, retry::with_retry}, +}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DepositedEntry { + pub deposit_id: u64, + pub sender: Address, + pub recipient_salt_hash: Bytes32, + pub token_index: u32, + pub amount: U256, + pub transaction_hash: TxHash, + pub block_timestamp: u64, +} + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +enum EventServerResponse { + Success(Vec), + Error(IntmaxErrorResponse), +} + +pub async fn get_deposit_events( + provider: &NormalProvider, + sender: Address, +) -> Result, IntmaxError> { + info!("get_availability"); + let settings = Settings::load().unwrap(); + let response = with_retry(|| async { + reqwest::Client::new() + .get(format!( + "{}/address/{}", + settings.api.event_server_url, + sender.to_string() + )) + .with_version_header() + .send() + .await + }) + .await + .map_err(|_| IntmaxError::NetworkError("failed to request availability server".to_string()))?; + let response_json: EventServerResponse = response + .json() + .await + .map_err(|e| IntmaxError::SerializeError(e.to_string()))?; + + match response_json { + EventServerResponse::Success(events) => { + let tx_hashes = events + .iter() + .map(|entry| entry.transaction_hash) + .collect::>(); + let txs = get_batch_transaction(provider, &tx_hashes).await?; + let mut deposited = Vec::new(); + for (event, tx) in events.iter().zip(txs.iter()) { + deposited.push(Deposited { + sender: event.sender, + token_index: event.token_index, + deposit_id: event.deposit_id, + recipient_salt_hash: event.recipient_salt_hash, + amount: event.amount, + tx_nonce: tx.nonce(), + timestamp: event.block_timestamp, + }); + } + Ok(deposited) + } + EventServerResponse::Error(error) => Err(IntmaxError::ServerError(error)), + } +} + +#[cfg(test)] +mod tests { + use std::env; + + use crate::external_api::contracts::utils::get_provider; + + use super::*; + + #[tokio::test] + async fn test_get_deposit_events() { + let rpc_url = env::var("RPC_URL").unwrap_or_else(|_| "http://localhost:8545".to_string()); + let provider = get_provider(&rpc_url).unwrap(); + let address: Address = "0x5C6Ad5968041d3C31F01f9AaADd5feF31C56050b" + .parse() + .unwrap(); + let result = get_deposit_events(&provider, address).await; + assert!(result.is_ok()); + } +} diff --git a/src/services/assets_status.rs b/src/services/assets_status.rs index 2296f8c..e6e48b0 100644 --- a/src/services/assets_status.rs +++ b/src/services/assets_status.rs @@ -4,8 +4,9 @@ use log::warn; use mining_circuit_v1::claim::claim_inner_circuit::get_deposit_nullifier; use crate::{ - external_api::contracts::{ - convert::convert_u256_to_alloy, events::Deposited, int1::DepositData, + external_api::{ + contracts::{convert::convert_u256_to_alloy, events::Deposited, int1::DepositData}, + intmax::event::get_deposit_events, }, state::state::State, utils::derive_key::derive_salt_from_private_key_nonce, @@ -35,13 +36,9 @@ pub async fn fetch_assets_status( deposit_address: Address, deposit_private_key: B256, ) -> anyhow::Result { - let graph_client = &state.graph_client; let int1 = &state.int1; let minter = &state.minter; - - let senders_deposits = graph_client - .get_deposited_event_by_sender(deposit_address) - .await?; + let senders_deposits = get_deposit_events(&state.provider, deposit_address).await?; let mut contained_indices = Vec::new(); let mut not_contained_indices = Vec::new(); for (index, event) in senders_deposits.iter().enumerate() { diff --git a/src/services/mod.rs b/src/services/mod.rs index 8b0e0a2..df32209 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -3,12 +3,16 @@ use crate::{ availability::check_availability, balance_validation::validate_withdrawal_address_balance, console::{print_assets_status, print_log, print_status, print_warning}, - }, external_api::intmax::circulation::get_circulation, services::mining::mining_task, state::{key::Key, state::State}, utils::{config::Settings, time::sleep_for} + }, + external_api::intmax::circulation::get_circulation, + services::mining::mining_task, + state::{key::Key, state::State}, + utils::{config::Settings, time::sleep_for}, }; use alloy::primitives::{B256, U256}; use chrono::TimeZone as _; use claim::claim_task; -use utils::{await_until_graph_syncs, is_address_used}; +use utils::is_address_used; const DEPOSIT_CLOSE_TIMESTAMP: u64 = 1751068800; // 2025-06-28 00:00:00 UTC @@ -33,7 +37,6 @@ pub async fn mining_loop( )); loop { check_availability().await?; - await_until_graph_syncs(&state.graph_client).await?; let assets_status = state.sync_and_fetch_assets(&key).await?; let is_qualified = !get_circulation(key.deposit_address).await?.is_excluded; let is_open = (chrono::Utc::now().timestamp() as u64) < DEPOSIT_CLOSE_TIMESTAMP; @@ -97,7 +100,6 @@ pub async fn exit_loop(state: &mut State, withdrawal_private_key: B256) -> anyho print_log(format!("Exit for deposit address{:?}", key.deposit_address)); loop { check_availability().await?; - await_until_graph_syncs(&state.graph_client).await?; let assets_status = state.sync_and_fetch_assets(&key).await?; if assets_status.pending_indices.is_empty() && assets_status.rejected_indices.is_empty() @@ -122,7 +124,6 @@ pub async fn legacy_exit_loop( let mut key_number = 0; loop { check_availability().await?; - await_until_graph_syncs(&state.graph_client).await?; let key = Key::new(withdrawal_private_key, key_number); if !is_address_used(&state.provider, key.deposit_address).await? { print_status("exit loop finished".to_string()); @@ -156,7 +157,6 @@ pub async fn claim_loop(state: &mut State, withdrawal_private_key: B256) -> anyh let key = Key::new(withdrawal_private_key, 0); for is_short_term in [true, false] { check_availability().await?; - await_until_graph_syncs(&state.graph_client).await?; if !is_address_used(&state.provider, key.deposit_address).await? { print_status("claim loop finished".to_string()); return Ok(()); @@ -190,7 +190,6 @@ pub async fn legacy_claim_loop( loop { for is_short_term in [true, false] { check_availability().await?; - await_until_graph_syncs(&state.graph_client).await?; let key = Key::new(withdrawal_private_key, key_number); if !is_address_used(&state.provider, key.deposit_address).await? { print_status("claim loop finished".to_string()); diff --git a/src/services/sync.rs b/src/services/sync.rs index d8c8f6f..4eae7a0 100644 --- a/src/services/sync.rs +++ b/src/services/sync.rs @@ -2,7 +2,6 @@ use crate::{ external_api::{ contracts::{int1::Int1Contract, minter::MinterContract}, github::{fetch_latest_tree_from_github, BinTrees}, - graph::client::GraphClient, }, utils::{ bin_parser::{BinDepositTree, BinEligibleTree, DepositTreeInfo, EligibleTreeInfo}, @@ -14,7 +13,7 @@ use crate::{ }; use anyhow::ensure; use chrono::{NaiveDateTime, Utc}; -use log::{info, warn}; +use log::warn; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -33,7 +32,6 @@ pub enum Error { const MAX_TRY_FETCH_TREE: usize = 10; pub async fn sync_trees( - graph_client: &GraphClient, int1: &Int1Contract, minter: &MinterContract, last_update: &mut NaiveDateTime, @@ -108,7 +106,7 @@ pub async fn sync_trees( } } // sync deposit tree only - sync_to_latest_deposit_tree(graph_client, int1, deposit_hash_tree) + sync_to_latest_deposit_tree(int1, deposit_hash_tree) .await .map_err(|e| { Error::SyncDepositTreeFromEventsError(format!("Failed to sync deposit tree: {}", e)) @@ -164,34 +162,9 @@ async fn parse_and_validate_bin_eligible_tree( } async fn sync_to_latest_deposit_tree( - graph_client: &GraphClient, int1: &Int1Contract, deposit_hash_tree: &mut DepositHashTree, ) -> anyhow::Result<()> { - let next_deposit_index = deposit_hash_tree.tree.len(); - let events = graph_client - .get_deposit_leaf_inserted_event(next_deposit_index as u32) - .await?; - info!( - "Syncing deposit tree, got {} events. Latest deposit_index={}", - events.len(), - events.last().map(|event| event.deposit_index).unwrap_or(0) - ); - let mut to_append = events - .iter() - .filter(|event| event.deposit_index as usize >= next_deposit_index) - .collect::>(); - to_append.sort_by_key(|event| event.deposit_index); - - for event in to_append { - ensure!( - event.deposit_index as usize == deposit_hash_tree.tree.len(), - "Deposit index mismatch: expected {}, got {}", - deposit_hash_tree.tree.len(), - event.deposit_index - ); - deposit_hash_tree.push(event.deposit_hash); - } let local_root = deposit_hash_tree.get_root(); log::info!( "Local deposit root: {}, total leaves: {}", @@ -234,7 +207,6 @@ mod tests { let mut last_update = chrono::NaiveDateTime::default(); super::sync_trees( - &state.graph_client, &state.int1, &state.minter, &mut last_update, diff --git a/src/services/utils.rs b/src/services/utils.rs index 87ba4a0..f8d1f14 100644 --- a/src/services/utils.rs +++ b/src/services/utils.rs @@ -5,7 +5,7 @@ use alloy::{ use crate::{ cli::console::{print_status, print_warning}, - external_api::{contracts::utils::NormalProvider, graph::client::GraphClient}, + external_api::contracts::utils::NormalProvider, utils::{config::Settings, env_config::EnvConfig, time::sleep_for}, }; @@ -70,27 +70,6 @@ pub async fn await_until_low_gas_price(provider: &NormalProvider) -> anyhow::Res Ok(()) } -pub async fn await_until_graph_syncs(graph: &GraphClient) -> anyhow::Result<()> { - let mut retries = 0; - loop { - match graph.health_check().await { - Ok(()) => { - log::info!("Graph is synced and healthy"); - break; - } - Err(e) => { - print_warning(format!("Graph is not synced yet: {}. Retrying...", e)); - sleep_for(10); - } - } - if retries >= 10 { - return Err(anyhow::anyhow!("Graph is not synced after 10 attempts")); - } - retries += 1; - } - Ok(()) -} - pub async fn is_address_used( provider: &NormalProvider, deposit_address: Address, diff --git a/src/state/state.rs b/src/state/state.rs index 8af1afa..1710328 100644 --- a/src/state/state.rs +++ b/src/state/state.rs @@ -2,14 +2,11 @@ use chrono::NaiveDateTime; use super::{key::Key, prover::Prover}; use crate::{ - external_api::{ - contracts::{ - int1::Int1Contract, - minter::MinterContract, - token::TokenContract, - utils::{get_provider, NormalProvider}, - }, - graph::client::GraphClient, + external_api::contracts::{ + int1::Int1Contract, + minter::MinterContract, + token::TokenContract, + utils::{get_provider, NormalProvider}, }, services::{ assets_status::{fetch_assets_status, AssetsStatus}, @@ -33,8 +30,6 @@ pub struct State { pub minter: MinterContract, pub token: TokenContract, pub provider: NormalProvider, - - pub graph_client: GraphClient, } impl State { @@ -53,12 +48,6 @@ impl State { provider.clone(), settings.blockchain.token_address.parse().unwrap(), ); - let graph_client = GraphClient::new( - provider.clone(), - &settings.blockchain.graph_url, - None, - settings.blockchain.graph_health_check_timeout_in_sec, - ); Self { deposit_hash_tree: DepositHashTree::new(), @@ -70,13 +59,11 @@ impl State { minter, token, provider, - graph_client, } } pub async fn sync_trees(&mut self) -> anyhow::Result<()> { sync_trees( - &self.graph_client, &self.int1, &self.minter, &mut self.last_tree_fetched_at, diff --git a/src/test/mod.rs b/src/test/mod.rs index 82114f2..f2642ee 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -5,14 +5,11 @@ use mining_circuit_v1::eligible_tree::EligibleLeaf; use num_bigint::BigUint; use crate::{ - external_api::{ - contracts::{ - int1::Int1Contract, - minter::MinterContract, - token::TokenContract, - utils::{get_address_from_private_key, get_provider}, - }, - graph::client::GraphClient, + external_api::contracts::{ + int1::Int1Contract, + minter::MinterContract, + token::TokenContract, + utils::{get_address_from_private_key, get_provider}, }, state::{key::Key, prover::Prover, state::State}, utils::{deposit_hash_tree::DepositHashTree, eligible_tree_with_map::EligibleTreeWithMap}, @@ -54,12 +51,6 @@ pub async fn get_dummy_state(rpc_url: &str) -> State { provider.clone(), settings.blockchain.token_address.parse().unwrap(), ); - let graph_client = GraphClient::new( - provider.clone(), - &settings.blockchain.graph_url, - None, - settings.blockchain.graph_health_check_timeout_in_sec, - ); State { deposit_hash_tree: DepositHashTree::new(), @@ -71,6 +62,5 @@ pub async fn get_dummy_state(rpc_url: &str) -> State { minter, token, provider, - graph_client, } } From 0ec9b0a8bd9780beace2d68867c8b38bb07c6776 Mon Sep 17 00:00:00 2001 From: kbizikav <132550763+kbizikav@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:11:58 +0700 Subject: [PATCH 4/9] chore: delete the graph --- src/external_api/graph/client.rs | 414 ------------------------------- src/external_api/graph/error.rs | 13 - src/external_api/graph/mod.rs | 2 - src/external_api/mod.rs | 1 - 4 files changed, 430 deletions(-) delete mode 100644 src/external_api/graph/client.rs delete mode 100644 src/external_api/graph/error.rs delete mode 100644 src/external_api/graph/mod.rs diff --git a/src/external_api/graph/client.rs b/src/external_api/graph/client.rs deleted file mode 100644 index f5c0cd6..0000000 --- a/src/external_api/graph/client.rs +++ /dev/null @@ -1,414 +0,0 @@ -use alloy::consensus::Transaction; -use alloy::primitives::{Address, TxHash}; -use intmax2_zkp::ethereum_types::bytes32::Bytes32; -use intmax2_zkp::ethereum_types::u256::U256; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use serde_with::serde_as; -use serde_with::DisplayFromStr; - -use crate::external_api::contracts::utils::get_batch_transaction; -use crate::external_api::{ - contracts::{ - events::{DepositLeafInserted, Deposited}, - utils::NormalProvider, - }, - query::post_request_with_bearer_token, -}; - -use super::error::GraphClientError; - -// A wrapper around TheGraphClient that provides additional functionality for interacting with the L1 and L2 providers. -#[derive(Clone, Debug)] -pub struct GraphClient { - pub url: String, - pub bearer_token: Option, - pub client: Client, - pub provider: NormalProvider, - pub health_check_timeout: u64, -} - -impl GraphClient { - pub fn new( - provider: NormalProvider, - url: &str, - bearer_token: Option, - health_check_timeout: u64, - ) -> Self { - let client = Client::new(); - GraphClient { - client, - provider, - url: url.to_string(), - bearer_token, - health_check_timeout, - } - } - - // get all deposited events by sender address - pub async fn get_deposited_event_by_sender( - &self, - deposit_address: Address, - ) -> Result, GraphClientError> { - let query = r#" - query MyQuery($senderAddress: String!) { - depositeds( - where: { sender: $senderAddress }, - orderBy: blockTimestamp, - orderDirection: asc - ) { - sender - tokenIndex - transactionHash - blockTimestamp - depositId - recipientSaltHash - amount - } - } - "#; - let request = json!({ - "query": query, - "variables": { - "senderAddress": deposit_address, - } - }); - let response: GraphQLResponse = post_request_with_bearer_token( - &self.url, - "", - self.bearer_token.clone(), - Some(&request), - ) - .await?; - - let tx_hashes = response - .data - .depositeds - .iter() - .map(|entry| entry.transaction_hash) - .collect::>(); - let txs = get_batch_transaction(&self.provider, &tx_hashes).await?; - - let mut deposited = Vec::new(); - for (event, tx) in response.data.depositeds.iter().zip(txs.iter()) { - deposited.push(Deposited { - sender: event.sender, - token_index: event.token_index, - deposit_id: event.deposit_id, - recipient_salt_hash: event.recipient_salt_hash, - amount: event.amount, - tx_nonce: tx.nonce(), - timestamp: event.block_timestamp, - }); - } - - Ok(deposited) - } - - pub async fn get_deposit_leaf_inserted_event( - &self, - next_deposit_index: u32, - ) -> Result, GraphClientError> { - let limit = 1000; // Adjust the limit as needed - let mut deposit_leaf_inserteds = Vec::new(); - let mut current_index = next_deposit_index; - loop { - let entries = self - .get_deposit_leaf_inserted_event_inner(current_index, limit) - .await?; - if entries.is_empty() { - break; - } - deposit_leaf_inserteds.extend(entries); - current_index = deposit_leaf_inserteds.last().unwrap().deposit_index + 1; - } - Ok(deposit_leaf_inserteds) - } - - async fn get_deposit_leaf_inserted_event_inner( - &self, - next_deposit_index: u32, - limit: u64, - ) -> Result, GraphClientError> { - let query = r#" - query MyQuery($nextDepositIndex: BigInt!, $limit: Int!) { - depositLeafInserteds( - where: { depositIndex_gte: $nextDepositIndex }, - orderBy: depositIndex, - orderDirection: asc, - first: $limit - ) { - blockNumber - depositIndex - depositHash - } - } - "#; - let request = json!({ - "query": query, - "variables": { - "nextDepositIndex": next_deposit_index, - "limit": limit, - } - }); - let response: GraphQLResponse = post_request_with_bearer_token( - &self.url, - "", - self.bearer_token.clone(), - Some(&request), - ) - .await?; - - let deposit_leaf_inserteds = response - .data - .deposit_leaf_inserteds - .into_iter() - .map(|entry| DepositLeafInserted { - deposit_index: entry.deposit_index, - deposit_hash: entry.deposit_hash, - block_number: entry.block_number, - }) - .collect::>(); - Ok(deposit_leaf_inserteds) - } - - pub async fn get_latest_deposit_timestamp( - &self, - sender: Address, - ) -> Result, GraphClientError> { - let mut depositeds = self.get_deposited_event_by_sender(sender).await?; - depositeds.sort_by_key(|d| d.timestamp); - if let Some(latest_deposit) = depositeds.last() { - Ok(Some(latest_deposit.timestamp)) - } else { - Ok(None) - } - } - - pub async fn get_latest_withdrawal_timestamp( - &self, - recipient: Address, - ) -> Result, GraphClientError> { - let query = r#" - query MyQuery($recipientAddress: String!) { - withdrawns( - where: { recipient: $recipientAddress }, - orderBy: blockTimestamp, - orderDirection: desc, - first: 1 - ) { - blockTimestamp - } - } - "#; - let request = json!({ - "query": query, - "variables": { - "recipientAddress": recipient, - } - }); - let response: GraphQLResponse = post_request_with_bearer_token( - &self.url, - "", - self.bearer_token.clone(), - Some(&request), - ) - .await?; - let timestamp = response - .data - .withdrawns - .first() - .map(|entry| entry.block_timestamp); - Ok(timestamp) - } - - pub async fn health_check(&self) -> Result<(), GraphClientError> { - let query = r#" - query MyQuery { - _meta { - block { - timestamp - } - hasIndexingErrors - } - } - "#; - let request = json!({ - "query": query, - }); - let response: GraphQLResponse = post_request_with_bearer_token( - &self.url, - "", - self.bearer_token.clone(), - Some(&request), - ) - .await?; - - if response.data.meta.has_indexing_errors { - return Err(GraphClientError::HealthCheckError( - "Graph has indexing errors".to_string(), - )); - } - let timestamp = response.data.meta.block.timestamp; - let current_timestamp = chrono::Utc::now().timestamp() as u64; - if current_timestamp.saturating_sub(timestamp) > self.health_check_timeout { - return Err(GraphClientError::HealthCheckError(format!( - "Graph is not up to date. Last update was at {} seconds ago", - current_timestamp.saturating_sub(timestamp) - ))); - } - Ok(()) - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GraphQLResponse { - pub data: T, -} - -#[derive(Debug, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct DepositedsData { - pub depositeds: Vec, -} - -#[serde_as] -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DepositedEntry { - #[serde_as(as = "DisplayFromStr")] - pub deposit_id: u64, - pub sender: Address, - pub recipient_salt_hash: Bytes32, - #[serde_as(as = "DisplayFromStr")] - pub token_index: u32, - pub amount: U256, - pub transaction_hash: TxHash, - #[serde_as(as = "DisplayFromStr")] - pub block_timestamp: u64, -} - -#[derive(Debug, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct DepositLeafInsertedsData { - pub deposit_leaf_inserteds: Vec, -} - -#[serde_as] -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DepositLeafInsertedEntry { - #[serde_as(as = "DisplayFromStr")] - pub deposit_index: u32, - pub deposit_hash: Bytes32, - #[serde_as(as = "DisplayFromStr")] - pub block_number: u64, -} - -#[derive(Debug, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct WithdrawnData { - pub withdrawns: Vec, -} - -#[serde_as] -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct WithdrawnEntry { - #[serde_as(as = "DisplayFromStr")] - pub block_timestamp: u64, -} - -#[derive(Debug, Deserialize, Serialize)] -struct MetaData { - #[serde(rename = "_meta")] - meta: MetaEntry, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -struct MetaEntry { - block: Block, - has_indexing_errors: bool, -} - -#[serde_as] -#[derive(Debug, Deserialize, Serialize)] -struct Block { - timestamp: u64, -} - -#[cfg(test)] -mod tests { - use std::env; - - use super::*; - use crate::external_api::contracts::utils::get_provider; - - fn get_client() -> Result { - let graph_url = - env::var("GRAPH_URL").unwrap_or_else(|_| "http://example.com/graphql".to_string()); - let rpc_url = env::var("RPC_URL").unwrap_or_else(|_| "http://localhost:8545".to_string()); - let provider = get_provider(&rpc_url).unwrap(); - - Ok(GraphClient::new(provider, &graph_url, None, 60)) - } - - #[tokio::test] - async fn test_graph_client_get_deposited_event_by_sender() { - let client = get_client().unwrap(); - let result = client - .get_deposited_event_by_sender( - "0x4c5187eea6df32a4a2eadb3459a395c83309f0be" - .parse() - .unwrap(), - ) - .await - .unwrap(); - dbg!(&result); - } - - #[tokio::test] - async fn test_graph_client_deposit_leaf_inserted_event() { - let client = get_client().unwrap(); - let result = client.get_deposit_leaf_inserted_event(0).await.unwrap(); - dbg!(&result.len()); - } - - #[tokio::test] - async fn test_graph_client_get_latest_deposit_timestamp() { - let client = get_client().unwrap(); - let result = client - .get_latest_deposit_timestamp( - "0x4c5187eea6df32a4a2eadb3459a395c83309f0be" - .parse() - .unwrap(), - ) - .await - .unwrap(); - dbg!(&result); - } - - #[tokio::test] - async fn test_graph_client_get_latest_withdrawal_timestamp() { - let client = get_client().unwrap(); - let result = client - .get_latest_withdrawal_timestamp( - "0xC2233d8937d2581F374caB4C2E89257828bB1BF8" - .parse() - .unwrap(), - ) - .await - .unwrap(); - dbg!(&result); - } - - #[tokio::test] - async fn test_graph_client_health_check() { - let client = get_client().unwrap(); - let result = client.health_check().await; - assert!(result.is_ok(), "Health check failed: {:?}", result); - } -} diff --git a/src/external_api/graph/error.rs b/src/external_api/graph/error.rs deleted file mode 100644 index 4f16742..0000000 --- a/src/external_api/graph/error.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::external_api::{contracts::error::BlockchainError, query::RequestError}; - -#[derive(Debug, thiserror::Error)] -pub enum GraphClientError { - #[error("Blockchain error: {0}")] - BlockchainError(#[from] BlockchainError), - - #[error("Request error: {0}")] - RequestError(#[from] RequestError), - - #[error("Health check error: {0}")] - HealthCheckError(String), -} diff --git a/src/external_api/graph/mod.rs b/src/external_api/graph/mod.rs deleted file mode 100644 index f27e761..0000000 --- a/src/external_api/graph/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod client; -pub mod error; diff --git a/src/external_api/mod.rs b/src/external_api/mod.rs index 6d20559..8887ddb 100644 --- a/src/external_api/mod.rs +++ b/src/external_api/mod.rs @@ -1,5 +1,4 @@ pub mod contracts; pub mod github; -pub mod graph; pub mod intmax; pub mod query; From 2a8875e34e2e564821adad784a27f77dea83140c Mon Sep 17 00:00:00 2001 From: kbizikav <132550763+kbizikav@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:12:38 +0700 Subject: [PATCH 5/9] chore: delete graph related configs --- config/config.base.toml | 2 -- config/config.mainnet.toml | 2 -- src/utils/config.rs | 2 -- 3 files changed, 6 deletions(-) diff --git a/config/config.base.toml b/config/config.base.toml index da1c83c..0e27e7e 100644 --- a/config/config.base.toml +++ b/config/config.base.toml @@ -13,8 +13,6 @@ gnark_get_proof_cooldown_in_sec = 60 [blockchain] chain_id = 8453 -graph_url = "https://v1.base-prod.graph.intmax.xyz/subgraphs/name/prod-int1" -graph_health_check_timeout_in_sec = 60 int1_address = "0x195F9b5F42435bB71E9765E66a9bdFE40d44A895" minter_address = "0x1f5a5Eb6C3894351425BD51ea2AE1ef3FEC93976" token_address = "0xf95117e3a5B7968703CeD3B66A9CbE0Bc9e1D8bf" diff --git a/config/config.mainnet.toml b/config/config.mainnet.toml index e9e50de..6a70bce 100644 --- a/config/config.mainnet.toml +++ b/config/config.mainnet.toml @@ -13,8 +13,6 @@ gnark_get_proof_cooldown_in_sec = 60 [blockchain] chain_id = 1 -graph_url = "https://mainnet.graph.intmax.xyz/subgraphs/name/legacy-int1" -graph_health_check_timeout_in_sec = 60 int1_address = "0x0Ac498bCA32B00E2584171844fb9A5943398D9c1" minter_address = "0x38DE07d2526Ae929f1903E5F109B70C50e12A8E0" token_address = "0xe24e207c6156241cAfb41D025B3b5F0677114C81" diff --git a/src/utils/config.rs b/src/utils/config.rs index 41f3499..68f192b 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -43,8 +43,6 @@ pub struct Api { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Blockchain { pub chain_id: u64, - pub graph_url: String, - pub graph_health_check_timeout_in_sec: u64, pub int1_address: String, pub minter_address: String, pub token_address: String, From f78e76743c1fe2b621ef34a3784b3e78f7c01bf1 Mon Sep 17 00:00:00 2001 From: kbizikav <132550763+kbizikav@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:19:58 +0700 Subject: [PATCH 6/9] chore: minor fix of tests --- src/external_api/intmax/event.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/external_api/intmax/event.rs b/src/external_api/intmax/event.rs index 42300d3..1bfc12d 100644 --- a/src/external_api/intmax/event.rs +++ b/src/external_api/intmax/event.rs @@ -98,10 +98,10 @@ mod tests { #[tokio::test] async fn test_get_deposit_events() { let rpc_url = env::var("RPC_URL").unwrap_or_else(|_| "http://localhost:8545".to_string()); + let address = env::var("DEPOSIT_ADDRESS") + .map(|s| s.parse().unwrap()) + .unwrap_or_default(); let provider = get_provider(&rpc_url).unwrap(); - let address: Address = "0x5C6Ad5968041d3C31F01f9AaADd5feF31C56050b" - .parse() - .unwrap(); let result = get_deposit_events(&provider, address).await; assert!(result.is_ok()); } From eb02a418b945afec792dea338b7355d4ca637821 Mon Sep 17 00:00:00 2001 From: kbizikav <132550763+kbizikav@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:21:09 +0700 Subject: [PATCH 7/9] chore: bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4f0abb..6edbdb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2716,7 +2716,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mining-cli" -version = "1.3.2" +version = "1.3.3" dependencies = [ "aes-gcm", "alloy", diff --git a/Cargo.toml b/Cargo.toml index 02b49c9..e969ee7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mining-cli" -version = "1.3.2" +version = "1.3.3" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 355b8f49e5aa7dfc104f4bd73d12f9a3b2907ffa Mon Sep 17 00:00:00 2001 From: kbizikav <132550763+kbizikav@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:23:53 +0700 Subject: [PATCH 8/9] fix: fix typo of src/external_api/intmax/event.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/external_api/intmax/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/external_api/intmax/event.rs b/src/external_api/intmax/event.rs index 1bfc12d..c49d2a9 100644 --- a/src/external_api/intmax/event.rs +++ b/src/external_api/intmax/event.rs @@ -42,7 +42,7 @@ pub async fn get_deposit_events( provider: &NormalProvider, sender: Address, ) -> Result, IntmaxError> { - info!("get_availability"); + info!("Fetching deposit events"); let settings = Settings::load().unwrap(); let response = with_retry(|| async { reqwest::Client::new() From 9b168a19867ac32e935ba3e8b00365e0b7ca6ad4 Mon Sep 17 00:00:00 2001 From: kbizikav <132550763+kbizikav@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:24:13 +0700 Subject: [PATCH 9/9] fix: fix typo of src/external_api/intmax/event.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/external_api/intmax/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/external_api/intmax/event.rs b/src/external_api/intmax/event.rs index c49d2a9..aa27c87 100644 --- a/src/external_api/intmax/event.rs +++ b/src/external_api/intmax/event.rs @@ -56,7 +56,7 @@ pub async fn get_deposit_events( .await }) .await - .map_err(|_| IntmaxError::NetworkError("failed to request availability server".to_string()))?; + .map_err(|_| IntmaxError::NetworkError("failed to request event server".to_string()))?; let response_json: EventServerResponse = response .json() .await