Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Expand Down
14 changes: 14 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,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<u8> seed_bytes);
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
void set_esplora_server(string esplora_server_url);
Expand All@@ -27,6 +28,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();
};

Expand DownExpand Up@@ -115,6 +117,18 @@ enum NodeError {
"InsufficientFunds",
};

[Error]
enum BuildError {
"InvalidSeedBytes",
"InvalidSeedFile",
"InvalidSystemTime",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
};

[Enum]
interface Event {
PaymentSuccessful( PaymentHash payment_hash );
Expand Down
133 changes: 93 additions & 40 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -66,6 +67,48 @@ 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., have invalid length.
InvalidSeedBytes,
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
InvalidSeedFile,
/// The current system time is invalid, clocks might have gone backwards.
InvalidSystemTime,
/// We failed to read data from the [`KVStore`].
ReadFailed,
/// We failed to write data to the [`KVStore`].
WriteFailed,
/// We failed to access the given `storage_dir_path`.
StoragePathAccessFailed,
/// We failed to setup the onchain wallet.
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
}

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::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
Self::InvalidSystemTime => {
write!(f, "System time is invalid. Clocks might have gone back in time.")
}
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => 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."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
}
}
}

impl std::error::Error for BuildError {}

/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
/// the getgo.
///
Expand DownExpand Up@@ -112,14 +155,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<u8>) -> &mut Self {
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -179,26 +222,28 @@ impl NodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Node<SqliteStore> {
pub fn build(&self) -> Result<Node<SqliteStore>, 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<FilesystemStore> {
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, 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)
}

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

let runtime = Arc::new(RwLock::new(None));
Expand DownExpand Up@@ -251,8 +296,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<u8>) {
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes);
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -301,21 +346,21 @@ impl ArcedNodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Arc<Node<SqliteStore>> {
Arc::new(self.inner.read().unwrap().build())
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, 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<Node<FilesystemStore>> {
Arc::new(self.inner.read().unwrap().build_with_fs_store())
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, 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<K: KVStore + Sync + Send + 'static>(
&self, kv_store: Arc<K>,
) -> Arc<Node<K>> {
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
) -> Result<Arc<Node<K>>, BuildError> {
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
}
}

Expand All@@ -325,26 +370,30 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
) -> Node<K> {
) -> Result<Node<K>, 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!(
"{}/logs/ldk_node_{}.log",
config.storage_dir_path,
chrono::offset::Local::now().format("%Y_%m_%d")
);
let logger = Arc::new(FilesystemLogger::new(log_file_path.clone(), config.log_level));
let logger = Arc::new(
FilesystemLogger::new(log_file_path.clone(), config.log_level)
.map_err(|_| BuildError::LoggerSetupFailed)?,
);

// Initialize the on-chain wallet and chain access
let seed_bytes = match entropy_source_config {
Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(),
Some(EntropySourceConfig::SeedFile(seed_path)) => {
io::utils::read_or_generate_seed_file(seed_path)
io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase {
Some(passphrase) => mnemonic.to_seed(passphrase),
Expand All@@ -353,20 +402,21 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
None => {
// Default to read or generate from the default location generate a seed file.
let seed_path = format!("{}/keys_seed", config.storage_dir_path);
io::utils::read_or_generate_seed_file(&seed_path)
io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
};

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),
Some(Bip84(xprv, bdk::KeychainKind::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);
Expand All@@ -377,7 +427,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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)) => {
Expand DownExpand Up@@ -413,13 +463,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
// 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,
cur_time.as_secs(),
cur_time.subsec_nanos(),
Arc::clone(&wallet),
Arc::clone(&logger),
));

// Initialize the network graph, scorer, and router
Expand All@@ -430,7 +481,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -450,7 +501,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&logger),
)))
} else {
panic!("Failed to read scorer: {}", e.to_string());
return Err(BuildError::ReadFailed);
}
}
};
Expand All@@ -474,7 +525,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand DownExpand Up@@ -507,8 +558,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
channel_monitor_references,
);
let (_hash, channel_manager) =
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
.expect("Failed to read channel manager from store");
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
BuildError::ReadFailed
})?;
channel_manager
} else {
// We're starting a fresh node.
Expand DownExpand Up@@ -553,7 +606,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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.
Expand All@@ -570,7 +623,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&kv_store),
Arc::clone(&logger),
)
.expect("Persistence failed");
.map_err(|_| BuildError::WriteFailed)?;
p2p_source
}
GossipSourceConfig::RapidGossipSync(rgs_server) => {
Expand DownExpand Up@@ -608,7 +661,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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 {},
Expand All@@ -620,8 +673,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
};

Expand All@@ -632,7 +685,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -643,14 +696,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};

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

Node {
Ok(Node {
runtime,
stop_sender,
stop_receiver,
Expand All@@ -669,5 +722,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
scorer,
peer_store,
payment_store,
}
})
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Expand Down
14 changes: 14 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,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<u8> seed_bytes);
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
void set_esplora_server(string esplora_server_url);
Expand All@@ -27,6 +28,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();
};

