Skip to content

Commit 59a425e

Browse files
authored
Merge pull request #119 from tnull/2023-06-dont-panic
Prefer returning errors over panicking where possible
2 parents cf0094b + 0e3ce7f commit 59a425e

File tree

10 files changed

+351
-160
lines changed

10 files changed

+351
-160
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface Builder {
1919
[Name=from_config]
2020
constructor(Config config);
2121
void set_entropy_seed_path(string seed_path);
22+
[Throws=BuildError]
2223
void set_entropy_seed_bytes(sequence<u8> seed_bytes);
2324
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
2425
void set_esplora_server(string esplora_server_url);
@@ -27,6 +28,7 @@ interface Builder {
2728
void set_storage_dir_path(string storage_dir_path);
2829
void set_network(Network network);
2930
void set_listening_address(NetAddress listening_address);
31+
[Throws=BuildError]
3032
LDKNode build();
3133
};
3234

@@ -115,6 +117,18 @@ enum NodeError {
115117
"InsufficientFunds",
116118
};
117119

120+
[Error]
121+
enum BuildError {
122+
"InvalidSeedBytes",
123+
"InvalidSeedFile",
124+
"InvalidSystemTime",
125+
"ReadFailed",
126+
"WriteFailed",
127+
"StoragePathAccessFailed",
128+
"WalletSetupFailed",
129+
"LoggerSetupFailed",
130+
};
131+
118132
[Enum]
119133
interface Event {
120134
PaymentSuccessful( PaymentHash payment_hash );

src/builder.rs

Lines changed: 93 additions & 40 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,48 @@ 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., have invalid length.
76+
InvalidSeedBytes,
77+
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
78+
InvalidSeedFile,
79+
/// The current system time is invalid, clocks might have gone backwards.
80+
InvalidSystemTime,
81+
/// We failed to read data from the [`KVStore`].
82+
ReadFailed,
83+
/// We failed to write data to the [`KVStore`].
84+
WriteFailed,
85+
/// We failed to access the given `storage_dir_path`.
86+
StoragePathAccessFailed,
87+
/// We failed to setup the onchain wallet.
88+
WalletSetupFailed,
89+
/// We failed to setup the logger.
90+
LoggerSetupFailed,
91+
}
92+
93+
impl fmt::Display for BuildError {
94+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95+
match *self {
96+
Self::InvalidSeedBytes => write!(f, "Given seed bytes are invalid."),
97+
Self::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
98+
Self::InvalidSystemTime => {
99+
write!(f, "System time is invalid. Clocks might have gone back in time.")
100+
}
101+
Self::ReadFailed => write!(f, "Failed to read from store."),
102+
Self::WriteFailed => write!(f, "Failed to write to store."),
103+
Self::StoragePathAccessFailed => write!(f, "Failed to access the given storage path."),
104+
Self::WalletSetupFailed => write!(f, "Failed to setup onchain wallet."),
105+
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
106+
}
107+
}
108+
}
109+
110+
impl std::error::Error for BuildError {}
111+
69112
/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
70113
/// the getgo.
71114
///
@@ -112,14 +155,14 @@ impl NodeBuilder {
112155
/// Configures the [`Node`] instance to source its wallet entropy from the given 64 seed bytes.
113156
///
114157
/// **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 {
158+
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> Result<&mut Self, BuildError> {
116159
if seed_bytes.len() != WALLET_KEYS_SEED_LEN {
117-
panic!("Failed to set seed due to invalid length.");
160+
return Err(BuildError::InvalidSeedBytes);
118161
}
119162
let mut bytes = [0u8; WALLET_KEYS_SEED_LEN];
120163
bytes.copy_from_slice(&seed_bytes);
121164
self.entropy_source_config = Some(EntropySourceConfig::SeedBytes(bytes));
122-
self
165+
Ok(self)
123166
}
124167

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

180223
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
181224
/// previously configured.
182-
pub fn build(&self) -> Node<SqliteStore> {
225+
pub fn build(&self) -> Result<Node<SqliteStore>, BuildError> {
183226
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");
227+
fs::create_dir_all(storage_dir_path.clone())
228+
.map_err(|_| BuildError::StoragePathAccessFailed)?;
185229
let kv_store = Arc::new(SqliteStore::new(storage_dir_path.into()));
186230
self.build_with_store(kv_store)
187231
}
188232

189233
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
190234
/// previously configured.
191-
pub fn build_with_fs_store(&self) -> Node<FilesystemStore> {
235+
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, BuildError> {
192236
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");
237+
fs::create_dir_all(storage_dir_path.clone())
238+
.map_err(|_| BuildError::StoragePathAccessFailed)?;
194239
let kv_store = Arc::new(FilesystemStore::new(storage_dir_path.into()));
195240
self.build_with_store(kv_store)
196241
}
197242

198243
/// Builds a [`Node`] instance according to the options previously configured.
199244
pub fn build_with_store<K: KVStore + Sync + Send + 'static>(
200245
&self, kv_store: Arc<K>,
201-
) -> Node<K> {
246+
) -> Result<Node<K>, BuildError> {
202247
let config = Arc::new(self.config.clone());
203248

204249
let runtime = Arc::new(RwLock::new(None));
@@ -251,8 +296,8 @@ impl ArcedNodeBuilder {
251296
/// Configures the [`Node`] instance to source its wallet entropy from the given 64 seed bytes.
252297
///
253298
/// **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);
299+
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> Result<(), BuildError> {
300+
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes).map(|_| ())
256301
}
257302

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

302347
/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
303348
/// previously configured.
304-
pub fn build(&self) -> Arc<Node<SqliteStore>> {
305-
Arc::new(self.inner.read().unwrap().build())
349+
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, BuildError> {
350+
self.inner.read().unwrap().build().map(Arc::new)
306351
}
307352

308353
/// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options
309354
/// previously configured.
310-
pub fn build_with_fs_store(&self) -> Arc<Node<FilesystemStore>> {
311-
Arc::new(self.inner.read().unwrap().build_with_fs_store())
355+
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, BuildError> {
356+
self.inner.read().unwrap().build_with_fs_store().map(Arc::new)
312357
}
313358

314359
/// Builds a [`Node`] instance according to the options previously configured.
315360
pub fn build_with_store<K: KVStore + Sync + Send + 'static>(
316361
&self, kv_store: Arc<K>,
317-
) -> Arc<Node<K>> {
318-
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
362+
) -> Result<Arc<Node<K>>, BuildError> {
363+
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
319364
}
320365
}
321366

@@ -325,26 +370,30 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
325370
chain_data_source_config: Option<&ChainDataSourceConfig>,
326371
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
327372
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
328-
) -> Node<K> {
373+
) -> Result<Node<K>, BuildError> {
329374
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");
375+
fs::create_dir_all(ldk_data_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?;
331376

332377
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");
378+
fs::create_dir_all(bdk_data_dir.clone()).map_err(|_| BuildError::StoragePathAccessFailed)?;
334379

335380
// Initialize the Logger
336381
let log_file_path = format!(
337382
"{}/logs/ldk_node_{}.log",
338383
config.storage_dir_path,
339384
chrono::offset::Local::now().format("%Y_%m_%d")
340385
);
341-
let logger = Arc::new(FilesystemLogger::new(log_file_path.clone(), config.log_level));
386+
let logger = Arc::new(
387+
FilesystemLogger::new(log_file_path.clone(), config.log_level)
388+
.map_err(|_| BuildError::LoggerSetupFailed)?,
389+
);
342390

343391
// Initialize the on-chain wallet and chain access
344392
let seed_bytes = match entropy_source_config {
345393
Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(),
346394
Some(EntropySourceConfig::SeedFile(seed_path)) => {
347-
io::utils::read_or_generate_seed_file(seed_path)
395+
io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger))
396+
.map_err(|_| BuildError::InvalidSeedFile)?
348397
}
349398
Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase {
350399
Some(passphrase) => mnemonic.to_seed(passphrase),
@@ -353,20 +402,21 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
353402
None => {
354403
// Default to read or generate from the default location generate a seed file.
355404
let seed_path = format!("{}/keys_seed", config.storage_dir_path);
356-
io::utils::read_or_generate_seed_file(&seed_path)
405+
io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger))
406+
.map_err(|_| BuildError::InvalidSeedFile)?
357407
}
358408
};
359409

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

363413
let wallet_name = bdk::wallet::wallet_name_from_descriptor(
364414
Bip84(xprv, bdk::KeychainKind::External),
365415
Some(Bip84(xprv, bdk::KeychainKind::Internal)),
366416
config.network,
367417
&Secp256k1::new(),
368418
)
369-
.expect("Failed to derive on-chain wallet name");
419+
.map_err(|_| BuildError::WalletSetupFailed)?;
370420

371421
let database_path = format!("{}/bdk_wallet_{}.sqlite", config.storage_dir_path, wallet_name);
372422
let database = SqliteDatabase::new(database_path);
@@ -377,7 +427,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
377427
config.network,
378428
database,
379429
)
380-
.expect("Failed to set up on-chain wallet");
430+
.map_err(|_| BuildError::WalletSetupFailed)?;
381431

