diff --git a/README.md b/README.md index 35f880e11..181b70e24 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ use ldk_node::bitcoin::Network; use std::str::FromStr; fn main() { - let builder = Builder::new(); + let mut builder = Builder::new(); builder.set_network(Network::Testnet); builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 000000000..61ae17233 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,673 @@ +use crate::event::EventQueue; +use crate::gossip::GossipSource; +use crate::io; +use crate::io::fs_store::FilesystemStore; +use crate::io::sqlite_store::SqliteStore; +use crate::io::{KVStore, CHANNEL_MANAGER_PERSISTENCE_KEY, CHANNEL_MANAGER_PERSISTENCE_NAMESPACE}; +use crate::logger::{log_error, FilesystemLogger, Logger}; +use crate::payment_store::PaymentStore; +use crate::peer_store::PeerStore; +use crate::types::{ + ChainMonitor, ChannelManager, GossipSync, KeysManager, NetAddress, NetworkGraph, + OnionMessenger, PeerManager, +}; +use crate::wallet::Wallet; +use crate::LogLevel; +use crate::{ + Config, Node, BDK_CLIENT_CONCURRENCY, BDK_CLIENT_STOP_GAP, DEFAULT_ESPLORA_SERVER_URL, + WALLET_KEYS_SEED_LEN, +}; + +use lightning::chain::keysinterface::EntropySource; +use lightning::chain::{chainmonitor, BestBlock, Watch}; +use lightning::ln::channelmanager::{self, ChainParameters, ChannelManagerReadArgs}; +use lightning::ln::msgs::RoutingMessageHandler; +use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler}; +use lightning::routing::router::DefaultRouter; +use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters}; + +use lightning::util::config::UserConfig; +use lightning::util::ser::ReadableArgs; + +use lightning_transaction_sync::EsploraSyncClient; + +use bdk::bitcoin::secp256k1::Secp256k1; +use bdk::blockchain::esplora::EsploraBlockchain; +use bdk::database::SqliteDatabase; +use bdk::template::Bip84; + +use bitcoin::Network; + +use bip39::Mnemonic; + +use bitcoin::BlockHash; + +use std::convert::TryInto; +use std::default::Default; +use std::fs; +use std::sync::{Arc, Mutex, RwLock}; +use std::time::SystemTime; + +#[derive(Debug, Clone)] +enum ChainDataSourceConfig { + Esplora(String), +} + +#[derive(Debug, Clone)] +enum EntropySourceConfig { + SeedFile(String), + SeedBytes([u8; WALLET_KEYS_SEED_LEN]), + Bip39Mnemonic { mnemonic: Mnemonic, passphrase: Option }, +} + +#[derive(Debug, Clone)] +enum GossipSourceConfig { + P2PNetwork, + RapidGossipSync(String), +} + +/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from +/// the getgo. +/// +/// ### Defaults +/// - Wallet entropy is sourced from a `keys_seed` file located under [`Config::storage_dir_path`] +/// - Chain data is sourced from the Esplora endpoint `https://blockstream.info/api` +/// - Gossip data is sourced via the peer-to-peer network +#[derive(Debug)] +pub struct NodeBuilder { + config: Config, + entropy_source_config: Option, + chain_data_source_config: Option, + gossip_source_config: Option, +} + +impl NodeBuilder { + /// Creates a new builder instance with the default configuration. + pub fn new() -> Self { + let config = Config::default(); + let entropy_source_config = None; + let chain_data_source_config = None; + let gossip_source_config = None; + Self { config, entropy_source_config, chain_data_source_config, gossip_source_config } + } + + /// Creates a new builder instance from an [`Config`]. + pub fn from_config(config: Config) -> Self { + let config = config; + let entropy_source_config = None; + let chain_data_source_config = None; + let gossip_source_config = None; + Self { config, entropy_source_config, chain_data_source_config, gossip_source_config } + } + + /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. + /// + /// If the given file does not exist a new random seed file will be generated and + /// stored at the given location. + pub fn set_entropy_seed_path(&mut self, seed_path: String) -> &mut Self { + self.entropy_source_config = Some(EntropySourceConfig::SeedFile(seed_path)); + self + } + + /// Configures the [`Node`] instance to source its wallet entropy from the given 64 seed bytes. + /// + /// **Note:** Panics if the length of the given `seed_bytes` differs from 64. + pub fn set_entropy_seed_bytes(&mut self, seed_bytes: Vec) -> &mut Self { + if seed_bytes.len() != WALLET_KEYS_SEED_LEN { + panic!("Failed to set seed due to invalid length."); + } + let mut bytes = [0u8; WALLET_KEYS_SEED_LEN]; + bytes.copy_from_slice(&seed_bytes); + self.entropy_source_config = Some(EntropySourceConfig::SeedBytes(bytes)); + self + } + + /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. + /// + /// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki + pub fn set_entropy_bip39_mnemonic( + &mut self, mnemonic: Mnemonic, passphrase: Option, + ) -> &mut Self { + self.entropy_source_config = + Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }); + self + } + + /// Configures the [`Node`] instance to source its chain data from the given Esplora server. + pub fn set_esplora_server(&mut self, esplora_server_url: String) -> &mut Self { + self.chain_data_source_config = Some(ChainDataSourceConfig::Esplora(esplora_server_url)); + self + } + + /// Configures the [`Node`] instance to source its gossip data from the Lightning peer-to-peer + /// network. + pub fn set_gossip_source_p2p(&mut self) -> &mut Self { + self.gossip_source_config = Some(GossipSourceConfig::P2PNetwork); + self + } + + /// Configures the [`Node`] instance to source its gossip data from the given RapidGossipSync + /// server. + pub fn set_gossip_source_rgs(&mut self, rgs_server_url: String) -> &mut Self { + self.gossip_source_config = Some(GossipSourceConfig::RapidGossipSync(rgs_server_url)); + self + } + + /// Sets the used storage directory path. + pub fn set_storage_dir_path(&mut self, storage_dir_path: String) -> &mut Self { + self.config.storage_dir_path = storage_dir_path; + self + } + + /// Sets the Bitcoin network used. + pub fn set_network(&mut self, network: Network) -> &mut Self { + self.config.network = network; + self + } + + /// Sets the IP address and TCP port on which [`Node`] will listen for incoming network connections. + pub fn set_listening_address(&mut self, listening_address: NetAddress) -> &mut Self { + self.config.listening_address = Some(listening_address); + self + } + + /// Sets the level at which [`Node`] will log messages. + pub fn set_log_level(&mut self, level: LogLevel) -> &mut Self { + self.config.log_level = level; + self + } + + /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options + /// previously configured. + pub fn build(&self) -> Node { + 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"); + 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 { + 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"); + 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( + &self, kv_store: Arc, + ) -> Node { + let config = Arc::new(self.config.clone()); + + let runtime = Arc::new(RwLock::new(None)); + build_with_store_internal( + config, + self.entropy_source_config.as_ref(), + self.chain_data_source_config.as_ref(), + self.gossip_source_config.as_ref(), + kv_store, + runtime, + ) + } +} + +/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from +/// the getgo. +/// +/// ### Defaults +/// - Wallet entropy is sourced from a `keys_seed` file located under [`Config::storage_dir_path`] +/// - Chain data is sourced from the Esplora endpoint `https://blockstream.info/api` +/// - Gossip data is sourced via the peer-to-peer network +#[derive(Debug)] +#[cfg(feature = "uniffi")] +pub struct ArcedNodeBuilder { + inner: RwLock, +} + +#[cfg(feature = "uniffi")] +impl ArcedNodeBuilder { + /// Creates a new builder instance with the default configuration. + pub fn new() -> Self { + let inner = RwLock::new(NodeBuilder::new()); + Self { inner } + } + + /// Creates a new builder instance from an [`Config`]. + pub fn from_config(config: Config) -> Self { + let inner = RwLock::new(NodeBuilder::from_config(config)); + Self { inner } + } + + /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. + /// + /// If the given file does not exist a new random seed file will be generated and + /// stored at the given location. + pub fn set_entropy_seed_path(&self, seed_path: String) { + self.inner.write().unwrap().set_entropy_seed_path(seed_path); + } + + /// Configures the [`Node`] instance to source its wallet entropy from the given 64 seed bytes. + /// + /// **Note:** Panics if the length of the given `seed_bytes` differs from 64. + pub fn set_entropy_seed_bytes(&self, seed_bytes: Vec) { + self.inner.write().unwrap().set_entropy_seed_bytes(seed_bytes); + } + + /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. + /// + /// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki + pub fn set_entropy_bip39_mnemonic(&self, mnemonic: Mnemonic, passphrase: Option) { + self.inner.write().unwrap().set_entropy_bip39_mnemonic(mnemonic, passphrase); + } + + /// Configures the [`Node`] instance to source its chain data from the given Esplora server. + pub fn set_esplora_server(&self, esplora_server_url: String) { + self.inner.write().unwrap().set_esplora_server(esplora_server_url); + } + + /// Configures the [`Node`] instance to source its gossip data from the Lightning peer-to-peer + /// network. + pub fn set_gossip_source_p2p(&self) { + self.inner.write().unwrap().set_gossip_source_p2p(); + } + + /// Configures the [`Node`] instance to source its gossip data from the given RapidGossipSync + /// server. + pub fn set_gossip_source_rgs(&self, rgs_server_url: String) { + self.inner.write().unwrap().set_gossip_source_rgs(rgs_server_url); + } + + /// Sets the used storage directory path. + pub fn set_storage_dir_path(&self, storage_dir_path: String) { + self.inner.write().unwrap().set_storage_dir_path(storage_dir_path); + } + + /// Sets the Bitcoin network used. + pub fn set_network(&self, network: Network) { + self.inner.write().unwrap().set_network(network); + } + + /// Sets the IP address and TCP port on which [`Node`] will listen for incoming network connections. + pub fn set_listening_address(&self, listening_address: NetAddress) { + self.inner.write().unwrap().set_listening_address(listening_address); + } + + /// Sets the level at which [`Node`] will log messages. + pub fn set_log_level(&self, level: LogLevel) { + self.inner.write().unwrap().set_log_level(level); + } + + /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options + /// previously configured. + pub fn build(&self) -> Arc> { + Arc::new(self.inner.read().unwrap().build()) + } + + /// Builds a [`Node`] instance with a [`FilesystemStore`] backend and according to the options + /// previously configured. + pub fn build_with_fs_store(&self) -> Arc> { + Arc::new(self.inner.read().unwrap().build_with_fs_store()) + } + + /// Builds a [`Node`] instance according to the options previously configured. + pub fn build_with_store( + &self, kv_store: Arc, + ) -> Arc> { + Arc::new(self.inner.read().unwrap().build_with_store(kv_store)) + } +} + +/// Builds a [`Node`] instance according to the options previously configured. +fn build_with_store_internal( + config: Arc, entropy_source_config: Option<&EntropySourceConfig>, + chain_data_source_config: Option<&ChainDataSourceConfig>, + gossip_source_config: Option<&GossipSourceConfig>, kv_store: Arc, + runtime: Arc>>, +) -> Node { + 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"); + + 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"); + + // 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)); + + // 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) + } + Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase { + Some(passphrase) => mnemonic.to_seed(passphrase), + None => mnemonic.to_seed(""), + }, + 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) + } + }; + + let xprv = bitcoin::util::bip32::ExtendedPrivKey::new_master(config.network, &seed_bytes) + .expect("Failed to read wallet master key"); + + 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"); + + let database_path = format!("{}/bdk_wallet_{}.sqlite", config.storage_dir_path, wallet_name); + let database = SqliteDatabase::new(database_path); + + let bdk_wallet = bdk::Wallet::new( + Bip84(xprv, bdk::KeychainKind::External), + Some(Bip84(xprv, bdk::KeychainKind::Internal)), + config.network, + database, + ) + .expect("Failed to set up on-chain wallet"); + + let (blockchain, tx_sync) = match chain_data_source_config { + Some(ChainDataSourceConfig::Esplora(server_url)) => { + let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger))); + let blockchain = + EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP) + .with_concurrency(BDK_CLIENT_CONCURRENCY); + (blockchain, tx_sync) + } + None => { + // Default to Esplora client. + let server_url = DEFAULT_ESPLORA_SERVER_URL.to_string(); + let tx_sync = Arc::new(EsploraSyncClient::new(server_url, Arc::clone(&logger))); + let blockchain = + EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP) + .with_concurrency(BDK_CLIENT_CONCURRENCY); + (blockchain, tx_sync) + } + }; + + let wallet = + Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger))); + + // Initialize the ChainMonitor + let chain_monitor: Arc> = Arc::new(chainmonitor::ChainMonitor::new( + Some(Arc::clone(&tx_sync)), + Arc::clone(&wallet), + Arc::clone(&logger), + Arc::clone(&wallet), + Arc::clone(&kv_store), + )); + + // Initialize the KeysManager + let cur_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("System time error: Clock may have gone backwards"); + 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), + )); + + // Initialize the network graph, scorer, and router + let network_graph = + match io::utils::read_network_graph(Arc::clone(&kv_store), Arc::clone(&logger)) { + Ok(graph) => Arc::new(graph), + Err(e) => { + 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()); + } + } + }; + + let scorer = match io::utils::read_scorer( + Arc::clone(&kv_store), + Arc::clone(&network_graph), + Arc::clone(&logger), + ) { + Ok(scorer) => Arc::new(Mutex::new(scorer)), + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + let params = ProbabilisticScoringParameters::default(); + Arc::new(Mutex::new(ProbabilisticScorer::new( + params, + Arc::clone(&network_graph), + Arc::clone(&logger), + ))) + } else { + panic!("Failed to read scorer: {}", e.to_string()); + } + } + }; + + let router = Arc::new(DefaultRouter::new( + Arc::clone(&network_graph), + Arc::clone(&logger), + keys_manager.get_secure_random_bytes(), + Arc::clone(&scorer), + )); + + // Read ChannelMonitor state from store + let mut channel_monitors = match io::utils::read_channel_monitors( + Arc::clone(&kv_store), + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), + ) { + Ok(monitors) => monitors, + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Vec::new() + } else { + log_error!(logger, "Failed to read channel monitors: {}", e.to_string()); + panic!("Failed to read channel monitors: {}", e.to_string()); + } + } + }; + + // Initialize the ChannelManager + let mut user_config = UserConfig::default(); + user_config.channel_handshake_limits.force_announced_channel_preference = false; + + if !config.trusted_peers_0conf.is_empty() { + // Manually accept inbound channels if we expect 0conf channel requests, avoid + // generating the events otherwise. + user_config.manually_accept_inbound_channels = true; + } + let channel_manager = { + if let Ok(mut reader) = + kv_store.read(CHANNEL_MANAGER_PERSISTENCE_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_KEY) + { + let channel_monitor_references = + channel_monitors.iter_mut().map(|(_, chanmon)| chanmon).collect(); + let read_args = ChannelManagerReadArgs::new( + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), + Arc::clone(&wallet), + Arc::clone(&chain_monitor), + Arc::clone(&wallet), + Arc::clone(&router), + Arc::clone(&logger), + user_config, + channel_monitor_references, + ); + let (_hash, channel_manager) = + <(BlockHash, ChannelManager)>::read(&mut reader, read_args) + .expect("Failed to read channel manager from store"); + channel_manager + } else { + // We're starting a fresh node. + let genesis_block_hash = + bitcoin::blockdata::constants::genesis_block(config.network).block_hash(); + + let chain_params = ChainParameters { + network: config.network, + best_block: BestBlock::new(genesis_block_hash, 0), + }; + channelmanager::ChannelManager::new( + Arc::clone(&wallet), + Arc::clone(&chain_monitor), + Arc::clone(&wallet), + Arc::clone(&router), + Arc::clone(&logger), + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), + user_config, + chain_params, + ) + } + }; + + let channel_manager = Arc::new(channel_manager); + + // Give ChannelMonitors to ChainMonitor + for (_blockhash, channel_monitor) in channel_monitors.into_iter() { + let funding_outpoint = channel_monitor.get_funding_txo().0; + chain_monitor.watch_channel(funding_outpoint, channel_monitor); + } + + // Initialize the PeerManager + let onion_messenger: Arc = Arc::new(OnionMessenger::new( + Arc::clone(&keys_manager), + Arc::clone(&keys_manager), + Arc::clone(&logger), + IgnoringMessageHandler {}, + )); + let ephemeral_bytes: [u8; 32] = keys_manager.get_secure_random_bytes(); + + let cur_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("System time error: Clock may have gone backwards"); + + // Initialize the GossipSource + // Use the configured gossip source, if the user set one, otherwise default to P2PNetwork. + let gossip_source_config = gossip_source_config.unwrap_or(&GossipSourceConfig::P2PNetwork); + + let gossip_source = match gossip_source_config { + GossipSourceConfig::P2PNetwork => { + let p2p_source = + Arc::new(GossipSource::new_p2p(Arc::clone(&network_graph), Arc::clone(&logger))); + + // Reset the RGS sync timestamp in case we somehow switch gossip sources + io::utils::write_latest_rgs_sync_timestamp( + 0, + Arc::clone(&kv_store), + Arc::clone(&logger), + ) + .expect("Persistence failed"); + p2p_source + } + GossipSourceConfig::RapidGossipSync(rgs_server) => { + let latest_sync_timestamp = io::utils::read_latest_rgs_sync_timestamp( + Arc::clone(&kv_store), + Arc::clone(&logger), + ) + .unwrap_or(0); + Arc::new(GossipSource::new_rgs( + rgs_server.clone(), + latest_sync_timestamp, + Arc::clone(&network_graph), + Arc::clone(&logger), + )) + } + }; + + let msg_handler = match gossip_source.as_gossip_sync() { + GossipSync::P2P(p2p_gossip_sync) => MessageHandler { + chan_handler: Arc::clone(&channel_manager), + route_handler: Arc::clone(&p2p_gossip_sync) + as Arc, + onion_message_handler: onion_messenger, + }, + GossipSync::Rapid(_) => MessageHandler { + chan_handler: Arc::clone(&channel_manager), + route_handler: Arc::new(IgnoringMessageHandler {}) + as Arc, + onion_message_handler: onion_messenger, + }, + GossipSync::None => { + unreachable!("We must always have a gossip sync!"); + } + }; + + let peer_manager = Arc::new(PeerManager::new( + msg_handler, + cur_time.as_secs().try_into().expect("System time error"), + &ephemeral_bytes, + Arc::clone(&logger), + IgnoringMessageHandler {}, + Arc::clone(&keys_manager), + )); + + // Init payment info storage + let payment_store = match io::utils::read_payments(Arc::clone(&kv_store), Arc::clone(&logger)) { + 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()); + } + }; + + let event_queue = match io::utils::read_event_queue(Arc::clone(&kv_store), Arc::clone(&logger)) + { + Ok(event_queue) => Arc::new(event_queue), + Err(e) => { + 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()); + } + } + }; + + let peer_store = match io::utils::read_peer_info(Arc::clone(&kv_store), Arc::clone(&logger)) { + Ok(peer_store) => Arc::new(peer_store), + Err(e) => { + 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()); + } + } + }; + + let (stop_sender, stop_receiver) = tokio::sync::watch::channel(()); + + Node { + runtime, + stop_sender, + stop_receiver, + config, + wallet, + tx_sync, + event_queue, + channel_manager, + chain_monitor, + peer_manager, + keys_manager, + network_graph, + gossip_source, + kv_store, + logger, + scorer, + peer_store, + payment_store, + } +} diff --git a/src/lib.rs b/src/lib.rs index c9cbe7770..1eb7753c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ //! use std::str::FromStr; //! //! fn main() { -//! let builder = Builder::new(); +//! let mut builder = Builder::new(); //! builder.set_network(Network::Testnet); //! builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); //! builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); @@ -79,6 +79,7 @@ #![allow(ellipsis_inclusive_range_patterns)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +mod builder; mod error; mod event; mod gossip; @@ -106,65 +107,50 @@ pub use event::Event; pub use types::NetAddress; #[cfg(feature = "uniffi")] -use {bitcoin::OutPoint, lightning::ln::PaymentSecret, uniffi_types::*}; +use {bip39::Mnemonic, bitcoin::OutPoint, lightning::ln::PaymentSecret, uniffi_types::*}; + +#[cfg(feature = "uniffi")] +pub use builder::ArcedNodeBuilder as Builder; +#[cfg(not(feature = "uniffi"))] +pub use builder::NodeBuilder as Builder; use event::{EventHandler, EventQueue}; use gossip::GossipSource; -use io::fs_store::FilesystemStore; -use io::sqlite_store::SqliteStore; -use io::{KVStore, CHANNEL_MANAGER_PERSISTENCE_KEY, CHANNEL_MANAGER_PERSISTENCE_NAMESPACE}; +use io::KVStore; use payment_store::PaymentStore; pub use payment_store::{PaymentDetails, PaymentDirection, PaymentStatus}; use peer_store::{PeerInfo, PeerStore}; -use types::{ - ChainMonitor, ChannelManager, GossipSync, KeysManager, NetworkGraph, OnionMessenger, - PeerManager, Scorer, -}; +use types::{ChainMonitor, ChannelManager, KeysManager, NetworkGraph, PeerManager, Scorer}; pub use types::{ChannelDetails, ChannelId, PeerDetails, UserChannelId}; use wallet::Wallet; use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger}; use lightning::chain::keysinterface::EntropySource; -use lightning::chain::{chainmonitor, BestBlock, Confirm, Watch}; -use lightning::ln::channelmanager::{ - self, ChainParameters, ChannelManagerReadArgs, PaymentId, RecipientOnionFields, Retry, -}; -use lightning::ln::msgs::RoutingMessageHandler; -use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler}; +use lightning::chain::Confirm; +use lightning::ln::channelmanager::{self, PaymentId, RecipientOnionFields, Retry}; use lightning::ln::{PaymentHash, PaymentPreimage}; -use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters}; use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig}; pub use lightning::util::logger::Level as LogLevel; -use lightning::util::ser::ReadableArgs; use lightning_background_processor::process_events_async; use lightning_transaction_sync::EsploraSyncClient; -use lightning::routing::router::{DefaultRouter, PaymentParameters, RouteParameters}; +use lightning::routing::router::{PaymentParameters, RouteParameters}; use lightning_invoice::{payment, Currency, Invoice}; -use bdk::bitcoin::secp256k1::Secp256k1; -use bdk::blockchain::esplora::EsploraBlockchain; -use bdk::database::SqliteDatabase; -use bdk::template::Bip84; - use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; use bitcoin::Network; -use bip39::Mnemonic; - -use bitcoin::{Address, BlockHash, Txid}; +use bitcoin::{Address, Txid}; use rand::Rng; -use std::convert::TryInto; use std::default::Default; -use std::fs; use std::net::ToSocketAddrs; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant, SystemTime}; @@ -276,509 +262,6 @@ impl Default for Config { } } -#[derive(Debug, Clone)] -enum ChainDataSourceConfig { - Esplora(String), -} - -#[derive(Debug, Clone)] -enum EntropySourceConfig { - SeedFile(String), - SeedBytes([u8; WALLET_KEYS_SEED_LEN]), - Bip39Mnemonic { mnemonic: Mnemonic, passphrase: Option }, -} - -#[derive(Debug, Clone)] -enum GossipSourceConfig { - P2PNetwork, - RapidGossipSync(String), -} - -/// A builder for an [`Node`] instance, allowing to set some configuration and module choices from -/// the getgo. -/// -/// ### Defaults -/// - Wallet entropy is sourced from a `keys_seed` file located under [`Config::storage_dir_path`] -/// - Chain data is sourced from the Esplora endpoint `https://blockstream.info/api` -/// - Gossip data is sourced via the peer-to-peer network -#[derive(Debug)] -pub struct Builder { - config: RwLock, - entropy_source_config: RwLock>, - chain_data_source_config: RwLock>, - gossip_source_config: RwLock>, -} - -impl Builder { - /// Creates a new builder instance with the default configuration. - pub fn new() -> Self { - let config = RwLock::new(Config::default()); - let entropy_source_config = RwLock::new(None); - let chain_data_source_config = RwLock::new(None); - let gossip_source_config = RwLock::new(None); - Self { config, entropy_source_config, chain_data_source_config, gossip_source_config } - } - - /// Creates a new builder instance from an [`Config`]. - pub fn from_config(config: Config) -> Self { - let config = RwLock::new(config); - let entropy_source_config = RwLock::new(None); - let chain_data_source_config = RwLock::new(None); - let gossip_source_config = RwLock::new(None); - Self { config, entropy_source_config, chain_data_source_config, gossip_source_config } - } - - /// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk. - /// - /// If the given file does not exist a new random seed file will be generated and - /// stored at the given location. - pub fn set_entropy_seed_path(&self, seed_path: String) { - *self.entropy_source_config.write().unwrap() = - Some(EntropySourceConfig::SeedFile(seed_path)); - } - - /// 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) { - if seed_bytes.len() != WALLET_KEYS_SEED_LEN { - panic!("Failed to set seed due to invalid length."); - } - let mut bytes = [0u8; WALLET_KEYS_SEED_LEN]; - bytes.copy_from_slice(&seed_bytes); - *self.entropy_source_config.write().unwrap() = Some(EntropySourceConfig::SeedBytes(bytes)); - } - - /// Configures the [`Node`] instance to source its wallet entropy from a [BIP 39] mnemonic. - /// - /// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki - pub fn set_entropy_bip39_mnemonic(&self, mnemonic: Mnemonic, passphrase: Option) { - *self.entropy_source_config.write().unwrap() = - Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }); - } - - /// Configures the [`Node`] instance to source its chain data from the given Esplora server. - pub fn set_esplora_server(&self, esplora_server_url: String) { - *self.chain_data_source_config.write().unwrap() = - Some(ChainDataSourceConfig::Esplora(esplora_server_url)); - } - - /// Configures the [`Node`] instance to source its gossip data from the Lightning peer-to-peer - /// network. - pub fn set_gossip_source_p2p(&self) { - *self.gossip_source_config.write().unwrap() = Some(GossipSourceConfig::P2PNetwork); - } - - /// Configures the [`Node`] instance to source its gossip data from the given RapidGossipSync - /// server. - pub fn set_gossip_source_rgs(&self, rgs_server_url: String) { - *self.gossip_source_config.write().unwrap() = - Some(GossipSourceConfig::RapidGossipSync(rgs_server_url)); - } - - /// Sets the used storage directory path. - pub fn set_storage_dir_path(&self, storage_dir_path: String) { - let mut config = self.config.write().unwrap(); - config.storage_dir_path = storage_dir_path; - } - - /// Sets the Bitcoin network used. - pub fn set_network(&self, network: Network) { - let mut config = self.config.write().unwrap(); - config.network = network; - } - - /// Sets the IP address and TCP port on which [`Node`] will listen for incoming network connections. - pub fn set_listening_address(&self, listening_address: NetAddress) { - let mut config = self.config.write().unwrap(); - config.listening_address = Some(listening_address); - } - - /// Sets the level at which [`Node`] will log messages. - pub fn set_log_level(&self, level: LogLevel) { - let mut config = self.config.write().unwrap(); - config.log_level = level; - } - - /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options - /// previously configured. - pub fn build(&self) -> Arc> { - let storage_dir_path = self.config.read().unwrap().storage_dir_path.clone(); - fs::create_dir_all(storage_dir_path.clone()).expect("Failed to create LDK data directory"); - 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) -> Arc> { - let storage_dir_path = self.config.read().unwrap().storage_dir_path.clone(); - fs::create_dir_all(storage_dir_path.clone()).expect("Failed to create LDK data directory"); - 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( - &self, kv_store: Arc, - ) -> Arc> { - let config = Arc::new(self.config.read().unwrap().clone()); - - // 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)); - - // Initialize the on-chain wallet and chain access - let seed_bytes = match &*self.entropy_source_config.read().unwrap() { - Some(EntropySourceConfig::SeedBytes(bytes)) => bytes.clone(), - Some(EntropySourceConfig::SeedFile(seed_path)) => { - io::utils::read_or_generate_seed_file(seed_path) - } - Some(EntropySourceConfig::Bip39Mnemonic { mnemonic, passphrase }) => match passphrase { - Some(passphrase) => mnemonic.to_seed(passphrase), - None => mnemonic.to_seed(""), - }, - 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) - } - }; - - let xprv = bitcoin::util::bip32::ExtendedPrivKey::new_master(config.network, &seed_bytes) - .expect("Failed to read wallet master key"); - - 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"); - - let database_path = - format!("{}/bdk_wallet_{}.sqlite", config.storage_dir_path, wallet_name); - let database = SqliteDatabase::new(database_path); - - let bdk_wallet = bdk::Wallet::new( - Bip84(xprv, bdk::KeychainKind::External), - Some(Bip84(xprv, bdk::KeychainKind::Internal)), - config.network, - database, - ) - .expect("Failed to set up on-chain wallet"); - - let (blockchain, tx_sync) = match &*self.chain_data_source_config.read().unwrap() { - Some(ChainDataSourceConfig::Esplora(server_url)) => { - let tx_sync = - Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger))); - let blockchain = - EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP) - .with_concurrency(BDK_CLIENT_CONCURRENCY); - (blockchain, tx_sync) - } - None => { - // Default to Esplora client. - let server_url = DEFAULT_ESPLORA_SERVER_URL.to_string(); - let tx_sync = Arc::new(EsploraSyncClient::new(server_url, Arc::clone(&logger))); - let blockchain = - EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP) - .with_concurrency(BDK_CLIENT_CONCURRENCY); - (blockchain, tx_sync) - } - }; - - let runtime = Arc::new(RwLock::new(None)); - let wallet = Arc::new(Wallet::new( - blockchain, - bdk_wallet, - Arc::clone(&runtime), - Arc::clone(&logger), - )); - - // Initialize the ChainMonitor - let chain_monitor: Arc> = Arc::new(chainmonitor::ChainMonitor::new( - Some(Arc::clone(&tx_sync)), - Arc::clone(&wallet), - Arc::clone(&logger), - Arc::clone(&wallet), - Arc::clone(&kv_store), - )); - - // Initialize the KeysManager - let cur_time = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("System time error: Clock may have gone backwards"); - 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), - )); - - // Initialize the network graph, scorer, and router - let network_graph = - match io::utils::read_network_graph(Arc::clone(&kv_store), Arc::clone(&logger)) { - Ok(graph) => Arc::new(graph), - Err(e) => { - 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()); - } - } - }; - - let scorer = match io::utils::read_scorer( - Arc::clone(&kv_store), - Arc::clone(&network_graph), - Arc::clone(&logger), - ) { - Ok(scorer) => Arc::new(Mutex::new(scorer)), - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - let params = ProbabilisticScoringParameters::default(); - Arc::new(Mutex::new(ProbabilisticScorer::new( - params, - Arc::clone(&network_graph), - Arc::clone(&logger), - ))) - } else { - panic!("Failed to read scorer: {}", e.to_string()); - } - } - }; - - let router = Arc::new(DefaultRouter::new( - Arc::clone(&network_graph), - Arc::clone(&logger), - keys_manager.get_secure_random_bytes(), - Arc::clone(&scorer), - )); - - // Read ChannelMonitor state from store - let mut channel_monitors = match io::utils::read_channel_monitors( - Arc::clone(&kv_store), - Arc::clone(&keys_manager), - Arc::clone(&keys_manager), - ) { - Ok(monitors) => monitors, - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - Vec::new() - } else { - log_error!(logger, "Failed to read channel monitors: {}", e.to_string()); - panic!("Failed to read channel monitors: {}", e.to_string()); - } - } - }; - - // Initialize the ChannelManager - let mut user_config = UserConfig::default(); - user_config.channel_handshake_limits.force_announced_channel_preference = false; - if !config.trusted_peers_0conf.is_empty() { - // Manually accept inbound channels if we expect 0conf channel requests, avoid - // generating the events otherwise. - user_config.manually_accept_inbound_channels = true; - } - let channel_manager = { - if let Ok(mut reader) = kv_store - .read(CHANNEL_MANAGER_PERSISTENCE_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_KEY) - { - let channel_monitor_references = - channel_monitors.iter_mut().map(|(_, chanmon)| chanmon).collect(); - let read_args = ChannelManagerReadArgs::new( - Arc::clone(&keys_manager), - Arc::clone(&keys_manager), - Arc::clone(&keys_manager), - Arc::clone(&wallet), - Arc::clone(&chain_monitor), - Arc::clone(&wallet), - Arc::clone(&router), - Arc::clone(&logger), - user_config, - channel_monitor_references, - ); - let (_hash, channel_manager) = - <(BlockHash, ChannelManager)>::read(&mut reader, read_args) - .expect("Failed to read channel manager from store"); - channel_manager - } else { - // We're starting a fresh node. - let genesis_block_hash = - bitcoin::blockdata::constants::genesis_block(config.network).block_hash(); - - let chain_params = ChainParameters { - network: config.network, - best_block: BestBlock::new(genesis_block_hash, 0), - }; - channelmanager::ChannelManager::new( - Arc::clone(&wallet), - Arc::clone(&chain_monitor), - Arc::clone(&wallet), - Arc::clone(&router), - Arc::clone(&logger), - Arc::clone(&keys_manager), - Arc::clone(&keys_manager), - Arc::clone(&keys_manager), - user_config, - chain_params, - ) - } - }; - - let channel_manager = Arc::new(channel_manager); - - // Give ChannelMonitors to ChainMonitor - for (_blockhash, channel_monitor) in channel_monitors.into_iter() { - let funding_outpoint = channel_monitor.get_funding_txo().0; - chain_monitor.watch_channel(funding_outpoint, channel_monitor); - } - - // Initialize the PeerManager - let onion_messenger: Arc = Arc::new(OnionMessenger::new( - Arc::clone(&keys_manager), - Arc::clone(&keys_manager), - Arc::clone(&logger), - IgnoringMessageHandler {}, - )); - let ephemeral_bytes: [u8; 32] = keys_manager.get_secure_random_bytes(); - - let cur_time = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("System time error: Clock may have gone backwards"); - - // Initialize the GossipSource - // Use the configured gossip source, if the user set one, otherwise default to P2PNetwork. - let gossip_source_config_lock = self.gossip_source_config.read().unwrap(); - let gossip_source_config = - gossip_source_config_lock.as_ref().unwrap_or(&GossipSourceConfig::P2PNetwork); - - let gossip_source = match gossip_source_config { - GossipSourceConfig::P2PNetwork => { - let p2p_source = Arc::new(GossipSource::new_p2p( - Arc::clone(&network_graph), - Arc::clone(&logger), - )); - - // Reset the RGS sync timestamp in case we somehow switch gossip sources - io::utils::write_latest_rgs_sync_timestamp( - 0, - Arc::clone(&kv_store), - Arc::clone(&logger), - ) - .expect("Persistence failed"); - p2p_source - } - GossipSourceConfig::RapidGossipSync(rgs_server) => { - let latest_sync_timestamp = io::utils::read_latest_rgs_sync_timestamp( - Arc::clone(&kv_store), - Arc::clone(&logger), - ) - .unwrap_or(0); - Arc::new(GossipSource::new_rgs( - rgs_server.clone(), - latest_sync_timestamp, - Arc::clone(&network_graph), - Arc::clone(&logger), - )) - } - }; - - let msg_handler = match gossip_source.as_gossip_sync() { - GossipSync::P2P(p2p_gossip_sync) => MessageHandler { - chan_handler: Arc::clone(&channel_manager), - route_handler: Arc::clone(&p2p_gossip_sync) - as Arc, - onion_message_handler: onion_messenger, - }, - GossipSync::Rapid(_) => MessageHandler { - chan_handler: Arc::clone(&channel_manager), - route_handler: Arc::new(IgnoringMessageHandler {}) - as Arc, - onion_message_handler: onion_messenger, - }, - GossipSync::None => { - unreachable!("We must always have a gossip sync!"); - } - }; - - let peer_manager = Arc::new(PeerManager::new( - msg_handler, - cur_time.as_secs().try_into().expect("System time error"), - &ephemeral_bytes, - Arc::clone(&logger), - IgnoringMessageHandler {}, - Arc::clone(&keys_manager), - )); - - // Init payment info storage - let payment_store = - match io::utils::read_payments(Arc::clone(&kv_store), Arc::clone(&logger)) { - 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()); - } - }; - - let event_queue = - match io::utils::read_event_queue(Arc::clone(&kv_store), Arc::clone(&logger)) { - Ok(event_queue) => Arc::new(event_queue), - Err(e) => { - 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()); - } - } - }; - - let peer_store = match io::utils::read_peer_info(Arc::clone(&kv_store), Arc::clone(&logger)) - { - Ok(peer_store) => Arc::new(peer_store), - Err(e) => { - 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()); - } - } - }; - - let (stop_sender, stop_receiver) = tokio::sync::watch::channel(()); - - Arc::new(Node { - runtime, - stop_sender, - stop_receiver, - config, - wallet, - tx_sync, - event_queue, - channel_manager, - chain_monitor, - peer_manager, - keys_manager, - network_graph, - gossip_source, - kv_store, - logger, - scorer, - peer_store, - payment_store, - }) - } -} - /// The main interface object of LDK Node, wrapping the necessary LDK and BDK functionalities. /// /// Needs to be initialized and instantiated through [`Builder::build`]. diff --git a/src/test/functional_tests.rs b/src/test/functional_tests.rs index ea83f1972..c838d0d33 100644 --- a/src/test/functional_tests.rs +++ b/src/test/functional_tests.rs @@ -1,28 +1,27 @@ +use crate::builder::NodeBuilder; use crate::io::KVStore; use crate::test::utils::*; use crate::test::utils::{expect_event, random_config}; -use crate::{Builder, Error, Event, Node, PaymentDirection, PaymentStatus}; +use crate::{Error, Event, Node, PaymentDirection, PaymentStatus}; use bitcoin::Amount; use electrsd::bitcoind::BitcoinD; use electrsd::ElectrsD; -use std::sync::Arc; - #[test] fn channel_full_cycle() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); println!("== Node A =="); let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let config_a = random_config(); - let builder_a = Builder::from_config(config_a); + let mut builder_a = NodeBuilder::from_config(config_a); builder_a.set_esplora_server(esplora_url.clone()); let node_a = builder_a.build(); node_a.start().unwrap(); println!("\n== Node B =="); let config_b = random_config(); - let builder_b = Builder::from_config(config_b); + let mut builder_b = NodeBuilder::from_config(config_b); builder_b.set_esplora_server(esplora_url); let node_b = builder_b.build(); node_b.start().unwrap(); @@ -36,7 +35,7 @@ fn channel_full_cycle_0conf() { println!("== Node A =="); let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let config_a = random_config(); - let builder_a = Builder::from_config(config_a); + let mut builder_a = NodeBuilder::from_config(config_a); builder_a.set_esplora_server(esplora_url.clone()); let node_a = builder_a.build(); node_a.start().unwrap(); @@ -45,7 +44,7 @@ fn channel_full_cycle_0conf() { let mut config_b = random_config(); config_b.trusted_peers_0conf.push(node_a.node_id()); - let builder_b = Builder::from_config(config_b); + let mut builder_b = NodeBuilder::from_config(config_b); builder_b.set_esplora_server(esplora_url.clone()); let node_b = builder_b.build(); @@ -55,8 +54,7 @@ fn channel_full_cycle_0conf() { } fn do_channel_full_cycle( - node_a: Arc>, node_b: Arc>, bitcoind: &BitcoinD, electrsd: &ElectrsD, - allow_0conf: bool, + node_a: Node, node_b: Node, bitcoind: &BitcoinD, electrsd: &ElectrsD, allow_0conf: bool, ) { let addr_a = node_a.new_funding_address().unwrap(); let addr_b = node_b.new_funding_address().unwrap(); @@ -276,7 +274,7 @@ fn channel_open_fails_when_funds_insufficient() { println!("== Node A =="); let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let config_a = random_config(); - let builder_a = Builder::from_config(config_a); + let mut builder_a = NodeBuilder::from_config(config_a); builder_a.set_esplora_server(esplora_url.clone()); let node_a = builder_a.build(); node_a.start().unwrap(); @@ -284,7 +282,7 @@ fn channel_open_fails_when_funds_insufficient() { println!("\n== Node B =="); let config_b = random_config(); - let builder_b = Builder::from_config(config_b); + let mut builder_b = NodeBuilder::from_config(config_b); builder_b.set_esplora_server(esplora_url); let node_b = builder_b.build(); node_b.start().unwrap(); @@ -320,7 +318,7 @@ fn channel_open_fails_when_funds_insufficient() { fn connect_to_public_testnet_esplora() { let mut config = random_config(); config.network = bitcoin::Network::Testnet; - let builder = Builder::from_config(config); + let mut builder = NodeBuilder::from_config(config); builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); let node = builder.build(); node.start().unwrap(); @@ -333,7 +331,7 @@ fn start_stop_reinit() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let config = random_config(); - let builder = Builder::from_config(config.clone()); + let mut builder = NodeBuilder::from_config(config.clone()); builder.set_esplora_server(esplora_url.clone()); let node = builder.build(); let expected_node_id = node.node_id(); @@ -363,7 +361,7 @@ fn start_stop_reinit() { assert_eq!(node.stop(), Err(Error::NotRunning)); drop(node); - let new_builder = Builder::from_config(config); + let mut new_builder = NodeBuilder::from_config(config); new_builder.set_esplora_server(esplora_url); let reinitialized_node = builder.build(); assert_eq!(reinitialized_node.node_id(), expected_node_id); @@ -389,7 +387,7 @@ fn start_stop_reinit_fs_store() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let config = random_config(); - let builder = Builder::from_config(config.clone()); + let mut builder = NodeBuilder::from_config(config.clone()); builder.set_esplora_server(esplora_url.clone()); let node = builder.build_with_fs_store(); let expected_node_id = node.node_id(); @@ -416,7 +414,7 @@ fn start_stop_reinit_fs_store() { assert_eq!(node.stop(), Err(Error::NotRunning)); drop(node); - let new_builder = Builder::from_config(config); + let mut new_builder = NodeBuilder::from_config(config); new_builder.set_esplora_server(esplora_url); let reinitialized_node = builder.build_with_fs_store(); assert_eq!(reinitialized_node.node_id(), expected_node_id); @@ -443,14 +441,14 @@ fn onchain_spend_receive() { let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let config_a = random_config(); - let builder_a = Builder::from_config(config_a); + let mut builder_a = NodeBuilder::from_config(config_a); builder_a.set_esplora_server(esplora_url.clone()); let node_a = builder_a.build(); node_a.start().unwrap(); let addr_a = node_a.new_funding_address().unwrap(); let config_b = random_config(); - let builder_b = Builder::from_config(config_b); + let mut builder_b = NodeBuilder::from_config(config_b); builder_b.set_esplora_server(esplora_url); let node_b = builder_b.build(); node_b.start().unwrap(); @@ -498,7 +496,7 @@ fn sign_verify_msg() { let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); let config = random_config(); - let builder = Builder::from_config(config.clone()); + let mut builder = NodeBuilder::from_config(config.clone()); builder.set_esplora_server(esplora_url.clone()); let node = builder.build();