mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-07-18 13:49:25 +00:00
fix(wallet): suggestions
This commit is contained in:
parent
441e43ef91
commit
0bc08c7357
1
Justfile
1
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
|
||||
|
||||
@ -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")?;
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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")]
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user