Skip to content

Commit eae4d06

Browse files
committed
Let Builder emit BuildError rather than panic
1 parent 09fb4ac commit eae4d06

File tree

5 files changed

+108
-54
lines changed

5 files changed

+108
-54
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn main() {
2323
builder.set_esplora_server("https://blockstream.info/testnet/api".to_string());
2424
builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string());
2525

26-
let node = builder.build();
26+
let node = builder.build().unwrap();
2727

2828
node.start().unwrap();
2929

bindings/ldk_node.udl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface Builder {
1818
[Name=from_config]
1919
constructor(Config config);
2020
void set_entropy_seed_path(string seed_path);
21+
[Throws=BuildError]
2122
void set_entropy_seed_bytes(sequence<u8> seed_bytes);
2223
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
2324
void set_esplora_server(string esplora_server_url);
@@ -26,6 +27,7 @@ interface Builder {
2627
void set_storage_dir_path(string storage_dir_path);
2728
void set_network(Network network);
2829
void set_listening_address(NetAddress listening_address);
30+
[Throws=BuildError]
2931
LDKNode build();
3032
};
3133

@@ -111,6 +113,16 @@ enum NodeError {
111113
"InsufficientFunds",
112114
};
113115

116+
[Error]
117+
enum BuildError {
118+
"InvalidSeedBytes",
119+
"InvalidSystemTime",
120+
"IOReadFailed",
121+
"IOWriteFailed",
122+
"StoragePathAccessFailed",
123+
"WalletSetupFailed",
124+
};
125+
114126
[Enum]
115127
interface Event {
116128
PaymentSuccessful( PaymentHash payment_hash );

src/builder.rs

Lines changed: 78 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use bitcoin::BlockHash;
4444

4545
use std::convert::TryInto;
4646
use std::default::Default;
47+
use std::fmt;
4748
use std::fs;
4849
use std::sync::{Arc, Mutex, RwLock};
4950
use std::time::SystemTime;
@@ -66,6 +67,42 @@ enum GossipSourceConfig {
6667
RapidGossipSync(String),
6768
}
6869

70+
/// An error encountered during building a [`Node`].
71+
///
72+
/// [`Node`]: crate::Node
73+
#[derive(Debug, Clone)]
74+
pub enum BuildError {
75+
/// The given seed bytes are invalid, e.g, are of invalid length.
76+
InvalidSeedBytes,
77+
/// The current system time is invalid, clocks might have gone backwards.
78+
InvalidSystemTime,
79+
/// We failed to read data from the [`KVStore`].
80+
IOReadFailed,
81+
/// We failed to write data to the [`KVStore`].
82+
IOWriteFailed,
83+
/// We failed to access the given `storage_dir_path`.
84+
StoragePathAccessFailed,
85+
/// We failed to setup the onchain wallet.
86+
WalletSetupFailed,
87+
}
88+
89+
impl fmt::Display for BuildError {
90+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91+
match *self {
92+
Self::InvalidSeedBytes => write!(f, "Given seed bytes are invalid."),
93+
Self::InvalidSystemTime => {
94+
write!(f, "System time is invalid. Clocks might have gone back in time.")
95+
}
96+
Self::IOReadFailed => write!(f, "Failed to read from store."),
97+
Self::IOWriteFailed => write!(f, "Failed to write to store."),
98+
Self::StoragePathAccessFailed => write!(f, "Failed to access the given storage path."),
99+
Self::WalletSetupFailed => write!(f, "Failed to setup onchain wallet."),
100+
}
101+
}
102+
}
103+
104+
impl std::error::Error for BuildError {}
105+
69106
/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
70107
/// the getgo.
71108
///
@@ -112,14 +149,14 @@ impl NodeBuilder {
112149
/// Configures the [`Node`] instance to source its wallet entropy from the given 64 seed bytes.
113150
///
114151
/// **Note:** Panics if the length of the given `seed_bytes` differs from 64.
115-
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> &mut Self {
152+
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> Result<&mut Self, BuildError> {
116153
if seed_bytes.len() != WALLET_KEYS_SEED_LEN {
117-
panic!("Failed to set seed due to invalid length.");
154+
return Err(BuildError::InvalidSeedBytes);
118155
}
119156
let mut bytes = [0u8; WALLET_KEYS_SEED_LEN];
120157
bytes.copy_from_slice(&seed_bytes);
121158
self.entropy_source_config = Some(EntropySourceConfig::SeedBytes(bytes));
122-
self
159+
Ok(self)
123160
}
124161

125162
/// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic.
@@ -179,26 +216,28 @@ impl NodeBuilder {
179216

180217
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
181218
/// previously configured.
182-
pub fn build(&self) -> Node<SqliteStore> {
219+
pub fn build(&self) -> Result<Node<SqliteStore>, BuildError> {
183220
let storage_dir_path = self.config.storage_dir_path.clone();
184-
fs::create_dir_all(storage_dir_path.clone()).expect("Failed to create LDK data directory");
221+
fs::create_dir_all(storage_dir_path.clone())
222+
.map_err(|_| BuildError::StoragePathAccessFailed)?;
185223
let kv_store = Arc::new(SqliteStore::new(storage_dir_path.into()));
186224
self.build_with_store(kv_store)
187225
}
188226

189227
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
190228
/// previously configured.
191-
pub fn build_with_fs_store(&self) -> Node<FilesystemStore> {
229+
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, BuildError> {
192230
let storage_dir_path = self.config.storage_dir_path.clone();
193-
fs::create_dir_all(storage_dir_path.clone()).expect("Failed to create LDK data directory");
231+
fs::create_dir_all(storage_dir_path.clone())
232+
.map_err(|_| BuildError::StoragePathAccessFailed)?;
194233
let kv_store = Arc::new(FilesystemStore::new(storage_dir_path.into()));
195234
self.build_with_store(kv_store)
196235
}
197236

198237
/// Builds a [`Node`] instance according to the options previously configured.
199238
pub fn build_with_store<K: KVStore + Sync + Send + 'static>(
200239
&self, kv_store: Arc<K>,
201-
) -> Node<K> {
240+
) -> Result<Node<K>, BuildError> {
202241
let config = Arc::new(self.config.clone());
203242

204243
let runtime = Arc::new(RwLock::new(None));
@@ -251,8 +290,8 @@ impl ArcedNodeBuilder {
251290
/// Configures the [`Node`] instance to source its wallet entropy from the given 64 seed bytes.
252291
///
253292
/// **Note:** Panics if the length of the given `seed_bytes` differs from 64.
254-
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) {
255-
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes);
293+
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> Result<(), BuildError> {
294+
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes).map(|_| ())
256295
}
257296

258297
/// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic.
@@ -301,21 +340,21 @@ impl ArcedNodeBuilder {
301340

302341
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
303342
/// previously configured.
304-
pub fn build(&self) -> Arc<Node<SqliteStore>> {
305-
Arc::new(self.inner.read().unwrap().build())
343+
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, BuildError> {
344+
self.inner.read().unwrap().build().map(Arc::new)
306345
}
307346

308347
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
309348
/// previously configured.
310-
pub fn build_with_fs_store(&self) -> Arc<Node<FilesystemStore>> {
311-
Arc::new(self.inner.read().unwrap().build_with_fs_store())
349+
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, BuildError> {
350+
self.inner.read().unwrap().build_with_fs_store().map(Arc::new)
312351
}
313352

314353
/// Builds a [`Node`] instance according to the options previously configured.
315354
pub fn build_with_store<K: KVStore + Sync + Send + 'static>(
316355
&self, kv_store: Arc<K>,
317-
) -> Arc<Node<K>> {
318-
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
356+
) -> Result<Arc<Node<K>>, BuildError> {
357+
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
319358
}
320359
}
321360

@@ -325,12 +364,12 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
325364
chain_data_source_config: Option<&ChainDataSourceConfig>,
326365
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
327366
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
328-
) -> Node<K> {
367+
) -> Result<Node<K>, BuildError> {
329368
let ldk_data_dir = format!("{}/ldk", config.storage_dir_path);
330-
fs::create_dir_all(ldk_data_dir.clone()).expect("Failed to create LDK data directory");
369+
fs::create_dir_all(ldk_data_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?;
331370

332371
let bdk_data_dir = format!("{}/bdk", config.storage_dir_path);
333-
fs::create_dir_all(bdk_data_dir.clone()).expect("Failed to create BDK data directory");
372+
fs::create_dir_all(bdk_data_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?;
334373

335374
// Initialize the Logger
336375
let log_file_path = format!(
@@ -358,15 +397,15 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
358397
};
359398

360399
let xprv = bitcoin::util::bip32::ExtendedPrivKey::new_master(config.network, &seed_bytes)
361-
.expect("Failed to read wallet master key");
400+
.map_err(|_| BuildError::InvalidSeedBytes)?;
362401

363402
let wallet_name = bdk::wallet::wallet_name_from_descriptor(
364403
Bip84(xprv, bdk::KeychainKind::External),
365404
Some(Bip84(xprv, bdk::KeychainKind::Internal)),
366405
config.network,
367406
&Secp256k1::new(),
368407
)
369-
.expect("Failed to derive on-chain wallet name");
408+
.map_err(|_| BuildError::WalletSetupFailed)?;
370409

371410
let database_path = format!("{}/bdk_wallet_{}.sqlite", config.storage_dir_path, wallet_name);
372411
let database = SqliteDatabase::new(database_path);
@@ -377,7 +416,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
377416
config.network,
378417
database,
379418
)
380-
.expect("Failed to set up on-chain wallet");
419+
.map_err(|_| BuildError::WalletSetupFailed)?;
381420

382421
let (blockchain, tx_sync) = match chain_data_source_config {
383422
Some(ChainDataSourceConfig::Esplora(server_url)) => {
@@ -413,7 +452,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
413452
// Initialize the KeysManager
414453
let cur_time = SystemTime::now()
415454
.duration_since(SystemTime::UNIX_EPOCH)
416-
.expect("System time error: Clock may have gone backwards");
455+
.map_err(|_| BuildError::InvalidSystemTime)?;
417456
let ldk_seed_bytes: [u8; 32] = xprv.private_key.secret_bytes();
418457
let keys_manager = Arc::new(KeysManager::new(
419458
&ldk_seed_bytes,
@@ -430,7 +469,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
430469
if e.kind() == std::io::ErrorKind::NotFound {
431470
Arc::new(NetworkGraph::new(config.network, Arc::clone(&logger)))
432471
} else {
433-
panic!("Failed to read network graph: {}", e.to_string());
472+
return Err(BuildError::IOReadFailed);
434473
}
435474
}
436475
};
@@ -450,7 +489,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
450489
Arc::clone(&logger),
451490
)))
452491
} else {
453-
panic!("Failed to read scorer: {}", e.to_string());
492+
return Err(BuildError::IOReadFailed);
454493
}
455494
}
456495
};
@@ -474,7 +513,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
474513
Vec::new()
475514
} else {
476515
log_error!(logger, "Failed to read channel monitors: {}", e.to_string());
477-
panic!("Failed to read channel monitors: {}", e.to_string());
516+
return Err(BuildError::IOReadFailed);
478517
}
479518
}
480519
};
@@ -507,8 +546,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
507546
channel_monitor_references,
508547
);
509548
let (_hash, channel_manager) =
510-
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
511-
.expect("Failed to read channel manager from store");
549+
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
550+
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
551+
BuildError::IOReadFailed
552+
})?;
512553
channel_manager
513554
} else {
514555
// We're starting a fresh node.
@@ -553,7 +594,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
553594

554595
let cur_time = SystemTime::now()
555596
.duration_since(SystemTime::UNIX_EPOCH)
556-
.expect("System time error: Clock may have gone backwards");
597+
.map_err(|_| BuildError::InvalidSystemTime)?;
557598

558599
// Initialize the GossipSource
559600
// Use the configured gossip source, if the user set one, otherwise default to P2PNetwork.
@@ -570,7 +611,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
570611
Arc::clone(&kv_store),
571612
Arc::clone(&logger),
572613
)
573-
.expect("Persistence failed");
614+
.map_err(|_| BuildError::IOWriteFailed)?;
574615
p2p_source
575616
}
576617
GossipSourceConfig::RapidGossipSync(rgs_server) => {
@@ -608,7 +649,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
608649

609650
let peer_manager = Arc::new(PeerManager::new(
610651
msg_handler,
611-
cur_time.as_secs().try_into().expect("System time error"),
652+
cur_time.as_secs().try_into().map_err(|_| BuildError::InvalidSystemTime)?,
612653
&ephemeral_bytes,
613654
Arc::clone(&logger),
614655
IgnoringMessageHandler {},
@@ -620,8 +661,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
620661
Ok(payments) => {
621662
Arc::new(PaymentStore::new(payments, Arc::clone(&kv_store), Arc::clone(&logger)))
622663
}
623-
Err(e) => {
624-
panic!("Failed to read payment information: {}", e.to_string());
664+
Err(_) => {
665+
return Err(BuildError::IOReadFailed);
625666
}
626667
};
627668

@@ -632,7 +673,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
632673
if e.kind() == std::io::ErrorKind::NotFound {
633674
Arc::new(EventQueue::new(Arc::clone(&kv_store), Arc::clone(&logger)))
634675
} else {
635-
panic!("Failed to read event queue: {}", e.to_string());
676+
return Err(BuildError::IOReadFailed);
636677
}
637678
}
638679
};
@@ -643,14 +684,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
643684
if e.kind() == std::io::ErrorKind::NotFound {
644685
Arc::new(PeerStore::new(Arc::clone(&kv_store), Arc::clone(&logger)))
645686
} else {
646-
panic!("Failed to read peer store: {}", e.to_string());
687+
return Err(BuildError::IOReadFailed);
647688
}
648689
}
649690
};
650691

