diff --git a/Cargo.lock b/Cargo.lock index 472ff3c3..bbc676a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,7 +328,9 @@ checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", "windows-targets", ] @@ -973,6 +975,7 @@ dependencies = [ "bitcoin 0.29.2", "bitcoind", "bytes", + "chrono", "configure_me", "configure_me_codegen", "core-rpc", @@ -1975,8 +1978,7 @@ dependencies = [ [[package]] name = "tonic_lnd" version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a207832efa21cc12bd0d520ce36554af91f5bbcc8873273bc1bab238b3365dbb" +source = "git+https://github.com/Kixunil/tonic_lnd?rev=fac4a67a8d4951d62fc020d61d38628c0064e6df#fac4a67a8d4951d62fc020d61d38628c0064e6df" dependencies = [ "hex 0.4.3", "prost 0.9.0", diff --git a/Cargo.toml b/Cargo.toml index bf9f5bed..ecd49e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ log = "0.4.17" simple_logger = "4.0.0" tokio = { version = "1.25.0", features = ["rt", "rt-multi-thread"] } tonic = "0.8.3" -tonic_lnd = "0.5.1" +tonic_lnd = { git = "https://github.com/Kixunil/tonic_lnd", rev = "fac4a67a8d4951d62fc020d61d38628c0064e6df" } hex = "0.4.3" configure_me = "0.4.0" bytes = "1.4.0" @@ -27,6 +27,7 @@ bytes = "1.4.0" [dev-dependencies] bitcoincore-rpc = { package="core-rpc", version = "0.17.0" } bitcoind = { version = "0.30.0", features = [ "22_0" ] } +chrono = { version = "0.4.26" } ldk-sample = { git = "https://github.com/lndk-org/ldk-sample" } mockall = "0.11.3" tempfile = "3.5.0" diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d896fc63..8c7290e5 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,17 +1,37 @@ +mod test_utils; + use bitcoin::network::constants::Network; +use bitcoin::secp256k1::PublicKey; use bitcoincore_rpc::{bitcoin::Network as BitcoindNetwork, json, RpcApi}; -use bitcoind::{get_available_port, BitcoinD, Conf}; +use bitcoind::{get_available_port, BitcoinD, Conf, ConnectParams}; +use chrono::Utc; use ldk_sample::config::LdkUserInfo; use ldk_sample::node_api::Node as LdkNode; +use std::net::SocketAddr; use std::path::PathBuf; +use std::process::{Child, Command, Stdio}; +use std::thread; use std::{env, fs}; -use tempfile::{tempdir, TempDir}; +use tempfile::{tempdir, Builder, TempDir}; +use tokio::time::Duration; +use tonic_lnd::lnrpc::GetInfoRequest; +use tonic_lnd::Client; const LNDK_TESTS_FOLDER: &str = "lndk-tests"; -pub async fn setup_test_infrastructure(test_name: &str) -> (BitcoindNode, LdkNode, LdkNode) { +pub async fn setup_test_infrastructure( + test_name: &str, +) -> (BitcoindNode, LndNode, LdkNode, LdkNode) { let bitcoind = setup_bitcoind().await; - let (ldk_test_dir, _lnd_test_dir) = setup_test_dirs(); + let (ldk_test_dir, lnd_test_dir) = setup_test_dirs(); + let mut lnd = LndNode::new( + bitcoind.node.params.clone(), + bitcoind.zmq_block_port, + bitcoind.zmq_tx_port, + lnd_test_dir, + test_name.clone(), + ); + lnd.setup_client().await; let connect_params = bitcoind.node.params.get_cookie_values().unwrap(); @@ -42,7 +62,7 @@ pub async fn setup_test_infrastructure(test_name: &str) -> (BitcoindNode, LdkNod let ldk1 = ldk_sample::start_ldk(ldk1_config, test_name).await; let ldk2 = ldk_sample::start_ldk(ldk2_config, test_name).await; - (bitcoind, ldk1, ldk2) + (bitcoind, lnd, ldk1, ldk2) } // Sets up /tmp/lndk-tests folder where we'll store the bins, data directories, and logs needed @@ -76,8 +96,8 @@ fn setup_test_dirs() -> (PathBuf, PathBuf) { pub struct BitcoindNode { node: BitcoinD, _data_dir: TempDir, - _zmq_block_port: u16, - _zmq_tx_port: u16, + zmq_block_port: u16, + zmq_tx_port: u16, } pub async fn setup_bitcoind() -> BitcoindNode { @@ -104,7 +124,188 @@ pub async fn setup_bitcoind() -> BitcoindNode { BitcoindNode { node: bitcoind, _data_dir: data_dir, - _zmq_block_port: zmq_block_port, - _zmq_tx_port: zmq_tx_port, + zmq_block_port, + zmq_tx_port, + } +} + +// LndNode holds the tools we need to interact with a Lightning node. +pub struct LndNode { + address: String, + _lnd_dir_tmp: TempDir, + cert_path: String, + macaroon_path: String, + _handle: Child, + client: Option, +} + +impl LndNode { + fn new( + bitcoind_connect_params: ConnectParams, + zmq_block_port: u16, + zmq_tx_port: u16, + lnd_data_dir: PathBuf, + test_name: &str, + ) -> LndNode { + let lnd_exe_dir = env::temp_dir().join("lndk-tests").join("bin"); + env::set_current_dir(lnd_exe_dir).expect("couldn't set current directory"); + + let lnd_dir_binding = Builder::new() + .prefix("lnd-data-") + .tempdir_in(lnd_data_dir.clone()) + .unwrap(); + let lnd_dir = lnd_dir_binding.path(); + + let macaroon_path = lnd_dir + .join("data/chain/bitcoin/regtest/admin.macaroon") + .to_str() + .unwrap() + .to_string(); + + let connect_params = bitcoind_connect_params.get_cookie_values().unwrap(); + let now_timestamp = Utc::now(); + let timestamp = now_timestamp.format("%d-%m-%Y-%H%M"); + let log_dir_path_buf = lnd_data_dir.join(format!("lnd-logs-{test_name}-{timestamp}")); + let log_dir = log_dir_path_buf.as_path(); + let data_dir = lnd_dir.join("data").to_str().unwrap().to_string(); + let cert_path = lnd_dir.to_str().unwrap().to_string() + "/tls.cert"; + let key_path = lnd_dir.to_str().unwrap().to_string() + "/tls.key"; + + // Have node run on a randomly assigned grpc port. That way, if we run more than one lnd node, they won't + // clash. + let port = bitcoind::get_available_port().unwrap(); + let rpc_addr = format!("localhost:{}", port); + let args = [ + format!("--rpclisten={}", rpc_addr), + format!("--norest"), + // With this flag, we don't have to unlock the wallet on startup. + format!("--noseedbackup"), + format!("--bitcoin.active"), + format!("--bitcoin.node=bitcoind"), + format!("--bitcoin.regtest"), + format!("--datadir={}", data_dir), + format!("--tlscertpath={}", cert_path), + format!("--tlskeypath={}", key_path), + format!("--logdir={}", log_dir.display()), + format!("--bitcoind.rpcuser={}", connect_params.0.unwrap()), + format!("--bitcoind.rpcpass={}", connect_params.1.unwrap()), + format!( + "--bitcoind.zmqpubrawblock=tcp://127.0.0.1:{}", + zmq_block_port + ), + format!("--bitcoind.zmqpubrawtx=tcp://127.0.0.1:{}", zmq_tx_port), + format!( + "--bitcoind.rpchost={:?}", + bitcoind_connect_params.rpc_socket + ), + ]; + + // TODO: For Windows we might need to add ".exe" at the end. + let cmd = Command::new("./lnd-itest") + .args(args) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to execute lnd command"); + + LndNode { + address: format!("https://{}", rpc_addr), + _lnd_dir_tmp: lnd_dir_binding, + cert_path: cert_path, + macaroon_path: macaroon_path, + _handle: cmd, + client: None, + } + } + + // Setup the client we need to interact with the LND node. + async fn setup_client(&mut self) { + // We need to give lnd some time to start up before we'll be able to interact with it via the client. + let mut retry = false; + let mut retry_num = 0; + while retry_num == 0 || retry { + thread::sleep(Duration::from_secs(2)); + + let client_result = tonic_lnd::connect( + self.address.clone(), + self.cert_path.clone(), + self.macaroon_path.clone(), + ) + .await; + + match client_result { + Ok(client) => { + self.client = Some(client); + + retry = false; + retry_num += 1; + } + Err(err) => { + println!( + "getting client error {err}, retrying call {} time", + retry_num + ); + if retry_num == 3 { + panic!("could not set up client: {err}") + } + retry = true; + retry_num += 1; + } + } + } + } + + #[allow(dead_code)] + pub async fn get_info(&mut self) -> tonic_lnd::lnrpc::GetInfoResponse { + let resp = if let Some(client) = self.client.clone() { + let get_info_req = GetInfoRequest {}; + let make_request = || async { + client + .clone() + .lightning() + .get_info(get_info_req.clone()) + .await + }; + let resp = test_utils::retry_async(make_request, String::from("get_info")); + resp.await.unwrap() + } else { + panic!("No client") + }; + + resp + } + + // connect_to_peer connects to the specified peer. + pub async fn connect_to_peer( + &mut self, + node_id: PublicKey, + addr: SocketAddr, + ) -> tonic_lnd::lnrpc::ConnectPeerResponse { + let ln_addr = tonic_lnd::lnrpc::LightningAddress { + pubkey: node_id.to_string(), + host: addr.to_string(), + }; + + let connect_req = tonic_lnd::lnrpc::ConnectPeerRequest { + addr: Some(ln_addr), + timeout: 20, + ..Default::default() + }; + + let resp = if let Some(client) = self.client.clone() { + let make_request = || async { + client + .clone() + .lightning() + .connect_peer(connect_req.clone()) + .await + }; + let resp = test_utils::retry_async(make_request, String::from("connect_peer")); + resp.await.unwrap() + } else { + panic!("No client") + }; + + resp } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 0a041eaa..5cf85425 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3,7 +3,7 @@ mod common; #[tokio::test(flavor = "multi_thread")] async fn test_ldk_send_onion_message() { let test_name = "send_onion_message"; - let (_bitcoind, ldk1, ldk2) = common::setup_test_infrastructure(test_name).await; + let (_bitcoind, _lnd, ldk1, ldk2) = common::setup_test_infrastructure(test_name).await; let (node_id_2, node_addr_2) = ldk2.get_node_info(); ldk1.connect_to_peer(node_id_2, node_addr_2).await.unwrap(); @@ -11,3 +11,11 @@ async fn test_ldk_send_onion_message() { let res = ldk1.send_onion_message(vec![node_id_2], 65, data).await; assert!(res.is_ok()); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_ldk_lnd_connect() { + let test_name = "ldk_lnd_connect"; + let (_bitcoind, mut lnd, ldk1, _ldk2) = common::setup_test_infrastructure(test_name).await; + let (pubkey, addr) = ldk1.get_node_info(); + lnd.connect_to_peer(pubkey, addr).await; +}