From 55f9f7baed7cddaac7a4598b4ace5002eb0faeb3 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 6 Aug 2024 16:14:08 -0500 Subject: [PATCH] Resync fedimint --- mutiny-core/Cargo.toml | 16 ++--- mutiny-core/src/federation.rs | 108 ++++++++++++++++++++++++++++++++++ mutiny-core/src/lib.rs | 45 +++++++++++++- mutiny-wasm/Cargo.toml | 2 +- mutiny-wasm/src/indexed_db.rs | 5 +- mutiny-wasm/src/lib.rs | 17 ++++++ 6 files changed, 181 insertions(+), 12 deletions(-) diff --git a/mutiny-core/Cargo.toml b/mutiny-core/Cargo.toml index e69b602cd..8abafd0df 100644 --- a/mutiny-core/Cargo.toml +++ b/mutiny-core/Cargo.toml @@ -50,14 +50,14 @@ hex-conservative = "0.1.1" async-lock = "3.2.0" bitcoin-waila = "0.5.0" -fedimint-client = "0.3.0" -fedimint-core = "0.3.0" -fedimint-wallet-client = "0.3.0" -fedimint-mint-client = "0.3.0" -fedimint-ln-client = "0.3.0" -fedimint-bip39 = "0.3.0" -fedimint-ln-common = "0.3.0" -fedimint-tbs = "0.3.0" +fedimint-client = "=0.3.0" +fedimint-core = "=0.3.0" +fedimint-wallet-client = "=0.3.0" +fedimint-mint-client = "=0.3.0" +fedimint-ln-client = "=0.3.0" +fedimint-bip39 = "=0.3.0" +fedimint-ln-common = "=0.3.0" +fedimint-tbs = "=0.3.0" moksha-core = "0.2.1" base64 = "0.13.0" diff --git a/mutiny-core/src/federation.rs b/mutiny-core/src/federation.rs index 4f7595136..f99c9004a 100644 --- a/mutiny-core/src/federation.rs +++ b/mutiny-core/src/federation.rs @@ -38,6 +38,7 @@ use fedimint_client::{ }; use fedimint_core::bitcoin_migration::bitcoin30_to_bitcoin29_address; use fedimint_core::config::ClientConfig; +use fedimint_core::core::LEGACY_HARDCODED_INSTANCE_ID_MINT; use fedimint_core::{ api::InviteCode, config::FederationId, @@ -143,6 +144,13 @@ impl From for HTLCStatus { } } +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct ResyncProgress { + pub total: u32, + pub complete: u32, + pub done: bool, +} + // This is the FederationStorage object saved to the DB #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct FederationStorage { @@ -262,6 +270,7 @@ impl FederationClient { network: Network, stop: Arc, logger: Arc, + safe_mode: bool, ) -> Result { log_info!(logger, "initializing a new federation client: {uuid}"); @@ -282,6 +291,10 @@ impl FederationClient { client_builder.with_primary_module(1); + if safe_mode { + client_builder.stopped(); + } + log_trace!(logger, "Building fedimint client db"); let secret = create_federation_secret(xprivkey, network)?; @@ -482,6 +495,101 @@ impl FederationClient { ); } + /// Starts a resync of the federation + pub async fn start_resync( + federation_code: InviteCode, + xprivkey: ExtendedPrivKey, + storage: S, + network: Network, + logger: Arc, + ) -> Result<(), MutinyError> { + let federation_id = federation_code.federation_id(); + + let storage_key = format!("resync_state/{federation_id}"); + storage.set_data(storage_key.clone(), ResyncProgress::default(), None)?; + + log_trace!(logger, "Building fedimint client db"); + let fedimint_storage = + FedimintStorage::new(storage.clone(), federation_id.to_string(), logger.clone()) + .await?; + let db = fedimint_storage.clone().into(); + + let mut client_builder = fedimint_client::Client::builder(db); + client_builder.with_module(WalletClientInit(None)); + client_builder.with_module(MintClientInit); + client_builder.with_module(LightningClientInit); + + client_builder.with_primary_module(1); + + log_trace!(logger, "Building fedimint client db"); + let secret = create_federation_secret(xprivkey, network)?; + + // need to use a fresh database for resync + fedimint_storage.delete_store().await?; + + let config = ClientConfig::download_from_invite_code(&federation_code) + .await + .map_err(|e| { + log_error!(logger, "Could not download federation info: {e}"); + e + })?; + + let fedimint_client = client_builder + .recover( + get_default_client_secret(&secret, &federation_id), + config, + None, + ) + .await + .map_err(|e| { + log_error!(logger, "Could not open federation client: {e}"); + MutinyError::FederationConnectionFailed + })?; + let fedimint_client = Arc::new(fedimint_client); + + log_debug!(logger, "Built fedimint resync client"); + + spawn(async move { + let mut stream = fedimint_client.subscribe_to_recovery_progress(); + + while let Some((id, progress)) = stream.next().await { + // only can rescan mint client, don't care about sync progress of others + if id != LEGACY_HARDCODED_INSTANCE_ID_MINT || progress.is_none() { + continue; + } + + log_debug!(logger, "Got recovery progress: {progress:?}"); + + // save progress state to storage so frontend can show progress + if let Err(e) = storage.set_data( + storage_key.clone(), + ResyncProgress { + total: progress.total, + complete: progress.complete, + done: progress.is_done(), + }, + None, + ) { + log_error!(logger, "Error saving resync progress: {e}"); + } + } + + log_debug!(logger, "No more progress, waiting for recoveries"); + + // wait for all recoveries to complete just to be sure + if let Err(e) = fedimint_client.wait_for_all_recoveries().await { + log_error!(logger, "Error waiting for recoveries: {e}"); + } + + // can now delete the progress state + if let Err(e) = storage.delete(&[storage_key]) { + log_error!(logger, "Error deleting resync progress state: {e}"); + } + }); + + Ok(()) + } + pub(crate) async fn gateway_fee(&self) -> Result { let gateway = self.gateway.read().await; Ok(gateway.as_ref().map(|x| x.fees.into()).unwrap_or_default()) diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 9c9bcf943..288f8cdfd 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -43,7 +43,7 @@ pub mod vss; #[cfg(test)] mod test_utils; -use crate::federation::get_federation_identity; +use crate::federation::{get_federation_identity, ResyncProgress}; pub use crate::gossip::{GOSSIP_SYNC_TIME_KEY, NETWORK_GRAPH_KEY, PROB_SCORER_KEY}; pub use crate::keymanager::generate_seed; pub use crate::ldkstorage::{CHANNEL_CLOSURE_PREFIX, CHANNEL_MANAGER_KEY, MONITORS_PREFIX_KEY}; @@ -1004,6 +1004,7 @@ impl MutinyWalletBuilder { esplora.clone(), stop.clone(), &logger, + self.safe_mode, ) .await?; log_debug!( @@ -2982,6 +2983,7 @@ impl MutinyWallet { self.esplora.clone(), federation_code, self.stop.clone(), + self.safe_mode, ) .await; log_trace!(self.logger, "finished calling new_federation"); @@ -3111,6 +3113,43 @@ impl MutinyWallet { Ok(FederationBalances { balances }) } + pub async fn resync_federation(&self, federation_id: FederationId) -> Result<(), MutinyError> { + if !self.safe_mode { + // cannot safely run unless in safe mode + return Err(MutinyError::AlreadyRunning); + } + + let invite_code = self + .federation_storage + .read() + .await + .federations + .values() + .find(|f| f.federation_code.federation_id() == federation_id) + .ok_or(MutinyError::NotFound)? + .federation_code + .clone(); + + FederationClient::start_resync( + invite_code, + self.xprivkey, + self.storage.clone(), + self.network, + self.logger.clone(), + ) + .await?; + + Ok(()) + } + + pub fn get_federation_resync_progress( + &self, + federation_id: FederationId, + ) -> Result, MutinyError> { + let storage_key = format!("resync_state/{federation_id}"); + self.storage.get_data(storage_key) + } + /// Starts a background process that will check pending fedimint operations pub(crate) async fn start_fedimint_background_checker(&self) { log_trace!(self.logger, "calling start_fedimint_background_checker"); @@ -3734,6 +3773,7 @@ async fn create_federations( esplora: Arc, stop: Arc, logger: &Arc, + safe_mode: bool, ) -> Result>>>>, MutinyError> { let mut federation_map = HashMap::with_capacity(federation_storage.federations.len()); for (uuid, federation_index) in federation_storage.federations { @@ -3746,6 +3786,7 @@ async fn create_federations( c.network, stop.clone(), logger.clone(), + safe_mode, ) .await?; @@ -3770,6 +3811,7 @@ pub(crate) async fn create_new_federation( esplora: Arc, federation_code: InviteCode, stop: Arc, + safe_mode: bool, ) -> Result { // Begin with a mutex lock so that nothing else can // save or alter the federation list while it is about to @@ -3801,6 +3843,7 @@ pub(crate) async fn create_new_federation( network, stop.clone(), logger.clone(), + safe_mode, ) .await?; diff --git a/mutiny-wasm/Cargo.toml b/mutiny-wasm/Cargo.toml index c6886e180..555dbe121 100644 --- a/mutiny-wasm/Cargo.toml +++ b/mutiny-wasm/Cargo.toml @@ -43,7 +43,7 @@ urlencoding = "2.1.2" once_cell = "1.18.0" hex-conservative = "0.1.1" payjoin = { version = "0.13.0", features = ["send", "base64"] } -fedimint-core = "0.3.0" +fedimint-core = "=0.3.0" moksha-core = "0.2.1" bitcoin-waila = "0.5.0" diff --git a/mutiny-wasm/src/indexed_db.rs b/mutiny-wasm/src/indexed_db.rs index 793d5cf37..278ed6d91 100644 --- a/mutiny-wasm/src/indexed_db.rs +++ b/mutiny-wasm/src/indexed_db.rs @@ -4,7 +4,7 @@ use bip39::Mnemonic; use futures::lock::Mutex; use gloo_utils::format::JsValueSerdeExt; use lightning::util::logger::Logger; -use lightning::{log_debug, log_error, log_trace}; +use lightning::{log_debug, log_error, log_info, log_trace}; use log::error; use mutiny_core::blindauth::TokenStorage; use mutiny_core::logging::LOGGING_KEY; @@ -346,11 +346,12 @@ impl IndexedDbStorage { match vss { None => { + log_info!(logger, "No VSS configured"); let final_map = map.memory.read().unwrap(); Ok(final_map.clone()) } Some(vss) => { - log_trace!(logger, "Reading from vss"); + log_info!(logger, "Reading from vss"); let start = instant::Instant::now(); let keys = vss.list_key_versions(None).await?; let mut futs = Vec::with_capacity(keys.len()); diff --git a/mutiny-wasm/src/lib.rs b/mutiny-wasm/src/lib.rs index 8d22c263a..902d20f26 100644 --- a/mutiny-wasm/src/lib.rs +++ b/mutiny-wasm/src/lib.rs @@ -2103,6 +2103,23 @@ impl MutinyWallet { Ok(()) } + pub async fn resync_federation(&self, federation_id: String) -> Result<(), MutinyJsError> { + let federation_id = FederationId::from_str(&federation_id) + .map_err(|_| MutinyJsError::InvalidArgumentsError)?; + self.inner.resync_federation(federation_id).await?; + Ok(()) + } + + pub fn get_federation_resync_progress( + &self, + federation_id: String, + ) -> Result { + let federation_id = FederationId::from_str(&federation_id) + .map_err(|_| MutinyJsError::InvalidArgumentsError)?; + let res = self.inner.get_federation_resync_progress(federation_id)?; + Ok(JsValue::from_serde(&res)?) + } + /// Restore's the mnemonic after deleting the previous state. /// /// Backup the state beforehand. Does not restore lightning data.