Expand DownExpand Up@@ -115,6 +117,18 @@ enum NodeError {
"InsufficientFunds",
};

[Error]
enum BuildError {
"InvalidSeedBytes",
"InvalidSeedFile",
"InvalidSystemTime",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
};

[Enum]
interface Event {
PaymentSuccessful( PaymentHash payment_hash );
Expand Down
133 changes: 93 additions & 40 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -66,6 +67,48 @@ 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., have invalid length.
InvalidSeedBytes,
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
InvalidSeedFile,
/// The current system time is invalid, clocks might have gone backwards.
InvalidSystemTime,
/// We failed to read data from the [`KVStore`].
ReadFailed,
/// We failed to write data to the [`KVStore`].
WriteFailed,
/// We failed to access the given `storage_dir_path`.
StoragePathAccessFailed,
/// We failed to setup the onchain wallet.
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
}

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::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
Self::InvalidSystemTime => {
write!(f, "System time is invalid. Clocks might have gone back in time.")
}
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => 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."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
}
}
}

impl std::error::Error for BuildError {}

/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
/// the getgo.
///
Expand DownExpand Up@@ -112,14 +155,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<u8>) -> &mut Self {
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -179,26 +222,28 @@ impl NodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Node<SqliteStore> {
pub fn build(&self) -> Result<Node<SqliteStore>, 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<FilesystemStore> {
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, 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)
}

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

let runtime = Arc::new(RwLock::new(None));
Expand DownExpand Up@@ -251,8 +296,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<u8>) {
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes);
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -301,21 +346,21 @@ impl ArcedNodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Arc<Node<SqliteStore>> {
Arc::new(self.inner.read().unwrap().build())
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, 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<Node<FilesystemStore>> {
Arc::new(self.inner.read().unwrap().build_with_fs_store())
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, 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<K: KVStore + Sync + Send + 'static>(
&self, kv_store: Arc<K>,
) -> Arc<Node<K>> {
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
) -> Result<Arc<Node<K>>, BuildError> {
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
}
}

Expand All@@ -325,26 +370,30 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
) -> Node<K> {
) -> Result<Node<K>, 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!(
"{}/logs/ldk_node_{}.log",
config.storage_dir_path,
chrono::offset::Local::now().format("%Y_%m_%d")
);
let logger = Arc::new(FilesystemLogger::new(log_file_path.clone(), config.log_level));
let logger = Arc::new(
FilesystemLogger::new(log_file_path.clone(), config.log_level)
.map_err(|_| BuildError::LoggerSetupFailed)?,
);

// Initialize the on-chain wallet and chain access
let seed_bytes = match entropy_source_config {
Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(),
Some(EntropySourceConfig::SeedFile(seed_path)) => {
io::utils::read_or_generate_seed_file(seed_path)
io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase {
Some(passphrase) => mnemonic.to_seed(passphrase),
Expand All@@ -353,20 +402,21 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
None => {
// Default to read or generate from the default location generate a seed file.
let seed_path = format!("{}/keys_seed", config.storage_dir_path);
io::utils::read_or_generate_seed_file(&seed_path)
io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
};

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),
Some(Bip84(xprv, bdk::KeychainKind::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);
Expand All@@ -377,7 +427,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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)) => {
Expand DownExpand Up@@ -413,13 +463,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
// 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,
cur_time.as_secs(),
cur_time.subsec_nanos(),
Arc::clone(&wallet),
Arc::clone(&logger),
));

// Initialize the network graph, scorer, and router
Expand All@@ -430,7 +481,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -450,7 +501,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&logger),
)))
} else {
panic!("Failed to read scorer: {}", e.to_string());
return Err(BuildError::ReadFailed);
}
}
};
Expand All@@ -474,7 +525,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand DownExpand Up@@ -507,8 +558,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
channel_monitor_references,
);
let (_hash, channel_manager) =
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
.expect("Failed to read channel manager from store");
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
BuildError::ReadFailed
})?;
channel_manager
} else {
// We're starting a fresh node.
Expand DownExpand Up@@ -553,7 +606,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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.
Expand All@@ -570,7 +623,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&kv_store),
Arc::clone(&logger),
)
.expect("Persistence failed");
.map_err(|_| BuildError::WriteFailed)?;
p2p_source
}
GossipSourceConfig::RapidGossipSync(rgs_server) => {
Expand DownExpand Up@@ -608,7 +661,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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 {},
Expand All@@ -620,8 +673,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
};

Expand All@@ -632,7 +685,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -643,14 +696,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};

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

Node {
Ok(Node {
runtime,
stop_sender,
stop_receiver,
Expand All@@ -669,5 +722,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
scorer,
peer_store,
payment_store,
}
})
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Expand Down
14 changes: 14 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,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<u8> seed_bytes);
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
void set_esplora_server(string esplora_server_url);
Expand All@@ -27,6 +28,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();
};

Expand DownExpand Up@@ -115,6 +117,18 @@ enum NodeError {
"InsufficientFunds",
};