382432
let (blockchain, tx_sync) = match chain_data_source_config {
383433
Some(ChainDataSourceConfig::Esplora(server_url)) => {
@@ -413,13 +463,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
413463
// Initialize the KeysManager
414464
let cur_time = SystemTime::now()
415465
.duration_since(SystemTime::UNIX_EPOCH)
416-
.expect("System time error: Clock may have gone backwards");
466+
.map_err(|_| BuildError::InvalidSystemTime)?;
417467
let ldk_seed_bytes: [u8; 32] = xprv.private_key.secret_bytes();
418468
let keys_manager = Arc::new(KeysManager::new(
419469
&ldk_seed_bytes,
420470
cur_time.as_secs(),
421471
cur_time.subsec_nanos(),
422472
Arc::clone(&wallet),
473+
Arc::clone(&logger),
423474
));
424475

425476
// Initialize the network graph, scorer, and router
@@ -430,7 +481,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
430481
if e.kind() == std::io::ErrorKind::NotFound {
431482
Arc::new(NetworkGraph::new(config.network, Arc::clone(&logger)))
432483
} else {
433-
panic!("Failed to read network graph: {}", e.to_string());
484+
return Err(BuildError::ReadFailed);
434485
}
435486
}
436487
};
@@ -450,7 +501,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
450501
Arc::clone(&logger),
451502
)))
452503
} else {
453-
panic!("Failed to read scorer: {}", e.to_string());
504+
return Err(BuildError::ReadFailed);
454505
}
455506
}
456507
};
@@ -474,7 +525,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
474525
Vec::new()
475526
} else {
476527
log_error!(logger, "Failed to read channel monitors: {}", e.to_string());
477-
panic!("Failed to read channel monitors: {}", e.to_string());
528+
return Err(BuildError::ReadFailed);
478529
}
479530
}
480531
};
@@ -507,8 +558,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
507558
channel_monitor_references,
508559
);
509560
let (_hash, channel_manager) =
510-
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
511-
.expect("Failed to read channel manager from store");
561+
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
562+
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
563+
BuildError::ReadFailed
564+
})?;
512565
channel_manager
513566
} else {
514567
// We're starting a fresh node.
@@ -553,7 +606,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
553606

554607
let cur_time = SystemTime::now()
555608
.duration_since(SystemTime::UNIX_EPOCH)
556-
.expect("System time error: Clock may have gone backwards");
609+
.map_err(|_| BuildError::InvalidSystemTime)?;
557610

558611
// Initialize the GossipSource
559612
// Use the configured gossip source, if the user set one, otherwise default to P2PNetwork.
@@ -570,7 +623,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
570623
Arc::clone(&kv_store),
571624
Arc::clone(&logger),
572625
)
573-
.expect("Persistence failed");
626+
.map_err(|_| BuildError::WriteFailed)?;
574627
p2p_source
575628
}
576629
GossipSourceConfig::RapidGossipSync(rgs_server) => {
@@ -608,7 +661,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
608661

609662
let peer_manager = Arc::new(PeerManager::new(
610663
msg_handler,
611-
cur_time.as_secs().try_into().expect("System time error"),
664+
cur_time.as_secs().try_into().map_err(|_| BuildError::InvalidSystemTime)?,
612665
&ephemeral_bytes,
613666
Arc::clone(&logger),
614667
IgnoringMessageHandler {},
@@ -620,8 +673,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
620673
Ok(payments) => {
621674
Arc::new(PaymentStore::new(payments, Arc::clone(&kv_store), Arc::clone(&logger)))
622675
}
623-
Err(e) => {
624-
panic!("Failed to read payment information: {}", e.to_string());
676+
Err(_) => {
677+
return Err(BuildError::ReadFailed);
625678
}
626679
};
627680

@@ -632,7 +685,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
632685
if e.kind() == std::io::ErrorKind::NotFound {
633686
Arc::new(EventQueue::new(Arc::clone(&kv_store), Arc::clone(&logger)))
634687
} else {
635-
panic!("Failed to read event queue: {}", e.to_string());
688+
return Err(BuildError::ReadFailed);
636689
}
637690
}
638691
};
@@ -643,14 +696,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
643696
if e.kind() == std::io::ErrorKind::NotFound {
644697
Arc::new(PeerStore::new(Arc::clone(&kv_store), Arc::clone(&logger)))
645698
} else {
646-
panic!("Failed to read peer store: {}", e.to_string());
699+
return Err(BuildError::ReadFailed);
647700
}
648701
}
649702
};
650703

651704
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(());
652705

653-
Node {
706+
Ok(Node {
654707
runtime,
655708
stop_sender,
656709
stop_receiver,
@@ -669,5 +722,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
669722
scorer,
670723
peer_store,
671724
payment_store,
672-
}
725+
})
673726
}

0 commit comments

Comments
 (0)