Skip to content

Commit f814cf8

Browse files
authored
Merge pull request #96 from EthanYuan/save-lnd-channel-state
feat: check vss state via lnd channels snapshot
2 parents 0dc5251 + 965e2a1 commit f814cf8

17 files changed

+495
-36
lines changed

Cargo.lock

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mutiny-core/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ argon2 = { version = "0.5.0", features = ["password-hash", "alloc"] }
6565
bincode = "1.3.3"
6666
hex-conservative = "0.1.1"
6767
async-lock = "3.2.0"
68+
once_cell = "1.18.0"
6869

6970
base64 = "0.13.0"
7071
pbkdf2 = "0.11"

mutiny-core/src/authclient.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use bitcoin::hashes::{hex::prelude::*, sha256, Hash};
44
use bitcoin::key::rand::Rng;
55
use bitcoin::secp256k1::rand::thread_rng;
66
use jwt_compact::UntrustedToken;
7-
use lightning::{log_debug, log_error, log_info};
8-
use lightning::{log_trace, util::logger::*};
7+
use lightning::{log_debug, log_error, log_info, log_trace, util::logger::*};
98
use reqwest::Client;
109
use reqwest::{Method, StatusCode, Url};
1110
use serde_json::Value;

mutiny-core/src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub enum MutinyError {
2121
/// Returned when trying to start Mutiny while it is already running.
2222
#[error("Mutiny is already running.")]
2323
AlreadyRunning,
24+
#[error("The stored LND snapshot is outdated.")]
25+
LndSnapshotOutdated,
2426
/// Returned when trying to stop Mutiny while it is not running.
2527
#[error("Mutiny is not running.")]
2628
NotRunning,

mutiny-core/src/ldkstorage.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ use crate::node::Router;
88
use crate::node::{default_user_config, ChainMonitor};
99
use crate::nodemanager::ChannelClosure;
1010
use crate::storage::{IndexItem, MutinyStorage, VersionedValue};
11-
use crate::utils;
12-
use crate::utils::{sleep, spawn};
11+
use crate::utils::{self, now, sleep, spawn};
1312
use crate::{chain::MutinyChain, scorer::HubPreferentialScorer};
1413
use anyhow::anyhow;
1514
use bitcoin::hashes::hex::FromHex;
@@ -47,7 +46,7 @@ const CHANNEL_OPENING_PARAMS_PREFIX: &str = "chan_open_params/";
4746
pub const CHANNEL_CLOSURE_PREFIX: &str = "channel_closure/";
4847
pub const CHANNEL_CLOSURE_BUMP_PREFIX: &str = "channel_closure_bump/";
4948
const FAILED_SPENDABLE_OUTPUT_DESCRIPTOR_KEY: &str = "failed_spendable_outputs";
50-
pub const BROADCAST_TX_1_IN_MULTI_OUT: &str = "broadcast_tx_1_in_multi_out/";
49+
pub const ACTIVE_NODE_ID_KEY: &str = "active_node_id";
5150

5251
pub(crate) type PhantomChannelManager<S: MutinyStorage> = LdkChannelManager<
5352
Arc<ChainMonitor<S>>,
@@ -464,6 +463,14 @@ impl<S: MutinyStorage> MutinyNodePersister<S> {
464463
Ok(())
465464
}
466465

466+
pub(crate) fn persist_node_id(&self, node_id: String) -> Result<(), MutinyError> {
467+
self.storage.write_data(
468+
ACTIVE_NODE_ID_KEY.to_string(),
469+
node_id,
470+
Some(now().as_secs() as u32),
471+
)
472+
}
473+
467474
/// Persists the failed spendable outputs to storage.
468475
/// Previously failed spendable outputs are not overwritten.
469476
///

mutiny-core/src/lib.rs

+92-13
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,17 @@ use crate::error::MutinyError;
4343
pub use crate::gossip::{GOSSIP_SYNC_TIME_KEY, NETWORK_GRAPH_KEY, PROB_SCORER_KEY};
4444
pub use crate::keymanager::generate_seed;
4545
pub use crate::ldkstorage::{
46-
BROADCAST_TX_1_IN_MULTI_OUT, CHANNEL_CLOSURE_BUMP_PREFIX, CHANNEL_CLOSURE_PREFIX,
47-
CHANNEL_MANAGER_KEY, MONITORS_PREFIX_KEY,
46+
ACTIVE_NODE_ID_KEY, CHANNEL_CLOSURE_BUMP_PREFIX, CHANNEL_CLOSURE_PREFIX, CHANNEL_MANAGER_KEY,
47+
MONITORS_PREFIX_KEY,
4848
};
49+
use crate::lsp::lndchannel::fetch_lnd_channels_snapshot;
50+
use crate::messagehandler::CommonLnEventCallback;
4951
use crate::nodemanager::NodeManager;
5052
use crate::nodemanager::{ChannelClosure, MutinyBip21RawMaterials};
5153
pub use crate::onchain::BroadcastTx1InMultiOut;
52-
use crate::storage::get_invoice_by_hash;
53-
use crate::utils::sleep;
54-
use crate::utils::spawn;
54+
use crate::storage::{get_invoice_by_hash, LND_CHANNELS_SNAPSHOT_KEY};
55+
use crate::utils::{now, sleep, spawn, spawn_with_handle, StopHandle};
56+
use crate::vss::VSS_MANAGER;
5557
use crate::{authclient::MutinyAuthClient, logging::MutinyLogger};
5658
use crate::{
5759
event::{HTLCStatus, MillisatAmount, PaymentInfo},
@@ -67,7 +69,7 @@ use crate::{
6769
PAYMENT_INBOUND_PREFIX_KEY, PAYMENT_OUTBOUND_PREFIX_KEY, TRANSACTION_DETAILS_PREFIX_KEY,
6870
},
6971
};
70-
use anyhow::Context;
72+
7173
use bdk_chain::ConfirmationTime;
7274
use bip39::Mnemonic;
7375
pub use bitcoin;
@@ -77,6 +79,7 @@ use bitcoin::{bip32::Xpriv, Transaction};
7779
use bitcoin::{hashes::sha256, Network, Txid};
7880
use bitcoin::{hashes::Hash, Address};
7981

82+
use anyhow::Context;
8083
use futures_util::lock::Mutex;
8184
use hex_conservative::{DisplayHex, FromHex};
8285
use itertools::Itertools;
@@ -87,10 +90,8 @@ use lightning::util::logger::Logger;
8790
use lightning::{log_debug, log_error, log_info, log_trace, log_warn};
8891
pub use lightning_invoice;
8992
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription};
90-
91-
use messagehandler::CommonLnEventCallback;
93+
use reqwest::Client;
9294
use serde::{Deserialize, Serialize};
93-
use utils::{spawn_with_handle, StopHandle};
9495

