Skip to content

Commit

Permalink
Add, remove, and list federations
Browse files Browse the repository at this point in the history
  • Loading branch information
TonyGiorgio committed Nov 23, 2023
1 parent 048f6eb commit 6c8fd87
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 4 deletions.
139 changes: 139 additions & 0 deletions mutiny-core/src/fedimint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use crate::{error::MutinyError, logging::MutinyLogger, storage::MutinyStorage};
use bitcoin::{secp256k1::Secp256k1, util::bip32::ExtendedPrivKey};
use bitcoin::{
util::bip32::{ChildNumber, DerivationPath},
Network,
};
use fedimint_client::{derivable_secret::DerivableSecret, ClientArc, FederationInfo};
use fedimint_core::db::mem_impl::MemDatabase;
use fedimint_core::{api::InviteCode, config::FederationId};
use fedimint_ln_client::LightningClientInit;
use fedimint_mint_client::MintClientInit;
use fedimint_wallet_client::WalletClientInit;
use lightning::log_info;
use lightning::util::logger::Logger;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
str::FromStr,
sync::{atomic::AtomicBool, Arc, RwLock},
};

const FEDIMINT_CLIENT_NONCE: &[u8] = b"Fedimint Client Salt";

// This is the FedimintStorage object saved to the DB
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct FedimintStorage {
pub fedimints: HashMap<String, FedimintIndex>,
#[serde(default)]
pub version: u32,
}

// This is the FedimintIdentity that refer to a specific node
// Used for public facing identification.
pub struct FedimintIdentity {
pub uuid: String,
pub federation_id: FederationId,
}

// This is the FedimintIndex reference that is saved to the DB
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct FedimintIndex {
pub child_index: u32,
pub federation_code: String,
pub archived: Option<bool>,
}

impl FedimintIndex {
pub fn is_archived(&self) -> bool {
self.archived.unwrap_or(false)
}
}

pub(crate) struct FedimintClient<S: MutinyStorage> {
pub _uuid: String,
pub child_index: u32,
pub federation_code: String,
pub fedimint_client: ClientArc,
stopped_components: Arc<RwLock<Vec<bool>>>,
storage: S,
network: Network,
pub(crate) logger: Arc<MutinyLogger>,
stop: Arc<AtomicBool>,
}

impl<S: MutinyStorage> FedimintClient<S> {
#[allow(clippy::too_many_arguments)]
pub(crate) async fn new(
uuid: String,
fedimint_index: &FedimintIndex,
federation_code: String,
xprivkey: ExtendedPrivKey,
storage: S,
network: Network,
logger: Arc<MutinyLogger>,
) -> Result<Self, MutinyError> {
log_info!(logger, "initializing a new fedimint client: {uuid}");

// a list of components that need to be stopped and whether or not they are stopped
let stopped_components = Arc::new(RwLock::new(vec![]));

let stop = Arc::new(AtomicBool::new(false));

log_info!(logger, "Joining federation {}", federation_code);

let invite_code = InviteCode::from_str(&federation_code)
.map_err(|_| MutinyError::InvalidArgumentsError)?;

let mut client_builder = fedimint_client::Client::builder();
client_builder.with_module(WalletClientInit(None));
client_builder.with_module(MintClientInit);
client_builder.with_module(LightningClientInit);
client_builder.with_database(MemDatabase::new().into()); // TODO not in memory
client_builder.with_primary_module(1);
client_builder.with_federation_info(FederationInfo::from_invite_code(invite_code).await?);

let secret = create_fedimint_secret(xprivkey, fedimint_index)?;

let fedimint_client = client_builder.build(secret).await?;

Ok(FedimintClient {
_uuid: uuid,
child_index: fedimint_index.child_index,
federation_code,
fedimint_client,
stopped_components,
storage,
network,
logger,
stop,
})
}

pub fn fedimint_index(&self) -> FedimintIndex {
FedimintIndex {
child_index: self.child_index,
federation_code: self.federation_code.clone(),
archived: Some(false),
}
}
}

