Skip to content

Commit 1c0c6c4

Browse files
committed
Safely migrate to FileSystemStoreV2
Before moving to PaginatedKVStore everywhere we need to use FileSystemStoreV2 instead of FileSystemStoreV1. This will safely migrate over to it on first start up. Also adds a test to make sure we handle it properly.
1 parent a8e852b commit 1c0c6c4

3 files changed

Lines changed: 102 additions & 14 deletions

File tree

src/builder.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ use lightning::util::persist::{
4343
use lightning::util::ser::ReadableArgs;
4444
use lightning::util::sweep::OutputSweeper;
4545
use lightning_dns_resolver::OMDomainResolver;
46-
use lightning_persister::fs_store::v1::FilesystemStore;
4746
use vss_client::headers::VssHeaderProvider;
4847

4948
use crate::chain::ChainSource;
@@ -59,8 +58,9 @@ use crate::fee_estimator::OnchainFeeEstimator;
5958
use crate::gossip::GossipSource;
6059
use crate::io::sqlite_store::SqliteStore;
6160
use crate::io::utils::{
62-
read_all_objects, read_event_queue, read_external_pathfinding_scores_from_cache,
63-
read_network_graph, read_node_metrics, read_output_sweeper, read_peer_info, read_scorer,
61+
open_or_migrate_fs_store, read_all_objects, read_event_queue,
62+
read_external_pathfinding_scores_from_cache, read_network_graph, read_node_metrics,
63+
read_output_sweeper, read_peer_info, read_scorer,
6464
};
6565
use crate::io::vss_store::VssStoreBuilder;
6666
use crate::io::{
@@ -640,15 +640,22 @@ impl NodeBuilder {
640640
self.build_with_store(node_entropy, kv_store)
641641
}
642642

643-
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
643+
/// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options
644644
/// previously configured.
645+
///
646+
/// If the storage directory contains data from a v1 filesystem store, it will be
647+
/// automatically migrated to the v2 format.
648+
///
649+
/// [`FilesystemStoreV2`]: lightning_persister::fs_store::v2::FilesystemStoreV2
645650
pub fn build_with_fs_store(&self, node_entropy: NodeEntropy) -> Result<Node, BuildError> {
646651
let mut storage_dir_path: PathBuf = self.config.storage_dir_path.clone().into();
647652
storage_dir_path.push("fs_store");
648653

649654
fs::create_dir_all(storage_dir_path.clone())
650655
.map_err(|_| BuildError::StoragePathAccessFailed)?;
651-
let kv_store = FilesystemStore::new(storage_dir_path);
656+
657+
let kv_store = open_or_migrate_fs_store(storage_dir_path)?;
658+
652659
self.build_with_store(node_entropy, kv_store)
653660
}
654661

@@ -1102,7 +1109,7 @@ impl ArcedNodeBuilder {
11021109
self.inner.read().expect("lock").build(*node_entropy).map(Arc::new)
11031110
}
11041111

1105-
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
1112+
/// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options
11061113
/// previously configured.
11071114
pub fn build_with_fs_store(
11081115
&self, node_entropy: Arc<NodeEntropy>,

src/io/utils.rs

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::io::Write;
1010
use std::ops::Deref;
1111
#[cfg(unix)]
1212
use std::os::unix::fs::OpenOptionsExt;
13-
use std::path::Path;
13+
use std::path::{Path, PathBuf};
1414
use std::sync::{Arc, RwLock};
1515

1616
use bdk_chain::indexer::keychain_txout::ChangeSet as BdkIndexerChangeSet;
@@ -26,14 +26,16 @@ use lightning::routing::scoring::{
2626
ChannelLiquidities, ProbabilisticScorer, ProbabilisticScoringDecayParameters,
2727
};
2828
use lightning::util::persist::{
29-
KVStore, KVStoreSync, KVSTORE_NAMESPACE_KEY_ALPHABET, KVSTORE_NAMESPACE_KEY_MAX_LEN,
30-
NETWORK_GRAPH_PERSISTENCE_KEY, NETWORK_GRAPH_PERSISTENCE_PRIMARY_NAMESPACE,
31-
NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE, OUTPUT_SWEEPER_PERSISTENCE_KEY,
32-
OUTPUT_SWEEPER_PERSISTENCE_PRIMARY_NAMESPACE, OUTPUT_SWEEPER_PERSISTENCE_SECONDARY_NAMESPACE,
33-
SCORER_PERSISTENCE_KEY, SCORER_PERSISTENCE_PRIMARY_NAMESPACE,
34-
SCORER_PERSISTENCE_SECONDARY_NAMESPACE,
29+
migrate_kv_store_data, KVStore, KVStoreSync, KVSTORE_NAMESPACE_KEY_ALPHABET,
30+
KVSTORE_NAMESPACE_KEY_MAX_LEN, NETWORK_GRAPH_PERSISTENCE_KEY,
31+
NETWORK_GRAPH_PERSISTENCE_PRIMARY_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE,
32+
OUTPUT_SWEEPER_PERSISTENCE_KEY, OUTPUT_SWEEPER_PERSISTENCE_PRIMARY_NAMESPACE,
33+
OUTPUT_SWEEPER_PERSISTENCE_SECONDARY_NAMESPACE, SCORER_PERSISTENCE_KEY,
34+
SCORER_PERSISTENCE_PRIMARY_NAMESPACE, SCORER_PERSISTENCE_SECONDARY_NAMESPACE,
3535
};
3636
use lightning::util::ser::{Readable, ReadableArgs, Writeable};
37+
use lightning_persister::fs_store::v1::FilesystemStore;
38+
use lightning_persister::fs_store::v2::{FilesystemStoreV2, FilesystemStoreV2Error};
3739
use lightning_types::string::PrintableString;
3840

3941
use super::*;
@@ -47,7 +49,7 @@ use crate::logger::{log_error, LdkLogger, Logger};
4749
use crate::peer_store::PeerStore;
4850
use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper};
4951
use crate::wallet::ser::{ChangeSetDeserWrapper, ChangeSetSerWrapper};
50-
use crate::{Error, EventQueue, NodeMetrics};
52+
use crate::{BuildError, Error, EventQueue, NodeMetrics};
5153

5254
pub const EXTERNAL_PATHFINDING_SCORES_CACHE_KEY: &str = "external_pathfinding_scores_cache";
5355

@@ -619,6 +621,42 @@ pub(crate) fn read_bdk_wallet_change_set(
619621
Ok(Some(change_set))
620622
}
621623

624+
/// Opens a [`FilesystemStoreV2`], automatically migrating from v1 format if necessary.
625+
///
626+
/// If the directory contains v1 data (files at the top level), the data is migrated to v2 format
627+
/// in a temporary directory, the original is renamed to `fs_store_v1_backup`, and the migrated
628+
/// directory is moved into place.
629+
pub(crate) fn open_or_migrate_fs_store(
630+
storage_dir_path: PathBuf,
631+
) -> Result<FilesystemStoreV2, BuildError> {
632+
match FilesystemStoreV2::new(storage_dir_path.clone()) {
633+
Ok(store) => Ok(store),
634+
Err(FilesystemStoreV2Error::V1DataDetected(_)) => {
635+
// The directory contains v1 data, migrate to v2.
636+
let mut v1_store = FilesystemStore::new(storage_dir_path.clone());
637+
638+
let mut v2_dir = storage_dir_path.clone();
639+
v2_dir.set_file_name("fs_store_v2_migrating");
640+
fs::create_dir_all(v2_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?;
641+
let mut v2_store = FilesystemStoreV2::new(v2_dir.clone())
642+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
643+
644+
migrate_kv_store_data(&mut v1_store, &mut v2_store)
645+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
646+
647+
// Swap directories: rename v1 out of the way, move v2 into place.
648+
let mut backup_dir = storage_dir_path.clone();
649+
backup_dir.set_file_name("fs_store_v1_backup");
650+
fs::rename(&storage_dir_path, &backup_dir)
651+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
652+
fs::rename(&v2_dir, &storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)?;
653+
654+
FilesystemStoreV2::new(storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)
655+
},
656+
Err(_) => Err(BuildError::KVStoreSetupFailed),
657+
}
658+
}
659+
622660
#[cfg(test)]
623661
mod tests {
624662
use super::read_or_generate_seed_file;

tests/integration_tests_rust.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2634,6 +2634,49 @@ async fn persistence_backwards_compatibility() {
26342634
do_persistence_backwards_compatibility(OldLdkVersion::V0_7_0).await;
26352635
}
26362636

2637+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2638+
async fn fs_store_persistence_backwards_compatibility() {
2639+
let (bitcoind, electrsd) = common::setup_bitcoind_and_electrsd();
2640+
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
2641+
2642+
let storage_path = common::random_storage_path().to_str().unwrap().to_owned();
2643+
let seed_bytes = [42u8; 64];
2644+
2645+
// Build a node using v0.7.0's build_with_fs_store (FilesystemStore v1).
2646+
let mut config = TestConfig::default();
2647+
config.node_config.storage_dir_path = storage_path.clone();
2648+
config.store_type = TestStoreType::FilesystemStore;
2649+
let (old_balance, old_node_id) =
2650+
build_0_7_0_node(&bitcoind, &electrsd, esplora_url.clone(), seed_bytes, &config).await;
2651+
2652+
// Now reopen with current code's build_with_fs_store, which should
2653+
// auto-migrate from FilesystemStore v1 to FilesystemStoreV2.
2654+
#[cfg(feature = "uniffi")]
2655+
let builder_new = Builder::new();
2656+
#[cfg(not(feature = "uniffi"))]
2657+
let mut builder_new = Builder::new();
2658+
builder_new.set_network(bitcoin::Network::Regtest);
2659+
builder_new.set_storage_dir_path(storage_path);
2660+
builder_new.set_chain_source_esplora(esplora_url, None);
2661+
2662+
#[cfg(feature = "uniffi")]
2663+
let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes.to_vec()).unwrap();
2664+
#[cfg(not(feature = "uniffi"))]
2665+
let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes);
2666+
let node_new = builder_new.build_with_fs_store(node_entropy.into()).unwrap();
2667+
2668+
node_new.start().unwrap();
2669+
node_new.sync_wallets().unwrap();
2670+
2671+
let new_balance = node_new.list_balances().spendable_onchain_balance_sats;
2672+
let new_node_id = node_new.node_id();
2673+
2674+
assert_eq!(old_node_id, new_node_id);
2675+
assert_eq!(old_balance, new_balance);
2676+
2677+
node_new.stop().unwrap();
2678+
}
2679+
26372680
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
26382681
async fn onchain_fee_bump_rbf() {
26392682
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();

0 commit comments

Comments
 (0)