Skip to content

Commit 898eab2

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 85db0b2 commit 898eab2

File tree

3 files changed

+102
-15
lines changed

3 files changed

+102
-15
lines changed

src/builder.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ use lightning::util::persist::{
3939
};
4040
use lightning::util::ser::ReadableArgs;
4141
use lightning::util::sweep::OutputSweeper;
42-
use lightning_persister::fs_store::v1::FilesystemStore;
4342
use vss_client::headers::VssHeaderProvider;
4443

4544
use crate::chain::ChainSource;
@@ -55,9 +54,9 @@ use crate::fee_estimator::OnchainFeeEstimator;
5554
use crate::gossip::GossipSource;
5655
use crate::io::sqlite_store::SqliteStore;
5756
use crate::io::utils::{
58-
read_event_queue, read_external_pathfinding_scores_from_cache, read_network_graph,
59-
read_node_metrics, read_output_sweeper, read_payments, read_peer_info, read_pending_payments,
60-
read_scorer, write_node_metrics,
57+
open_or_migrate_fs_store, read_event_queue, read_external_pathfinding_scores_from_cache,
58+
read_network_graph, read_node_metrics, read_output_sweeper, read_payments, read_peer_info,
59+
read_pending_payments, read_scorer, write_node_metrics,
6160
};
6261
use crate::io::vss_store::VssStoreBuilder;
6362
use crate::io::{
@@ -629,15 +628,22 @@ impl NodeBuilder {
629628
self.build_with_store(node_entropy, kv_store)
630629
}
631630

632-
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
631+
/// Builds a [`Node`] instance with a [`FilesystemStoreV2`] backend and according to the options
633632
/// previously configured.
633+
///
634+
/// If the storage directory contains data from a v1 filesystem store, it will be
635+
/// automatically migrated to the v2 format.
636+
///
637+
/// [`FilesystemStoreV2`]: lightning_persister::fs_store::v2::FilesystemStoreV2
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>,

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;
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;
3739
use lightning_types::string::PrintableString;
3840

3941
use super::*;
@@ -48,7 +50,7 @@ use crate::payment::PendingPaymentDetails;
4850
use crate::peer_store::PeerStore;
4951
use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper};
5052
use crate::wallet::ser::{ChangeSetDeserWrapper, ChangeSetSerWrapper};
51-
use crate::{Error, EventQueue, NodeMetrics, PaymentDetails};
53+
use crate::{BuildError, Error, EventQueue, NodeMetrics, PaymentDetails};
5254

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

@@ -702,6 +704,42 @@ where
702704
Ok(res)
703705
}
704706

707+
/// Opens a [`FilesystemStoreV2`], automatically migrating from v1 format if necessary.
708+
///
709+
/// If the directory contains v1 data (files at the top level), the data is migrated to v2 format
710+
/// in a temporary directory, the original is renamed to `fs_store_v1_backup`, and the migrated
711+
/// directory is moved into place.
712+
pub(crate) fn open_or_migrate_fs_store(
713+
storage_dir_path: PathBuf,
714+
) -> Result<FilesystemStoreV2, BuildError> {
715+
match FilesystemStoreV2::new(storage_dir_path.clone()) {
716+
Ok(store) => Ok(store),
717+
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
718+
// The directory contains v1 data, migrate to v2.
719+
let mut v1_store = FilesystemStore::new(storage_dir_path.clone());
720+
721+
let mut v2_dir = storage_dir_path.clone();
722+
v2_dir.set_file_name("fs_store_v2_migrating");
723+
fs::create_dir_all(v2_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?;
724+
let mut v2_store = FilesystemStoreV2::new(v2_dir.clone())
725+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
726+
727+
migrate_kv_store_data(&mut v1_store, &mut v2_store)
728+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
729+
730+
// Swap directories: rename v1 out of the way, move v2 into place.
731+
let mut backup_dir = storage_dir_path.clone();
732+
backup_dir.set_file_name("fs_store_v1_backup");
733+
fs::rename(&storage_dir_path, &backup_dir)
734+
.map_err(|_| BuildError::KVStoreSetupFailed)?;
735+
fs::rename(&v2_dir, &storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)?;
736+
737+
FilesystemStoreV2::new(storage_dir_path).map_err(|_| BuildError::KVStoreSetupFailed)
738+
},
739+
Err(_) => Err(BuildError::KVStoreSetupFailed),
740+
}
741+
}
742+
705743
#[cfg(test)]
706744
mod tests {
707745
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
@@ -2550,6 +2550,49 @@ async fn persistence_backwards_compatibility() {
25502550
do_persistence_backwards_compatibility(OldLdkVersion::V0_7_0).await;
25512551
}
25522552

2553+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2554+
async fn fs_store_persistence_backwards_compatibility() {
2555+
let (bitcoind, electrsd) = common::setup_bitcoind_and_electrsd();
2556+
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
2557+
2558+
let storage_path = common::random_storage_path().to_str().unwrap().to_owned();
2559+
let seed_bytes = [42u8; 64];
2560+
2561+
// Build a node using v0.7.0's build_with_fs_store (FilesystemStore v1).
2562+
let mut config = TestConfig::default();
2563+
config.node_config.storage_dir_path = storage_path.clone();
2564+
config.store_type = TestStoreType::FilesystemStore;
2565+
let (old_balance, old_node_id) =
2566+
build_0_7_0_node(&bitcoind, &electrsd, esplora_url.clone(), seed_bytes, &config).await;
2567+
2568+
// Now reopen with current code's build_with_fs_store, which should
2569+
// auto-migrate from FilesystemStore v1 to FilesystemStoreV2.
2570+
#[cfg(feature = "uniffi")]
2571+
let builder_new = Builder::new();
2572+
#[cfg(not(feature = "uniffi"))]
2573+
let mut builder_new = Builder::new();
2574+
builder_new.set_network(bitcoin::Network::Regtest);
2575+
builder_new.set_storage_dir_path(storage_path);
2576+
builder_new.set_chain_source_esplora(esplora_url, None);
2577+
2578+
#[cfg(feature = "uniffi")]
2579+
let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes.to_vec()).unwrap();
2580+
#[cfg(not(feature = "uniffi"))]
2581+
let node_entropy = NodeEntropy::from_seed_bytes(seed_bytes);
2582+
let node_new = builder_new.build_with_fs_store(node_entropy.into()).unwrap();
2583+
2584+
node_new.start().unwrap();
2585+
node_new.sync_wallets().unwrap();
2586+
2587+
let new_balance = node_new.list_balances().spendable_onchain_balance_sats;
2588+
let new_node_id = node_new.node_id();
2589+
2590+
assert_eq!(old_node_id, new_node_id);
2591+
assert_eq!(old_balance, new_balance);
2592+
2593+
node_new.stop().unwrap();
2594+
}
2595+
25532596
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
25542597
async fn onchain_fee_bump_rbf() {
25552598
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();

0 commit comments

Comments
 (0)