use std::{collections::HashSet, fmt, iter, num::NonZeroUsize, path::PathBuf};
use linera_base::{
data_types::{ApplicationPermissions, TimeDelta},
identifiers::{AccountOwner, ApplicationId, ChainId},
ownership::{ChainOwnership, TimeoutConfig},
time::Duration,
};
use linera_core::{client::BlanketMessagePolicy, DEFAULT_GRACE_PERIOD};
use linera_execution::ResourceControlPolicy;
#[cfg(any(with_indexed_db, not(with_persist)))]
use crate::{config::WalletState, wallet::Wallet};
use crate::{persistent, util};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("no wallet found")]
NonexistentWallet,
#[error("there are {public_keys} public keys but {weights} weights")]
MisalignedWeights { public_keys: usize, weights: usize },
#[error("persistence error: {0}")]
Persistence(#[from] Box<dyn std::error::Error + Send + Sync>),
#[error("config error: {0}")]
Config(#[from] crate::config::Error),
}
#[cfg(feature = "fs")]
util::impl_from_dynamic!(Error:Persistence, persistent::file::Error);
#[cfg(with_indexed_db)]
util::impl_from_dynamic!(Error:Persistence, persistent::indexed_db::Error);
util::impl_from_infallible!(Error);
#[derive(Clone, clap::Parser)]
pub struct ClientContextOptions {
#[arg(long = "wallet")]
pub wallet_state_path: Option<PathBuf>,
#[arg(long, short = 'w', value_parser = util::parse_ascii_alphanumeric_string)]
pub with_wallet: Option<String>,
#[arg(long = "send-timeout-ms", default_value = "4000", value_parser = util::parse_millis)]
pub send_timeout: Duration,
#[arg(long = "recv-timeout-ms", default_value = "4000", value_parser = util::parse_millis)]
pub recv_timeout: Duration,
#[arg(long, default_value = "10")]
pub max_pending_message_bundles: usize,
#[arg(long, default_value = "40")]
pub max_loaded_chains: NonZeroUsize,
#[arg(
long = "retry-delay-ms",
default_value = "1000",
value_parser = util::parse_millis
)]
pub retry_delay: Duration,
#[arg(long, default_value = "10")]
pub max_retries: u32,
#[arg(long)]
pub wait_for_outgoing_messages: bool,
#[arg(long)]
pub long_lived_services: bool,
#[arg(long, default_value = "accept")]
pub blanket_message_policy: BlanketMessagePolicy,
#[arg(long, value_parser = util::parse_chain_set)]
pub restrict_chain_ids_to: Option<HashSet<ChainId>>,
#[arg(long, default_value_t = DEFAULT_GRACE_PERIOD)]
pub grace_period: f64,
#[arg(
long = "blob-download-timeout-ms",
default_value = "1000",
value_parser = util::parse_millis
)]
pub blob_download_timeout: Duration,
}
#[cfg(with_indexed_db)]
impl ClientContextOptions {
pub async fn wallet(&self) -> Result<WalletState<persistent::IndexedDb<Wallet>>, Error> {
Ok(WalletState::new(
persistent::IndexedDb::read("linera-wallet")
.await?
.ok_or(Error::NonexistentWallet)?,
))
}
}
#[cfg(not(with_persist))]
impl ClientContextOptions {
pub async fn wallet(&self) -> Result<WalletState<persistent::Memory<Wallet>>, Error> {
#![allow(unreachable_code)]
let _wallet = unimplemented!("No persistence backend selected for wallet; please use one of the `fs` or `indexed-db` features");
Ok(WalletState::new(persistent::Memory::new(_wallet)))
}
}
#[derive(Debug, Clone, clap::Args)]
pub struct ChainOwnershipConfig {
#[arg(long, num_args(0..))]
super_owners: Vec<AccountOwner>,
#[arg(long, num_args(0..))]
owners: Vec<AccountOwner>,
#[arg(long, num_args(0..))]
owner_weights: Vec<u64>,
#[arg(long)]
multi_leader_rounds: Option<u32>,
#[arg(long)]
open_multi_leader_rounds: bool,
#[arg(long = "fast-round-ms", value_parser = util::parse_millis_delta)]
fast_round_duration: Option<TimeDelta>,
#[arg(
long = "base-timeout-ms",
default_value = "10000",
value_parser = util::parse_millis_delta
)]
base_timeout: TimeDelta,
#[arg(
long = "timeout-increment-ms",
default_value = "1000",
value_parser = util::parse_millis_delta
)]
timeout_increment: TimeDelta,
#[arg(
long = "fallback-duration-ms",
default_value = "86400000", value_parser = util::parse_millis_delta
)]
pub fallback_duration: TimeDelta,
}
impl TryFrom<ChainOwnershipConfig> for ChainOwnership {
type Error = Error;
fn try_from(config: ChainOwnershipConfig) -> Result<ChainOwnership, Error> {
let ChainOwnershipConfig {
super_owners,
owners,
owner_weights,
multi_leader_rounds,
fast_round_duration,
open_multi_leader_rounds,
base_timeout,
timeout_increment,
fallback_duration,
} = config;
if !owner_weights.is_empty() && owner_weights.len() != owners.len() {
return Err(Error::MisalignedWeights {
public_keys: owners.len(),
weights: owner_weights.len(),
});
}
let super_owners = super_owners.into_iter().collect();
let owners = owners
.into_iter()
.zip(owner_weights.into_iter().chain(iter::repeat(100)))
.collect();
let multi_leader_rounds = multi_leader_rounds.unwrap_or(u32::MAX);
let timeout_config = TimeoutConfig {
fast_round_duration,
base_timeout,
timeout_increment,
fallback_duration,
};
Ok(ChainOwnership {
super_owners,
owners,
multi_leader_rounds,
open_multi_leader_rounds,
timeout_config,
})
}
}
#[derive(Debug, Clone, clap::Args)]
pub struct ApplicationPermissionsConfig {
#[arg(long)]
pub execute_operations: Option<Vec<ApplicationId>>,
#[arg(long)]
pub mandatory_applications: Option<Vec<ApplicationId>>,
#[arg(long)]
pub close_chain: Option<Vec<ApplicationId>>,
#[arg(long)]
pub change_application_permissions: Option<Vec<ApplicationId>>,
#[arg(long)]
pub call_service_as_oracle: Option<Vec<ApplicationId>>,
#[arg(long)]
pub make_http_requests: Option<Vec<ApplicationId>>,
}
impl From<ApplicationPermissionsConfig> for ApplicationPermissions {
fn from(config: ApplicationPermissionsConfig) -> ApplicationPermissions {
ApplicationPermissions {
execute_operations: config.execute_operations,
mandatory_applications: config.mandatory_applications.unwrap_or_default(),
close_chain: config.close_chain.unwrap_or_default(),
change_application_permissions: config
.change_application_permissions
.unwrap_or_default(),
call_service_as_oracle: config.call_service_as_oracle,
make_http_requests: config.make_http_requests,
}
}
}
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResourceControlPolicyConfig {
NoFees,
Testnet,
#[cfg(with_testing)]
OnlyFuel,
#[cfg(with_testing)]
FuelAndBlock,
#[cfg(with_testing)]
AllCategories,
}
impl ResourceControlPolicyConfig {
pub fn into_policy(self) -> ResourceControlPolicy {
match self {
ResourceControlPolicyConfig::NoFees => ResourceControlPolicy::no_fees(),
ResourceControlPolicyConfig::Testnet => ResourceControlPolicy::testnet(),
#[cfg(with_testing)]
ResourceControlPolicyConfig::OnlyFuel => ResourceControlPolicy::only_fuel(),
#[cfg(with_testing)]
ResourceControlPolicyConfig::FuelAndBlock => ResourceControlPolicy::fuel_and_block(),
#[cfg(with_testing)]
ResourceControlPolicyConfig::AllCategories => ResourceControlPolicy::all_categories(),
}
}
}
impl std::str::FromStr for ResourceControlPolicyConfig {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
clap::ValueEnum::from_str(s, true)
}
}
impl fmt::Display for ResourceControlPolicyConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}