[Error]
enum BuildError {
"InvalidSeedBytes",
"InvalidSeedFile",
"InvalidSystemTime",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
};

[Enum]
interface Event {
PaymentSuccessful( PaymentHash payment_hash );
Expand Down
133 changes: 93 additions & 40 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -66,6 +67,48 @@ 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., have invalid length.
InvalidSeedBytes,
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
InvalidSeedFile,
/// The current system time is invalid, clocks might have gone backwards.
InvalidSystemTime,
/// We failed to read data from the [`KVStore`].
ReadFailed,
/// We failed to write data to the [`KVStore`].
WriteFailed,
/// We failed to access the given `storage_dir_path`.
StoragePathAccessFailed,
/// We failed to setup the onchain wallet.
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
}

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::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
Self::InvalidSystemTime => {
write!(f, "System time is invalid. Clocks might have gone back in time.")
}
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => 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."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
}
}
}

impl std::error::Error for BuildError {}

/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
/// the getgo.
///
Expand DownExpand Up@@ -112,14 +155,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<u8>) -> &mut Self {
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -179,26 +222,28 @@ impl NodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Node<SqliteStore> {
pub fn build(&self) -> Result<Node<SqliteStore>, 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<FilesystemStore> {
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, 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)
}

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

let runtime = Arc::new(RwLock::new(None));
Expand DownExpand Up@@ -251,8 +296,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<u8>) {
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes);
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -301,21 +346,21 @@ impl ArcedNodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Arc<Node<SqliteStore>> {
Arc::new(self.inner.read().unwrap().build())
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, 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<Node<FilesystemStore>> {
Arc::new(self.inner.read().unwrap().build_with_fs_store())
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, 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<K: KVStore + Sync + Send + 'static>(
&self, kv_store: Arc<K>,
) -> Arc<Node<K>> {
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
) -> Result<Arc<Node<K>>, BuildError> {
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
}
}

Expand All@@ -325,26 +370,30 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
) -> Node<K> {
) -> Result<Node<K>, 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!(
"{}/logs/ldk_node_{}.log",
config.storage_dir_path,
chrono::offset::Local::now().format("%Y_%m_%d")
);
let logger = Arc::new(FilesystemLogger::new(log_file_path.clone(), config.log_level));
let logger = Arc::new(
FilesystemLogger::new(log_file_path.clone(), config.log_level)
.map_err(|_| BuildError::LoggerSetupFailed)?,
);

// Initialize the on-chain wallet and chain access
let seed_bytes = match entropy_source_config {
Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(),
Some(EntropySourceConfig::SeedFile(seed_path)) => {
io::utils::read_or_generate_seed_file(seed_path)
io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase {
Some(passphrase) => mnemonic.to_seed(passphrase),
Expand All@@ -353,20 +402,21 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
None => {
// Default to read or generate from the default location generate a seed file.
let seed_path = format!("{}/keys_seed", config.storage_dir_path);
io::utils::read_or_generate_seed_file(&seed_path)
io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
};

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),
Some(Bip84(xprv, bdk::KeychainKind::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);
Expand All@@ -377,7 +427,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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)) => {
Expand DownExpand Up@@ -413,13 +463,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
// 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,
cur_time.as_secs(),
cur_time.subsec_nanos(),
Arc::clone(&wallet),
Arc::clone(&logger),
));

// Initialize the network graph, scorer, and router
Expand All@@ -430,7 +481,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -450,7 +501,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&logger),
)))
} else {
panic!("Failed to read scorer: {}", e.to_string());
return Err(BuildError::ReadFailed);
}
}
};
Expand All@@ -474,7 +525,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand DownExpand Up@@ -507,8 +558,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
channel_monitor_references,
);
let (_hash, channel_manager) =
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
.expect("Failed to read channel manager from store");
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
BuildError::ReadFailed
})?;
channel_manager
} else {
// We're starting a fresh node.
Expand DownExpand Up@@ -553,7 +606,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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.
Expand All@@ -570,7 +623,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&kv_store),
Arc::clone(&logger),
)
.expect("Persistence failed");
.map_err(|_| BuildError::WriteFailed)?;
p2p_source
}
GossipSourceConfig::RapidGossipSync(rgs_server) => {
Expand DownExpand Up@@ -608,7 +661,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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 {},
Expand All@@ -620,8 +673,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
};

Expand All@@ -632,7 +685,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -643,14 +696,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};

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

Node {
Ok(Node {
runtime,
stop_sender,
stop_receiver,
Expand All@@ -669,5 +722,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
scorer,
peer_store,
payment_store,
}
})
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Expand Down
14 changes: 14 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,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<u8> seed_bytes);
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
void set_esplora_server(string esplora_server_url);
Expand All@@ -27,6 +28,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();
};

Expand DownExpand Up@@ -115,6 +117,18 @@ enum NodeError {
"InsufficientFunds",
};

[Error]
enum BuildError {
"InvalidSeedBytes",
"InvalidSeedFile",
"InvalidSystemTime",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
};

