lssa/wallet/src/config.rs

356 lines
12 KiB
Rust
Raw Normal View History

use std::{
collections::HashMap,
io::{BufReader, Write as _},
path::Path,
2026-03-14 03:20:37 +03:00
str::FromStr as _,
time::Duration,
};
2025-12-08 18:26:35 +03:00
use anyhow::{Context as _, Result};
use common::config::BasicAuth;
use humantime_serde;
2025-11-10 16:29:33 +02:00
use key_protocol::key_management::{
KeyChain,
key_tree::{
chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
},
};
use log::warn;
2025-08-19 14:14:09 +03:00
use serde::{Deserialize, Serialize};
use url::Url;
2024-12-03 09:32:35 +02:00
2025-08-19 14:14:09 +03:00
#[derive(Debug, Clone, Serialize, Deserialize)]
2025-09-11 18:32:46 +03:00
pub struct InitialAccountDataPublic {
pub account_id: nssa::AccountId,
2025-08-19 14:14:09 +03:00
pub pub_sign_key: nssa::PrivateKey,
}
2025-02-28 12:32:54 +02:00
2025-09-02 09:01:33 +03:00
#[derive(Debug, Clone, Serialize, Deserialize)]
2025-09-11 18:32:46 +03:00
pub struct PersistentAccountDataPublic {
pub account_id: nssa::AccountId,
2025-11-10 16:29:33 +02:00
pub chain_index: ChainIndex,
pub data: ChildKeysPublic,
2025-09-02 09:01:33 +03:00
}
2025-09-11 18:32:46 +03:00
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitialAccountDataPrivate {
pub account_id: nssa::AccountId,
2025-09-11 18:32:46 +03:00
pub account: nssa_core::account::Account,
pub key_chain: KeyChain,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentAccountDataPrivate {
pub account_id: nssa::AccountId,
2025-11-10 16:29:33 +02:00
pub chain_index: ChainIndex,
pub data: ChildKeysPrivate,
2025-09-11 18:32:46 +03:00
}
2025-11-26 00:27:20 +03:00
// Big difference in enum variants sizes
// however it is improbable, that we will have that much accounts, that it will substantialy affect
// memory
2025-09-11 18:32:46 +03:00
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InitialAccountData {
Public(InitialAccountDataPublic),
2026-03-04 18:42:33 +03:00
Private(Box<InitialAccountDataPrivate>),
2025-09-11 18:32:46 +03:00
}
2025-11-26 00:27:20 +03:00
// Big difference in enum variants sizes
// however it is improbable, that we will have that much accounts, that it will substantialy affect
// memory
2025-09-11 18:32:46 +03:00
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PersistentAccountData {
Public(PersistentAccountDataPublic),
2026-03-04 18:42:33 +03:00
Private(Box<PersistentAccountDataPrivate>),
2025-11-11 12:15:20 +02:00
Preconfigured(InitialAccountData),
2025-09-11 18:32:46 +03:00
}
/// A human-readable label for an account.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Label(String);
impl Label {
2026-03-03 23:21:08 +03:00
#[must_use]
2026-03-09 18:27:56 +03:00
pub const fn new(label: String) -> Self {
Self(label)
}
}
impl std::fmt::Display for Label {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
2025-10-28 16:02:30 +02:00
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistentStorage {
pub accounts: Vec<PersistentAccountData>,
pub last_synced_block: u64,
/// Account labels keyed by account ID string (e.g.,
2026-03-10 00:17:43 +03:00
/// "2rnKprXqWGWJTkDZKsQbFXa4ctKRbapsdoTKQFnaVGG8").
#[serde(default)]
pub labels: HashMap<String, Label>,
2025-10-28 16:02:30 +02:00
}
impl PersistentStorage {
pub fn from_path(path: &Path) -> Result<Self> {
2026-03-04 18:42:33 +03:00
#[expect(
clippy::wildcard_enum_match_arm,
reason = "We want to provide a specific error message for not found case"
)]
match std::fs::File::open(path) {
Ok(file) => {
let storage_content = BufReader::new(file);
Ok(serde_json::from_reader(storage_content)?)
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => {
anyhow::bail!("Not found, please setup roots from config command beforehand");
}
_ => {
anyhow::bail!("IO error {err:#?}");
}
},
}
}
}
2025-09-11 18:32:46 +03:00
impl InitialAccountData {
2026-03-03 23:21:08 +03:00
#[must_use]
pub fn account_id(&self) -> nssa::AccountId {
2025-09-11 18:32:46 +03:00
match &self {
Self::Public(acc) => acc.account_id,
Self::Private(acc) => acc.account_id,
2025-09-11 18:32:46 +03:00
}
}
}
impl PersistentAccountData {
2026-03-03 23:21:08 +03:00
#[must_use]
pub fn account_id(&self) -> nssa::AccountId {
2025-09-11 18:32:46 +03:00
match &self {
Self::Public(acc) => acc.account_id,
Self::Private(acc) => acc.account_id,
Self::Preconfigured(acc) => acc.account_id(),
2025-09-11 18:32:46 +03:00
}
}
}
impl From<InitialAccountDataPublic> for InitialAccountData {
fn from(value: InitialAccountDataPublic) -> Self {
Self::Public(value)
}
}
impl From<InitialAccountDataPrivate> for InitialAccountData {
fn from(value: InitialAccountDataPrivate) -> Self {
2026-03-04 18:42:33 +03:00
Self::Private(Box::new(value))
2025-09-11 18:32:46 +03:00
}
}
impl From<PersistentAccountDataPublic> for PersistentAccountData {
fn from(value: PersistentAccountDataPublic) -> Self {
Self::Public(value)
}
}
impl From<PersistentAccountDataPrivate> for PersistentAccountData {
fn from(value: PersistentAccountDataPrivate) -> Self {
2026-03-04 18:42:33 +03:00
Self::Private(Box::new(value))
2025-09-11 18:32:46 +03:00
}
}
2025-11-11 12:15:20 +02:00
impl From<InitialAccountData> for PersistentAccountData {
fn from(value: InitialAccountData) -> Self {
Self::Preconfigured(value)
}
}
2025-02-28 12:32:54 +02:00
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GasConfig {
2026-03-10 00:17:43 +03:00
/// Gas spent per deploying one byte of data.
2025-02-28 12:32:54 +02:00
pub gas_fee_per_byte_deploy: u64,
2026-03-10 00:17:43 +03:00
/// Gas spent per reading one byte of data in VM.
2025-02-28 12:32:54 +02:00
pub gas_fee_per_input_buffer_runtime: u64,
2026-03-10 00:17:43 +03:00
/// Gas spent per one byte of contract data in runtime.
2025-02-28 12:32:54 +02:00
pub gas_fee_per_byte_runtime: u64,
2026-03-10 00:17:43 +03:00
/// Cost of one gas of runtime in public balance.
2025-02-28 12:32:54 +02:00
pub gas_cost_runtime: u64,
2026-03-10 00:17:43 +03:00
/// Cost of one gas of deployment in public balance.
2025-02-28 12:32:54 +02:00
pub gas_cost_deploy: u64,
2026-03-10 00:17:43 +03:00
/// Gas limit for deployment.
2025-02-28 12:32:54 +02:00
pub gas_limit_deploy: u64,
2026-03-10 00:17:43 +03:00
/// Gas limit for runtime.
2025-02-28 12:32:54 +02:00
pub gas_limit_runtime: u64,
}
2026-02-03 19:04:41 -03:00
#[optfield::optfield(pub WalletConfigOverrides, rewrap, attrs = (derive(Debug, Default, Clone)))]
2024-12-03 09:32:35 +02:00
#[derive(Debug, Clone, Serialize, Deserialize)]
2025-08-11 08:55:08 +03:00
pub struct WalletConfig {
2026-03-10 00:17:43 +03:00
/// Sequencer URL.
pub sequencer_addr: Url,
2026-03-10 00:17:43 +03:00
/// Sequencer polling duration for new blocks.
#[serde(with = "humantime_serde")]
pub seq_poll_timeout: Duration,
2026-03-10 00:17:43 +03:00
/// Sequencer polling max number of blocks to find transaction.
pub seq_tx_poll_max_blocks: usize,
2026-03-10 00:17:43 +03:00
/// Sequencer polling max number error retries.
2025-08-21 15:58:31 +03:00
pub seq_poll_max_retries: u64,
2026-03-10 00:17:43 +03:00
/// Max amount of blocks to poll in one request.
pub seq_block_poll_max_amount: u64,
2026-03-10 00:17:43 +03:00
/// Initial accounts for wallet.
2025-08-19 14:14:09 +03:00
pub initial_accounts: Vec<InitialAccountData>,
2026-03-10 00:17:43 +03:00
/// Basic authentication credentials.
#[serde(skip_serializing_if = "Option::is_none")]
2025-12-08 18:26:35 +03:00
pub basic_auth: Option<BasicAuth>,
2024-12-03 09:32:35 +02:00
}
2025-10-29 13:23:07 +02:00
impl Default for WalletConfig {
fn default() -> Self {
Self {
sequencer_addr: "http://127.0.0.1:3040".parse().unwrap(),
seq_poll_timeout: Duration::from_secs(12),
seq_tx_poll_max_blocks: 5,
2025-10-29 13:23:07 +02:00
seq_poll_max_retries: 5,
seq_block_poll_max_amount: 100,
2025-12-08 18:26:35 +03:00
basic_auth: None,
2026-03-14 03:20:37 +03:00
initial_accounts: vec![
InitialAccountData::Public(InitialAccountDataPublic {
account_id: nssa::AccountId::from_str(
"CbgR6tj5kWx5oziiFptM7jMvrQeYY3Mzaao6ciuhSr2r",
)
.unwrap(),
pub_sign_key: nssa::PrivateKey::try_new([
127, 39, 48, 152, 242, 91, 113, 230, 192, 5, 169, 81, 159, 38, 120, 218,
141, 28, 127, 1, 246, 162, 119, 120, 226, 217, 148, 138, 189, 249, 1, 251,
])
.unwrap(),
}),
InitialAccountData::Public(InitialAccountDataPublic {
account_id: nssa::AccountId::from_str(
"7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo",
)
.unwrap(),
pub_sign_key: nssa::PrivateKey::try_new([
244, 52, 248, 116, 23, 32, 1, 69, 134, 174, 67, 53, 109, 42, 236, 98, 87,
218, 8, 98, 34, 246, 4, 221, 183, 93, 105, 115, 59, 134, 252, 76,
])
.unwrap(),
}),
InitialAccountData::Private(Box::new(InitialAccountDataPrivate {
account_id: nssa::AccountId::from_str(
"HWkW5qd4XK3me6sCAb4bfPj462k33DjtKtEcYpuzNwB",
)
.unwrap(),
account: nssa::Account {
balance: 10_000,
..Default::default()
2025-10-29 13:23:07 +02:00
},
2026-03-14 03:20:37 +03:00
key_chain: KeyChain::new_mnemonic("default_private_account_1".to_owned()),
})),
InitialAccountData::Private(Box::new(InitialAccountDataPrivate {
account_id: nssa::AccountId::from_str(
"HUpbRQ1vEcZv5y6TDYv9tpt1VA64ji2v4RDLJfK2rpZn",
)
.unwrap(),
account: nssa::Account {
balance: 20_000,
..Default::default()
2025-10-29 13:23:07 +02:00
},
2026-03-14 03:20:37 +03:00
key_chain: KeyChain::new_mnemonic("default_private_account_2".to_owned()),
})),
],
2025-10-29 13:23:07 +02:00
}
}
}
impl WalletConfig {
2026-03-09 18:27:56 +03:00
pub fn from_path_or_initialize_default(config_path: &Path) -> Result<Self> {
match std::fs::File::open(config_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!("Config not found, setting up default config");
let config_home = config_path.parent().ok_or_else(|| {
anyhow::anyhow!(
2026-03-03 23:21:08 +03:00
"Could not get parent directory of config file at {}",
config_path.display()
)
})?;
std::fs::create_dir_all(config_home)?;
2026-03-03 23:21:08 +03:00
println!("Created configs dir at path {}", config_home.display());
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(config_path)?;
2026-03-09 18:27:56 +03:00
let config = Self::default();
let default_config_serialized = serde_json::to_vec_pretty(&config).unwrap();
file.write_all(&default_config_serialized)?;
println!("Configs set up");
Ok(config)
}
Err(err) => Err(err).context("IO error"),
}
}
pub fn apply_overrides(&mut self, overrides: WalletConfigOverrides) {
2026-03-09 18:27:56 +03:00
let Self {
sequencer_addr,
seq_poll_timeout,
seq_tx_poll_max_blocks,
seq_poll_max_retries,
seq_block_poll_max_amount,
initial_accounts,
basic_auth,
} = self;
let WalletConfigOverrides {
sequencer_addr: o_sequencer_addr,
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,
initial_accounts: o_initial_accounts,
basic_auth: o_basic_auth,
} = overrides;
if let Some(v) = o_sequencer_addr {
warn!("Overriding wallet config 'sequencer_addr' to {v}");
*sequencer_addr = v;
}
if let Some(v) = o_seq_poll_timeout {
warn!("Overriding wallet config 'seq_poll_timeout' to {v:?}");
*seq_poll_timeout = v;
}
if let Some(v) = o_seq_tx_poll_max_blocks {
warn!("Overriding wallet config 'seq_tx_poll_max_blocks' to {v}");
*seq_tx_poll_max_blocks = v;
}
if let Some(v) = o_seq_poll_max_retries {
warn!("Overriding wallet config 'seq_poll_max_retries' to {v}");
*seq_poll_max_retries = v;
}
if let Some(v) = o_seq_block_poll_max_amount {
warn!("Overriding wallet config 'seq_block_poll_max_amount' to {v}");
*seq_block_poll_max_amount = v;
}
if let Some(v) = o_initial_accounts {
warn!("Overriding wallet config 'initial_accounts' to {v:#?}");
*initial_accounts = v;
}
if let Some(v) = o_basic_auth {
warn!("Overriding wallet config 'basic_auth' to {v:#?}");
*basic_auth = v;
}
}
}