9596
use std::collections::HashMap;
9697
use std::collections::HashSet;
@@ -99,7 +100,6 @@ use std::sync::Arc;
99100
use std::time::Duration;
100101
#[cfg(not(target_arch = "wasm32"))]
101102
use std::time::Instant;
102-
103103
#[cfg(target_arch = "wasm32")]
104104
use web_time::Instant;
105105

@@ -548,6 +548,7 @@ pub struct MutinyWalletConfigBuilder {
548548
skip_device_lock: bool,
549549
pub safe_mode: bool,
550550
skip_hodl_invoices: bool,
551+
check_lnd_snapshot: bool,
551552
}
552553

553554
impl MutinyWalletConfigBuilder {
@@ -572,6 +573,7 @@ impl MutinyWalletConfigBuilder {
572573
skip_device_lock: false,
573574
safe_mode: false,
574575
skip_hodl_invoices: true,
576+
check_lnd_snapshot: false,
575577
}
576578
}
577579

@@ -647,6 +649,10 @@ impl MutinyWalletConfigBuilder {
647649
self.skip_hodl_invoices = false;
648650
}
649651

652+
pub fn do_check_lnd_snapshot(&mut self) {
653+
self.check_lnd_snapshot = true;
654+
}
655+
650656
pub fn build(self) -> MutinyWalletConfig {
651657
let network = self.network.expect("network is required");
652658

@@ -670,6 +676,7 @@ impl MutinyWalletConfigBuilder {
670676
skip_device_lock: self.skip_device_lock,
671677
safe_mode: self.safe_mode,
672678
skip_hodl_invoices: self.skip_hodl_invoices,
679+
check_lnd_snapshot: self.check_lnd_snapshot,
673680
}
674681
}
675682
}
@@ -695,6 +702,7 @@ pub struct MutinyWalletConfig {
695702
skip_device_lock: bool,
696703
pub safe_mode: bool,
697704
skip_hodl_invoices: bool,
705+
check_lnd_snapshot: bool,
698706
}
699707

