diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index da117c057..43991325d 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -48,7 +48,7 @@ pub use crate::ldkstorage::{CHANNEL_MANAGER_KEY, MONITORS_PREFIX_KEY}; use crate::auth::MutinyAuthClient; use crate::labels::{Contact, LabelStorage}; use crate::nostr::nwc::SpendingConditions; -use crate::storage::MutinyStorage; +use crate::storage::{MutinyStorage, NEED_FULL_SYNC_KEY}; use crate::{error::MutinyError, nostr::ReservedProfile}; use crate::{nodemanager::NodeManager, nostr::ProfileType}; use crate::{nostr::NostrManager, utils::sleep}; @@ -163,6 +163,15 @@ impl MutinyWallet { // start the nostr wallet connect background process mw.start_nostr_wallet_connect(first_node).await; + #[cfg(not(test))] + { + // if we need a full sync from a restore + if mw.storage.get(NEED_FULL_SYNC_KEY)?.unwrap_or_default() { + mw.node_manager.wallet.full_sync().await?; + mw.storage.delete(&[NEED_FULL_SYNC_KEY])?; + } + } + Ok(mw) } @@ -417,6 +426,8 @@ impl MutinyWallet { self.start().await?; + self.node_manager.wallet.full_sync().await?; + Ok(()) } @@ -429,6 +440,7 @@ impl MutinyWallet { S::clear().await?; storage.start().await?; storage.insert_mnemonic(m)?; + storage.set_data(NEED_FULL_SYNC_KEY, true, None)?; Ok(()) } } diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index ceb96f56e..f7c57ec4c 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -11,7 +11,7 @@ use crate::scb::{ EncryptedSCB, StaticChannelBackup, StaticChannelBackupStorage, SCB_ENCRYPTION_KEY_DERIVATION_PATH, }; -use crate::storage::{MutinyStorage, DEVICE_ID_KEY, KEYCHAIN_STORE_KEY}; +use crate::storage::{MutinyStorage, DEVICE_ID_KEY, KEYCHAIN_STORE_KEY, NEED_FULL_SYNC_KEY}; use crate::utils::sleep; use crate::MutinyWalletConfig; use crate::{ @@ -521,7 +521,7 @@ pub struct NodeManager { #[cfg(target_arch = "wasm32")] websocket_proxy_addr: String, esplora: Arc, - wallet: Arc>, + pub(crate) wallet: Arc>, gossip_sync: Arc, scorer: Arc>, chain: Arc>, @@ -2278,6 +2278,7 @@ impl NodeManager { // delete the bdk keychain store self.storage.delete(&[KEYCHAIN_STORE_KEY])?; + self.storage.set_data(NEED_FULL_SYNC_KEY, true, None)?; // shut back down after reading if it was already closed if needs_db_connection { diff --git a/mutiny-core/src/onchain.rs b/mutiny-core/src/onchain.rs index 2310ca7f7..ec9448f51 100644 --- a/mutiny-core/src/onchain.rs +++ b/mutiny-core/src/onchain.rs @@ -1,5 +1,5 @@ use anyhow::anyhow; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; @@ -91,6 +91,83 @@ impl OnChainWallet { } pub async fn sync(&self) -> Result<(), MutinyError> { + // get first wallet lock that only needs to read + let (checkpoints, spks, txids) = { + if let Ok(wallet) = self.wallet.try_read() { + let checkpoints = wallet.checkpoints(); + + let spk_vec = wallet + .spk_index() + .unused_spks(..) + .map(|(k, v)| (*k, v.clone())) + .collect::>(); + + let mut spk_map = BTreeMap::new(); + for ((a, b), c) in spk_vec { + spk_map.entry(a).or_insert_with(Vec::new).push((b, c)); + } + + let chain = wallet.local_chain(); + let chain_tip = chain.tip().unwrap_or_default(); + + let unconfirmed_txids = wallet + .tx_graph() + .list_chain_txs(chain, chain_tip) + .filter(|canonical_tx| !canonical_tx.observed_as.is_confirmed()) + .map(|canonical_tx| canonical_tx.node.txid) + .collect::>(); + + (checkpoints.clone(), spk_map, unconfirmed_txids) + } else { + log_error!(self.logger, "Could not get wallet lock to sync"); + return Err(MutinyError::WalletOperationFailed); + } + }; + + let update = self + .blockchain + .scan(&checkpoints, spks, txids, core::iter::empty(), 20, 5) + .await?; + + // get new wallet lock for writing and apply the update + for _ in 0..10 { + match self.wallet.try_write() { + Ok(mut wallet) => match wallet.apply_update(update) { + Ok(changed) => { + // commit the changes if there were any + if changed { + wallet.commit()?; + } + + return Ok(()); + } + Err(e) => { + // failed to apply wallet update + log_error!(self.logger, "Could not apply wallet update: {e}"); + return Err(MutinyError::Other(anyhow!("Could not apply update: {e}"))); + } + }, + Err(e) => { + // if we can't get the lock, we just return and try again later + log_error!( + self.logger, + "Could not get wallet lock: {e}, retrying in 250ms" + ); + + if self.stop.load(Ordering::Relaxed) { + return Err(MutinyError::NotRunning); + }; + + sleep(250).await; + } + } + } + + log_error!(self.logger, "Could not get wallet lock after 10 retries"); + Err(MutinyError::WalletOperationFailed) + } + + pub async fn full_sync(&self) -> Result<(), MutinyError> { // get first wallet lock that only needs to read let (checkpoints, spks) = { if let Ok(wallet) = self.wallet.try_read() { diff --git a/mutiny-core/src/storage.rs b/mutiny-core/src/storage.rs index b602184e7..b6388296d 100644 --- a/mutiny-core/src/storage.rs +++ b/mutiny-core/src/storage.rs @@ -16,6 +16,7 @@ use uuid::Uuid; pub const KEYCHAIN_STORE_KEY: &str = "bdk_keychain"; pub(crate) const MNEMONIC_KEY: &str = "mnemonic"; +pub(crate) const NEED_FULL_SYNC_KEY: &str = "needs_full_sync"; pub const NODES_KEY: &str = "nodes"; const FEE_ESTIMATES_KEY: &str = "fee_estimates"; const FIRST_SYNC_KEY: &str = "first_sync";