From: valentinewallace Date: Thu, 5 Jan 2023 17:14:43 +0000 (-0500) Subject: Merge pull request #85 from ch1ru/main X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=90ff6896459523d237ab2a8401dfddf9e99ae34e;hp=d868dd44678e49dcc5f1ac6b3b9f34bed70d81ac;p=ldk-sample Merge pull request #85 from ch1ru/main Add `disconnectpeer` subcommand --- diff --git a/.gitignore b/.gitignore index bf68587..29b24ef 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk .ldk + +# RPC auth +.env diff --git a/Cargo.lock b/Cargo.lock index 66a7e11..fd00a3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,7 +316,7 @@ dependencies = [ ] [[package]] -name = "ldk-tutorial-node" +name = "ldk-sample" version = "0.1.0" dependencies = [ "base64", diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..deb1704 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,371 @@ +use crate::cli::LdkUserInfo; +use bitcoin::network::constants::Network; +use lightning::ln::msgs::NetAddress; +use std::collections::HashMap; +use std::env; +use std::fs; +use std::net::IpAddr; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +pub(crate) fn parse_startup_args() -> Result { + if env::args().len() < 3 { + println!("ldk-tutorial-node requires 3 arguments: `cargo run [:@]: ldk_storage_directory_path [] [bitcoin-network] [announced-node-name announced-listen-addr*]`"); + return Err(()); + } + let bitcoind_rpc_info = env::args().skip(1).next().unwrap(); + let bitcoind_rpc_info_parts: Vec<&str> = bitcoind_rpc_info.rsplitn(2, "@").collect(); + + // Parse rpc auth after getting network for default .cookie location + let bitcoind_rpc_path: Vec<&str> = bitcoind_rpc_info_parts[0].split(":").collect(); + if bitcoind_rpc_path.len() != 2 { + println!("ERROR: bad bitcoind RPC path provided"); + return Err(()); + } + let bitcoind_rpc_host = bitcoind_rpc_path[0].to_string(); + let bitcoind_rpc_port = bitcoind_rpc_path[1].parse::().unwrap(); + + let ldk_storage_dir_path = env::args().skip(2).next().unwrap(); + + let mut ldk_peer_port_set = true; + let ldk_peer_listening_port: u16 = match env::args().skip(3).next().map(|p| p.parse()) { + Some(Ok(p)) => p, + Some(Err(_)) => { + ldk_peer_port_set = false; + 9735 + } + None => { + ldk_peer_port_set = false; + 9735 + } + }; + + let mut arg_idx = match ldk_peer_port_set { + true => 4, + false => 3, + }; + let network: Network = match env::args().skip(arg_idx).next().as_ref().map(String::as_str) { + Some("testnet") => Network::Testnet, + Some("regtest") => Network::Regtest, + Some("signet") => Network::Signet, + Some(net) => { + panic!("Unsupported network provided. Options are: `regtest`, `testnet`, and `signet`. Got {}", net); + } + None => Network::Testnet, + }; + + let (bitcoind_rpc_username, bitcoind_rpc_password) = if bitcoind_rpc_info_parts.len() == 1 { + get_rpc_auth_from_cookie(None, Some(network), None) + .or(get_rpc_auth_from_env_file(None)) + .or(get_rpc_auth_from_env_vars()) + .or({ + println!("ERROR: unable to get bitcoind RPC username and password"); + print_rpc_auth_help(); + Err(()) + })? + } else if bitcoind_rpc_info_parts.len() == 2 { + parse_rpc_auth(bitcoind_rpc_info_parts[1])? + } else { + println!("ERROR: bad bitcoind RPC URL provided"); + return Err(()); + }; + + let ldk_announced_node_name = match env::args().skip(arg_idx + 1).next().as_ref() { + Some(s) => { + if s.len() > 32 { + panic!("Node Alias can not be longer than 32 bytes"); + } + arg_idx += 1; + let mut bytes = [0; 32]; + bytes[..s.len()].copy_from_slice(s.as_bytes()); + bytes + } + None => [0; 32], + }; + + let mut ldk_announced_listen_addr = Vec::new(); + loop { + match env::args().skip(arg_idx + 1).next().as_ref() { + Some(s) => match IpAddr::from_str(s) { + Ok(IpAddr::V4(a)) => { + ldk_announced_listen_addr + .push(NetAddress::IPv4 { addr: a.octets(), port: ldk_peer_listening_port }); + arg_idx += 1; + } + Ok(IpAddr::V6(a)) => { + ldk_announced_listen_addr + .push(NetAddress::IPv6 { addr: a.octets(), port: ldk_peer_listening_port }); + arg_idx += 1; + } + Err(_) => panic!("Failed to parse announced-listen-addr into an IP address"), + }, + None => break, + } + } + + Ok(LdkUserInfo { + bitcoind_rpc_username, + bitcoind_rpc_password, + bitcoind_rpc_host, + bitcoind_rpc_port, + ldk_storage_dir_path, + ldk_peer_listening_port, + ldk_announced_listen_addr, + ldk_announced_node_name, + network, + }) +} + +// Default datadir relative to home directory +#[cfg(target_os = "windows")] +const DEFAULT_BITCOIN_DATADIR: &str = "AppData/Roaming/Bitcoin"; +#[cfg(target_os = "linux")] +const DEFAULT_BITCOIN_DATADIR: &str = ".bitcoin"; +#[cfg(target_os = "macos")] +const DEFAULT_BITCOIN_DATADIR: &str = "Library/Application Support/Bitcoin"; + +// Environment variable/.env keys +const BITCOIND_RPC_USER_KEY: &str = "RPC_USER"; +const BITCOIND_RPC_PASSWORD_KEY: &str = "RPC_PASSWORD"; + +fn print_rpc_auth_help() { + // Get the default data directory + let home_dir = env::home_dir() + .as_ref() + .map(|ref p| p.to_str()) + .flatten() + .unwrap_or("$HOME") + .replace("\\", "/"); + let data_dir = format!("{}/{}", home_dir, DEFAULT_BITCOIN_DATADIR); + println!("To provide the bitcoind RPC username and password, you can either:"); + println!( + "1. Provide the username and password as the first argument to this program in the format: \ + :@:" + ); + println!("2. Provide : in a .cookie file in the default \ + bitcoind data directory (automatically created by bitcoind on startup): `{}`", data_dir); + println!( + "3. Set the {} and {} environment variables", + BITCOIND_RPC_USER_KEY, BITCOIND_RPC_PASSWORD_KEY + ); + println!( + "4. Provide {} and {} fields in a .env file in the current directory", + BITCOIND_RPC_USER_KEY, BITCOIND_RPC_PASSWORD_KEY + ); +} + +fn parse_rpc_auth(rpc_auth: &str) -> Result<(String, String), ()> { + let rpc_auth_info: Vec<&str> = rpc_auth.split(':').collect(); + if rpc_auth_info.len() != 2 { + println!("ERROR: bad bitcoind RPC username/password combo provided"); + return Err(()); + } + let rpc_username = rpc_auth_info[0].to_string(); + let rpc_password = rpc_auth_info[1].to_string(); + Ok((rpc_username, rpc_password)) +} + +fn get_cookie_path( + data_dir: Option<(&str, bool)>, network: Option, cookie_file_name: Option<&str>, +) -> Result { + let data_dir_path = match data_dir { + Some((dir, true)) => env::home_dir().ok_or(())?.join(dir), + Some((dir, false)) => PathBuf::from(dir), + None => env::home_dir().ok_or(())?.join(DEFAULT_BITCOIN_DATADIR), + }; + + let data_dir_path_with_net = match network { + Some(Network::Testnet) => data_dir_path.join("testnet"), + Some(Network::Regtest) => data_dir_path.join("regtest"), + Some(Network::Signet) => data_dir_path.join("signet"), + _ => data_dir_path, + }; + + let cookie_path = data_dir_path_with_net.join(cookie_file_name.unwrap_or(".cookie")); + + Ok(cookie_path) +} + +fn get_rpc_auth_from_cookie( + data_dir: Option<(&str, bool)>, network: Option, cookie_file_name: Option<&str>, +) -> Result<(String, String), ()> { + let cookie_path = get_cookie_path(data_dir, network, cookie_file_name)?; + let cookie_contents = fs::read_to_string(cookie_path).or(Err(()))?; + parse_rpc_auth(&cookie_contents) +} + +fn get_rpc_auth_from_env_vars() -> Result<(String, String), ()> { + if let (Ok(username), Ok(password)) = + (env::var(BITCOIND_RPC_USER_KEY), env::var(BITCOIND_RPC_PASSWORD_KEY)) + { + Ok((username, password)) + } else { + Err(()) + } +} + +fn get_rpc_auth_from_env_file(env_file_name: Option<&str>) -> Result<(String, String), ()> { + let env_file_map = parse_env_file(env_file_name)?; + if let (Some(username), Some(password)) = + (env_file_map.get(BITCOIND_RPC_USER_KEY), env_file_map.get(BITCOIND_RPC_PASSWORD_KEY)) + { + Ok((username.to_string(), password.to_string())) + } else { + Err(()) + } +} + +fn parse_env_file(env_file_name: Option<&str>) -> Result, ()> { + // Default .env file name is .env + let env_file_name = match env_file_name { + Some(filename) => filename, + None => ".env", + }; + + // Read .env file + let env_file_path = Path::new(env_file_name); + let env_file_contents = fs::read_to_string(env_file_path).or(Err(()))?; + + // Collect key-value pairs from .env file into a map + let mut env_file_map: HashMap = HashMap::new(); + for line in env_file_contents.lines() { + let line_parts: Vec<&str> = line.splitn(2, '=').collect(); + if line_parts.len() != 2 { + println!("ERROR: bad .env file format"); + return Err(()); + } + env_file_map.insert(line_parts[0].to_string(), line_parts[1].to_string()); + } + + Ok(env_file_map) +} + +#[cfg(test)] +mod rpc_auth_tests { + use super::*; + + const TEST_ENV_FILE: &str = "test_data/test_env_file"; + const TEST_ENV_FILE_BAD: &str = "test_data/test_env_file_bad"; + const TEST_ABSENT_FILE: &str = "nonexistent_file"; + const TEST_DATA_DIR: &str = "test_data"; + const TEST_COOKIE: &str = "test_cookie"; + const TEST_COOKIE_BAD: &str = "test_cookie_bad"; + const EXPECTED_USER: &str = "testuser"; + const EXPECTED_PASSWORD: &str = "testpassword"; + + #[test] + fn test_parse_rpc_auth_success() { + let (username, password) = parse_rpc_auth("testuser:testpassword").unwrap(); + assert_eq!(username, EXPECTED_USER); + assert_eq!(password, EXPECTED_PASSWORD); + } + + #[test] + fn test_parse_rpc_auth_fail() { + let result = parse_rpc_auth("testuser"); + assert!(result.is_err()); + } + + #[test] + fn test_get_cookie_path_success() { + let test_cases = vec![ + ( + None, + None, + None, + env::home_dir().unwrap().join(DEFAULT_BITCOIN_DATADIR).join(".cookie"), + ), + ( + Some((TEST_DATA_DIR, true)), + Some(Network::Testnet), + None, + env::home_dir().unwrap().join(TEST_DATA_DIR).join("testnet").join(".cookie"), + ), + ( + Some((TEST_DATA_DIR, false)), + Some(Network::Regtest), + Some(TEST_COOKIE), + PathBuf::from(TEST_DATA_DIR).join("regtest").join(TEST_COOKIE), + ), + ( + Some((TEST_DATA_DIR, false)), + Some(Network::Signet), + None, + PathBuf::from(TEST_DATA_DIR).join("signet").join(".cookie"), + ), + ( + Some((TEST_DATA_DIR, false)), + Some(Network::Bitcoin), + None, + PathBuf::from(TEST_DATA_DIR).join(".cookie"), + ), + ]; + + for (data_dir, network, cookie_file, expected_path) in test_cases { + let path = get_cookie_path(data_dir, network, cookie_file).unwrap(); + assert_eq!(path, expected_path); + } + } + + #[test] + fn test_get_rpc_auth_from_cookie_success() { + let (username, password) = get_rpc_auth_from_cookie( + Some((TEST_DATA_DIR, false)), + Some(Network::Bitcoin), + Some(TEST_COOKIE), + ) + .unwrap(); + assert_eq!(username, EXPECTED_USER); + assert_eq!(password, EXPECTED_PASSWORD); + } + + #[test] + fn test_get_rpc_auth_from_cookie_fail() { + let result = get_rpc_auth_from_cookie( + Some((TEST_DATA_DIR, false)), + Some(Network::Bitcoin), + Some(TEST_COOKIE_BAD), + ); + assert!(result.is_err()); + } + + #[test] + fn test_parse_env_file_success() { + let env_file_map = parse_env_file(Some(TEST_ENV_FILE)).unwrap(); + assert_eq!(env_file_map.get(BITCOIND_RPC_USER_KEY).unwrap(), EXPECTED_USER); + assert_eq!(env_file_map.get(BITCOIND_RPC_PASSWORD_KEY).unwrap(), EXPECTED_PASSWORD); + } + + #[test] + fn test_parse_env_file_fail() { + let env_file_map = parse_env_file(Some(TEST_ENV_FILE_BAD)); + assert!(env_file_map.is_err()); + + // Make sure the test file doesn't exist + assert!(!Path::new(TEST_ABSENT_FILE).exists()); + let env_file_map = parse_env_file(Some(TEST_ABSENT_FILE)); + assert!(env_file_map.is_err()); + } + + #[test] + fn test_get_rpc_auth_from_env_file_success() { + let (username, password) = get_rpc_auth_from_env_file(Some(TEST_ENV_FILE)).unwrap(); + assert_eq!(username, EXPECTED_USER); + assert_eq!(password, EXPECTED_PASSWORD); + } + + #[test] + fn test_get_rpc_auth_from_env_file_fail() { + let rpc_user_and_password = get_rpc_auth_from_env_file(Some(TEST_ABSENT_FILE)); + assert!(rpc_user_and_password.is_err()); + } + + #[test] + fn test_get_rpc_auth_from_env_vars_success() { + env::set_var(BITCOIND_RPC_USER_KEY, EXPECTED_USER); + env::set_var(BITCOIND_RPC_PASSWORD_KEY, EXPECTED_PASSWORD); + let (username, password) = get_rpc_auth_from_env_vars().unwrap(); + assert_eq!(username, EXPECTED_USER); + assert_eq!(password, EXPECTED_PASSWORD); + } +} diff --git a/src/cli.rs b/src/cli.rs index 64ee04d..030109e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,7 +21,7 @@ use lightning_invoice::{utils, Currency, Invoice}; use std::env; use std::io; use std::io::Write; -use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; +use std::net::{SocketAddr, ToSocketAddrs}; use std::ops::Deref; use std::path::Path; use std::str::FromStr; @@ -40,107 +40,6 @@ pub(crate) struct LdkUserInfo { pub(crate) network: Network, } -pub(crate) fn parse_startup_args() -> Result { - if env::args().len() < 3 { - println!("ldk-tutorial-node requires 3 arguments: `cargo run :@: ldk_storage_directory_path [] [bitcoin-network] [announced-node-name announced-listen-addr*]`"); - return Err(()); - } - let bitcoind_rpc_info = env::args().skip(1).next().unwrap(); - let bitcoind_rpc_info_parts: Vec<&str> = bitcoind_rpc_info.rsplitn(2, "@").collect(); - if bitcoind_rpc_info_parts.len() != 2 { - println!("ERROR: bad bitcoind RPC URL provided"); - return Err(()); - } - let rpc_user_and_password: Vec<&str> = bitcoind_rpc_info_parts[1].split(":").collect(); - if rpc_user_and_password.len() != 2 { - println!("ERROR: bad bitcoind RPC username/password combo provided"); - return Err(()); - } - let bitcoind_rpc_username = rpc_user_and_password[0].to_string(); - let bitcoind_rpc_password = rpc_user_and_password[1].to_string(); - let bitcoind_rpc_path: Vec<&str> = bitcoind_rpc_info_parts[0].split(":").collect(); - if bitcoind_rpc_path.len() != 2 { - println!("ERROR: bad bitcoind RPC path provided"); - return Err(()); - } - let bitcoind_rpc_host = bitcoind_rpc_path[0].to_string(); - let bitcoind_rpc_port = bitcoind_rpc_path[1].parse::().unwrap(); - - let ldk_storage_dir_path = env::args().skip(2).next().unwrap(); - - let mut ldk_peer_port_set = true; - let ldk_peer_listening_port: u16 = match env::args().skip(3).next().map(|p| p.parse()) { - Some(Ok(p)) => p, - Some(Err(_)) => { - ldk_peer_port_set = false; - 9735 - } - None => { - ldk_peer_port_set = false; - 9735 - } - }; - - let mut arg_idx = match ldk_peer_port_set { - true => 4, - false => 3, - }; - let network: Network = match env::args().skip(arg_idx).next().as_ref().map(String::as_str) { - Some("testnet") => Network::Testnet, - Some("regtest") => Network::Regtest, - Some("signet") => Network::Signet, - Some(net) => { - panic!("Unsupported network provided. Options are: `regtest`, `testnet`, and `signet`. Got {}", net); - } - None => Network::Testnet, - }; - - let ldk_announced_node_name = match env::args().skip(arg_idx + 1).next().as_ref() { - Some(s) => { - if s.len() > 32 { - panic!("Node Alias can not be longer than 32 bytes"); - } - arg_idx += 1; - let mut bytes = [0; 32]; - bytes[..s.len()].copy_from_slice(s.as_bytes()); - bytes - } - None => [0; 32], - }; - - let mut ldk_announced_listen_addr = Vec::new(); - loop { - match env::args().skip(arg_idx + 1).next().as_ref() { - Some(s) => match IpAddr::from_str(s) { - Ok(IpAddr::V4(a)) => { - ldk_announced_listen_addr - .push(NetAddress::IPv4 { addr: a.octets(), port: ldk_peer_listening_port }); - arg_idx += 1; - } - Ok(IpAddr::V6(a)) => { - ldk_announced_listen_addr - .push(NetAddress::IPv6 { addr: a.octets(), port: ldk_peer_listening_port }); - arg_idx += 1; - } - Err(_) => panic!("Failed to parse announced-listen-addr into an IP address"), - }, - None => break, - } - } - - Ok(LdkUserInfo { - bitcoind_rpc_username, - bitcoind_rpc_password, - bitcoind_rpc_host, - bitcoind_rpc_port, - ldk_storage_dir_path, - ldk_peer_listening_port, - ldk_announced_listen_addr, - ldk_announced_node_name, - network, - }) -} - struct UserOnionMessageContents { tlv_type: u64, data: Vec, diff --git a/src/main.rs b/src/main.rs index 4314ac8..63459ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod args; pub mod bitcoind_client; mod cli; mod convert; @@ -17,7 +18,7 @@ use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Recipient}; use lightning::chain::{chainmonitor, ChannelMonitorUpdateStatus}; -use lightning::chain::{BestBlock, Filter, Watch}; +use lightning::chain::{Filter, Watch}; use lightning::ln::channelmanager; use lightning::ln::channelmanager::{ ChainParameters, ChannelManagerReadArgs, SimpleArcChannelManager, @@ -358,7 +359,7 @@ async fn handle_ldk_events( } async fn start_ldk() { - let args = match cli::parse_startup_args() { + let args = match args::parse_startup_args() { Ok(user_args) => user_args, Err(()) => return, }; @@ -458,7 +459,12 @@ async fn start_ldk() { // Step 7: Read ChannelMonitor state from disk let mut channelmonitors = persister.read_channelmonitors(keys_manager.clone()).unwrap(); - // Step 8: Initialize the ChannelManager + // Step 8: Poll for the best chain tip, which may be used by the channel manager & spv client + let polled_chain_tip = init::validate_best_block_header(bitcoind_client.as_ref()) + .await + .expect("Failed to fetch best block header and best block"); + + // Step 9: Initialize the ChannelManager let mut user_config = UserConfig::default(); user_config.channel_handshake_limits.force_announced_channel_preference = false; let mut restarting_node = true; @@ -481,15 +487,11 @@ async fn start_ldk() { } else { // We're starting a fresh node. restarting_node = false; - let getinfo_resp = bitcoind_client.get_blockchain_info().await; - - let chain_params = ChainParameters { - network: args.network, - best_block: BestBlock::new( - getinfo_resp.latest_blockhash, - getinfo_resp.latest_height as u32, - ), - }; + + let polled_best_block = polled_chain_tip.to_best_block(); + let polled_best_block_hash = polled_best_block.block_hash(); + let chain_params = + ChainParameters { network: args.network, best_block: polled_best_block }; let fresh_channel_manager = channelmanager::ChannelManager::new( fee_estimator.clone(), chain_monitor.clone(), @@ -499,15 +501,14 @@ async fn start_ldk() { user_config, chain_params, ); - (getinfo_resp.latest_blockhash, fresh_channel_manager) + (polled_best_block_hash, fresh_channel_manager) } }; - // Step 9: Sync ChannelMonitors and ChannelManager to chain tip + // Step 10: Sync ChannelMonitors and ChannelManager to chain tip let mut chain_listener_channel_monitors = Vec::new(); let mut cache = UnboundedCache::new(); - let mut chain_tip: Option = None; - if restarting_node { + let chain_tip = if restarting_node { let mut chain_listeners = vec![( channel_manager_blockhash, &channel_manager as &(dyn chain::Listen + Send + Sync), @@ -528,19 +529,20 @@ async fn start_ldk() { &monitor_listener_info.1 as &(dyn chain::Listen + Send + Sync), )); } - chain_tip = Some( - init::synchronize_listeners( - bitcoind_client.as_ref(), - args.network, - &mut cache, - chain_listeners, - ) - .await - .unwrap(), - ); - } - // Step 10: Give ChannelMonitors to ChainMonitor + init::synchronize_listeners( + bitcoind_client.as_ref(), + args.network, + &mut cache, + chain_listeners, + ) + .await + .unwrap() + } else { + polled_chain_tip + }; + + // Step 11: Give ChannelMonitors to ChainMonitor for item in chain_listener_channel_monitors.drain(..) { let channel_monitor = item.1 .0; let funding_outpoint = item.2; @@ -550,7 +552,7 @@ async fn start_ldk() { ); } - // Step 11: Optional: Initialize the P2PGossipSync + // Step 12: Optional: Initialize the P2PGossipSync let genesis = genesis_block(args.network).header.block_hash(); let network_graph_path = format!("{}/network_graph", ldk_data_dir.clone()); let network_graph = @@ -561,7 +563,7 @@ async fn start_ldk() { logger.clone(), )); - // Step 12: Initialize the PeerManager + // Step 13: Initialize the PeerManager let channel_manager: Arc = Arc::new(channel_manager); let onion_messenger: Arc = Arc::new(OnionMessenger::new( Arc::clone(&keys_manager), @@ -586,7 +588,7 @@ async fn start_ldk() { )); // ## Running LDK - // Step 13: Initialize networking + // Step 14: Initialize networking let peer_manager_connection_handler = peer_manager.clone(); let listening_port = args.ldk_peer_listening_port; @@ -612,10 +614,7 @@ async fn start_ldk() { } }); - // Step 14: Connect and Disconnect Blocks - if chain_tip.is_none() { - chain_tip = Some(init::validate_best_block_header(bitcoind_client.as_ref()).await.unwrap()); - } + // Step 15: Connect and Disconnect Blocks let channel_manager_listener = channel_manager.clone(); let chain_monitor_listener = chain_monitor.clone(); let bitcoind_block_source = bitcoind_client.clone(); @@ -623,15 +622,14 @@ async fn start_ldk() { tokio::spawn(async move { let chain_poller = poll::ChainPoller::new(bitcoind_block_source.as_ref(), network); let chain_listener = (chain_monitor_listener, channel_manager_listener); - let mut spv_client = - SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, &chain_listener); + let mut spv_client = SpvClient::new(chain_tip, chain_poller, &mut cache, &chain_listener); loop { spv_client.poll_best_tip().await.unwrap(); tokio::time::sleep(Duration::from_secs(1)).await; } }); - // Step 15: Handle LDK Events + // Step 16: Handle LDK Events let channel_manager_event_listener = channel_manager.clone(); let keys_manager_listener = keys_manager.clone(); // TODO: persist payment info to disk @@ -656,7 +654,7 @@ async fn start_ldk() { )); }; - // Step 16: Initialize routing ProbabilisticScorer + // Step 17: Initialize routing ProbabilisticScorer let scorer_path = format!("{}/scorer", ldk_data_dir.clone()); let scorer = Arc::new(Mutex::new(disk::read_scorer( Path::new(&scorer_path), @@ -664,7 +662,7 @@ async fn start_ldk() { Arc::clone(&logger), ))); - // Step 17: Create InvoicePayer + // Step 18: Create InvoicePayer let router = DefaultRouter::new( network_graph.clone(), logger.clone(), @@ -679,10 +677,10 @@ async fn start_ldk() { payment::Retry::Timeout(Duration::from_secs(10)), )); - // Step 18: Persist ChannelManager and NetworkGraph + // Step 19: Persist ChannelManager and NetworkGraph let persister = Arc::new(FilesystemPersister::new(ldk_data_dir.clone())); - // Step 19: Background Processing + // Step 20: Background Processing let background_processor = BackgroundProcessor::start( persister, invoice_payer.clone(), diff --git a/test_data/test_cookie b/test_data/test_cookie new file mode 100644 index 0000000..3e5bffe --- /dev/null +++ b/test_data/test_cookie @@ -0,0 +1 @@ +testuser:testpassword \ No newline at end of file diff --git a/test_data/test_cookie_bad b/test_data/test_cookie_bad new file mode 100644 index 0000000..cc8c9d3 --- /dev/null +++ b/test_data/test_cookie_bad @@ -0,0 +1 @@ +testuser \ No newline at end of file diff --git a/test_data/test_env_file b/test_data/test_env_file new file mode 100644 index 0000000..b56dce1 --- /dev/null +++ b/test_data/test_env_file @@ -0,0 +1,2 @@ +RPC_USER=testuser +RPC_PASSWORD=testpassword \ No newline at end of file diff --git a/test_data/test_env_file_bad b/test_data/test_env_file_bad new file mode 100644 index 0000000..25ff0c3 --- /dev/null +++ b/test_data/test_env_file_bad @@ -0,0 +1,2 @@ +RPC_USER=testuser +RPC_PASSWORD \ No newline at end of file