Skip to content

Commit

Permalink
chore: multimint into fedimint-clientd
Browse files Browse the repository at this point in the history
  • Loading branch information
Kodylow committed Mar 19, 2024
1 parent 724809c commit 7d21095
Show file tree
Hide file tree
Showing 57 changed files with 626 additions and 52 deletions.
28 changes: 24 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 6 additions & 44 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,44 +1,6 @@
[package]
name = "fedimint-clientd"
version = "0.1.5"
edition = "2021"
description = "A fedimint client daemon for server side applications to hold, use, and manage Bitcoin"
repository = "https://github.com/fedimint/fedimint-clientd"
keywords = ["fedimint", "fedimint-cli"]
license = "MIT"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"
axum = { version = "0.7.1", features = ["json", "ws"] }
axum-macros = "0.4.0"
dotenv = "0.15.0"
fedimint = "0.0.1"
serde = "1.0.193"
serde_json = "1.0.108"
tokio = { version = "1.34.0", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
fedimint-client = "0.2.2"
fedimint-core = "0.2.2"
fedimint-wallet-client = "0.2.2"
fedimint-mint-client = "0.2.2"
fedimint-ln-client = "0.2.2"
fedimint-rocksdb = "0.2.2"
url = "2.5.0"
lazy_static = "1.4.0"
async-utility = "0.2.0"
tower-http = { version = "0.5.0", features = ["cors", "auth", "trace"] }
bitcoin = "0.29.2"
itertools = "0.12.0"
lnurl-rs = { version = "0.4.0", features = ["async"], default-features = false }
reqwest = "0.11.23"
lightning-invoice = { version = "0.26.0", features = ["serde"] }
bitcoin_hashes = "0.11.0"
time = { version = "0.3.25", features = ["formatting"] }
chrono = "0.4.31"
futures-util = "0.3.30"
clap = { version = "4.4.13", features = ["derive", "env"] }
multimint = "0.1.7"
axum-otel-metrics = "0.8.0"
[workspace]
members = [
"multimint",
"fedimint-clientd",
]
resolver = "2"
47 changes: 47 additions & 0 deletions fedimint-clientd/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[package]
name = "fedimint-clientd"
version = "0.1.5"
edition = "2021"
description = "A fedimint client daemon for server side applications to hold, use, and manage Bitcoin"
repository = "https://github.com/fedimint/fedimint-clientd"
keywords = ["fedimint", "fedimint-cli"]
license = "MIT"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"
axum = { version = "0.7.1", features = ["json", "ws"] }
axum-macros = "0.4.0"
dotenv = "0.15.0"
fedimint = "0.0.1"
serde = "1.0.193"
serde_json = "1.0.108"
tokio = { version = "1.34.0", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
fedimint-client = "0.2.2"
fedimint-core = "0.2.2"
fedimint-wallet-client = "0.2.2"
fedimint-mint-client = "0.2.2"
fedimint-ln-client = "0.2.2"
fedimint-rocksdb = "0.2.2"
url = "2.5.0"
lazy_static = "1.4.0"
async-utility = "0.2.0"
tower-http = { version = "0.5.0", features = ["cors", "auth", "trace"] }
bitcoin = "0.29.2"
itertools = "0.12.0"
lnurl-rs = { version = "0.4.0", features = ["async"], default-features = false }
reqwest = "0.11.23"
lightning-invoice = { version = "0.26.0", features = ["serde"] }
bitcoin_hashes = "0.11.0"
time = { version = "0.3.25", features = ["formatting"] }
chrono = "0.4.31"
futures-util = "0.3.30"
clap = { version = "4.4.13", features = ["derive", "env"] }
multimint = "0.1.7"
axum-otel-metrics = "0.8.0"

[patch.crates-io]
multimint = { path = "../multimint" }
File renamed without changes.
File renamed without changes.
14 changes: 13 additions & 1 deletion src/main.rs → fedimint-clientd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ struct Cli {
#[clap(long, env = "FEDIMINT_CLIENTD_ADDR", required = true)]
addr: String,

/// Manual secret
#[clap(long, env = "FEDIMINT_CLIENTD_MANUAL_SECRET", required = false)]
manual_secret: Option<String>,

/// Mode: ws, rest
#[clap(long, default_value = "rest")]
mode: Mode,
Expand All @@ -72,9 +76,17 @@ async fn main() -> Result<()> {

let mut state = AppState::new(cli.db_path).await?;

let manual_secret = match cli.manual_secret {
Some(secret) => Some(secret),
None => match std::env::var("FEDIMINT_CLIENTD_MANUAL_SECRET") {
Ok(secret) => Some(secret),
Err(_) => None,
},
};

match InviteCode::from_str(&cli.invite_code) {
Ok(invite_code) => {
let federation_id = state.multimint.register_new(invite_code, true).await?;
let federation_id = state.multimint.register_new(invite_code, manual_secret).await?;
info!("Created client for federation id: {:?}", federation_id);
if cli.mode == Mode::Cashu {
state.cashu_mint = Some(federation_id);
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::env;

use anyhow::{anyhow, Error};
use axum::extract::State;
use axum::http::StatusCode;
Expand All @@ -15,7 +17,7 @@ use crate::state::AppState;
#[serde(rename_all = "camelCase")]
pub struct JoinRequest {
pub invite_code: InviteCode,
pub set_default: bool,
pub use_manual_secret: bool,
}

#[derive(Debug, Serialize)]
Expand All @@ -25,8 +27,20 @@ pub struct JoinResponse {
}

async fn _join(mut multimint: MultiMint, req: JoinRequest) -> Result<JoinResponse, Error> {
let manual_secret = if req.use_manual_secret {
match env::var("FEDIMINT_CLIENTD_MANUAL_SECRET") {
Ok(secret) => Some(secret),
Err(_) => {
return Err(anyhow!("FEDIMINT_CLIENTD_MANUAL_SECRET must be set to join with manual secret"))
}
}
} else {
None
};


let _ = multimint
.register_new(req.invite_code.clone(), req.set_default)
.register_new(req.invite_code.clone(), manual_secret)
.await?;

let federation_ids = multimint.ids().await.into_iter().collect::<Vec<_>>();
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion mprocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ procs:
shell: zsh
stop: SIGKILL
fedimint-clientd:
shell: cargo run
shell: cargo run --manifest-path fedimint-clientd/Cargo.toml
stop: SIGTERM
ngrok:
shell: ngrok http http://localhost:3333
Expand Down
25 changes: 25 additions & 0 deletions multimint/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "multimint"
version = "0.1.12"
edition = "2021"
description = "A library for managing fedimint clients across multiple federations"
license = "MIT"

[lib]
path = "src/lib.rs"

[dependencies]
anyhow = "1.0.75"
serde = "1.0.193"
serde_json = "1.0.108"
tokio = { version = "1.34.0", features = ["full"] }
fedimint-client = "0.2.2"
fedimint-core = "0.2.2"
fedimint-wallet-client = "0.2.2"
fedimint-mint-client = "0.2.2"
fedimint-ln-client = "0.2.2"
fedimint-rocksdb = "0.2.2"
futures-util = "0.3.30"
rand = "0.8.5"
tracing = "0.1.40"
hex = "0.4.3"
103 changes: 103 additions & 0 deletions multimint/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//! LocalClientBuilder is a builder pattern for adding Fedimint Clients to the multimint
use anyhow::Result;
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::path::PathBuf;

use fedimint_client::secret::{PlainRootSecretStrategy, RootSecretStrategy};
use fedimint_client::{get_config_from_db, Client, FederationInfo};
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 {
/// Build a new client with the given config and optional manual secret
#[allow(clippy::too_many_arguments)]
pub async fn build(&self, config: FederationConfig, manual_secret: Option<[u8; 64]>) -> Result<fedimint_client::ClientArc> {
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();
client_builder.with_database(db.clone());
client_builder.with_module(WalletClientInit(None));
client_builder.with_module(MintClientInit);
client_builder.with_module(LightningClientInit);
client_builder.with_primary_module(1);

let client_secret = match client_builder.load_decodable_client_secret().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_builder.store_encodable_client_secret(manual_secret).await?;
manual_secret
} else {
info!("Generating new secret and writing to client storage");
let secret = PlainRootSecretStrategy::random(&mut thread_rng());
client_builder.store_encodable_client_secret(secret).await?;
secret
}
}
};

let root_secret = PlainRootSecretStrategy::to_root_secret(&client_secret);

if get_config_from_db(&db).await.is_none() {
let federation_info = FederationInfo::from_invite_code(config.invite_code).await?;
client_builder.with_federation_info(federation_info);
};

let client_res = client_builder.build(root_secret.clone()).await?;

Ok(client_res)
}

/// Save the federation config to the database
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<_>>()
}
}
38 changes: 38 additions & 0 deletions multimint/src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use fedimint_core::api::InviteCode;
use fedimint_core::config::FederationId;
use fedimint_core::encoding::{Decodable, Encodable};
use fedimint_core::{impl_db_lookup, impl_db_record};
use serde::{Deserialize, Serialize};

#[repr(u8)]
#[derive(Clone, Debug)]
pub enum DbKeyPrefix {
FederationConfig = 0x04,
}

impl std::fmt::Display for DbKeyPrefix {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{self:?}")
}
}

#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct FederationIdKey {
pub id: FederationId,
}

#[derive(Debug, Encodable, Decodable)]
pub struct FederationIdKeyPrefix;

#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
pub struct FederationConfig {
pub invite_code: InviteCode,
}

impl_db_record!(
key = FederationIdKey,
value = FederationConfig,
db_prefix = DbKeyPrefix::FederationConfig,
);

impl_db_lookup!(key = FederationIdKey, query_prefix = FederationIdKeyPrefix);
Loading

0 comments on commit 7d21095

Please sign in to comment.