From 636860f20be04a74488370ade20f5c383ee4f9f9 Mon Sep 17 00:00:00 2001 From: "kody.low" Date: Sat, 25 May 2024 13:43:27 -0700 Subject: [PATCH] refactor: managers and services --- fedimint-nwc/src/main.rs | 10 +- fedimint-nwc/src/managers/key.rs | 64 ++++++++ fedimint-nwc/src/managers/mod.rs | 3 + fedimint-nwc/src/nwc.rs | 2 +- fedimint-nwc/src/services/mod.rs | 5 + fedimint-nwc/src/services/multimint.rs | 40 +++++ fedimint-nwc/src/services/nostr.rs | 62 +++++++ fedimint-nwc/src/state.rs | 215 +++---------------------- 8 files changed, 202 insertions(+), 199 deletions(-) create mode 100644 fedimint-nwc/src/managers/key.rs create mode 100644 fedimint-nwc/src/managers/mod.rs create mode 100644 fedimint-nwc/src/services/mod.rs create mode 100644 fedimint-nwc/src/services/multimint.rs create mode 100644 fedimint-nwc/src/services/nostr.rs diff --git a/fedimint-nwc/src/main.rs b/fedimint-nwc/src/main.rs index 2b454c2..40dfbd1 100644 --- a/fedimint-nwc/src/main.rs +++ b/fedimint-nwc/src/main.rs @@ -8,7 +8,9 @@ use tokio::sync::oneshot; use tracing::{debug, info}; pub mod config; +pub mod managers; pub mod nwc; +pub mod services; pub mod state; use state::AppState; @@ -21,7 +23,7 @@ async fn main() -> Result<()> { dotenv::dotenv().ok(); let cli = Cli::parse(); - let state = state::init(cli).await?; + let state = AppState::new(cli).await?; // Shutdown signal handler let (tx, rx) = oneshot::channel::<()>(); @@ -61,7 +63,7 @@ async fn handle_signals(tx: oneshot::Sender<()>) -> Result<()> { } async fn event_loop(state: AppState) -> Result<()> { - state.nostr_client.connect().await; + state.nostr_service.connect().await; loop { info!("Listening for events..."); let (tx, _) = tokio::sync::watch::channel(()); @@ -70,7 +72,7 @@ async fn event_loop(state: AppState) -> Result<()> { let _ = tx.send(()); }); - let mut notifications = state.nostr_client.notifications(); + let mut notifications = state.nostr_service.notifications(); while let Ok(notification) = notifications.recv().await { match notification { RelayPoolNotification::Event { event, .. } => state.handle_event(*event).await, @@ -82,6 +84,6 @@ async fn event_loop(state: AppState) -> Result<()> { } } - state.nostr_client.disconnect().await?; + state.nostr_service.disconnect().await?; } } diff --git a/fedimint-nwc/src/managers/key.rs b/fedimint-nwc/src/managers/key.rs new file mode 100644 index 0000000..6945edb --- /dev/null +++ b/fedimint-nwc/src/managers/key.rs @@ -0,0 +1,64 @@ +use std::fs::{create_dir_all, File}; +use std::io::{BufReader, Write}; +use std::path::Path; + +use anyhow::{Context, Result}; +use nostr_sdk::secp256k1::SecretKey; +use nostr_sdk::Keys; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct KeyManager { + server_key: SecretKey, + user_key: SecretKey, + #[serde(default)] + pub sent_info: bool, +} + +impl KeyManager { + pub fn new(keys_file: &str) -> Result { + let path = Path::new(keys_file); + match File::open(path) { + Ok(file) => { + let reader = BufReader::new(file); + serde_json::from_reader(reader).context("Failed to parse JSON") + } + Err(_) => { + let keys = Self::generate()?; + Self::write_keys(&keys, path)?; + Ok(keys) + } + } + } + + fn generate() -> Result { + let server_keys = Keys::generate(); + let server_key = server_keys.secret_key()?; + let user_keys = Keys::generate(); + let user_key = user_keys.secret_key()?; + Ok(Self { + server_key: **server_key, + user_key: **user_key, + sent_info: false, + }) + } + + fn write_keys(keys: &Self, path: &Path) -> Result<()> { + let json_str = serde_json::to_string(keys).context("Failed to serialize data")?; + if let Some(parent) = path.parent() { + create_dir_all(parent).context("Failed to create directory")?; + } + let mut file = File::create(path).context("Failed to create file")?; + file.write_all(json_str.as_bytes()) + .context("Failed to write to file")?; + Ok(()) + } + + pub fn server_keys(&self) -> Keys { + Keys::new(self.server_key.into()) + } + + pub fn user_keys(&self) -> Keys { + Keys::new(self.user_key.into()) + } +} diff --git a/fedimint-nwc/src/managers/mod.rs b/fedimint-nwc/src/managers/mod.rs new file mode 100644 index 0000000..5f2e9f2 --- /dev/null +++ b/fedimint-nwc/src/managers/mod.rs @@ -0,0 +1,3 @@ +pub mod key; + +pub use key::KeyManager; diff --git a/fedimint-nwc/src/nwc.rs b/fedimint-nwc/src/nwc.rs index a231fd7..d8a95a8 100644 --- a/fedimint-nwc/src/nwc.rs +++ b/fedimint-nwc/src/nwc.rs @@ -1,4 +1,4 @@ -use nostr_sdk::nips::nip47::Method; +use nostr::nips::nip47::Method; use nostr_sdk::Event; use crate::state::AppState; diff --git a/fedimint-nwc/src/services/mod.rs b/fedimint-nwc/src/services/mod.rs new file mode 100644 index 0000000..ed3a576 --- /dev/null +++ b/fedimint-nwc/src/services/mod.rs @@ -0,0 +1,5 @@ +pub mod multimint; +pub mod nostr; + +pub use multimint::MultiMintService; +pub use nostr::NostrService; diff --git a/fedimint-nwc/src/services/multimint.rs b/fedimint-nwc/src/services/multimint.rs new file mode 100644 index 0000000..6ac77de --- /dev/null +++ b/fedimint-nwc/src/services/multimint.rs @@ -0,0 +1,40 @@ +use std::path::PathBuf; +use std::str::FromStr; + +use anyhow::Result; +use multimint::fedimint_core::api::InviteCode; +use multimint::MultiMint; + +#[derive(Debug, Clone)] +pub struct MultiMintService { + multimint: MultiMint, +} + +impl MultiMintService { + pub async fn new(db_path: PathBuf) -> Result { + let clients = MultiMint::new(db_path).await?; + clients.update_gateway_caches().await?; + Ok(Self { multimint: clients }) + } + + pub async fn init_multimint( + &mut self, + invite_code: &str, + manual_secret: Option, + ) -> Result<()> { + match InviteCode::from_str(invite_code) { + Ok(invite_code) => { + let federation_id = self + .multimint + .register_new(invite_code, manual_secret) + .await?; + tracing::info!("Created client for federation id: {:?}", federation_id); + Ok(()) + } + Err(e) => { + tracing::error!("Invalid federation invite code: {}", e); + Err(e.into()) + } + } + } +} diff --git a/fedimint-nwc/src/services/nostr.rs b/fedimint-nwc/src/services/nostr.rs new file mode 100644 index 0000000..f353678 --- /dev/null +++ b/fedimint-nwc/src/services/nostr.rs @@ -0,0 +1,62 @@ +use anyhow::Result; +use nostr_sdk::{Client, EventBuilder, EventId, Kind, RelayPoolNotification}; +use tokio::sync::broadcast::Receiver; + +use crate::managers::key::KeyManager; +use crate::nwc::METHODS; + +#[derive(Debug, Clone)] +pub struct NostrService { + client: Client, +} + +impl NostrService { + pub async fn new(key_manager: &KeyManager, relays: &str) -> Result { + let client = Client::new(&key_manager.server_keys()); + Self::add_relays(&client, relays).await?; + Ok(Self { client }) + } + + async fn add_relays(client: &Client, relays: &str) -> Result<()> { + let lines = relays.split(',').collect::>(); + let relays = lines + .iter() + .map(|line| line.trim()) + .filter(|line| !line.is_empty()) + .map(|line| line.to_string()) + .collect::>(); + for relay in relays { + client.add_relay(relay).await?; + } + Ok(()) + } + + pub async fn broadcast_info_event(&self, keys: &KeyManager) -> Result { + let content = METHODS + .iter() + .map(ToString::to_string) + .collect::>() + .join(" "); + let info = EventBuilder::new(Kind::WalletConnectInfo, content, []) + .to_event(&keys.server_keys())?; + self.client + .send_event(info) + .await + .map_err(|e| anyhow::anyhow!("Failed to send event: {}", e)) + } + + pub async fn connect(&self) -> () { + self.client.connect().await + } + + pub async fn disconnect(&self) -> Result<()> { + self.client + .disconnect() + .await + .map_err(|e| anyhow::anyhow!("Failed to disconnect: {}", e)) + } + + pub fn notifications(&self) -> Receiver { + self.client.notifications() + } +} diff --git a/fedimint-nwc/src/state.rs b/fedimint-nwc/src/state.rs index c663c27..c70e8f1 100644 --- a/fedimint-nwc/src/state.rs +++ b/fedimint-nwc/src/state.rs @@ -1,131 +1,50 @@ use std::collections::BTreeSet; -use std::fs::{create_dir_all, File}; -use std::io::{BufReader, Write}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; use std::sync::Arc; use std::time::Duration; -use anyhow::{Context, Result}; -use multimint::fedimint_core::api::InviteCode; -use multimint::MultiMint; -use nostr_sdk::secp256k1::SecretKey; -use nostr_sdk::{Client, Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind, Timestamp}; -use serde::{Deserialize, Serialize}; +use nostr_sdk::{Event, EventId, JsonUtil, Kind}; use tokio::sync::Mutex; use tracing::{debug, error, info}; use crate::config::Cli; -use crate::nwc::{handle_nwc_request, METHODS}; - -pub async fn init(cli: Cli) -> Result { - let db_path = cli.db_path.clone(); - let keys_file = cli.keys_file.clone(); - let relays = cli.relays.clone(); - - let mut state = AppState::new(db_path, &keys_file, &relays).await?; - - let manual_secret = AppState::load_manual_secret(&cli).await; - let invite_code = cli.invite_code.clone(); - state.init_multimint(&invite_code, manual_secret).await?; - if state.multimint.all().await.is_empty() { - return Err(anyhow::anyhow!( - "No multimint clients found, must have at least one client to start the server." - )); - } - - // Broadcast info event on startup - state.broadcast_info_event().await?; - - Ok(state) -} +use crate::managers::KeyManager; +use crate::nwc::handle_nwc_request; +use crate::services::{MultiMintService, NostrService}; #[derive(Debug, Clone)] pub struct AppState { - pub multimint: MultiMint, - pub user_keys: Keys, - pub server_keys: Keys, - pub sent_info: bool, - pub nostr_client: Client, + pub multimint_service: MultiMintService, + pub nostr_service: NostrService, + pub key_manager: KeyManager, pub active_requests: Arc>>, } impl AppState { - pub async fn new(fm_db_path: PathBuf, keys_file: &str, relays: &str) -> Result { - let clients = Self::init_multimint_clients(fm_db_path).await?; - let keys = Nip47Keys::load_or_generate(keys_file)?; - let nostr_client = Self::setup_nostr_client(&keys, relays).await?; + pub async fn new(cli: Cli) -> Result { + let key_manager = KeyManager::new(&cli.keys_file)?; + let multimint_service = MultiMintService::new(cli.db_path).await?; + let nostr_service = NostrService::new(&key_manager, &cli.relays).await?; let active_requests = Arc::new(Mutex::new(BTreeSet::new())); Ok(Self { - multimint: clients, - user_keys: keys.user_keys(), - server_keys: keys.server_keys(), - sent_info: keys.sent_info, - nostr_client, + multimint_service, + nostr_service, + key_manager, active_requests, }) } - pub async fn init_multimint( - &mut self, - invite_code: &str, - manual_secret: Option, - ) -> Result<()> { - match InviteCode::from_str(invite_code) { - Ok(invite_code) => { - let federation_id = self - .multimint - .register_new(invite_code, manual_secret) - .await?; - info!("Created client for federation id: {:?}", federation_id); - Ok(()) - } - Err(e) => { - error!("Invalid federation invite code: {}", e); - Err(e.into()) - } - } - } - - async fn init_multimint_clients(fm_db_path: PathBuf) -> Result { - let clients = MultiMint::new(fm_db_path).await?; - clients.update_gateway_caches().await?; - Ok(clients) - } - - async fn setup_nostr_client(keys: &Nip47Keys, relays: &str) -> Result { - info!("Setting up nostr client..."); - let nostr_client = Client::new(&keys.server_keys()); - Self::add_relays(&nostr_client, relays).await?; - info!("Setting NWC subscription..."); - let subscription = setup_subscription(keys); - nostr_client.subscribe(vec![subscription], None).await; - Ok(nostr_client) - } - - async fn add_relays(nostr_client: &Client, relays: &str) -> Result<()> { - let lines = relays.split(',').collect::>(); - let relays = lines - .iter() - .map(|line| line.trim()) - .filter(|line| !line.is_empty()) - .map(|line| line.to_string()) - .collect::>(); - info!("Adding relays..."); - for relay in relays { - nostr_client.add_relay(relay).await?; - } + pub async fn init(&mut self, cli: &Cli) -> Result<(), anyhow::Error> { + self.multimint_service + .init_multimint(&cli.invite_code, cli.manual_secret.clone()) + .await?; + self.nostr_service + .broadcast_info_event(&self.key_manager) + .await?; Ok(()) } - pub async fn load_manual_secret(cli: &Cli) -> Option { - cli.manual_secret - .clone() - .or_else(|| std::env::var("FEDIMINT_CLIENTD_MANUAL_SECRET").ok()) - } - pub async fn wait_for_active_requests(&self) { let requests = self.active_requests.lock().await; loop { @@ -137,30 +56,6 @@ impl AppState { } } - pub async fn broadcast_info_event(&mut self) -> Result<()> { - let content = METHODS - .iter() - .map(ToString::to_string) - .collect::>() - .join(" "); - let info = - EventBuilder::new(Kind::WalletConnectInfo, content, []).to_event(&self.server_keys)?; - let res = self - .nostr_client - .send_event(info) - .await - .map_err(|e| anyhow::anyhow!("Failed to send event: {}", e)); - - if res.is_ok() { - self.sent_info = true; - info!("Sent info event..."); - } else { - error!("Failed to send info event: {}", res.err().unwrap()); - } - - Ok(()) - } - pub async fn handle_event(&self, event: Event) { if event.kind == Kind::WalletConnectRequest && event.verify().is_ok() { info!("Received event: {}", event.as_json()); @@ -181,71 +76,3 @@ impl AppState { } } } - -#[derive(Debug, Clone, Deserialize, Serialize)] -struct Nip47Keys { - server_key: SecretKey, - user_key: SecretKey, - #[serde(default)] - sent_info: bool, -} - -impl Nip47Keys { - fn generate() -> Result { - let server_keys = Keys::generate(); - let server_key = server_keys.secret_key()?; - - let user_keys = Keys::generate(); - let user_key = user_keys.secret_key()?; - - Ok(Nip47Keys { - server_key: **server_key, - user_key: **user_key, - sent_info: false, - }) - } - - fn load_or_generate(keys_file: &str) -> Result { - let path = Path::new(keys_file); - match File::open(path) { - Ok(file) => { - let reader = BufReader::new(file); - serde_json::from_reader(reader).context("Failed to parse JSON") - } - Err(_) => { - let keys = Self::generate()?; - Self::write_keys(&keys, path)?; - Ok(keys) - } - } - } - - fn write_keys(keys: &Nip47Keys, path: &Path) -> Result<()> { - let json_str = serde_json::to_string(keys).context("Failed to serialize data")?; - - if let Some(parent) = path.parent() { - create_dir_all(parent).context("Failed to create directory")?; - } - - let mut file = File::create(path).context("Failed to create file")?; - file.write_all(json_str.as_bytes()) - .context("Failed to write to file")?; - Ok(()) - } - - fn server_keys(&self) -> Keys { - Keys::new(self.server_key.into()) - } - - fn user_keys(&self) -> Keys { - Keys::new(self.user_key.into()) - } -} - -fn setup_subscription(keys: &Nip47Keys) -> Filter { - Filter::new() - .kinds(vec![Kind::WalletConnectRequest]) - .author(keys.user_keys().public_key()) - .pubkey(keys.server_keys().public_key()) - .since(Timestamp::now()) -}