[Enum]
interface Event {
PaymentSuccessful( PaymentHash payment_hash );
Expand Down
133 changes: 93 additions & 40 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -66,6 +67,48 @@ 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., have invalid length.
InvalidSeedBytes,
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
InvalidSeedFile,
/// The current system time is invalid, clocks might have gone backwards.
InvalidSystemTime,
/// We failed to read data from the [`KVStore`].
ReadFailed,
/// We failed to write data to the [`KVStore`].
WriteFailed,
/// We failed to access the given `storage_dir_path`.
StoragePathAccessFailed,
/// We failed to setup the onchain wallet.
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
}

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::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
Self::InvalidSystemTime => {
write!(f, "System time is invalid. Clocks might have gone back in time.")
}
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => 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."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
}
}
}

impl std::error::Error for BuildError {}

/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
/// the getgo.
///
Expand DownExpand Up@@ -112,14 +155,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<u8>) -> &mut Self {
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -179,26 +222,28 @@ impl NodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Node<SqliteStore> {
pub fn build(&self) -> Result<Node<SqliteStore>, 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<FilesystemStore> {
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, 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)
}

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

let runtime = Arc::new(RwLock::new(None));
Expand DownExpand Up@@ -251,8 +296,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<u8>) {
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes);
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -301,21 +346,21 @@ impl ArcedNodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Arc<Node<SqliteStore>> {
Arc::new(self.inner.read().unwrap().build())
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, 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<Node<FilesystemStore>> {
Arc::new(self.inner.read().unwrap().build_with_fs_store())
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, 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<K: KVStore + Sync + Send + 'static>(
&self, kv_store: Arc<K>,
) -> Arc<Node<K>> {
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
) -> Result<Arc<Node<K>>, BuildError> {
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
}
}

Expand All@@ -325,26 +370,30 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
) -> Node<K> {
) -> Result<Node<K>, 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!(
"{}/logs/ldk_node_{}.log",
config.storage_dir_path,
chrono::offset::Local::now().format("%Y_%m_%d")
);
let logger = Arc::new(FilesystemLogger::new(log_file_path.clone(), config.log_level));
let logger = Arc::new(
FilesystemLogger::new(log_file_path.clone(), config.log_level)
.map_err(|_| BuildError::LoggerSetupFailed)?,
);

// Initialize the on-chain wallet and chain access
let seed_bytes = match entropy_source_config {
Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(),
Some(EntropySourceConfig::SeedFile(seed_path)) => {
io::utils::read_or_generate_seed_file(seed_path)
io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase {
Some(passphrase) => mnemonic.to_seed(passphrase),
Expand All@@ -353,20 +402,21 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
None => {
// Default to read or generate from the default location generate a seed file.
let seed_path = format!("{}/keys_seed", config.storage_dir_path);
io::utils::read_or_generate_seed_file(&seed_path)
io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
};

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),
Some(Bip84(xprv, bdk::KeychainKind::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);
Expand All@@ -377,7 +427,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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)) => {
Expand DownExpand Up@@ -413,13 +463,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
// 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,
cur_time.as_secs(),
cur_time.subsec_nanos(),
Arc::clone(&wallet),
Arc::clone(&logger),
));

// Initialize the network graph, scorer, and router
Expand All@@ -430,7 +481,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -450,7 +501,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&logger),
)))
} else {
panic!("Failed to read scorer: {}", e.to_string());
return Err(BuildError::ReadFailed);
}
}
};
Expand All@@ -474,7 +525,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand DownExpand Up@@ -507,8 +558,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
channel_monitor_references,
);
let (_hash, channel_manager) =
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
.expect("Failed to read channel manager from store");
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
BuildError::ReadFailed
})?;
channel_manager
} else {
// We're starting a fresh node.
Expand DownExpand Up@@ -553,7 +606,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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.
Expand All@@ -570,7 +623,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&kv_store),
Arc::clone(&logger),
)
.expect("Persistence failed");
.map_err(|_| BuildError::WriteFailed)?;
p2p_source
}
GossipSourceConfig::RapidGossipSync(rgs_server) => {
Expand DownExpand Up@@ -608,7 +661,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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 {},
Expand All@@ -620,8 +673,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
};

Expand All@@ -632,7 +685,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -643,14 +696,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};

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

Node {
Ok(Node {
runtime,
stop_sender,
stop_receiver,
Expand All@@ -669,5 +722,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
scorer,
peer_store,
payment_store,
}
})
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Expand Down
14 changes: 14 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,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<u8> seed_bytes);
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
void set_esplora_server(string esplora_server_url);
Expand All@@ -27,6 +28,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();
};

Expand DownExpand Up@@ -115,6 +117,18 @@ enum NodeError {
"InsufficientFunds",
};

[Error]
enum BuildError {
"InvalidSeedBytes",
"InvalidSeedFile",
"InvalidSystemTime",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
};

