Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fedimint resync #1239

Merged
merged 1 commit into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading