Skip to content

Commit f66c589

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 3aef2b3 commit f66c589

File tree

2 files changed

+122
-5
lines changed

2 files changed

+122
-5
lines changed

src/builder.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@ use lightning::routing::scoring::{
3434
use lightning::sign::{EntropySource, NodeSigner};
3535
use lightning::util::config::HTLCInterceptionFlags;
3636
use lightning::util::persist::{
37-
KVStore, CHANNEL_MANAGER_PERSISTENCE_KEY, CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE,
38-
CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE,
37+
migrate_kv_store_data, KVStore, CHANNEL_MANAGER_PERSISTENCE_KEY,
38+
CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE,
3939
};
4040
use lightning::util::ser::ReadableArgs;
4141
use lightning::util::sweep::OutputSweeper;
4242
use lightning_persister::fs_store::v1::FilesystemStore;
43+
use lightning_persister::fs_store::v2::FilesystemStoreV2;
4344
use vss_client::headers::VssHeaderProvider;
4445

4546
use crate::chain::ChainSource;
@@ -629,15 +630,20 @@ impl NodeBuilder {
629630
self.build_with_store(node_entropy, kv_store)
630631
}
631632

632-
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
633+
/// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options
633634
/// previously configured.
635+
///
636+
/// If the storage directory contains data from a v1 filesystem store, it will be
637+
/// automatically migrated to the v2 format.
634638
pub fn build_with_fs_store(&self, node_entropy: NodeEntropy) -> Result<Node, BuildError> {
635639
let mut storage_dir_path: PathBuf = self.config.storage_dir_path.clone().into();
636640
storage_dir_path.push("fs_store");
637641

638642
fs::create_dir_all(storage_dir_path.clone())
639643
.map_err(|_| BuildError::StoragePathAccessFailed)?;
640-
let kv_store = FilesystemStore::new(storage_dir_path);
644+
645+
let kv_store = open_or_migrate_fs_store(storage_dir_path)?;
646+
641647
self.build_with_store(node_entropy, kv_store)
642648
}
643649

@@ -1087,7 +1093,7 @@ impl ArcedNodeBuilder {
10871093
self.inner.read().unwrap().build(*node_entropy).map(Arc::new)
10881094
}
10891095

1090-
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
1096+
/// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options
10911097
/// previously configured.
10921098
pub fn build_with_fs_store(
10931099
&self, node_entropy: Arc<NodeEntropy>,
@@ -1998,6 +2004,40 @@ fn build_with_store_internal(
19982004
})
19992005
}
20002006

2007+
/// Opens a [`FilesystemStoreV2`], automatically migrating from v1 format if necessary.
2008+
///
2009+
/// If the directory contains v1 data (files at the top level), the data is migrated to v2 format
2010+
/// in a temporary directory, the original is renamed to `fs_store_v1_backup`, and the migrated
2011+
/// directory is moved into place.
2012+
fn open_or_migrate_fs_store(storage_dir_path: PathBuf) -> Result<FilesystemStoreV2, BuildError> {
2013+
match FilesystemStoreV2::new(storage_dir_path.clone()) {
2014+
Ok(store) => Ok(store),
2015+
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
2016+
// The directory contains v1 data, migrate to v2.
2017+
let mut v1_store = FilesystemStore::new(storage_dir_path.clone());
2018+
2019+
let mut v2_dir = storage_dir_path.clone();
2020+
v2_dir.set_file_name("fs_store_v2_migrating");
2021+
fs::create_dir_all(v2_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?;
2022+
let mut v2_store = FilesystemStoreV2::new(v2_dir.clone())
2023+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
2024+
2025+
migrate_kv_store_data(&mut v1_store, &mut v2_store)
2026+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
2027+
2028+
// Swap directories: rename v1 out of the way, move v2 into place.
2029+
let mut backup_dir = storage_dir_path.clone();
2030+
backup_dir.set_file_name("fs_store_v1_backup");
2031+
fs::rename(&storage_dir_path, &backup_dir)
2032+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
2033+
fs::rename(&v2_dir, &storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)?;
2034+
2035+
FilesystemStoreV2::new(storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)
2036+
},
2037+
Err(_) => Err(BuildError::KVStoreSetupFailed),
2038+
}
2039+
}
2040+
20012041
fn optionally_install_rustls_cryptoprovider() {
20022042
// Acquire a global Mutex, ensuring that only one process at a time install the provider. This
20032043
// is mostly required for running tests concurrently.

tests/integration_tests_rust.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2487,6 +2487,37 @@ async fn build_0_7_0_node(
24872487
(balance, node_id)
24882488
}
24892489

2490+
async fn build_0_7_0_node_fs_store(
2491+
bitcoind: &BitcoinD, electrsd: &ElectrsD, storage_path: String, esplora_url: String,
2492+
seed_bytes: [u8; 64],
2493+
) -> (u64, bitcoin::secp256k1::PublicKey) {
2494+
let mut builder_old = ldk_node_070::Builder::new();
2495+
builder_old.set_network(bitcoin::Network::Regtest);
2496+
builder_old.set_storage_dir_path(storage_path);
2497+
builder_old.set_entropy_seed_bytes(seed_bytes);
2498+
builder_old.set_chain_source_esplora(esplora_url, None);
2499+
let node_old = builder_old.build_with_fs_store().unwrap();
2500+
2501+
node_old.start().unwrap();
2502+
let addr_old = node_old.onchain_payment().new_address().unwrap();
2503+
premine_and_distribute_funds(
2504+
&bitcoind.client,
2505+
&electrsd.client,
2506+
vec![addr_old],
2507+
Amount::from_sat(100_000),
2508+
)
2509+
.await;
2510+
node_old.sync_wallets().unwrap();
2511+
2512+
let balance = node_old.list_balances().spendable_onchain_balance_sats;
2513+
assert!(balance > 0);
2514+
let node_id = node_old.node_id();
2515+
2516+
node_old.stop().unwrap();
2517+
2518+
(balance, node_id)
2519+
}
2520+
24902521
async fn do_persistence_backwards_compatibility(version: OldLdkVersion) {
24912522
let (bitcoind, electrsd) = common::setup_bitcoind_and_electrsd();
24922523
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
@@ -2550,6 +2581,52 @@ async fn persistence_backwards_compatibility() {
25502581
do_persistence_backwards_compatibility(OldLdkVersion::V0_7_0).await;
25512582
}
25522583

2584+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2585+
async fn fs_store_persistence_backwards_compatibility() {
2586+
let (bitcoind, electrsd) = common::setup_bitcoind_and_electrsd();
2587+
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
2588+
2589+
let storage_path = common::random_storage_path().to_str().unwrap().to_owned();
2590+
let seed_bytes = [42u8; 64];
2591+
2592+
// Build a node using v0.7.0's build_with_fs_store (FilesystemStore v1).
2593+
let (old_balance, old_node_id) = build_0_7_0_node_fs_store(
2594+
&bitcoind,
2595+
&electrsd,
2596+
storage_path.clone(),
2597+
esplora_url.clone(),
2598+
seed_bytes,
2599+
)
2600+
.await;
2601+
2602+
// Now reopen with current code's build_with_fs_store, which should
2603+
// auto-migrate from FilesystemStore v1 to FilesystemStoreV2.
2604+
#[cfg(feature = "uniffi")]
2605+
let builder_new = Builder::new();
2606+
#[cfg(not(feature = "uniffi"))]
2607+
let mut builder_new = Builder::new();
2608+
builder_new.set_network(bitcoin::Network::Regtest);
2609+
builder_new.set_storage_dir_path(storage_path);
2610+
builder_new.set_chain_source_esplora(esplora_url, None);
2611+
2612+
#[cfg(feature = "uniffi")]
2613+
let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes.to_vec()).unwrap();
2614+
#[cfg(not(feature = "uniffi"))]
2615+
let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes);
2616+
let node_new = builder_new.build_with_fs_store(node_entropy.into()).unwrap();
2617+
2618+
node_new.start().unwrap();
2619+
node_new.sync_wallets().unwrap();
2620+
2621+
let new_balance = node_new.list_balances().spendable_onchain_balance_sats;
2622+
let new_node_id = node_new.node_id();
2623+
2624+
assert_eq!(old_node_id, new_node_id);
2625+
assert_eq!(old_balance, new_balance);
2626+
2627+
node_new.stop().unwrap();
2628+
}
2629+
25532630
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
25542631
async fn onchain_fee_bump_rbf() {
25552632
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();

0 commit comments

Comments
 (0)