[Enum]
interface Event {
PaymentSuccessful( PaymentHash payment_hash );
Expand Down
133 changes: 93 additions & 40 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -66,6 +67,48 @@ 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., have invalid length.
InvalidSeedBytes,
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
InvalidSeedFile,
/// The current system time is invalid, clocks might have gone backwards.
InvalidSystemTime,
/// We failed to read data from the [`KVStore`].
ReadFailed,
/// We failed to write data to the [`KVStore`].
WriteFailed,
/// We failed to access the given `storage_dir_path`.
StoragePathAccessFailed,
/// We failed to setup the onchain wallet.
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
}

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::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
Self::InvalidSystemTime => {
write!(f, "System time is invalid. Clocks might have gone back in time.")
}
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => 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."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
}
}
}

impl std::error::Error for BuildError {}

/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
/// the getgo.
///
Expand DownExpand Up@@ -112,14 +155,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<u8>) -> &mut Self {
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -179,26 +222,28 @@ impl NodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Node<SqliteStore> {
pub fn build(&self) -> Result<Node<SqliteStore>, 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<FilesystemStore> {
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, 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)
}

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

let runtime = Arc::new(RwLock::new(None));
Expand DownExpand Up@@ -251,8 +296,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<u8>) {
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes);
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -301,21 +346,21 @@ impl ArcedNodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Arc<Node<SqliteStore>> {
Arc::new(self.inner.read().unwrap().build())
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, 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<Node<FilesystemStore>> {
Arc::new(self.inner.read().unwrap().build_with_fs_store())
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, 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<K: KVStore + Sync + Send + 'static>(
&self, kv_store: Arc<K>,
) -> Arc<Node<K>> {
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
) -> Result<Arc<Node<K>>, BuildError> {
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
}
}

Expand All@@ -325,26 +370,30 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
) -> Node<K> {
) -> Result<Node<K>, 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!(
"{}/logs/ldk_node_{}.log",
config.storage_dir_path,
chrono::offset::Local::now().format("%Y_%m_%d")
);
let logger = Arc::new(FilesystemLogger::new(log_file_path.clone(), config.log_level));
let logger = Arc::new(
FilesystemLogger::new(log_file_path.clone(), config.log_level)
.map_err(|_| BuildError::LoggerSetupFailed)?,
);

// Initialize the on-chain wallet and chain access
let seed_bytes = match entropy_source_config {
Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(),
Some(EntropySourceConfig::SeedFile(seed_path)) => {
io::utils::read_or_generate_seed_file(seed_path)
io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase {
Some(passphrase) => mnemonic.to_seed(passphrase),
Expand All@@ -353,20 +402,21 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
None => {
// Default to read or generate from the default location generate a seed file.
let seed_path = format!("{}/keys_seed", config.storage_dir_path);
io::utils::read_or_generate_seed_file(&seed_path)
io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
};

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),
Some(Bip84(xprv, bdk::KeychainKind::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);
Expand All@@ -377,7 +427,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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)) => {
Expand DownExpand Up@@ -413,13 +463,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
// 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,
cur_time.as_secs(),
cur_time.subsec_nanos(),
Arc::clone(&wallet),
Arc::clone(&logger),
));

// Initialize the network graph, scorer, and router
Expand All@@ -430,7 +481,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -450,7 +501,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&logger),
)))
} else {
panic!("Failed to read scorer: {}", e.to_string());
return Err(BuildError::ReadFailed);
}
}
};
Expand All@@ -474,7 +525,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand DownExpand Up@@ -507,8 +558,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
channel_monitor_references,
);
let (_hash, channel_manager) =
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
.expect("Failed to read channel manager from store");
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
BuildError::ReadFailed
})?;
channel_manager
} else {
// We're starting a fresh node.
Expand DownExpand Up@@ -553,7 +606,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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.
Expand All@@ -570,7 +623,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&kv_store),
Arc::clone(&logger),
)
.expect("Persistence failed");
.map_err(|_| BuildError::WriteFailed)?;
p2p_source
}
GossipSourceConfig::RapidGossipSync(rgs_server) => {
Expand DownExpand Up@@ -608,7 +661,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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 {},
Expand All@@ -620,8 +673,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
};

Expand All@@ -632,7 +685,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -643,14 +696,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};

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

Node {
Ok(Node {
runtime,
stop_sender,
stop_receiver,
Expand All@@ -669,5 +722,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
scorer,
peer_store,
payment_store,
}
})
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Expand Down
14 changes: 14 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,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<u8> seed_bytes);
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
void set_esplora_server(string esplora_server_url);
Expand All@@ -27,6 +28,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();
};

Expand DownExpand Up@@ -115,6 +117,18 @@ enum NodeError {
"InsufficientFunds",
};

[Error]
enum BuildError {
"InvalidSeedBytes",
"InvalidSeedFile",
"InvalidSystemTime",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
};