// A fedimint private key will be derived from `m/1'/X'`, where X is the index of a specific fedimint.
// Fedimint will derive further keys from there.
fn create_fedimint_secret(
xprivkey: ExtendedPrivKey,
fedimint_index: &FedimintIndex,
) -> Result<DerivableSecret, MutinyError> {
let context = Secp256k1::new();
let xpriv = xprivkey.derive_priv(
&context,
&DerivationPath::from(vec![
ChildNumber::from_hardened_idx(1)?,
ChildNumber::from_hardened_idx(fedimint_index.child_index)?,
]),
)?;
let secret =
DerivableSecret::new_root(&xpriv.private_key.secret_bytes(), FEDIMINT_CLIENT_NONCE);
Ok(secret)
}
1 change: 1 addition & 0 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod encrypt;
pub mod error;
pub mod esplora;
mod event;
pub mod fedimint;
mod fees;
mod gossip;
mod keymanager;
Expand Down
165 changes: 162 additions & 3 deletions mutiny-core/src/nodemanager.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
use crate::lnurlauth::AuthManager;
use crate::logging::LOGGING_KEY;
use crate::multiesplora::MultiEsploraClient;
use crate::redshift::{RedshiftManager, RedshiftStatus, RedshiftStorage};
use crate::storage::{MutinyStorage, DEVICE_ID_KEY, KEYCHAIN_STORE_KEY, NEED_FULL_SYNC_KEY};
use crate::utils::{sleep, spawn};
Expand All @@ -23,6 +20,12 @@ use crate::{
event::{HTLCStatus, PaymentInfo},
lnurlauth::make_lnurl_auth_connection,
};
use crate::{fedimint::FedimintIdentity, multiesplora::MultiEsploraClient};
use crate::{fedimint::FedimintStorage, lnurlauth::AuthManager};
use crate::{
fedimint::{FedimintClient, FedimintIndex},
logging::LOGGING_KEY,
};
use crate::{gossip::*, scorer::HubPreferentialScorer};
use crate::{labels::LabelStorage, subscription::MutinySubscriptionClient};
use anyhow::anyhow;
Expand All @@ -36,6 +39,7 @@ use bitcoin::util::bip32::ExtendedPrivKey;
use bitcoin::{Address, Network, OutPoint, Transaction, Txid};
use core::time::Duration;
use esplora_client::Builder;
use fedimint_core::config::FederationId;
use futures::{future::join_all, lock::Mutex};
use lightning::chain::Confirm;
use lightning::events::ClosureReason;
Expand Down Expand Up @@ -505,6 +509,8 @@ pub struct NodeManager<S: MutinyStorage> {
pub(crate) storage: S,
pub(crate) node_storage: Mutex<NodeStorage>,
pub(crate) nodes: Arc<Mutex<HashMap<PublicKey, Arc<Node<S>>>>>,
pub(crate) fedimint_storage: Mutex<FedimintStorage>,
pub(crate) fedimints: Arc<Mutex<HashMap<FederationId, Arc<FedimintClient<S>>>>>,
auth: AuthManager,
lnurl_client: Arc<LnUrlClient>,
pub(crate) lsp_clients: Vec<LspClient>,
Expand Down Expand Up @@ -708,6 +714,35 @@ impl<S: MutinyStorage> NodeManager<S> {
Arc::new(Mutex::new(nodes_map))
};

let fedimint_storage = storage.get_fedimints()?;

let unarchived_fedimints = fedimint_storage
.clone()
.fedimints
.into_iter()
.filter(|(_, n)| !n.is_archived());

let mut fedimint_map = HashMap::new();

for fedimint_item in unarchived_fedimints {
let fedimint = FedimintClient::new(
fedimint_item.0,
&fedimint_item.1,
fedimint_item.1.federation_code.clone(),
c.xprivkey,
storage.clone(),
c.network,
logger.clone(),
)
.await?;

let id = fedimint.fedimint_client.federation_id();

fedimint_map.insert(id, Arc::new(fedimint));
}

let fedimints = Arc::new(Mutex::new(fedimint_map));

let lnurl_client = Arc::new(
lnurl::Builder::default()
.build_async()
Expand Down Expand Up @@ -749,6 +784,8 @@ impl<S: MutinyStorage> NodeManager<S> {
storage,
node_storage: Mutex::new(node_storage),
nodes,
fedimint_storage: Mutex::new(fedimint_storage),
fedimints,
#[cfg(target_arch = "wasm32")]
websocket_proxy_addr,
user_rgs_url: c.user_rgs_url,
Expand Down Expand Up @@ -2200,6 +2237,54 @@ impl<S: MutinyStorage> NodeManager<S> {
Ok(storage_peers)
}

/// Lists the federation id's of the fedimint clients in the manager.
pub async fn list_federations(&self) -> Result<Vec<FederationId>, MutinyError> {
let fedimints = self.fedimints.lock().await;
let federation_ids = fedimints
.iter()
.map(|(_, n)| n.fedimint_client.federation_id())
.collect();
Ok(federation_ids)
}

/// Add a federation based on it's federation code
pub async fn new_federation(
&self,
federation_code: String,
) -> Result<FedimintIdentity, MutinyError> {
create_new_fedimint_from_node_manager(self, federation_code).await
}

/// Removes a federation by setting its archived status to true, based on the FederationId.
pub async fn remove_federation(&self, federation_id: FederationId) -> Result<(), MutinyError> {
// Lock the fedimints to find the federation client
let mut fedimints_guard = self.fedimints.lock().await;

if let Some(fedimint_client) = fedimints_guard.get(&federation_id) {
let uuid = &fedimint_client._uuid;

// Lock the fedimint storage to safely modify the federation storage
let mut fedimint_storage_guard = self.fedimint_storage.lock().await;

if let Some(fedimint) = fedimint_storage_guard.fedimints.get_mut(uuid) {
fedimint.archived = Some(true);

// Save the updated storage
self.storage
.insert_fedimints(fedimint_storage_guard.clone())?;
} else {
return Err(MutinyError::NotFound);
}

// Remove the federation from the fedimints hashmap
fedimints_guard.remove(&federation_id);
} else {
return Err(MutinyError::NotFound);
}

Ok(())
}

/// Checks whether or not the user is subscribed to Mutiny+.
///
/// Returns None if there's no subscription at all.
Expand Down Expand Up @@ -2531,6 +2616,80 @@ pub(crate) async fn create_new_node_from_node_manager<S: MutinyStorage>(
})
}

// This will create a new federation with a node manager and return the PublicKey of the node created.
pub(crate) async fn create_new_fedimint_from_node_manager<S: MutinyStorage>(
node_manager: &NodeManager<S>,
federation_code: String,
) -> Result<FedimintIdentity, MutinyError> {
// Begin with a mutex lock so that nothing else can
// save or alter the node list while it is about to
// be saved.
let mut fedimint_mutex = node_manager.fedimint_storage.lock().await;

// Get the current fedimints and their bip32 indices
// so that we can create another federation with the next.
// Always get it from our storage, the fedimint_mutex is
// mostly for read only and locking.
let mut existing_fedimints = node_manager.storage.get_fedimints()?;
let next_fedimint_index = match existing_fedimints
.fedimints
.iter()
.max_by_key(|(_, v)| v.child_index)
{
None => 0,
Some((_, v)) => v.child_index + 1,
};

// Create and save a new fedimint using the next child index
let next_fedimint_uuid = Uuid::new_v4().to_string();

let next_fedimint = FedimintIndex {
child_index: next_fedimint_index,
federation_code: federation_code.clone(),
archived: Some(false),
};

existing_fedimints.version += 1;
existing_fedimints
.fedimints
.insert(next_fedimint_uuid.clone(), next_fedimint.clone());

node_manager
.storage
.insert_fedimints(existing_fedimints.clone())?;
fedimint_mutex.fedimints = existing_fedimints.fedimints.clone();

// now create the node process and init it
let new_fedimint_res = FedimintClient::new(
next_fedimint_uuid.clone(),
&next_fedimint,
federation_code,
node_manager.xprivkey,
node_manager.storage.clone(),
node_manager.network,
node_manager.logger.clone(),
)
.await;

let new_fedimint = match new_fedimint_res {
Ok(new_fedimint) => new_fedimint,
Err(e) => return Err(e),
};

let federation_id = new_fedimint.fedimint_client.federation_id();
node_manager
.fedimints
.clone()
.lock()
.await
.insert(federation_id, Arc::new(new_fedimint));

Ok(FedimintIdentity {
uuid: next_fedimint_uuid.clone(),
federation_id,
})
}

#[cfg(test)]
mod tests {
use crate::{
Expand Down
Loading

0 comments on commit 6c8fd87

Please sign in to comment.