use std::collections::BTreeMap;
use std::fmt::Debug;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::Result;
use fedimint_client::secret::{PlainRootSecretStrategy, RootSecretStrategy};
use fedimint_client::Client;
use fedimint_core::db::{
Committable, Database, DatabaseTransaction, IDatabaseTransactionOpsCoreTyped,
};
use fedimint_ln_client::LightningClientInit;
use fedimint_mint_client::MintClientInit;
use fedimint_wallet_client::WalletClientInit;
use futures_util::StreamExt;
use rand::thread_rng;
use tracing::info;
use crate::db::{FederationConfig, FederationIdKey, FederationIdKeyPrefix};
#[derive(Debug, Clone)]
pub struct LocalClientBuilder {
work_dir: PathBuf,
}
impl LocalClientBuilder {
pub fn new(work_dir: PathBuf) -> Self {
Self { work_dir }
}
}
impl LocalClientBuilder {
#[allow(clippy::too_many_arguments)]
pub async fn build(
&self,
config: FederationConfig,
manual_secret: Option<[u8; 64]>,
) -> Result<fedimint_client::ClientHandleArc> {
let federation_id = config.invite_code.federation_id();
let db_path = self.work_dir.join(format!("{federation_id}.db"));
let db = Database::new(
fedimint_rocksdb::RocksDb::open(db_path.clone())?,
Default::default(),
);
let mut client_builder = Client::builder(db.clone()).await?;
client_builder.with_module(WalletClientInit(None));
client_builder.with_module(MintClientInit);
client_builder.with_module(LightningClientInit::default());
client_builder.with_primary_module(1);
let client_secret = match Client::load_decodable_client_secret::<[u8; 64]>(&db).await {
Ok(secret) => secret,
Err(_) => {
if let Some(manual_secret) = manual_secret {
info!("Using manual secret provided by user and writing to client storage");
Client::store_encodable_client_secret(&db, manual_secret).await?;
manual_secret
} else {
info!("Generating new secret and writing to client storage");
let secret = PlainRootSecretStrategy::random(&mut thread_rng());
Client::store_encodable_client_secret(&db, secret).await?;
secret
}
}
};
let root_secret = PlainRootSecretStrategy::to_root_secret(&client_secret);
let client_res = if Client::is_initialized(&db).await {
client_builder.open(root_secret).await
} else {
let client_config =
fedimint_api_client::download_from_invite_code(&config.invite_code).await?;
client_builder
.join(root_secret, client_config.to_owned(), None)
.await
}?;
Ok(Arc::new(client_res))
}
pub async fn save_config(
&self,
config: FederationConfig,
mut dbtx: DatabaseTransaction<'_, Committable>,
) -> Result<()> {
let id = config.invite_code.federation_id();
dbtx.insert_entry(&FederationIdKey { id }, &config).await;
dbtx.commit_tx_result()
.await
.map_err(|e| anyhow::anyhow!("Failed to save config: {:?}", e))
}
pub async fn load_configs(&self, mut dbtx: DatabaseTransaction<'_>) -> Vec<FederationConfig> {
dbtx.find_by_prefix(&FederationIdKeyPrefix)
.await
.collect::<BTreeMap<FederationIdKey, FederationConfig>>()
.await
.values()
.cloned()
.collect::<Vec<_>>()
}
}