fix(wallet): suggestions

This commit is contained in:
Pravdyvy 2026-07-17 16:57:12 +03:00
parent 441e43ef91
commit 0bc08c7357
13 changed files with 343 additions and 359 deletions

View File

@ -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

View File

@ -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")?;

View File

@ -86,7 +86,7 @@ fn c_str_to_path(ptr: *const c_char, name: &str) -> Result<PathBuf, WalletFfiErr
/// # 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
@ -99,7 +99,7 @@ fn c_str_to_path(ptr: *const c_char, name: &str) -> Result<PathBuf, WalletFfiErr
pub unsafe extern "C" fn wallet_ffi_create_new(
config_path: *const c_char,
storage_path: *const c_char,
metrics_path: *const c_char,
statistics_path: *const c_char,
password: *const c_char,
) -> 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) => {

View File

@ -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.

View File

@ -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
}

View File

@ -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);

View File

@ -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<String>,
/// Wallet command.
#[command(subcommand)]
pub command: Option<Command>,
@ -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)
}

View File

@ -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<SequencerConnectionData>,
pub sequencers: Vec<SequencerConnectionData>,
/// 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;
}
}
}

View File

@ -66,13 +66,13 @@ pub fn fetch_persistent_storage_path() -> Result<PathBuf> {
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<PathBuf> {
pub fn fetch_statistics_path() -> Result<PathBuf> {
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")]

View File

@ -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<Url, Metrics>,
/// Wrapping metric updates in Arc<RwLock> to not break interfaces too much.
///
/// It is assumed, that wallet methods can be accesed via immutable reference.
metric_updates: Arc<RwLock<Vec<MetricsUpdate>>>,
statistics_path: PathBuf,
statistics: HashMap<Url, Statistics>,
multi_sequencer_client: MultiSequencerClient,
}
@ -114,15 +109,15 @@ impl WalletCore {
pub async fn from_env() -> Result<Self> {
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<WalletConfigOverrides>,
) -> Result<Self> {
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<WalletConfigOverrides>,
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<WalletConfigOverrides>,
storage: Storage,
) -> Result<Self> {
@ -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<u128> {
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<Vec<Nonce>> {
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<Account> {
@ -535,59 +505,35 @@ impl WalletCore {
}
pub async fn get_last_block_id(&self) -> Result<u64> {
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<Option<Block>> {
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<Option<(LeeTransaction, BlockId)>> {
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<Account> {
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<BTreeMap<String, ProgramId>> {
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<Commitment>,
) -> Result<(Vec<Option<MembershipProof>>, 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<u8>) -> Result<HashType> {
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<u64> {

View File

@ -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();

View File

@ -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<u64>,
pub new_latest_block_id: Option<BlockId>,
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<RwLock> to not break interfaces too much.
///
/// It is assumed, that wallet methods can be accesed via immutable reference.
statistic_updates: Arc<RwLock<Vec<StatisticsUpdate>>>,
}
impl MultiSequencerClient {
pub async fn new(
conn_data: &[SequencerConnectionData],
metrics: &mut HashMap<Url, Metrics>,
callibration_limit: usize,
statistics: &mut HashMap<Url, Statistics>,
calibration_limit: usize,
) -> Result<Self> {
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<R, E, I: AsyncFn(&SequencerClient) -> Result<R, E>>(
&self,
call: I,
) -> (Result<R, E>, MetricsUpdate) {
let resp = tokio::join!(call(self.leader_ref()), actualize_client(self.leader_ref()));
) -> Result<R, E> {
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<u64>,
pub latest_block_id: Option<BlockId>,
/// 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<HashMap<Url, Metrics>, anyhow::Error> {
pub fn extract_statistics_from_path(
path: &Path,
) -> Result<HashMap<Url, Statistics>, 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<BlockId>) {
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<Statistics> {
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::<u128>() 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<Url, SequencerClient>,
metrics: &HashMap<Url, Metrics>,
statistics: &HashMap<Url, Statistics>,
) -> 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<Url, Metrics>,
fn update_statistics(
statistics: &mut HashMap<Url, Statistics>,
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);
}

View File

@ -194,7 +194,7 @@ pub fn genesis_from_accounts(
pub fn wallet_config(sequencer_addr: SocketAddr) -> Result<WalletConfig> {
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<WalletConfig> {
seq_tx_poll_max_blocks: 15,
seq_poll_max_retries: 10,
seq_block_poll_max_amount: 100,
callibration_limit: 1,
calibration_limit: 1,
})
}