Merge pull request #85 from ch1ru/main
authorvalentinewallace <valentinewallace@users.noreply.github.com>
Thu, 5 Jan 2023 17:14:43 +0000 (12:14 -0500)
committerGitHub <noreply@github.com>
Thu, 5 Jan 2023 17:14:43 +0000 (12:14 -0500)
Add `disconnectpeer` subcommand

.gitignore
Cargo.lock
src/args.rs [new file with mode: 0644]
src/cli.rs
src/main.rs
test_data/test_cookie [new file with mode: 0644]
test_data/test_cookie_bad [new file with mode: 0644]
test_data/test_env_file [new file with mode: 0644]
test_data/test_env_file_bad [new file with mode: 0644]

index bf68587d855fbcf7926fa4b8a739b27af82765b7..29b24ef23305ed1c71fbbab55d3616a633d71f34 100644 (file)
@@ -9,3 +9,6 @@ Cargo.lock
 # These are backup files generated by rustfmt
 **/*.rs.bk
 .ldk
+
+# RPC auth
+.env
index 66a7e11303bbdbe4bc91830c7c598aea148598b6..fd00a3add685e438b18062ce79043c09d89b5ce5 100644 (file)
@@ -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 (file)
index 0000000..deb1704
--- /dev/null
@@ -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<LdkUserInfo, ()> {
+       if env::args().len() < 3 {
+               println!("ldk-tutorial-node requires 3 arguments: `cargo run [<bitcoind-rpc-username>:<bitcoind-rpc-password>@]<bitcoind-rpc-host>:<bitcoind-rpc-port> ldk_storage_directory_path [<ldk-incoming-peer-listening-port>] [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::<u16>().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: \
+               <bitcoind-rpc-username>:<bitcoind-rpc-password>@<bitcoind-rpc-host>:<bitcoind-rpc-port>"
+       );
+       println!("2. Provide <bitcoind-rpc-username>:<bitcoind-rpc-password> 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<Network>, cookie_file_name: Option<&str>,
+) -> Result<PathBuf, ()> {
+       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<Network>, 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<HashMap<String, String>, ()> {
+       // 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<String, String> = 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);
+       }
+}
index 64ee04df71874ee24bfbd356f37ceb0ea0a85322..030109ecf792d353cf41b6adae34f58d3a05f946 100644 (file)
@@ -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<LdkUserInfo, ()> {
-       if env::args().len() < 3 {
-               println!("ldk-tutorial-node requires 3 arguments: `cargo run <bitcoind-rpc-username>:<bitcoind-rpc-password>@<bitcoind-rpc-host>:<bitcoind-rpc-port> ldk_storage_directory_path [<ldk-incoming-peer-listening-port>] [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::<u16>().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<u8>,
index 4314ac87131dc05ee92f56b51ebfc36e5f3336f2..63459ed99f32332bc4373ece8fb31dfba99332ec 100644 (file)
@@ -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<poll::ValidatedBlockHeader> = 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<ChannelManager> = Arc::new(channel_manager);
        let onion_messenger: Arc<OnionMessenger> = 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 (file)
index 0000000..3e5bffe
--- /dev/null
@@ -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 (file)
index 0000000..cc8c9d3
--- /dev/null
@@ -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 (file)
index 0000000..b56dce1
--- /dev/null
@@ -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 (file)
index 0000000..25ff0c3
--- /dev/null
@@ -0,0 +1,2 @@
+RPC_USER=testuser
+RPC_PASSWORD
\ No newline at end of file