Skip to content

Commit

Permalink
Merge pull request #1239 from MutinyWallet/fedimint-resync
Browse files Browse the repository at this point in the history
Fedimint resync
  • Loading branch information
benthecarman authored Aug 7, 2024
2 parents 07cc2df + 55f9f7b commit c381689
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 12 deletions.
16 changes: 8 additions & 8 deletions mutiny-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
108 changes: 108 additions & 0 deletions mutiny-core/src/federation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -143,6 +144,13 @@ impl From<LnPayState> 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 {
Expand Down Expand Up @@ -262,6 +270,7 @@ impl<S: MutinyStorage> FederationClient<S> {
network: Network,
stop: Arc<AtomicBool>,
logger: Arc<MutinyLogger>,
safe_mode: bool,
) -> Result<Self, MutinyError> {
log_info!(logger, "initializing a new federation client: {uuid}");

Expand All @@ -282,6 +291,10 @@ impl<S: MutinyStorage> FederationClient<S> {

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)?;

Expand Down Expand Up @@ -482,6 +495,101 @@ impl<S: MutinyStorage> FederationClient<S> {
);
}

/// Starts a resync of the federation
pub async fn start_resync(
federation_code: InviteCode,
xprivkey: ExtendedPrivKey,
storage: S,
network: Network,
logger: Arc<MutinyLogger>,
) -> 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<GatewayFees, MutinyError> {
let gateway = self.gateway.read().await;
Ok(gateway.as_ref().map(|x| x.fees.into()).unwrap_or_default())
Expand Down
45 changes: 44 additions & 1 deletion mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -1004,6 +1004,7 @@ impl<S: MutinyStorage> MutinyWalletBuilder<S> {
esplora.clone(),
stop.clone(),
&logger,
self.safe_mode,
)
.await?;
log_debug!(
Expand Down Expand Up @@ -2982,6 +2983,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
self.esplora.clone(),
federation_code,
self.stop.clone(),
self.safe_mode,
)
.await;
log_trace!(self.logger, "finished calling new_federation");
Expand Down Expand Up @@ -3111,6 +3113,43 @@ impl<S: MutinyStorage> MutinyWallet<S> {
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<Option<ResyncProgress>, 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");
Expand Down Expand Up @@ -3734,6 +3773,7 @@ async fn create_federations<S: MutinyStorage>(
esplora: Arc<AsyncClient>,
stop: Arc<AtomicBool>,
logger: &Arc<MutinyLogger>,
safe_mode: bool,
) -> Result<Arc<RwLock<HashMap<FederationId, Arc<FederationClient<S>>>>>, MutinyError> {
let mut federation_map = HashMap::with_capacity(federation_storage.federations.len());
for (uuid, federation_index) in federation_storage.federations {
Expand All @@ -3746,6 +3786,7 @@ async fn create_federations<S: MutinyStorage>(
c.network,
stop.clone(),
logger.clone(),
safe_mode,
)
.await?;

Expand All @@ -3770,6 +3811,7 @@ pub(crate) async fn create_new_federation<S: MutinyStorage>(
esplora: Arc<AsyncClient>,
federation_code: InviteCode,
stop: Arc<AtomicBool>,
safe_mode: bool,
) -> Result<FederationIdentity, MutinyError> {
// Begin with a mutex lock so that nothing else can
// save or alter the federation list while it is about to
Expand Down Expand Up @@ -3801,6 +3843,7 @@ pub(crate) async fn create_new_federation<S: MutinyStorage>(
network,
stop.clone(),
logger.clone(),
safe_mode,
)
.await?;

Expand Down
2 changes: 1 addition & 1 deletion mutiny-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 3 additions & 2 deletions mutiny-wasm/src/indexed_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
17 changes: 17 additions & 0 deletions mutiny-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<JsValue, MutinyJsError> {
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.
Expand Down

0 comments on commit c381689

Please sign in to comment.