[Enum]
interface Event {
PaymentSuccessful( PaymentHash payment_hash );
Expand Down
133 changes: 93 additions & 40 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -66,6 +67,48 @@ 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., have invalid length.
InvalidSeedBytes,
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
InvalidSeedFile,
/// The current system time is invalid, clocks might have gone backwards.
InvalidSystemTime,
/// We failed to read data from the [`KVStore`].
ReadFailed,
/// We failed to write data to the [`KVStore`].
WriteFailed,
/// We failed to access the given `storage_dir_path`.
StoragePathAccessFailed,
/// We failed to setup the onchain wallet.
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
}

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::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
Self::InvalidSystemTime => {
write!(f, "System time is invalid. Clocks might have gone back in time.")
}
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => 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."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
}
}
}

impl std::error::Error for BuildError {}

/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
/// the getgo.
///
Expand DownExpand Up@@ -112,14 +155,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<u8>) -> &mut Self {
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -179,26 +222,28 @@ impl NodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Node<SqliteStore> {
pub fn build(&self) -> Result<Node<SqliteStore>, 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<FilesystemStore> {
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, 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)
}

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

let runtime = Arc::new(RwLock::new(None));
Expand DownExpand Up@@ -251,8 +296,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<u8>) {
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes);
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -301,21 +346,21 @@ impl ArcedNodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Arc<Node<SqliteStore>> {
Arc::new(self.inner.read().unwrap().build())
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, 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<Node<FilesystemStore>> {
Arc::new(self.inner.read().unwrap().build_with_fs_store())
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, 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<K: KVStore + Sync + Send + 'static>(
&self, kv_store: Arc<K>,
) -> Arc<Node<K>> {
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
) -> Result<Arc<Node<K>>, BuildError> {
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
}
}

Expand All@@ -325,26 +370,30 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
) -> Node<K> {
) -> Result<Node<K>, 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!(
"{}/logs/ldk_node_{}.log",
config.storage_dir_path,
chrono::offset::Local::now().format("%Y_%m_%d")
);
let logger = Arc::new(FilesystemLogger::new(log_file_path.clone(), config.log_level));
let logger = Arc::new(
FilesystemLogger::new(log_file_path.clone(), config.log_level)
.map_err(|_| BuildError::LoggerSetupFailed)?,
);

// Initialize the on-chain wallet and chain access
let seed_bytes = match entropy_source_config {
Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(),
Some(EntropySourceConfig::SeedFile(seed_path)) => {
io::utils::read_or_generate_seed_file(seed_path)
io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase {
Some(passphrase) => mnemonic.to_seed(passphrase),
Expand All@@ -353,20 +402,21 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
None => {
// Default to read or generate from the default location generate a seed file.
let seed_path = format!("{}/keys_seed", config.storage_dir_path);
io::utils::read_or_generate_seed_file(&seed_path)
io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
};

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),
Some(Bip84(xprv, bdk::KeychainKind::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);
Expand All@@ -377,7 +427,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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)) => {
Expand DownExpand Up@@ -413,13 +463,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
// 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,
cur_time.as_secs(),
cur_time.subsec_nanos(),
Arc::clone(&wallet),
Arc::clone(&logger),
));

// Initialize the network graph, scorer, and router
Expand All@@ -430,7 +481,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -450,7 +501,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&logger),
)))
} else {
panic!("Failed to read scorer: {}", e.to_string());
return Err(BuildError::ReadFailed);
}
}
};
Expand All@@ -474,7 +525,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand DownExpand Up@@ -507,8 +558,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
channel_monitor_references,
);
let (_hash, channel_manager) =
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
.expect("Failed to read channel manager from store");
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
BuildError::ReadFailed
})?;
channel_manager
} else {
// We're starting a fresh node.
Expand DownExpand Up@@ -553,7 +606,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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.
Expand All@@ -570,7 +623,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&kv_store),
Arc::clone(&logger),
)
.expect("Persistence failed");
.map_err(|_| BuildError::WriteFailed)?;
p2p_source
}
GossipSourceConfig::RapidGossipSync(rgs_server) => {
Expand DownExpand Up@@ -608,7 +661,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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 {},
Expand All@@ -620,8 +673,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
};

Expand All@@ -632,7 +685,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -643,14 +696,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};

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

Node {
Ok(Node {
runtime,
stop_sender,
stop_receiver,
Expand All@@ -669,5 +722,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
scorer,
peer_store,
payment_store,
}
})
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Expand Down
14 changes: 14 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,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<u8> seed_bytes);
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
void set_esplora_server(string esplora_server_url);
Expand All@@ -27,6 +28,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();
};

Expand DownExpand Up@@ -115,6 +117,18 @@ enum NodeError {
"InsufficientFunds",
};

[Error]
enum BuildError {
"InvalidSeedBytes",
"InvalidSeedFile",
"InvalidSystemTime",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
};

[Enum]
interface Event {
PaymentSuccessful( PaymentHash payment_hash );
Expand Down
133 changes: 93 additions & 40 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -66,6 +67,48 @@ 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., have invalid length.
InvalidSeedBytes,
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
InvalidSeedFile,
/// The current system time is invalid, clocks might have gone backwards.
InvalidSystemTime,
/// We failed to read data from the [`KVStore`].
ReadFailed,
/// We failed to write data to the [`KVStore`].
WriteFailed,
/// We failed to access the given `storage_dir_path`.
StoragePathAccessFailed,
/// We failed to setup the onchain wallet.
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
}

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::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
Self::InvalidSystemTime => {
write!(f, "System time is invalid. Clocks might have gone back in time.")
}
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => 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."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
}
}
}