700708
pub struct MutinyWalletBuilder<S: MutinyStorage> {
@@ -817,7 +825,7 @@ impl<S: MutinyStorage> MutinyWalletBuilder<S> {
817825
let network = self
818826
.network
819827
.map_or_else(|| Err(MutinyError::InvalidArgumentsError), Ok)?;
820-
let config = self.config.unwrap_or(
828+
let config = self.config.clone().unwrap_or(
821829
MutinyWalletConfigBuilder::new(self.xprivkey)
822830
.with_network(network)
823831
.build(),
@@ -844,12 +852,15 @@ impl<S: MutinyStorage> MutinyWalletBuilder<S> {
844852

845853
// Need to prevent other devices from running at the same time
846854
log_debug!(logger, "checking device lock");
855+
let lsp_url = config.lsp_url.clone();
847856
if !config.skip_device_lock {
848857
let start = Instant::now();
849858
if let Some(lock) = self.storage.get_device_lock()? {
850859
log_info!(logger, "Current device lock: {lock:?}");
851860
}
852-
self.storage.set_device_lock(&logger)?;
861+
self.storage
862+
.set_device_lock(&logger, lsp_url.clone(), config.check_lnd_snapshot)
863+
.await?;
853864
log_debug!(
854865
logger,
855866
"Device lock set: took {}ms",
@@ -905,15 +916,83 @@ impl<S: MutinyStorage> MutinyWalletBuilder<S> {
905916
loop {
906917
if stop_signal.stopping() {
907918
log_debug!(logger_clone, "stopping claim device lock");
919+
while VSS_MANAGER.has_in_progress() {
920+
log_debug!(logger_clone, "waiting for VSS to finish");
921+
sleep(300).await;
922+
}
908923
if let Err(e) = storage_clone.release_device_lock(&logger_clone) {
909924
log_error!(logger_clone, "Error releasing device lock: {e}");
910925
}
911926
break;
912927
}
913-
if let Err(e) = storage_clone.set_device_lock(&logger_clone) {
928+
929+
let config = self.config.as_ref().expect("config is required");
930+
if let (Some(lsp_url), Ok(Some(node_id))) =
931+
(config.lsp_url.as_ref(), storage_clone.get_node_id())
932+
{
933+
match fetch_lnd_channels_snapshot(
934+
&Client::new(),
935+
lsp_url,
936+
&node_id,
937+
&logger_clone,
938+
)
939+
.await
940+
{
941+
Ok(first_lnd_snapshot) => {
942+
log_debug!(
943+
logger_clone,
944+
"First fetched lnd snapshot: {:?}",
945+
first_lnd_snapshot
946+
);
947+
if !VSS_MANAGER.has_in_progress() {
948+
if let Ok(second_lnd_snapshot) = fetch_lnd_channels_snapshot(
949+
&Client::new(),
950+
lsp_url,
951+
&node_id,
952+
&logger_clone,
953+
)
954+
.await
955+
{
956+
log_debug!(
957+
logger_clone,
958+
"Second fetched lnd snapshot: {:?}",
959+
second_lnd_snapshot
960+
);
961+
if first_lnd_snapshot.snapshot == second_lnd_snapshot.snapshot {
962+
log_debug!(logger_clone, "Saving lnd snapshot");
963+
if let Err(e) = storage_clone.write_data(
964+
LND_CHANNELS_SNAPSHOT_KEY.to_string(),
965+
&second_lnd_snapshot,
966+
Some(now().as_secs() as u32),
967+
) {
968+
log_error!(
969+
logger_clone,
970+
"Error saving lnd snapshot: {e}"
971+
);
972+
}
973+
}
974+
}
975+
}
976+
}
977+
Err(e) => {
978+
log_error!(logger_clone, "Error fetching lnd channels: {e}");
979+
}
980+
}
981+
}
982+
983+
if let Err(e) = storage_clone
984+
.set_device_lock(&logger_clone, lsp_url.clone(), config.check_lnd_snapshot)
985+
.await
986+
{
914987
log_error!(logger_clone, "Error setting device lock: {e}");
915988
}
916989

990+
log_debug!(
991+
logger_clone,
992+
"Vss pending writes: {:?}",
993+
VSS_MANAGER.get_pending_writes()
994+
);
995+
917996
let mut remained_sleep_ms = (DEVICE_LOCK_INTERVAL_SECS * 1000) as i32;
918997
while !stop_signal.stopping() && remained_sleep_ms > 0 {
919998
let sleep_ms = 300;

0 commit comments

Comments
 (0)