diff --git a/README.md b/README.md index 181b70e24..d72c29bae 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ fn main() { builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); - let node = builder.build(); + let node = builder.build().unwrap(); node.start().unwrap(); diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 4aa1074ff..424209e93 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -18,6 +18,7 @@ interface Builder { [Name=from_config] constructor(Config config); void set_entropy_seed_path(string seed_path); + [Throws=BuildError] void set_entropy_seed_bytes(sequence seed_bytes); void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase); void set_esplora_server(string esplora_server_url); @@ -26,6 +27,7 @@ interface Builder { void set_storage_dir_path(string storage_dir_path); void set_network(Network network); void set_listening_address(NetAddress listening_address); + [Throws=BuildError] LDKNode build(); }; @@ -111,6 +113,16 @@ enum NodeError { "InsufficientFunds", }; +[Error] +enum BuildError { + "InvalidSeedBytes", + "InvalidSystemTime", + "IOReadFailed", + "IOWriteFailed", + "StoragePathAccessFailed", + "WalletSetupFailed", +}; + [Enum] interface Event { PaymentSuccessful( PaymentHash payment_hash ); diff --git a/src/builder.rs b/src/builder.rs index 61ae17233..6ad42cff4 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -44,6 +44,7 @@ use bitcoin::BlockHash; use std::convert::TryInto; use std::default::Default; +use std::fmt; use std::fs; use std::sync::{Arc, Mutex, RwLock}; use std::time::SystemTime; @@ -66,6 +67,42 @@ enum GossipSourceConfig { RapidGossipSync(String), } +/// An error encountered during building a [`Node`]. +/// +/// [`Node`]: crate::Node +#[derive(Debug, Clone)] +pub enum BuildError { + /// The given seed bytes are invalid, e.g, are of invalid length. + InvalidSeedBytes, + /// The current system time is invalid, clocks might have gone backwards. + InvalidSystemTime, + /// We failed to read data from the [`KVStore`]. + IOReadFailed, + /// We failed to write data to the [`KVStore`]. + IOWriteFailed, + /// We failed to access the given `storage_dir_path`. + StoragePathAccessFailed, + /// We failed to setup the onchain wallet. + WalletSetupFailed, +} + +impl fmt::Display for BuildError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::InvalidSeedBytes => write!(f, "Given seed bytes are invalid."), + Self::InvalidSystemTime => { + write!(f, "System time is invalid. Clocks might have gone back in time.") + } + Self::IOReadFailed => write!(f, "Failed to read from store."), + Self::IOWriteFailed => write!(f, "Failed to write to store."), + Self::StoragePathAccessFailed => write!(f, "Failed to access the given storage path."), + Self::WalletSetupFailed => write!(f, "Failed to setup onchain wallet."), + } + } +} + +impl std::error::Error for BuildError {} + /// A builder for an [`Node`] instance, allowing to set some configuration and module choices from /// the getgo. /// @@ -112,14 +149,14 @@ impl NodeBuilder { /// Configures the [`Node`] instance to source its wallet entropy from the given 64 seed bytes. /// /// **Note:** Panics if the length of the given `seed_bytes` differs from 64. - pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec) -> &mut Self { + pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec) -> Result<&mut Self, BuildError> { if seed_bytes.len() != WALLET_KEYS_SEED_LEN { - panic!("Failed to set seed due to invalid length."); + return Err(BuildError::InvalidSeedBytes); } let mut bytes = [0u8; WALLET_KEYS_SEED_LEN]; bytes.copy_from_slice(&seed_bytes); self.entropy_source_config = Some(EntropySourceConfig::SeedBytes(bytes)); - self + Ok(self) } /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. @@ -179,18 +216,20 @@ impl NodeBuilder { /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. - pub fn build(&self) -> Node { + pub fn build(&self) -> Result, BuildError> { let storage_dir_path = self.config.storage_dir_path.clone(); - fs::create_dir_all(storage_dir_path.clone()).expect("Failed to create LDK data directory"); + fs::create_dir_all(storage_dir_path.clone()) + .map_err(|_| BuildError::StoragePathAccessFailed)?; let kv_store = Arc::new(SqliteStore::new(storage_dir_path.into())); self.build_with_store(kv_store) } /// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options /// previously configured. - pub fn build_with_fs_store(&self) -> Node { + pub fn build_with_fs_store(&self) -> Result, BuildError> { let storage_dir_path = self.config.storage_dir_path.clone(); - fs::create_dir_all(storage_dir_path.clone()).expect("Failed to create LDK data directory"); + fs::create_dir_all(storage_dir_path.clone()) + .map_err(|_| BuildError::StoragePathAccessFailed)?; let kv_store = Arc::new(FilesystemStore::new(storage_dir_path.into())); self.build_with_store(kv_store) } @@ -198,7 +237,7 @@ impl NodeBuilder { /// Builds a [`Node`] instance according to the options previously configured. pub fn build_with_store( &self, kv_store: Arc, - ) -> Node { + ) -> Result, BuildError> { let config = Arc::new(self.config.clone()); let runtime = Arc::new(RwLock::new(None)); @@ -251,8 +290,8 @@ impl ArcedNodeBuilder { /// Configures the [`Node`] instance to source its wallet entropy from the given 64 seed bytes. /// /// **Note:** Panics if the length of the given `seed_bytes` differs from 64. - pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec) { - self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes); + pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec) -> Result<(), BuildError> { + self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes).map(|_| ()) } /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. @@ -301,21 +340,21 @@ impl ArcedNodeBuilder { /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. - pub fn build(&self) -> Arc> { - Arc::new(self.inner.read().unwrap().build()) + pub fn build(&self) -> Result>, BuildError> { + self.inner.read().unwrap().build().map(Arc::new) } /// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options /// previously configured. - pub fn build_with_fs_store(&self) -> Arc> { - Arc::new(self.inner.read().unwrap().build_with_fs_store()) + pub fn build_with_fs_store(&self) -> Result>, BuildError> { + self.inner.read().unwrap().build_with_fs_store().map(Arc::new) } /// Builds a [`Node`] instance according to the options previously configured. pub fn build_with_store( &self, kv_store: Arc, - ) -> Arc> { - Arc::new(self.inner.read().unwrap().build_with_store(kv_store)) + ) -> Result>, BuildError> { + self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new) } } @@ -325,12 +364,12 @@ fn build_with_store_internal( chain_data_source_config: Option<&ChainDataSourceConfig>, gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc, runtime: Arc>>, -) -> Node { +) -> Result, BuildError> { let ldk_data_dir = format!("{}/ldk", config.storage_dir_path); - fs::create_dir_all(ldk_data_dir.clone()).expect("Failed to create LDK data directory"); + fs::create_dir_all(ldk_data_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?; let bdk_data_dir = format!("{}/bdk", config.storage_dir_path); - fs::create_dir_all(bdk_data_dir.clone()).expect("Failed to create BDK data directory"); + fs::create_dir_all(bdk_data_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?; // Initialize the Logger let log_file_path = format!( @@ -358,7 +397,7 @@ fn build_with_store_internal( }; let xprv = bitcoin::util::bip32::ExtendedPrivKey::new_master(config.network, &seed_bytes) - .expect("Failed to read wallet master key"); + .map_err(|_| BuildError::InvalidSeedBytes)?; let wallet_name = bdk::wallet::wallet_name_from_descriptor( Bip84(xprv, bdk::KeychainKind::External), @@ -366,7 +405,7 @@ fn build_with_store_internal( config.network, &Secp256k1::new(), ) - .expect("Failed to derive on-chain wallet name"); + .map_err(|_| BuildError::WalletSetupFailed)?; let database_path = format!("{}/bdk_wallet_{}.sqlite", config.storage_dir_path, wallet_name); let database = SqliteDatabase::new(database_path); @@ -377,7 +416,7 @@ fn build_with_store_internal( config.network, database, ) - .expect("Failed to set up on-chain wallet"); + .map_err(|_| BuildError::WalletSetupFailed)?; let (blockchain, tx_sync) = match chain_data_source_config { Some(ChainDataSourceConfig::Esplora(server_url)) => { @@ -413,7 +452,7 @@ fn build_with_store_internal( // Initialize the KeysManager let cur_time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) - .expect("System time error: Clock may have gone backwards"); + .map_err(|_| BuildError::InvalidSystemTime)?; let ldk_seed_bytes: [u8; 32] = xprv.private_key.secret_bytes(); let keys_manager = Arc::new(KeysManager::new( &ldk_seed_bytes, @@ -430,7 +469,7 @@ fn build_with_store_internal( if e.kind() == std::io::ErrorKind::NotFound { Arc::new(NetworkGraph::new(config.network, Arc::clone(&logger))) } else { - panic!("Failed to read network graph: {}", e.to_string()); + return Err(BuildError::IOReadFailed); } } }; @@ -450,7 +489,7 @@ fn build_with_store_internal( Arc::clone(&logger), ))) } else { - panic!("Failed to read scorer: {}", e.to_string()); + return Err(BuildError::IOReadFailed); } } }; @@ -474,7 +513,7 @@ fn build_with_store_internal( Vec::new() } else { log_error!(logger, "Failed to read channel monitors: {}", e.to_string()); - panic!("Failed to read channel monitors: {}", e.to_string()); + return Err(BuildError::IOReadFailed); } } }; @@ -507,8 +546,10 @@ fn build_with_store_internal( channel_monitor_references, ); let (_hash, channel_manager) = - <(BlockHash, ChannelManager)>::read(&mut reader, read_args) - .expect("Failed to read channel manager from store"); + <(BlockHash, ChannelManager)>::read(&mut reader, read_args).map_err(|e| { + log_error!(logger, "Failed to read channel manager from KVStore: {}", e); + BuildError::IOReadFailed + })?; channel_manager } else { // We're starting a fresh node. @@ -553,7 +594,7 @@ fn build_with_store_internal( let cur_time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) - .expect("System time error: Clock may have gone backwards"); + .map_err(|_| BuildError::InvalidSystemTime)?; // Initialize the GossipSource // Use the configured gossip source, if the user set one, otherwise default to P2PNetwork. @@ -570,7 +611,7 @@ fn build_with_store_internal( Arc::clone(&kv_store), Arc::clone(&logger), ) - .expect("Persistence failed"); + .map_err(|_| BuildError::IOWriteFailed)?; p2p_source } GossipSourceConfig::RapidGossipSync(rgs_server) => { @@ -608,7 +649,7 @@ fn build_with_store_internal( let peer_manager = Arc::new(PeerManager::new( msg_handler, - cur_time.as_secs().try_into().expect("System time error"), + cur_time.as_secs().try_into().map_err(|_| BuildError::InvalidSystemTime)?, &ephemeral_bytes, Arc::clone(&logger), IgnoringMessageHandler {}, @@ -620,8 +661,8 @@ fn build_with_store_internal( Ok(payments) => { Arc::new(PaymentStore::new(payments, Arc::clone(&kv_store), Arc::clone(&logger))) } - Err(e) => { - panic!("Failed to read payment information: {}", e.to_string()); + Err(_) => { + return Err(BuildError::IOReadFailed); } }; @@ -632,7 +673,7 @@ fn build_with_store_internal( if e.kind() == std::io::ErrorKind::NotFound { Arc::new(EventQueue::new(Arc::clone(&kv_store), Arc::clone(&logger))) } else { - panic!("Failed to read event queue: {}", e.to_string()); + return Err(BuildError::IOReadFailed); } } }; @@ -643,14 +684,14 @@ fn build_with_store_internal( if e.kind() == std::io::ErrorKind::NotFound { Arc::new(PeerStore::new(Arc::clone(&kv_store), Arc::clone(&logger))) } else { - panic!("Failed to read peer store: {}", e.to_string()); + return Err(BuildError::IOReadFailed); } } }; let (stop_sender, stop_receiver) = tokio::sync::watch::channel(()); - Node { + Ok(Node { runtime, stop_sender, stop_receiver, @@ -669,5 +710,5 @@ fn build_with_store_internal( scorer, peer_store, payment_store, - } + }) } diff --git a/src/lib.rs b/src/lib.rs index a69f5174c..da6719a9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ //! builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); //! builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); //! -//! let node = builder.build(); +//! let node = builder.build().unwrap(); //! //! node.start().unwrap(); //! @@ -111,6 +111,7 @@ use {bip39::Mnemonic, bitcoin::OutPoint, lightning::ln::PaymentSecret, uniffi_ty #[cfg(feature = "uniffi")] pub use builder::ArcedNodeBuilder as Builder; +pub use builder::BuildError; #[cfg(not(feature = "uniffi"))] pub use builder::NodeBuilder as Builder; @@ -1320,7 +1321,7 @@ impl Node { /// # config.network = Network::Regtest; /// # config.storage_dir_path = "/tmp/ldk_node_test/".to_string(); /// # let builder = Builder::from_config(config); - /// # let node = builder.build(); + /// # let node = builder.build().unwrap(); /// node.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound); /// ``` pub fn list_payments_with_filter bool>( diff --git a/src/test/functional_tests.rs b/src/test/functional_tests.rs index c838d0d33..c02fc88da 100644 --- a/src/test/functional_tests.rs +++ b/src/test/functional_tests.rs @@ -16,14 +16,14 @@ fn channel_full_cycle() { let config_a = random_config(); let mut builder_a = NodeBuilder::from_config(config_a); builder_a.set_esplora_server(esplora_url.clone()); - let node_a = builder_a.build(); + let node_a = builder_a.build().unwrap(); node_a.start().unwrap(); println!("\n== Node B =="); let config_b = random_config(); let mut builder_b = NodeBuilder::from_config(config_b); builder_b.set_esplora_server(esplora_url); - let node_b = builder_b.build(); + let node_b = builder_b.build().unwrap(); node_b.start().unwrap(); do_channel_full_cycle(node_a, node_b, &bitcoind, &electrsd, false); @@ -37,7 +37,7 @@ fn channel_full_cycle_0conf() { let config_a = random_config(); let mut builder_a = NodeBuilder::from_config(config_a); builder_a.set_esplora_server(esplora_url.clone()); - let node_a = builder_a.build(); + let node_a = builder_a.build().unwrap(); node_a.start().unwrap(); println!("\n== Node B =="); @@ -46,7 +46,7 @@ fn channel_full_cycle_0conf() { let mut builder_b = NodeBuilder::from_config(config_b); builder_b.set_esplora_server(esplora_url.clone()); - let node_b = builder_b.build(); + let node_b = builder_b.build().unwrap(); node_b.start().unwrap(); @@ -276,7 +276,7 @@ fn channel_open_fails_when_funds_insufficient() { let config_a = random_config(); let mut builder_a = NodeBuilder::from_config(config_a); builder_a.set_esplora_server(esplora_url.clone()); - let node_a = builder_a.build(); + let node_a = builder_a.build().unwrap(); node_a.start().unwrap(); let addr_a = node_a.new_funding_address().unwrap(); @@ -284,7 +284,7 @@ fn channel_open_fails_when_funds_insufficient() { let config_b = random_config(); let mut builder_b = NodeBuilder::from_config(config_b); builder_b.set_esplora_server(esplora_url); - let node_b = builder_b.build(); + let node_b = builder_b.build().unwrap(); node_b.start().unwrap(); let addr_b = node_b.new_funding_address().unwrap(); @@ -320,7 +320,7 @@ fn connect_to_public_testnet_esplora() { config.network = bitcoin::Network::Testnet; let mut builder = NodeBuilder::from_config(config); builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); - let node = builder.build(); + let node = builder.build().unwrap(); node.start().unwrap(); node.sync_wallets().unwrap(); node.stop().unwrap(); @@ -333,7 +333,7 @@ fn start_stop_reinit() { let config = random_config(); let mut builder = NodeBuilder::from_config(config.clone()); builder.set_esplora_server(esplora_url.clone()); - let node = builder.build(); + let node = builder.build().unwrap(); let expected_node_id = node.node_id(); let funding_address = node.new_funding_address().unwrap(); @@ -363,7 +363,7 @@ fn start_stop_reinit() { let mut new_builder = NodeBuilder::from_config(config); new_builder.set_esplora_server(esplora_url); - let reinitialized_node = builder.build(); + let reinitialized_node = builder.build().unwrap(); assert_eq!(reinitialized_node.node_id(), expected_node_id); reinitialized_node.start().unwrap(); @@ -389,7 +389,7 @@ fn start_stop_reinit_fs_store() { let config = random_config(); let mut builder = NodeBuilder::from_config(config.clone()); builder.set_esplora_server(esplora_url.clone()); - let node = builder.build_with_fs_store(); + let node = builder.build_with_fs_store().unwrap(); let expected_node_id = node.node_id(); let funding_address = node.new_funding_address().unwrap(); @@ -416,7 +416,7 @@ fn start_stop_reinit_fs_store() { let mut new_builder = NodeBuilder::from_config(config); new_builder.set_esplora_server(esplora_url); - let reinitialized_node = builder.build_with_fs_store(); + let reinitialized_node = builder.build_with_fs_store().unwrap(); assert_eq!(reinitialized_node.node_id(), expected_node_id); reinitialized_node.start().unwrap(); @@ -443,14 +443,14 @@ fn onchain_spend_receive() { let config_a = random_config(); let mut builder_a = NodeBuilder::from_config(config_a); builder_a.set_esplora_server(esplora_url.clone()); - let node_a = builder_a.build(); + let node_a = builder_a.build().unwrap(); node_a.start().unwrap(); let addr_a = node_a.new_funding_address().unwrap(); let config_b = random_config(); let mut builder_b = NodeBuilder::from_config(config_b); builder_b.set_esplora_server(esplora_url); - let node_b = builder_b.build(); + let node_b = builder_b.build().unwrap(); node_b.start().unwrap(); let addr_b = node_b.new_funding_address().unwrap(); @@ -498,7 +498,7 @@ fn sign_verify_msg() { let config = random_config(); let mut builder = NodeBuilder::from_config(config.clone()); builder.set_esplora_server(esplora_url.clone()); - let node = builder.build(); + let node = builder.build().unwrap(); node.start().unwrap();