impl std::error::Error for BuildError {}

/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
/// the getgo.
///
Expand DownExpand Up@@ -112,14 +155,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<u8>) -> &mut Self {
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -179,26 +222,28 @@ impl NodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Node<SqliteStore> {
pub fn build(&self) -> Result<Node<SqliteStore>, 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<FilesystemStore> {
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, 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)
}

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

let runtime = Arc::new(RwLock::new(None));
Expand DownExpand Up@@ -251,8 +296,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<u8>) {
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes);
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -301,21 +346,21 @@ impl ArcedNodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Arc<Node<SqliteStore>> {
Arc::new(self.inner.read().unwrap().build())
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, 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<Node<FilesystemStore>> {
Arc::new(self.inner.read().unwrap().build_with_fs_store())
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, 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<K: KVStore + Sync + Send + 'static>(
&self, kv_store: Arc<K>,
) -> Arc<Node<K>> {
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
) -> Result<Arc<Node<K>>, BuildError> {
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
}
}

Expand All@@ -325,26 +370,30 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
) -> Node<K> {
) -> Result<Node<K>, 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!(
"{}/logs/ldk_node_{}.log",
config.storage_dir_path,
chrono::offset::Local::now().format("%Y_%m_%d")
);
let logger = Arc::new(FilesystemLogger::new(log_file_path.clone(), config.log_level));
let logger = Arc::new(
FilesystemLogger::new(log_file_path.clone(), config.log_level)
.map_err(|_| BuildError::LoggerSetupFailed)?,
);

// Initialize the on-chain wallet and chain access
let seed_bytes = match entropy_source_config {
Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(),
Some(EntropySourceConfig::SeedFile(seed_path)) => {
io::utils::read_or_generate_seed_file(seed_path)
io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase {
Some(passphrase) => mnemonic.to_seed(passphrase),
Expand All@@ -353,20 +402,21 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
None => {
// Default to read or generate from the default location generate a seed file.
let seed_path = format!("{}/keys_seed", config.storage_dir_path);
io::utils::read_or_generate_seed_file(&seed_path)
io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
};

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),
Some(Bip84(xprv, bdk::KeychainKind::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);
Expand All@@ -377,7 +427,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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)) => {
Expand DownExpand Up@@ -413,13 +463,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
// 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,
cur_time.as_secs(),
cur_time.subsec_nanos(),
Arc::clone(&wallet),
Arc::clone(&logger),
));

// Initialize the network graph, scorer, and router
Expand All@@ -430,7 +481,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -450,7 +501,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&logger),
)))
} else {
panic!("Failed to read scorer: {}", e.to_string());
return Err(BuildError::ReadFailed);
}
}
};
Expand All@@ -474,7 +525,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand DownExpand Up@@ -507,8 +558,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
channel_monitor_references,
);
let (_hash, channel_manager) =
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
.expect("Failed to read channel manager from store");
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
BuildError::ReadFailed
})?;
channel_manager
} else {
// We're starting a fresh node.
Expand DownExpand Up@@ -553,7 +606,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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.
Expand All@@ -570,7 +623,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&kv_store),
Arc::clone(&logger),
)
.expect("Persistence failed");
.map_err(|_| BuildError::WriteFailed)?;
p2p_source
}
GossipSourceConfig::RapidGossipSync(rgs_server) => {
Expand DownExpand Up@@ -608,7 +661,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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 {},
Expand All@@ -620,8 +673,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
};

Expand All@@ -632,7 +685,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -643,14 +696,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};

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

Node {
Ok(Node {
runtime,
stop_sender,
stop_receiver,
Expand All@@ -669,5 +722,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
scorer,
peer_store,
payment_store,
}
})
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Expand Down
14 changes: 14 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,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<u8> seed_bytes);
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
void set_esplora_server(string esplora_server_url);
Expand All@@ -27,6 +28,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();
};

Expand DownExpand Up@@ -115,6 +117,18 @@ enum NodeError {
"InsufficientFunds",
};

[Error]
enum BuildError {
"InvalidSeedBytes",
"InvalidSeedFile",
"InvalidSystemTime",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
"WalletSetupFailed",
"LoggerSetupFailed",
};

[Enum]
interface Event {
PaymentSuccessful( PaymentHash payment_hash );
Expand Down
133 changes: 93 additions & 40 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -66,6 +67,48 @@ 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., have invalid length.
InvalidSeedBytes,
/// The given seed file is invalid, e.g., has invalid length, or could not be read.
InvalidSeedFile,
/// The current system time is invalid, clocks might have gone backwards.
InvalidSystemTime,
/// We failed to read data from the [`KVStore`].
ReadFailed,
/// We failed to write data to the [`KVStore`].
WriteFailed,
/// We failed to access the given `storage_dir_path`.
StoragePathAccessFailed,
/// We failed to setup the onchain wallet.
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
}

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::InvalidSeedFile => write!(f, "Given seed file is invalid or could not be read."),
Self::InvalidSystemTime => {
write!(f, "System time is invalid. Clocks might have gone back in time.")
}
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => 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."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
}
}
}