651692
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(());
652693

653-
Node {
694+
Ok(Node {
654695
runtime,
655696
stop_sender,
656697
stop_receiver,
@@ -669,5 +710,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
669710
scorer,
670711
peer_store,
671712
payment_store,
672-
}
713+
})
673714
}

src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
//! builder.set_esplora_server("https://blockstream.info/testnet/api".to_string());
3939
//! builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string());
4040
//!
41-
//! let node = builder.build();
41+
//! let node = builder.build().unwrap();
4242
//!
4343
//! node.start().unwrap();
4444
//!
@@ -111,6 +111,7 @@ use {bip39::Mnemonic, bitcoin::OutPoint, lightning::ln::PaymentSecret, uniffi_ty
111111

112112
#[cfg(feature = "uniffi")]
113113
pub use builder::ArcedNodeBuilder as Builder;
114+
pub use builder::BuildError;
114115
#[cfg(not(feature = "uniffi"))]
115116
pub use builder::NodeBuilder as Builder;
116117

@@ -1320,7 +1321,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
13201321
/// # config.network = Network::Regtest;
13211322
/// # config.storage_dir_path = "/tmp/ldk_node_test/".to_string();
13221323
/// # let builder = Builder::from_config(config);
1323-
/// # let node = builder.build();
1324+
/// # let node = builder.build().unwrap();
13241325
/// node.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound);
13251326
/// ```
13261327
pub fn list_payments_with_filter<F: FnMut(&&PaymentDetails) -> bool>(

0 commit comments

Comments
 (0)