diff --git a/Justfile b/Justfile index cf7c833e..277403c7 100644 --- a/Justfile +++ b/Justfile @@ -131,5 +131,6 @@ clean: rm -rf lez/sequencer/service/rocksdb rm -rf lez/indexer/service/rocksdb* rm -rf lez/wallet/configs/debug/storage.json + rm -rf lez/wallet/configs/debug/statistics.json rm -rf rocksdb* cd bedrock && docker compose down -v diff --git a/integration_tests/tests/bridge.rs b/integration_tests/tests/bridge.rs index 68088bc0..8da35f4d 100644 --- a/integration_tests/tests/bridge.rs +++ b/integration_tests/tests/bridge.rs @@ -440,7 +440,7 @@ async fn bedrock_deposit_claim_and_withdraw_round_trip_succeeds() -> anyhow::Res // Now claim funds from vault back to recipient let nonces = ctx - .wallet_mut() + .wallet() .get_accounts_nonces(&[recipient_id]) .await .context("Failed to get nonce for vault claim")?; diff --git a/lez/wallet-ffi/src/wallet.rs b/lez/wallet-ffi/src/wallet.rs index 703da3a5..79113824 100644 --- a/lez/wallet-ffi/src/wallet.rs +++ b/lez/wallet-ffi/src/wallet.rs @@ -86,7 +86,7 @@ fn c_str_to_path(ptr: *const c_char, name: &str) -> Result Result FfiCreateWalletOutput { let Ok(config_path) = c_str_to_path(config_path, "config_path") else { @@ -114,14 +114,14 @@ pub unsafe extern "C" fn wallet_ffi_create_new( return FfiCreateWalletOutput::default(); }; - let Ok(metrics_path) = c_str_to_path(metrics_path, "metrics_path") else { + let Ok(statistics_path) = c_str_to_path(statistics_path, "statistics_path") else { return FfiCreateWalletOutput::default(); }; match block_on(WalletCore::new_init_storage( config_path, storage_path, - metrics_path, + statistics_path, None, &password, )) { @@ -156,7 +156,7 @@ pub unsafe extern "C" fn wallet_ffi_create_new( /// # Parameters /// - `handle` - Valid wallet handle /// - `config_path`: Path to the wallet configuration file (JSON) -/// - `metrics_path`: Path to the wallet metrics file (JSON) +/// - `statistics_path`: Path to the wallet statistics file (JSON) /// /// # Returns /// - Opaque wallet handle on success @@ -169,7 +169,7 @@ pub unsafe extern "C" fn wallet_ffi_create_new( pub unsafe extern "C" fn wallet_ffi_open( config_path: *const c_char, storage_path: *const c_char, - metrics_path: *const c_char, + statistics_path: *const c_char, ) -> *mut WalletHandle { let Ok(config_path) = c_str_to_path(config_path, "config_path") else { return ptr::null_mut(); @@ -179,14 +179,14 @@ pub unsafe extern "C" fn wallet_ffi_open( return ptr::null_mut(); }; - let Ok(metrics_path) = c_str_to_path(metrics_path, "metrics_path") else { + let Ok(statistics_path) = c_str_to_path(statistics_path, "statistics_path") else { return ptr::null_mut(); }; match block_on(WalletCore::new_update_chain( config_path, storage_path, - metrics_path, + statistics_path, None, )) { Ok(core) => { @@ -250,7 +250,7 @@ pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfi match wallet .store_persistent_data() - .and_then(|()| block_on(wallet.store_metrics())) + .and_then(|()| block_on(wallet.dump_statistics())) { Ok(()) => WalletFfiError::Success, Err(e) => { diff --git a/lez/wallet-ffi/wallet_ffi.h b/lez/wallet-ffi/wallet_ffi.h index 4b4ef549..4a795c23 100644 --- a/lez/wallet-ffi/wallet_ffi.h +++ b/lez/wallet-ffi/wallet_ffi.h @@ -1612,7 +1612,7 @@ enum WalletFfiError wallet_ffi_vault_claim_private(struct WalletHandle *handle, * # Parameters * - `config_path`: Path to the wallet configuration file (JSON) * - `storage_path`: Path where wallet data will be stored - * - `metrics_path`: Path to the wallet metrics file (JSON) + * - `statistics_path`: Path to the wallet statistics file (JSON) * - `password`: Password for encrypting the wallet seed * * # Returns @@ -1624,7 +1624,7 @@ enum WalletFfiError wallet_ffi_vault_claim_private(struct WalletHandle *handle, */ struct FfiCreateWalletOutput wallet_ffi_create_new(const char *config_path, const char *storage_path, - const char *metrics_path, + const char *statistics_path, const char *password); /** @@ -1635,7 +1635,7 @@ struct FfiCreateWalletOutput wallet_ffi_create_new(const char *config_path, * # Parameters * - `handle` - Valid wallet handle * - `config_path`: Path to the wallet configuration file (JSON) - * - `metrics_path`: Path to the wallet metrics file (JSON) + * - `statistics_path`: Path to the wallet statistics file (JSON) * * # Returns * - Opaque wallet handle on success @@ -1647,7 +1647,7 @@ struct FfiCreateWalletOutput wallet_ffi_create_new(const char *config_path, */ struct WalletHandle *wallet_ffi_open(const char *config_path, const char *storage_path, - const char *metrics_path); + const char *statistics_path); /** * Destroy a wallet handle and free its resources. diff --git a/lez/wallet/configs/debug/wallet_config.json b/lez/wallet/configs/debug/wallet_config.json index 995007fe..c8bd872b 100644 --- a/lez/wallet/configs/debug/wallet_config.json +++ b/lez/wallet/configs/debug/wallet_config.json @@ -1,9 +1,10 @@ { - "sequencers_conn_data": [{ + "sequencers": [{ "sequencer_addr": "http://127.0.0.1:3040" }], "seq_poll_timeout": "30s", "seq_tx_poll_max_blocks": 15, "seq_poll_max_retries": 10, - "seq_block_poll_max_amount": 100 + "seq_block_poll_max_amount": 100, + "calibration_limit": 100 } \ No newline at end of file diff --git a/lez/wallet/src/cli/config.rs b/lez/wallet/src/cli/config.rs index b6d0c523..8c8c4b5c 100644 --- a/lez/wallet/src/cli/config.rs +++ b/lez/wallet/src/cli/config.rs @@ -36,8 +36,8 @@ impl ConfigSubcommand { println!("{config_str}"); } else if let Some(key) = key { match key.as_str() { - "sequencer_addr" => { - println!("{:?}", config.sequencers_conn_data); + "sequencers" => { + println!("{:?}", config.sequencers); } "seq_poll_timeout" => { println!("{:?}", config.seq_poll_timeout); diff --git a/lez/wallet/src/cli/mod.rs b/lez/wallet/src/cli/mod.rs index d01cd860..d7ef49bd 100644 --- a/lez/wallet/src/cli/mod.rs +++ b/lez/wallet/src/cli/mod.rs @@ -107,9 +107,6 @@ pub struct Args { /// Continious run flag. #[arg(short, long)] pub continuous_run: bool, - /// Basic authentication in the format `user` or `user:password`. - #[arg(long)] - pub auth: Option, /// Wallet command. #[command(subcommand)] pub command: Option, @@ -296,11 +293,11 @@ pub async fn execute_subcommand( }; // Kind of a sledgehammer solution, but it is not clear if there is the case to not store - // metrics + // statistics wallet_core - .store_metrics() + .dump_statistics() .await - .context("Failed to store metrics")?; + .context("Failed to store statistics")?; Ok(subcommand_ret) } diff --git a/lez/wallet/src/config.rs b/lez/wallet/src/config.rs index 0063c06d..cb09f5c2 100644 --- a/lez/wallet/src/config.rs +++ b/lez/wallet/src/config.rs @@ -38,7 +38,7 @@ pub struct GasConfig { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WalletConfig { /// Connection data of all known sequencers. - pub sequencers_conn_data: Vec, + pub sequencers: Vec, /// Sequencer polling duration for new blocks. #[serde(with = "humantime_serde")] pub seq_poll_timeout: Duration, @@ -48,14 +48,14 @@ pub struct WalletConfig { pub seq_poll_max_retries: u64, /// Max amount of blocks to poll in one request. pub seq_block_poll_max_amount: u64, - /// Limit number of sequencer polls during callibration, should not be zero - pub callibration_limit: usize, + /// Limit number of sequencer polls during calibration, should not be zero + pub calibration_limit: usize, } impl Default for WalletConfig { fn default() -> Self { Self { - sequencers_conn_data: vec![SequencerConnectionData { + sequencers: vec![SequencerConnectionData { sequencer_addr: "http://127.0.0.1:3040".parse().unwrap(), basic_auth: None, }], @@ -63,7 +63,7 @@ impl Default for WalletConfig { seq_tx_poll_max_blocks: 5, seq_poll_max_retries: 5, seq_block_poll_max_amount: 100, - callibration_limit: 100, + calibration_limit: 100, } } } @@ -108,26 +108,26 @@ impl WalletConfig { pub fn apply_overrides(&mut self, overrides: WalletConfigOverrides) { let Self { - sequencers_conn_data, + sequencers, seq_poll_timeout, seq_tx_poll_max_blocks, seq_poll_max_retries, seq_block_poll_max_amount, - callibration_limit, + calibration_limit, } = self; let WalletConfigOverrides { - sequencers_conn_data: o_sequencers_conn_data, + sequencers: o_sequencers, seq_poll_timeout: o_seq_poll_timeout, seq_tx_poll_max_blocks: o_seq_tx_poll_max_blocks, seq_poll_max_retries: o_seq_poll_max_retries, seq_block_poll_max_amount: o_seq_block_poll_max_amount, - callibration_limit: o_callibration_limit, + calibration_limit: o_calibration_limit, } = overrides; - if let Some(v) = o_sequencers_conn_data { - warn!("Overriding wallet config 'sequencers_conn_data' to {v:?}"); - *sequencers_conn_data = v; + if let Some(v) = o_sequencers { + warn!("Overriding wallet config 'sequencers' to {v:?}"); + *sequencers = v; } if let Some(v) = o_seq_poll_timeout { warn!("Overriding wallet config 'seq_poll_timeout' to {v:?}"); @@ -145,9 +145,9 @@ impl WalletConfig { warn!("Overriding wallet config 'seq_block_poll_max_amount' to {v}"); *seq_block_poll_max_amount = v; } - if let Some(v) = o_callibration_limit { - warn!("Overriding wallet config 'callibration_limit' to {v}"); - *callibration_limit = v; + if let Some(v) = o_calibration_limit { + warn!("Overriding wallet config 'calibration_limit' to {v}"); + *calibration_limit = v; } } } diff --git a/lez/wallet/src/helperfunctions.rs b/lez/wallet/src/helperfunctions.rs index 3898d94c..0af99c1f 100644 --- a/lez/wallet/src/helperfunctions.rs +++ b/lez/wallet/src/helperfunctions.rs @@ -66,13 +66,13 @@ pub fn fetch_persistent_storage_path() -> Result { Ok(accs_path) } -/// Fetch path to metrics storage from default home. +/// Fetch path to statistics storage from default home. /// /// File must be created through setup beforehand. -pub fn fetch_metrics_path() -> Result { +pub fn fetch_statistics_path() -> Result { let home = get_home()?; - let metrics_path = home.join("metrics.json"); - Ok(metrics_path) + let statistics_path = home.join("statistics.json"); + Ok(statistics_path) } #[expect(dead_code, reason = "Maybe used later")] diff --git a/lez/wallet/src/lib.rs b/lez/wallet/src/lib.rs index d9f1b891..b884f4eb 100644 --- a/lez/wallet/src/lib.rs +++ b/lez/wallet/src/lib.rs @@ -10,7 +10,6 @@ use std::{ collections::{BTreeMap, HashMap, HashSet}, path::PathBuf, - sync::Arc, }; pub use account_manager::AccountIdentity; @@ -33,13 +32,13 @@ use lee_core::{ use log::info; use sequencer_service_rpc::{RpcClient as _, SequencerClient}; use storage::Storage; -use tokio::{io::AsyncWriteExt as _, sync::RwLock}; +use tokio::io::AsyncWriteExt as _; use url::Url; use crate::{ account::{AccountIdWithPrivacy, Label}, config::WalletConfigOverrides, - multi_client::{Metrics, MetricsUpdate, MultiSequencerClient, extract_metrics_from_path}, + multi_client::{MultiSequencerClient, Statistics, extract_statistics_from_path}, poller::TxPoller, storage::key_chain::{NullifierIndex, SharedAccountEntry}, }; @@ -99,12 +98,8 @@ pub struct WalletCore { storage: Storage, storage_path: PathBuf, - metrics_path: PathBuf, - metrics: HashMap, - /// Wrapping metric updates in Arc to not break interfaces too much. - /// - /// It is assumed, that wallet methods can be accesed via immutable reference. - metric_updates: Arc>>, + statistics_path: PathBuf, + statistics: HashMap, multi_sequencer_client: MultiSequencerClient, } @@ -114,15 +109,15 @@ impl WalletCore { pub async fn from_env() -> Result { let config_path = helperfunctions::fetch_config_path()?; let storage_path = helperfunctions::fetch_persistent_storage_path()?; - let metrics_path = helperfunctions::fetch_metrics_path()?; + let statistics_path = helperfunctions::fetch_statistics_path()?; - Self::new_update_chain(config_path, storage_path, metrics_path, None).await + Self::new_update_chain(config_path, storage_path, statistics_path, None).await } pub async fn new_update_chain( config_path: PathBuf, storage_path: PathBuf, - metrics_path: PathBuf, + statistics_path: PathBuf, config_overrides: Option, ) -> Result { let storage = Storage::from_path(&storage_path) @@ -131,7 +126,7 @@ impl WalletCore { Self::new( config_path, storage_path, - metrics_path, + statistics_path, config_overrides, storage, ) @@ -141,7 +136,7 @@ impl WalletCore { pub async fn new_init_storage( config_path: PathBuf, storage_path: PathBuf, - metrics_path: PathBuf, + statistics_path: PathBuf, config_overrides: Option, password: &str, ) -> Result<(Self, Mnemonic)> { @@ -149,7 +144,7 @@ impl WalletCore { let wallet = Self::new( config_path, storage_path, - metrics_path, + statistics_path, config_overrides, storage, ) @@ -161,7 +156,7 @@ impl WalletCore { async fn new( config_path: PathBuf, storage_path: PathBuf, - metrics_path: PathBuf, + statistics_path: PathBuf, config_overrides: Option, storage: Storage, ) -> Result { @@ -176,12 +171,12 @@ impl WalletCore { config.apply_overrides(config_overrides); } - let mut metrics = extract_metrics_from_path(&metrics_path)?; + let mut statistics = extract_statistics_from_path(&statistics_path)?; let multi_sequencer_client = MultiSequencerClient::new( - &config.sequencers_conn_data, - &mut metrics, - config.callibration_limit, + &config.sequencers, + &mut statistics, + config.calibration_limit, ) .await?; @@ -189,11 +184,10 @@ impl WalletCore { config_path, config_overrides, config, - storage_path, storage, - metrics_path, - metrics, - metric_updates: Arc::new(RwLock::new(vec![])), + storage_path, + statistics_path, + statistics, multi_sequencer_client, }) } @@ -215,12 +209,12 @@ impl WalletCore { #[must_use] pub fn leader_owned(&self) -> SequencerClient { - self.multi_sequencer_client.leader.clone() + self.multi_sequencer_client.leader().clone() } #[must_use] pub fn leader_url(&self) -> Url { - self.multi_sequencer_client.leader_url.clone() + self.multi_sequencer_client.leader_url().clone() } /// Get storage. @@ -259,39 +253,26 @@ impl WalletCore { Ok(()) } - async fn update_metrics(&mut self) -> Result<()> { - let leader_metric = self - .metrics - .get_mut(&self.multi_sequencer_client.leader_url) - .ok_or_else(|| anyhow::anyhow!("Leader URL is not present in metrics"))?; + pub async fn dump_statistics(&mut self) -> Result<()> { + let leader_statistic = self + .statistics + .get_mut(self.multi_sequencer_client.leader_url()) + .ok_or_else(|| anyhow::anyhow!("Leader URL is not present in statistics"))?; - { - let metric_updates = self.metric_updates.read().await; - leader_metric.apply_updates(metric_updates.as_ref()); - } + self.multi_sequencer_client + .update_statistics(leader_statistic) + .await; - // Clear updates - { - let mut metric_updates = self.metric_updates.write().await; - metric_updates.clear(); - } - - Ok(()) - } - - pub async fn store_metrics(&mut self) -> Result<()> { - self.update_metrics().await?; - - let metrics_serialized = serde_json::to_vec_pretty(&self.metrics)?; - let mut file = tokio::fs::File::create(&self.metrics_path) + let statistics_serialized = serde_json::to_vec_pretty(&self.statistics)?; + let mut file = tokio::fs::File::create(&self.statistics_path) .await .context("Failed to create file")?; - file.write_all(&metrics_serialized) + file.write_all(&statistics_serialized) .await .context("Failed to write to file")?; file.sync_all().await.context("Failed to sync file")?; - println!("Stored metrics at {}", self.metrics_path.display()); + println!("Stored statistics at {}", self.statistics_path.display()); Ok(()) } @@ -488,37 +469,26 @@ impl WalletCore { } #[must_use] - pub fn get_metrics(&self, sequencer_url: &Url) -> Option<&Metrics> { - self.metrics.get(sequencer_url) + pub fn get_statistics(&self, sequencer_url: &Url) -> Option<&Statistics> { + self.statistics.get(sequencer_url) } /// Get account balance. pub async fn get_account_balance(&self, acc: AccountId) -> Result { - let call_f = async |client: &SequencerClient| client.get_account_balance(acc).await; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(call_res?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| client.get_account_balance(acc).await) + .await?) } /// Get accounts nonces. pub async fn get_accounts_nonces(&self, accs: &[AccountId]) -> Result> { - let call_f = - async |client: &SequencerClient| client.get_accounts_nonces(accs.to_vec()).await; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(call_res?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| { + client.get_accounts_nonces(accs.to_vec()).await + }) + .await?) } pub async fn get_account(&self, account_id: AccountIdWithPrivacy) -> Result { @@ -535,59 +505,35 @@ impl WalletCore { } pub async fn get_last_block_id(&self) -> Result { - let call_f = async |client: &SequencerClient| client.get_last_block_id().await; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(call_res?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| client.get_last_block_id().await) + .await?) } pub async fn get_block(&self, block_id: u64) -> Result> { - let call_f = async |client: &SequencerClient| client.get_block(block_id).await; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(call_res?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| client.get_block(block_id).await) + .await?) } pub async fn get_transaction( &self, hash: HashType, ) -> Result> { - let call_f = async |client: &SequencerClient| client.get_transaction(hash).await; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(call_res?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| client.get_transaction(hash).await) + .await?) } /// Get public account. pub async fn get_account_public(&self, account_id: AccountId) -> Result { - let call_f = async |client: &SequencerClient| client.get_account(account_id).await; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(call_res?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| client.get_account(account_id).await) + .await?) } #[must_use] @@ -623,16 +569,10 @@ impl WalletCore { } pub async fn get_program_ids(&self) -> Result> { - let call_f = async |client: &SequencerClient| client.get_program_ids().await; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(call_res?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| client.get_program_ids().await) + .await?) } /// Poll transactions. @@ -647,18 +587,12 @@ impl WalletCore { &self, commitments: Vec, ) -> Result<(Vec>, CommitmentSetDigest)> { - let call_f = - async |client: &SequencerClient| client.get_proofs_and_root(commitments.clone()).await; - - let (proofs_and_root, metrics_update) = - self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(proofs_and_root?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| { + client.get_proofs_and_root(commitments.clone()).await + }) + .await?) } pub fn decode_insert_privacy_preserving_transaction_results( @@ -797,18 +731,14 @@ impl WalletCore { .map(|keys| keys.ssk) .collect(); - let call_f = async |client: &SequencerClient| { - client - .send_transaction(LeeTransaction::PrivacyPreserving(tx.clone())) - .await - }; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } + let call_res = self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| { + client + .send_transaction(LeeTransaction::PrivacyPreserving(tx.clone())) + .await + }) + .await; Ok((call_res?, shared_secrets)) } @@ -869,40 +799,28 @@ impl WalletCore { let tx = lee::public_transaction::PublicTransaction::new(message, witness_set); - let call_f = async |client: &SequencerClient| { - client - .send_transaction(LeeTransaction::Public(tx.clone())) - .await - }; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(call_res?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| { + client + .send_transaction(LeeTransaction::Public(tx.clone())) + .await + }) + .await?) } pub async fn send_program_deployment_transaction(&self, bytecode: Vec) -> Result { let message = lee::program_deployment_transaction::Message::new(bytecode); let transaction = ProgramDeploymentTransaction::new(message); - let call_f = async |client: &SequencerClient| { - client - .send_transaction(LeeTransaction::ProgramDeployment(transaction.clone())) - .await - }; - - let (call_res, metrics_update) = self.multi_sequencer_client.metered_call(call_f).await; - - { - let mut metric_updates_guard = self.metric_updates.write().await; - metric_updates_guard.push(metrics_update); - } - - Ok(call_res?) + Ok(self + .multi_sequencer_client + .metered_call(async |client: &SequencerClient| { + client + .send_transaction(LeeTransaction::ProgramDeployment(transaction.clone())) + .await + }) + .await?) } pub async fn sync_to_latest_block(&mut self) -> Result { diff --git a/lez/wallet/src/main.rs b/lez/wallet/src/main.rs index 8a66a642..c9cf0820 100644 --- a/lez/wallet/src/main.rs +++ b/lez/wallet/src/main.rs @@ -8,7 +8,7 @@ use clap::{CommandFactory as _, Parser as _}; use wallet::{ WalletCore, cli::{Args, execute_continuous_run, execute_subcommand, read_password_from_stdin}, - helperfunctions::{fetch_config_path, fetch_metrics_path, fetch_persistent_storage_path}, + helperfunctions::{fetch_config_path, fetch_persistent_storage_path, fetch_statistics_path}, }; // TODO #169: We have sample configs for sequencer, but not for wallet @@ -20,7 +20,6 @@ use wallet::{ async fn main() -> Result<()> { let Args { continuous_run, - auth: _auth, command, } = Args::parse(); @@ -29,11 +28,11 @@ async fn main() -> Result<()> { let config_path = fetch_config_path().context("Could not fetch config path")?; let storage_path = fetch_persistent_storage_path().context("Could not fetch persistent storage path")?; - let metrics_path = fetch_metrics_path().context("Could not fetch metrics path")?; + let statistics_path = fetch_statistics_path().context("Could not fetch statistics path")?; if let Some(command) = command { let mut wallet = if storage_path.exists() { - WalletCore::new_update_chain(config_path, storage_path, metrics_path, None).await? + WalletCore::new_update_chain(config_path, storage_path, statistics_path, None).await? } else { // TODO: Maybe move to `WalletCore::from_env()` or similar? @@ -43,7 +42,7 @@ async fn main() -> Result<()> { let (wallet, mnemonic) = WalletCore::new_init_storage( config_path, storage_path, - metrics_path, + statistics_path, None, &password, ) @@ -64,7 +63,7 @@ async fn main() -> Result<()> { Ok(()) } else if continuous_run { let mut wallet = - WalletCore::new_update_chain(config_path, storage_path, metrics_path, None).await?; + WalletCore::new_update_chain(config_path, storage_path, statistics_path, None).await?; execute_continuous_run(&mut wallet).await } else { let help = Args::command().render_long_help(); diff --git a/lez/wallet/src/multi_client.rs b/lez/wallet/src/multi_client.rs index f4b01e21..ee41aa06 100644 --- a/lez/wallet/src/multi_client.rs +++ b/lez/wallet/src/multi_client.rs @@ -7,33 +7,35 @@ reason = "Operated numbers is not big enough to have precision loss" )] -use std::{collections::HashMap, path::Path}; +use std::{collections::HashMap, path::Path, sync::Arc}; use anyhow::{Context as _, Result}; +use lee_core::BlockId; use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder}; use serde::{Deserialize, Serialize}; +use tokio::sync::RwLock; use url::Url; use crate::config::SequencerConnectionData; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Metrics { +pub struct Statistics { pub latency_avg: f32, pub latency_var: f32, pub sample_size: usize, - pub latest_block_id: u64, + pub latest_block_id: BlockId, pub errors: u64, } #[derive(Debug, Clone)] -pub struct MetricsUpdate { +pub struct StatisticsUpdate { pub latency: f32, - pub new_latest_block_id: Option, + pub new_latest_block_id: Option, pub is_failed: bool, } -impl Metrics { - pub fn apply_updates(&mut self, updates: &[MetricsUpdate]) { +impl Statistics { + pub fn apply_updates(&mut self, updates: &[StatisticsUpdate]) { let CumulativeUpdates { failure_count, latest_block_id, @@ -77,15 +79,19 @@ pub struct MultiSequencerClient { // For now we store only leader, it is possible, that // in future for important sends(for example for transactions) // we would want to distribute call between known sequencers - pub leader: SequencerClient, - pub leader_url: Url, + leader: SequencerClient, + leader_url: Url, + /// Wrapping statistic updates in Arc to not break interfaces too much. + /// + /// It is assumed, that wallet methods can be accesed via immutable reference. + statistic_updates: Arc>>, } impl MultiSequencerClient { pub async fn new( conn_data: &[SequencerConnectionData], - metrics: &mut HashMap, - callibration_limit: usize, + statistics: &mut HashMap, + calibration_limit: usize, ) -> Result { let mut client_list = HashMap::new(); @@ -112,66 +118,91 @@ impl MultiSequencerClient { .context("Failed to create sequencer client")? }; - // If there is no metrics for client, callibrate it - if metrics.contains_key(sequencer_addr) { + // If there is statistics for client, actualize it + if statistics.contains_key(sequencer_addr) { let metric_updates = actualize_client(&sequencer_client).await; log::debug!( "Metered call for {sequencer_addr:?}, metric updates is {metric_updates:?}" ); - let metric_mut = metrics.get_mut(sequencer_addr).unwrap(); + let metric_mut = statistics.get_mut(sequencer_addr).unwrap(); metric_mut.apply_updates(&[metric_updates]); - // Otherwise actualize client data + // Otherwise calibrate client data + } else if let Some(client_statistics) = + calibrate_client(&sequencer_client, calibration_limit).await + { + statistics.insert(sequencer_addr.clone(), client_statistics); } else { - metrics.insert( - sequencer_addr.clone(), - callibrate_client(&sequencer_client, callibration_limit).await, - ); + log::warn!("Client {sequencer_addr:?} failed all {calibration_limit} calibration attempts, it may be unhealthy. + \n Consider bumping calibration_limit or remove this client altogether"); } client_list.insert(sequencer_addr.clone(), sequencer_client); } - let (leader_url, leader) = choose_leader(&client_list, metrics) + let (leader_url, leader) = choose_leader(&client_list, statistics) .ok_or_else(|| anyhow::anyhow!("Failed to find leader"))?; log::info!("Chosen leader is {leader_url:?}"); // Dropping client list, for reasons why, see comment in structure definition. - Ok(Self { leader, leader_url }) + Ok(Self { + leader, + leader_url, + statistic_updates: Arc::new(RwLock::new(vec![])), + }) } #[must_use] - pub const fn leader_ref(&self) -> &SequencerClient { + pub const fn leader(&self) -> &SequencerClient { &self.leader } #[must_use] - pub fn leader_clone(&self) -> SequencerClient { - self.leader.clone() + pub const fn leader_url(&self) -> &Url { + &self.leader_url } // Keeping this call abstract, in case if we need to do more than one request pub async fn metered_call Result>( &self, call: I, - ) -> (Result, MetricsUpdate) { - let resp = tokio::join!(call(self.leader_ref()), actualize_client(self.leader_ref())); + ) -> Result { + let (resp, statistics_update) = + tokio::join!(call(self.leader()), actualize_client(self.leader())); log::debug!( "Metered call for {:?}, metric updates is {:?}", self.leader_url, - resp.1 + statistics_update ); + { + let mut statistic_updates_guard = self.statistic_updates.write().await; + statistic_updates_guard.push(statistics_update); + } + resp } + + pub async fn update_statistics(&self, leader_statistic: &mut Statistics) { + { + let statistic_updates = self.statistic_updates.read().await; + leader_statistic.apply_updates(statistic_updates.as_ref()); + } + + // Clear updates + { + let mut statistic_updates = self.statistic_updates.write().await; + statistic_updates.clear(); + } + } } struct CumulativeUpdates { pub failure_count: u64, - pub latest_block_id: Option, + pub latest_block_id: Option, /// Necessary for cumulative average calculation. pub cumulative_latency: f32, /// Necessary for cumulative variance calculation. @@ -180,12 +211,12 @@ struct CumulativeUpdates { } impl CumulativeUpdates { - fn from_metric_updates(metric_updates: &[MetricsUpdate]) -> Self { + fn from_metric_updates(metric_updates: &[StatisticsUpdate]) -> Self { let (failure_count, latest_block_id, cumulative_latency, cumulative_latency_squares) = metric_updates .iter() .fold((0_u64, None, 0_f32, 0_f32), |acc, x| { - let MetricsUpdate { + let StatisticsUpdate { latency, new_latest_block_id, is_failed, @@ -222,34 +253,45 @@ impl CumulativeUpdates { } } -pub fn extract_metrics_from_path(path: &Path) -> Result, anyhow::Error> { +pub fn extract_statistics_from_path( + path: &Path, +) -> Result, anyhow::Error> { match std::fs::File::open(path) { Ok(file) => { let reader = std::io::BufReader::new(file); Ok(serde_json::from_reader(reader)?) } Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - println!("Metrics not found, choosing empty"); + println!("Statistics not found, choosing empty"); Ok(HashMap::new()) } Err(err) => Err(err).context("IO error"), } } -pub async fn callibrate_client(client: &SequencerClient, callibration_limit: usize) -> Metrics { +/// Measuring `get_last_block_id` as it should be the fastest request on sequencer. +async fn measure_request_duration(client: &SequencerClient) -> (u128, Option) { + let now = tokio::time::Instant::now(); + let block_id = client.get_last_block_id().await.ok(); + ( + tokio::time::Instant::now().duration_since(now).as_millis(), + block_id, + ) +} + +pub async fn calibrate_client( + client: &SequencerClient, + calibration_limit: usize, +) -> Option { let mut latencies = vec![]; let mut latest_block_id = 0; let mut errors: u64 = 0; // ToDo: Add some DDoS adaptation - for _ in 0..callibration_limit { - let now = tokio::time::Instant::now(); + for _ in 0..calibration_limit { + let (latency, block_id) = measure_request_duration(client).await; - let block_id = client.get_last_block_id().await; - - let latency = tokio::time::Instant::now().duration_since(now).as_millis(); - - let Ok(block_id) = block_id else { + let Some(block_id) = block_id else { errors = errors.saturating_add(1); continue; }; @@ -258,7 +300,12 @@ pub async fn callibrate_client(client: &SequencerClient, callibration_limit: usi latencies.push(latency); } - // Precision loss if fine there + // There is no point in guard numbers, exclude client if it fails all requests. + if latencies.is_empty() { + return None; + } + + // Precision loss is fine there let sample_size = latencies.len(); #[expect(clippy::as_conversions, reason = "int to float conversion is safe")] let latency_avg = (latencies.iter().sum::() as f32) / (sample_size as f32); @@ -267,24 +314,22 @@ pub async fn callibrate_client(client: &SequencerClient, callibration_limit: usi ((*x as f32) - latency_avg).mul_add((*x as f32) - latency_avg, acc) }) / (sample_size as f32); - Metrics { + Some(Statistics { latency_avg, latency_var, sample_size, latest_block_id, errors, - } + }) } -pub async fn actualize_client(client: &SequencerClient) -> MetricsUpdate { - let now = tokio::time::Instant::now(); - - let block_id = client.get_last_block_id().await.ok(); +pub async fn actualize_client(client: &SequencerClient) -> StatisticsUpdate { + let (latency, block_id) = measure_request_duration(client).await; #[expect(clippy::as_conversions, reason = "int to float conversion is safe")] - let latency = tokio::time::Instant::now().duration_since(now).as_millis() as f32; + let latency = latency as f32; - MetricsUpdate { + StatisticsUpdate { latency, new_latest_block_id: block_id, is_failed: block_id.is_none(), @@ -294,14 +339,14 @@ pub async fn actualize_client(client: &SequencerClient) -> MetricsUpdate { #[must_use] pub fn choose_leader( client_list: &HashMap, - metrics: &HashMap, + statistics: &HashMap, ) -> Option<(Url, SequencerClient)> { let mut client_vec = vec![]; // Sort out all unmetered clients client_vec = client_list .keys() - .filter(|item| metrics.contains_key(*item)) + .filter(|item| statistics.contains_key(*item)) .collect(); if client_vec.is_empty() { @@ -310,8 +355,8 @@ pub fn choose_leader( // Considering the nature of our requests, the latest_block_id is the dominant characteristic let max_block_id_addr = client_vec.iter().fold(client_vec[0], |acc, x| { - let old_latest_block_id = metrics.get(acc).unwrap().latest_block_id; - let new_latest_block_id = metrics.get(*x).unwrap().latest_block_id; + let old_latest_block_id = statistics.get(acc).unwrap().latest_block_id; + let new_latest_block_id = statistics.get(*x).unwrap().latest_block_id; if new_latest_block_id > old_latest_block_id { *x } else { @@ -319,51 +364,64 @@ pub fn choose_leader( } }); - let max_block_id = metrics.get(max_block_id_addr).unwrap().latest_block_id; + let max_block_id = statistics.get(max_block_id_addr).unwrap().latest_block_id; // Sort out all clients running late client_vec = client_vec .iter() .filter_map(|x| { - let latest_block_id = metrics.get(*x).unwrap().latest_block_id; + let latest_block_id = statistics.get(*x).unwrap().latest_block_id; (latest_block_id == max_block_id).then_some(*x) }) .collect(); - // Get the clients with lesser or equal to average error count + // Get the clients with lesser or equal to average error ratio #[expect(clippy::as_conversions, reason = "int to float conversion is safe")] - let avg_err_count = (client_vec.iter().fold(0_u64, |acc, x| { - acc.saturating_add(metrics.get(*x).unwrap().errors) - }) as f32) - / (client_vec.len() as f32); + let avg_err_ratio = client_vec.iter().fold(0_f32, |acc, x| { + acc + error_ratio( + statistics.get(*x).unwrap().errors, + statistics.get(*x).unwrap().sample_size, + ) + }) / (client_vec.len() as f32); client_vec.sort_by(|a, b| { - metrics - .get(*a) - .unwrap() - .errors - .cmp(&metrics.get(*b).unwrap().errors) + let err_ratio_a = error_ratio( + statistics.get(*a).unwrap().errors, + statistics.get(*a).unwrap().sample_size, + ); + let err_ratio_b = error_ratio( + statistics.get(*b).unwrap().errors, + statistics.get(*b).unwrap().sample_size, + ); + + err_ratio_a + .partial_cmp(&err_ratio_b) + .expect("Ratios must be a valid numbers") }); - #[expect(clippy::as_conversions, reason = "int to float conversion is safe")] let client_vec = client_vec[..(client_vec .iter() - .position(|item| (metrics.get(*item).unwrap().errors as f32) > avg_err_count) + .position(|item| { + error_ratio( + statistics.get(*item).unwrap().errors, + statistics.get(*item).unwrap().sample_size, + ) > avg_err_ratio + }) .unwrap_or(client_vec.len()))] .to_vec(); // Choose clients with least latency and variance let min_lat_var_addr = client_vec.iter().fold(client_vec[0], |acc, x| { - let old = metrics.get(acc).unwrap(); + let old = statistics.get(acc).unwrap(); let (old_lat, old_var) = (old.latency_avg, old.latency_var); - let new = metrics.get(*x).unwrap(); + let new = statistics.get(*x).unwrap(); let (new_lat, new_var) = (new.latency_avg, new.latency_var); let new_std = new_var.sqrt(); let old_std = old_var.sqrt(); - // Client is better if its averabe is better and variance does not make it worse + // Client is better if its average is better and variance does not make it worse // So basically we want this: // [-old_std............new_lat.......old_lat...............+new_std.........+old_std] // @@ -447,6 +505,15 @@ fn cumulative_var( / (orig_size_f + mod_size_f) } +fn error_ratio(errors: u64, size: usize) -> f32 { + #[expect(clippy::as_conversions, reason = "int to float conversion is safe")] + let errors_f = errors as f32; + #[expect(clippy::as_conversions, reason = "int to float conversion is safe")] + let size_f = size as f32; + + errors_f / (errors_f + size_f) +} + #[cfg(test)] mod tests { use std::collections::HashMap; @@ -455,17 +522,18 @@ mod tests { use url::Url; use crate::multi_client::{ - CumulativeUpdates, Metrics, MetricsUpdate, choose_leader, cumulative_avg, cumulative_var, + CumulativeUpdates, Statistics, StatisticsUpdate, choose_leader, cumulative_avg, + cumulative_var, }; - fn update_metrics( - metrics: &mut HashMap, + fn update_statistics( + statistics: &mut HashMap, leader_url: &Url, - metric_updates: &[MetricsUpdate], + metric_updates: &[StatisticsUpdate], ) -> Result<(), anyhow::Error> { - let leader_metric = metrics + let leader_metric = statistics .get_mut(leader_url) - .ok_or_else(|| anyhow::anyhow!("Leader URL is not present in metrics"))?; + .ok_or_else(|| anyhow::anyhow!("Leader URL is not present in statistics"))?; leader_metric.apply_updates(metric_updates); @@ -479,18 +547,18 @@ mod tests { #[test] fn cumulative_updates_test() { - let metrics_updates_vec = vec![ - MetricsUpdate { + let statistics_updates_vec = vec![ + StatisticsUpdate { latency: 100_f32, new_latest_block_id: Some(15), is_failed: false, }, - MetricsUpdate { + StatisticsUpdate { latency: 115_f32, new_latest_block_id: Some(16), is_failed: false, }, - MetricsUpdate { + StatisticsUpdate { latency: 50_f32, new_latest_block_id: None, is_failed: true, @@ -503,7 +571,7 @@ mod tests { cumulative_latency, cumulative_latency_squares, additional_sample_size, - } = CumulativeUpdates::from_metric_updates(&metrics_updates_vec); + } = CumulativeUpdates::from_metric_updates(&statistics_updates_vec); let epsilon = 0.01_f32; @@ -600,18 +668,18 @@ mod tests { #[test] fn metric_updates_correctness() { - let metrics_updates_vec = vec![ - MetricsUpdate { + let statistics_updates_vec = vec![ + StatisticsUpdate { latency: 100_f32, new_latest_block_id: Some(105), is_failed: false, }, - MetricsUpdate { + StatisticsUpdate { latency: 115_f32, new_latest_block_id: Some(106), is_failed: false, }, - MetricsUpdate { + StatisticsUpdate { latency: 50_f32, new_latest_block_id: None, is_failed: true, @@ -620,7 +688,7 @@ mod tests { let addr_leader = Url::parse("https://127.0.0.1:3040").unwrap(); - let leader_metrics = Metrics { + let leader_statistics = Statistics { latency_avg: 100_f32, latency_var: 25_f32, sample_size: 10, @@ -632,15 +700,15 @@ mod tests { let cumulative_latency_squares = 100_f32.mul_add(100_f32, 115_f32 * 115_f32); let avg_manual = cumulative_avg( - leader_metrics.latency_avg, + leader_statistics.latency_avg, cumulative_latency, 10_f32, 2_f32, ); let var_manual = cumulative_var( - leader_metrics.latency_avg, + leader_statistics.latency_avg, avg_manual, - leader_metrics.latency_var, + leader_statistics.latency_var, cumulative_latency, cumulative_latency_squares, 10_f32, @@ -648,11 +716,11 @@ mod tests { ); let mut metric_map = HashMap::new(); - metric_map.insert(addr_leader.clone(), leader_metrics); + metric_map.insert(addr_leader.clone(), leader_statistics); - update_metrics(&mut metric_map, &addr_leader, &metrics_updates_vec).unwrap(); + update_statistics(&mut metric_map, &addr_leader, &statistics_updates_vec).unwrap(); - let Metrics { + let Statistics { latency_avg, latency_var, sample_size, @@ -688,11 +756,11 @@ mod tests { client_list.insert(addr_2.clone(), client_2); client_list.insert(addr_3.clone(), client_3); - let mut metrics = HashMap::new(); + let mut statistics = HashMap::new(); - metrics.insert( + statistics.insert( addr_3, - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -701,9 +769,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_2, - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -712,9 +780,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_1, - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -723,9 +791,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_leader.clone(), - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -734,7 +802,7 @@ mod tests { }, ); - let (leader_url, _) = choose_leader(&client_list, &metrics).unwrap(); + let (leader_url, _) = choose_leader(&client_list, &statistics).unwrap(); assert_eq!(leader_url, addr_leader); } @@ -758,11 +826,11 @@ mod tests { client_list.insert(addr_2.clone(), client_2); client_list.insert(addr_3.clone(), client_3); - let mut metrics = HashMap::new(); + let mut statistics = HashMap::new(); - metrics.insert( + statistics.insert( addr_3, - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -771,9 +839,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_2, - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -782,9 +850,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_1, - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -793,9 +861,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_leader.clone(), - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -804,7 +872,7 @@ mod tests { }, ); - let (leader_url, _) = choose_leader(&client_list, &metrics).unwrap(); + let (leader_url, _) = choose_leader(&client_list, &statistics).unwrap(); assert_eq!(leader_url, addr_leader); } @@ -828,11 +896,11 @@ mod tests { client_list.insert(addr_2.clone(), client_2); client_list.insert(addr_3.clone(), client_3); - let mut metrics = HashMap::new(); + let mut statistics = HashMap::new(); - metrics.insert( + statistics.insert( addr_3, - Metrics { + Statistics { latency_avg: 103_f32, latency_var: 10_f32, sample_size: 10, @@ -841,9 +909,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_2, - Metrics { + Statistics { latency_avg: 102_f32, latency_var: 10_f32, sample_size: 10, @@ -852,9 +920,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_1, - Metrics { + Statistics { latency_avg: 101_f32, latency_var: 10_f32, sample_size: 10, @@ -863,9 +931,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_leader.clone(), - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -874,7 +942,7 @@ mod tests { }, ); - let (leader_url, _) = choose_leader(&client_list, &metrics).unwrap(); + let (leader_url, _) = choose_leader(&client_list, &statistics).unwrap(); assert_eq!(leader_url, addr_leader); } @@ -898,11 +966,11 @@ mod tests { client_list.insert(addr_2.clone(), client_2); client_list.insert(addr_3.clone(), client_3); - let mut metrics = HashMap::new(); + let mut statistics = HashMap::new(); - metrics.insert( + statistics.insert( addr_3, - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 13_f32, sample_size: 10, @@ -911,9 +979,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_2, - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 12_f32, sample_size: 10, @@ -922,9 +990,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_1, - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 11_f32, sample_size: 10, @@ -933,9 +1001,9 @@ mod tests { }, ); - metrics.insert( + statistics.insert( addr_leader.clone(), - Metrics { + Statistics { latency_avg: 100_f32, latency_var: 10_f32, sample_size: 10, @@ -944,7 +1012,7 @@ mod tests { }, ); - let (leader_url, _) = choose_leader(&client_list, &metrics).unwrap(); + let (leader_url, _) = choose_leader(&client_list, &statistics).unwrap(); assert_eq!(leader_url, addr_leader); } diff --git a/test_fixtures/src/config.rs b/test_fixtures/src/config.rs index bd7cbc32..b35f7f16 100644 --- a/test_fixtures/src/config.rs +++ b/test_fixtures/src/config.rs @@ -194,7 +194,7 @@ pub fn genesis_from_accounts( pub fn wallet_config(sequencer_addr: SocketAddr) -> Result { Ok(WalletConfig { - sequencers_conn_data: vec![SequencerConnectionData { + sequencers: vec![SequencerConnectionData { sequencer_addr: addr_to_url(UrlProtocol::Http, sequencer_addr) .context("Failed to convert sequencer addr to URL")?, basic_auth: None, @@ -203,7 +203,7 @@ pub fn wallet_config(sequencer_addr: SocketAddr) -> Result { seq_tx_poll_max_blocks: 15, seq_poll_max_retries: 10, seq_block_poll_max_amount: 100, - callibration_limit: 1, + calibration_limit: 1, }) }