impl std::error::Error for BuildError {}

/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from
/// the getgo.
///
Expand DownExpand Up@@ -112,14 +155,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<u8>) -> &mut Self {
pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -179,26 +222,28 @@ impl NodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Node<SqliteStore> {
pub fn build(&self) -> Result<Node<SqliteStore>, 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<FilesystemStore> {
pub fn build_with_fs_store(&self) -> Result<Node<FilesystemStore>, 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)
}

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

let runtime = Arc::new(RwLock::new(None));
Expand DownExpand Up@@ -251,8 +296,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<u8>) {
self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes);
pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec<u8>) -> 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.
Expand DownExpand Up@@ -301,21 +346,21 @@ impl ArcedNodeBuilder {

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Arc<Node<SqliteStore>> {
Arc::new(self.inner.read().unwrap().build())
pub fn build(&self) -> Result<Arc<Node<SqliteStore>>, 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<Node<FilesystemStore>> {
Arc::new(self.inner.read().unwrap().build_with_fs_store())
pub fn build_with_fs_store(&self) -> Result<Arc<Node<FilesystemStore>>, 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<K: KVStore + Sync + Send + 'static>(
&self, kv_store: Arc<K>,
) -> Arc<Node<K>> {
Arc::new(self.inner.read().unwrap().build_with_store(kv_store))
) -> Result<Arc<Node<K>>, BuildError> {
self.inner.read().unwrap().build_with_store(kv_store).map(Arc::new)
}
}

Expand All@@ -325,26 +370,30 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc<K>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
) -> Node<K> {
) -> Result<Node<K>, 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!(
"{}/logs/ldk_node_{}.log",
config.storage_dir_path,
chrono::offset::Local::now().format("%Y_%m_%d")
);
let logger = Arc::new(FilesystemLogger::new(log_file_path.clone(), config.log_level));
let logger = Arc::new(
FilesystemLogger::new(log_file_path.clone(), config.log_level)
.map_err(|_| BuildError::LoggerSetupFailed)?,
);

// Initialize the on-chain wallet and chain access
let seed_bytes = match entropy_source_config {
Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(),
Some(EntropySourceConfig::SeedFile(seed_path)) => {
io::utils::read_or_generate_seed_file(seed_path)
io::utils::read_or_generate_seed_file(seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase {
Some(passphrase) => mnemonic.to_seed(passphrase),
Expand All@@ -353,20 +402,21 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
None => {
// Default to read or generate from the default location generate a seed file.
let seed_path = format!("{}/keys_seed", config.storage_dir_path);
io::utils::read_or_generate_seed_file(&seed_path)
io::utils::read_or_generate_seed_file(&seed_path, Arc::clone(&logger))
.map_err(|_| BuildError::InvalidSeedFile)?
}
};

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),
Some(Bip84(xprv, bdk::KeychainKind::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);
Expand All@@ -377,7 +427,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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)) => {
Expand DownExpand Up@@ -413,13 +463,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
// 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,
cur_time.as_secs(),
cur_time.subsec_nanos(),
Arc::clone(&wallet),
Arc::clone(&logger),
));

// Initialize the network graph, scorer, and router
Expand All@@ -430,7 +481,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -450,7 +501,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&logger),
)))
} else {
panic!("Failed to read scorer: {}", e.to_string());
return Err(BuildError::ReadFailed);
}
}
};
Expand All@@ -474,7 +525,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand DownExpand Up@@ -507,8 +558,10 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
channel_monitor_references,
);
let (_hash, channel_manager) =
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args)
.expect("Failed to read channel manager from store");
<(BlockHash, ChannelManager<K>)>::read(&mut reader, read_args).map_err(|e| {
log_error!(logger, "Failed to read channel manager from KVStore: {}", e);
BuildError::ReadFailed
})?;
channel_manager
} else {
// We're starting a fresh node.
Expand DownExpand Up@@ -553,7 +606,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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.
Expand All@@ -570,7 +623,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&kv_store),
Arc::clone(&logger),
)
.expect("Persistence failed");
.map_err(|_| BuildError::WriteFailed)?;
p2p_source
}
GossipSourceConfig::RapidGossipSync(rgs_server) => {
Expand DownExpand Up@@ -608,7 +661,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(

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 {},
Expand All@@ -620,8 +673,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
};

Expand All@@ -632,7 +685,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};
Expand All@@ -643,14 +696,14 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
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::ReadFailed);
}
}
};

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

Node {
Ok(Node {
runtime,
stop_sender,
stop_receiver,
Expand All@@ -669,5 +722,5 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
scorer,
peer_store,
payment_store,
}
})
}
Loading