From 31ab894ca34a362a5f2b4c59b4c702ddee771c22 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Dec 2024 16:45:24 +0700 Subject: [PATCH 1/4] feat: migrate from sqlite to VSS (WIP) --- bindings/ldk_node.udl | 6 +++ src/builder.rs | 111 +++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 4 +- src/types.rs | 9 ++++ 4 files changed, 126 insertions(+), 4 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index ab53bac3d..a4f659b80 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -52,6 +52,7 @@ interface Builder { Node build_with_fs_store(); void restore_encoded_channel_monitors(sequence monitors); void reset_state(ResetState what); + void migrate_storage(MigrateStorage what); [Throws=BuildError] Node build_with_vss_store(string vss_url, string store_id, string lnurl_auth_server_url, record fixed_headers); [Throws=BuildError] @@ -651,3 +652,8 @@ enum ResetState { "NetworkGraph", "All", }; + +enum MigrateStorage { + "VSS", + // "Sqlite", + }; diff --git a/src/builder.rs b/src/builder.rs index 70b0327c3..feaf96309 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -26,13 +26,15 @@ use crate::peer_store::PeerStore; use crate::tx_broadcaster::TransactionBroadcaster; use crate::types::{ ChainMonitor, ChannelManager, DynStore, GossipSync, Graph, KeyValue, KeysManager, - MessageRouter, OnionMessenger, PeerManager, ResetState, + MessageRouter, MigrateStorage, OnionMessenger, PeerManager, ResetState, }; use crate::wallet::persist::KVStoreWalletPersister; use crate::wallet::Wallet; use crate::{io, NodeMetrics}; use crate::{LogLevel, Node}; +use lightning::util::persist::KVStore; +use chrono::Local; use lightning::chain::{chainmonitor, BestBlock, Watch}; use lightning::io::Cursor; use lightning::ln::channelmanager::{self, ChainParameters, ChannelManagerReadArgs}; @@ -48,6 +50,7 @@ use lightning::sign::EntropySource; use lightning::util::persist::{ read_channel_monitors, CHANNEL_MANAGER_PERSISTENCE_KEY, CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_KEY, NETWORK_GRAPH_PERSISTENCE_PRIMARY_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE, SCORER_PERSISTENCE_KEY, SCORER_PERSISTENCE_PRIMARY_NAMESPACE, SCORER_PERSISTENCE_SECONDARY_NAMESPACE, @@ -75,7 +78,7 @@ use std::convert::TryInto; use std::default::Default; use std::fmt; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex, RwLock}; use std::time::SystemTime; @@ -190,6 +193,7 @@ pub struct NodeBuilder { liquidity_source_config: Option, monitors_to_restore: Option>, reset_state: Option, + migrate_storage: Option, } impl NodeBuilder { @@ -207,6 +211,7 @@ impl NodeBuilder { let liquidity_source_config = None; let monitors_to_restore = None; let reset_state = None; + let migrate_storage = None; Self { config, entropy_source_config, @@ -215,6 +220,7 @@ impl NodeBuilder { liquidity_source_config, monitors_to_restore, reset_state, + migrate_storage, } } @@ -230,6 +236,12 @@ impl NodeBuilder { self } + /// Alby: migrate storage on startup. + pub fn migrate_storage(&mut self, what: MigrateStorage) -> &mut Self { + self.migrate_storage = Some(what); + self + } + /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. /// /// If the given file does not exist a new random seed file will be generated and @@ -495,6 +507,40 @@ impl NodeBuilder { let vss_seed_bytes: [u8; 32] = vss_xprv.private_key.secret_bytes(); + // Alby: migrate from sqlite to VSS + let mut migrate_from_store = None; + let migrate_to_vss = match self.migrate_storage { + Some(MigrateStorage::VSS) => true, + _ => false, + }; + if migrate_to_vss { + // rename and read existing file + let storage_dir_path = config.storage_dir_path.clone(); + + // Get the current date and time + let now = Local::now(); + let timestamp = now.format("%Y%m%d_%H%M%S").to_string(); + + // Create a backup filename based on the current date and time + let backup_filename = format!("ldk_node_data_{}.sqlite", timestamp); + let old_file_path = + Path::new(storage_dir_path.as_str()).join(io::sqlite_store::SQLITE_DB_FILE_NAME); + let new_file_path = Path::new(storage_dir_path.as_str()).join(backup_filename.clone()); + + // Rename the file, so that we start fresh + fs::rename(&old_file_path, &new_file_path).unwrap(); + + // Read from the old file + migrate_from_store = Some(Arc::new( + SqliteStore::new( + storage_dir_path.into(), + Some(backup_filename), + Some(io::sqlite_store::KV_TABLE_NAME.to_string()), + ) + .map_err(|_| BuildError::KVStoreSetupFailed)?, + ) as Arc); + } + // Alby: use a secondary KV store for non-essential data (not needed by VSS) let storage_dir_path = config.storage_dir_path.clone(); let secondary_kv_store = Arc::new( @@ -512,6 +558,62 @@ impl NodeBuilder { log_error!(logger, "Failed to setup VssStore: {}", e); BuildError::KVStoreSetupFailed })?; + + if migrate_from_store.is_some() { + // write essential data from old store to new store + let from_store = migrate_from_store.unwrap(); + + // TODO: migrate peers (IMPORTANT otherwise channel is offline!) + // TODO: error handling + // TODO: add logging (log all keys that were migrated) + // TODO: cleanup the code + // TODO: close the DB connection + + let channel_monitor_keys = from_store + .list( + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + ) + .unwrap(); + + for key in channel_monitor_keys { + let channel_monitor_value = from_store + .read( + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + key.as_str(), + ) + .unwrap(); + // write value to new store + vss_store + .write( + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + key.as_str(), + &channel_monitor_value, + ) + .unwrap(); + } + + // migrate channel manager + let channel_manager_value = from_store + .read( + CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_KEY, + ) + .unwrap(); + // write value to new store + vss_store + .write( + CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_KEY, + &channel_manager_value, + ) + .unwrap(); + } + build_with_store_internal( config, self.chain_data_source_config.as_ref(), @@ -595,6 +697,11 @@ impl ArcedNodeBuilder { self.inner.write().unwrap().reset_state(what); } + /// Alby: migrate storage on startup. + pub fn migrate_storage(&self, what: MigrateStorage) { + self.inner.write().unwrap().migrate_storage(what); + } + /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. /// /// If the given file does not exist a new random seed file will be generated and diff --git a/src/lib.rs b/src/lib.rs index e9b57b7db..e1a570616 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,13 +136,13 @@ use payment::{ UnifiedQrPayment, }; use peer_store::{PeerInfo, PeerStore}; -#[cfg(feature = "uniffi")] -use types::ResetState; use types::{ Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph, KeysManager, OnionMessenger, PeerManager, Router, Scorer, Sweeper, Wallet, }; pub use types::{ChannelDetails, ChannelType, KeyValue, PeerDetails, TlvEntry, UserChannelId}; +#[cfg(feature = "uniffi")] +use types::{MigrateStorage, ResetState}; use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger}; diff --git a/src/types.rs b/src/types.rs index 62554e436..25e62de2c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -415,3 +415,12 @@ pub enum ResetState { /// All of the above. All, } + +/// Options to migrate from one storage type to another. +#[derive(Debug, Copy, Clone)] +pub enum MigrateStorage { + /// SQLite to VSS + VSS, + // VSS to SQLite - currently unsupported + // Sqlite, +} From 8d5ca353ed25b82be9ca4ef8e88181062a2b0713 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 10 Dec 2024 16:48:33 +0700 Subject: [PATCH 2/4] feat: migrate peers from sqlite to vss, add error handling and logging --- src/builder.rs | 83 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 16 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index feaf96309..df544b2d5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -17,6 +17,8 @@ use crate::io::utils::{read_node_metrics, write_node_metrics}; use crate::io::vss_store::VssStore; use crate::io::{ NODE_METRICS_KEY, NODE_METRICS_PRIMARY_NAMESPACE, NODE_METRICS_SECONDARY_NAMESPACE, + PEER_INFO_PERSISTENCE_KEY, PEER_INFO_PERSISTENCE_PRIMARY_NAMESPACE, + PEER_INFO_PERSISTENCE_SECONDARY_NAMESPACE, }; use crate::liquidity::LiquiditySource; use crate::logger::{log_error, log_info, FilesystemLogger, Logger}; @@ -507,7 +509,7 @@ impl NodeBuilder { let vss_seed_bytes: [u8; 32] = vss_xprv.private_key.secret_bytes(); - // Alby: migrate from sqlite to VSS + // Alby: move sqlite store for migration from sqlite to VSS let mut migrate_from_store = None; let migrate_to_vss = match self.migrate_storage { Some(MigrateStorage::VSS) => true, @@ -523,12 +525,21 @@ impl NodeBuilder { // Create a backup filename based on the current date and time let backup_filename = format!("ldk_node_data_{}.sqlite", timestamp); - let old_file_path = + let current_file_path = Path::new(storage_dir_path.as_str()).join(io::sqlite_store::SQLITE_DB_FILE_NAME); - let new_file_path = Path::new(storage_dir_path.as_str()).join(backup_filename.clone()); + let backup_file_path = + Path::new(storage_dir_path.as_str()).join(backup_filename.clone()); // Rename the file, so that we start fresh - fs::rename(&old_file_path, &new_file_path).unwrap(); + log_info!( + logger, + "Migrating to VSS - Moving sqlite db to backup file: {}", + backup_file_path.to_str().expect("Invalid backup file path") + ); + fs::rename(¤t_file_path, &backup_file_path).map_err(|e| { + log_error!(logger, "Failed to rename existing sqlite file: {}", e); + BuildError::KVStoreSetupFailed + })?; // Read from the old file migrate_from_store = Some(Arc::new( @@ -559,31 +570,34 @@ impl NodeBuilder { BuildError::KVStoreSetupFailed })?; + // Alby: migrate from backed up sqlite store to VSS if migrate_from_store.is_some() { + log_info!(logger, "Migrating to VSS - migrating store data"); // write essential data from old store to new store - let from_store = migrate_from_store.unwrap(); - - // TODO: migrate peers (IMPORTANT otherwise channel is offline!) - // TODO: error handling - // TODO: add logging (log all keys that were migrated) - // TODO: cleanup the code - // TODO: close the DB connection + let from_store = migrate_from_store.expect("Invalid migrate_from_store"); let channel_monitor_keys = from_store .list( CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, ) - .unwrap(); + .map_err(|e| { + log_error!(logger, "Failed to fetch channel_monitor_keys: {}", e); + BuildError::KVStoreSetupFailed + })?; for key in channel_monitor_keys { + log_info!(logger, "Migrating channel monitor key {}", key); let channel_monitor_value = from_store .read( CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, key.as_str(), ) - .unwrap(); + .map_err(|e| { + log_error!(logger, "Failed to fetch channel monitor value: {}", e); + BuildError::KVStoreSetupFailed + })?; // write value to new store vss_store .write( @@ -592,17 +606,24 @@ impl NodeBuilder { key.as_str(), &channel_monitor_value, ) - .unwrap(); + .map_err(|e| { + log_error!(logger, "Failed to migrate channel monitor value: {}", e); + BuildError::KVStoreSetupFailed + })?; } // migrate channel manager + log_info!(logger, "Migrating channel manager key {}", CHANNEL_MANAGER_PERSISTENCE_KEY); let channel_manager_value = from_store .read( CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_KEY, ) - .unwrap(); + .map_err(|e| { + log_error!(logger, "Failed to fetch channel manager value: {}", e); + BuildError::KVStoreSetupFailed + })?; // write value to new store vss_store .write( @@ -611,7 +632,37 @@ impl NodeBuilder { CHANNEL_MANAGER_PERSISTENCE_KEY, &channel_manager_value, ) - .unwrap(); + .map_err(|e| { + log_error!(logger, "Failed to migrate channel manager value: {}", e); + BuildError::KVStoreSetupFailed + })?; + + // migrate peers + log_info!(logger, "Migrating peers key {}", PEER_INFO_PERSISTENCE_KEY); + let channel_manager_value = from_store + .read( + PEER_INFO_PERSISTENCE_PRIMARY_NAMESPACE, + PEER_INFO_PERSISTENCE_SECONDARY_NAMESPACE, + PEER_INFO_PERSISTENCE_KEY, + ) + .map_err(|e| { + log_error!(logger, "Failed to fetch peers value: {}", e); + BuildError::KVStoreSetupFailed + })?; + // write value to new store + vss_store + .write( + PEER_INFO_PERSISTENCE_PRIMARY_NAMESPACE, + PEER_INFO_PERSISTENCE_SECONDARY_NAMESPACE, + PEER_INFO_PERSISTENCE_KEY, + &channel_manager_value, + ) + .map_err(|e| { + log_error!(logger, "Failed to migrate peers value: {}", e); + BuildError::KVStoreSetupFailed + })?; + + log_info!(logger, "Migration to VSS completed successfully"); } build_with_store_internal( From a9c9890abc1ed2f4c512b6a5ca05b04b2f018fb9 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Wed, 11 Dec 2024 11:27:28 +0700 Subject: [PATCH 3/4] chore: extract migrate key value function --- src/builder.rs | 113 ++++++++++++++++++------------------------------- 1 file changed, 42 insertions(+), 71 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index df544b2d5..d43edaa4e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -571,10 +571,35 @@ impl NodeBuilder { })?; // Alby: migrate from backed up sqlite store to VSS - if migrate_from_store.is_some() { + if let Some(from_store) = migrate_from_store { log_info!(logger, "Migrating to VSS - migrating store data"); // write essential data from old store to new store - let from_store = migrate_from_store.expect("Invalid migrate_from_store"); + + let migrate_kv = |primary_namespace: &str, + secondary_namespace: &str, + key: &str| + -> Result<(), BuildError> { + log_info!( + logger, + "Migrating key {} {} {}", + primary_namespace, + secondary_namespace, + key + ); + let channel_monitor_value = + from_store.read(primary_namespace, secondary_namespace, key).map_err(|e| { + log_error!(logger, "Failed to fetch value: {}", e); + BuildError::KVStoreSetupFailed + })?; + // write value to new store + vss_store + .write(primary_namespace, secondary_namespace, key, &channel_monitor_value) + .map_err(|e| { + log_error!(logger, "Failed to migrate value: {}", e); + BuildError::KVStoreSetupFailed + })?; + Ok(()) + }; let channel_monitor_keys = from_store .list( @@ -587,80 +612,26 @@ impl NodeBuilder { })?; for key in channel_monitor_keys { - log_info!(logger, "Migrating channel monitor key {}", key); - let channel_monitor_value = from_store - .read( - CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, - CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, - key.as_str(), - ) - .map_err(|e| { - log_error!(logger, "Failed to fetch channel monitor value: {}", e); - BuildError::KVStoreSetupFailed - })?; - // write value to new store - vss_store - .write( - CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, - CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, - key.as_str(), - &channel_monitor_value, - ) - .map_err(|e| { - log_error!(logger, "Failed to migrate channel monitor value: {}", e); - BuildError::KVStoreSetupFailed - })?; + migrate_kv( + CHANNEL_MONITOR_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MONITOR_PERSISTENCE_SECONDARY_NAMESPACE, + key.as_str(), + )?; } // migrate channel manager - log_info!(logger, "Migrating channel manager key {}", CHANNEL_MANAGER_PERSISTENCE_KEY); - let channel_manager_value = from_store - .read( - CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, - CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, - CHANNEL_MANAGER_PERSISTENCE_KEY, - ) - .map_err(|e| { - log_error!(logger, "Failed to fetch channel manager value: {}", e); - BuildError::KVStoreSetupFailed - })?; - // write value to new store - vss_store - .write( - CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, - CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, - CHANNEL_MANAGER_PERSISTENCE_KEY, - &channel_manager_value, - ) - .map_err(|e| { - log_error!(logger, "Failed to migrate channel manager value: {}", e); - BuildError::KVStoreSetupFailed - })?; + migrate_kv( + CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, + CHANNEL_MANAGER_PERSISTENCE_KEY, + )?; // migrate peers - log_info!(logger, "Migrating peers key {}", PEER_INFO_PERSISTENCE_KEY); - let channel_manager_value = from_store - .read( - PEER_INFO_PERSISTENCE_PRIMARY_NAMESPACE, - PEER_INFO_PERSISTENCE_SECONDARY_NAMESPACE, - PEER_INFO_PERSISTENCE_KEY, - ) - .map_err(|e| { - log_error!(logger, "Failed to fetch peers value: {}", e); - BuildError::KVStoreSetupFailed - })?; - // write value to new store - vss_store - .write( - PEER_INFO_PERSISTENCE_PRIMARY_NAMESPACE, - PEER_INFO_PERSISTENCE_SECONDARY_NAMESPACE, - PEER_INFO_PERSISTENCE_KEY, - &channel_manager_value, - ) - .map_err(|e| { - log_error!(logger, "Failed to migrate peers value: {}", e); - BuildError::KVStoreSetupFailed - })?; + migrate_kv( + PEER_INFO_PERSISTENCE_PRIMARY_NAMESPACE, + PEER_INFO_PERSISTENCE_SECONDARY_NAMESPACE, + PEER_INFO_PERSISTENCE_KEY, + )?; log_info!(logger, "Migration to VSS completed successfully"); } From c3077a5e2883bacc7139fef210c6682f5a9c2a2d Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Wed, 11 Dec 2024 11:53:50 +0700 Subject: [PATCH 4/4] chore: rename variable --- src/builder.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index d43edaa4e..0573d6947 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -586,18 +586,18 @@ impl NodeBuilder { secondary_namespace, key ); - let channel_monitor_value = + let value = from_store.read(primary_namespace, secondary_namespace, key).map_err(|e| { log_error!(logger, "Failed to fetch value: {}", e); BuildError::KVStoreSetupFailed })?; // write value to new store - vss_store - .write(primary_namespace, secondary_namespace, key, &channel_monitor_value) - .map_err(|e| { + vss_store.write(primary_namespace, secondary_namespace, key, &value).map_err( + |e| { log_error!(logger, "Failed to migrate value: {}", e); BuildError::KVStoreSetupFailed - })?; + }, + )?; Ok(()) };