From 7b5dea14d6f892d954db588edf850539325e5011 Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 8 Dec 2023 14:48:03 -0600 Subject: [PATCH 1/8] Fix run tests Makefile typo We fix a typo in the Makefile so it only runs the integration tests instead of all the tests (unit and integration). --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 51d4d7db..aa669da7 100644 --- a/Makefile +++ b/Makefile @@ -14,5 +14,5 @@ itest: @$(call print, "Building lnd for itests.") git submodule update --init --recursive cd lnd/cmd/lnd; $(GO_BUILD) -tags="peersrpc signrpc walletrpc dev" -o $(TMP_DIR)/lndk-tests/bin/lnd-itest$(EXEC_SUFFIX) - $(CARGO_TEST) -- -- test '*' --test-threads=1 --nocapture + $(CARGO_TEST) --test '*' -- --test-threads=1 --nocapture From dd65d2966dadd5164661bf5e6c05fa1d55be6e80 Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 8 Dec 2023 14:53:09 -0600 Subject: [PATCH 2/8] Export MessengerUtilities and LndNodeSigner for cli and tests --- src/lnd.rs | 4 ++-- src/onion_messenger.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lnd.rs b/src/lnd.rs index f5b8c2a4..b5bcb6b0 100644 --- a/src/lnd.rs +++ b/src/lnd.rs @@ -53,14 +53,14 @@ pub(crate) fn features_support_onion_messages( } /// LndNodeSigner provides signing operations using LND's signer subserver. -pub(crate) struct LndNodeSigner<'a> { +pub struct LndNodeSigner<'a> { pubkey: PublicKey, secp_ctx: Secp256k1, signer: RefCell<&'a mut tonic_lnd::SignerClient>, } impl<'a> LndNodeSigner<'a> { - pub(crate) fn new(pubkey: PublicKey, signer: &'a mut tonic_lnd::SignerClient) -> Self { + pub fn new(pubkey: PublicKey, signer: &'a mut tonic_lnd::SignerClient) -> Self { LndNodeSigner { pubkey, secp_ctx: Secp256k1::new(), diff --git a/src/onion_messenger.rs b/src/onion_messenger.rs index 3e9eb374..942e3a56 100644 --- a/src/onion_messenger.rs +++ b/src/onion_messenger.rs @@ -51,12 +51,12 @@ const DEFAULT_CALL_FREQUENCY: Duration = Duration::from_secs(1); /// A refcell is used for entropy_source to provide interior mutability for ChaCha20Rng. We need a mutable reference /// to be able to use the chacha library’s fill_bytes method, but the EntropySource interface in LDK is for an /// immutable reference. -pub(crate) struct MessengerUtilities { +pub struct MessengerUtilities { entropy_source: RefCell, } impl MessengerUtilities { - pub(crate) fn new() -> Self { + pub fn new() -> Self { MessengerUtilities { entropy_source: RefCell::new(ChaCha20Rng::from_entropy()), } From 88b20956cc6456ddb584b07ba8814ec8f176ba95 Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 8 Dec 2023 15:35:43 -0600 Subject: [PATCH 3/8] Add offers handler to handle invoice response --- src/onion_messenger.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/onion_messenger.rs b/src/onion_messenger.rs index 942e3a56..31db5b72 100644 --- a/src/onion_messenger.rs +++ b/src/onion_messenger.rs @@ -8,8 +8,10 @@ use bitcoin::secp256k1::PublicKey; use core::ops::Deref; use lightning::ln::features::InitFeatures; use lightning::ln::msgs::{Init, OnionMessage, OnionMessageHandler}; +use lightning::offers::invoice::Bolt12Invoice; use lightning::onion_message::{ - CustomOnionMessageHandler, MessageRouter, OffersMessageHandler, OnionMessenger, + CustomOnionMessageHandler, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessenger, + PendingOnionMessage, }; use lightning::sign::EntropySource; use lightning::sign::NodeSigner; @@ -19,12 +21,13 @@ use log::{debug, error, info, trace, warn}; use rand_chacha::ChaCha20Rng; use rand_core::{RngCore, SeedableRng}; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::error::Error; use std::fmt; use std::io::Cursor; use std::marker::Copy; use std::str::FromStr; +use std::sync::{Arc, Mutex}; use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::{select, time, time::Duration, time::Interval}; use tonic_lnd::{ @@ -699,6 +702,32 @@ pub(crate) async fn relay_outgoing_msg_event( } } +// A simple offers message handler. +#[derive(Clone)] +pub struct InvoiceHandler { + pub invoices: Arc>>, +} + +impl OffersMessageHandler for InvoiceHandler { + fn handle_message(&self, message: OffersMessage) -> Option { + match message { + OffersMessage::Invoice(invoice) => { + println!("Received an invoice offers message."); + self.invoices.lock().unwrap().push_back(invoice); + None + } + _ => { + println!("Received an unsupported offers message."); + None + } + } + } + + fn release_pending_messages(&self) -> Vec> { + vec![] + } +} + #[cfg(test)] mod tests { use super::*; From a3d2eb9a63789683e6ccc7be716f09c6cc816be0 Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 8 Dec 2023 22:43:32 -0600 Subject: [PATCH 4/8] Move onion_messenger outside request_invoice function --- src/lndk_offers.rs | 34 +++++++++------------------------- tests/integration_tests.rs | 25 +++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/lndk_offers.rs b/src/lndk_offers.rs index 4c4644e7..d68dc0b2 100644 --- a/src/lndk_offers.rs +++ b/src/lndk_offers.rs @@ -1,7 +1,5 @@ -use crate::lnd::{LndNodeSigner, MessageSigner, ONION_MESSAGES_OPTIONAL}; -use crate::onion_messenger::{ - lookup_onion_support, relay_outgoing_msg_event, MessengerUtilities, SendCustomMessage, -}; +use crate::lnd::{MessageSigner, ONION_MESSAGES_OPTIONAL}; +use crate::onion_messenger::{lookup_onion_support, relay_outgoing_msg_event, SendCustomMessage}; use async_trait::async_trait; use bitcoin::blockdata::constants::ChainHash; use bitcoin::hashes::sha256::Hash; @@ -13,14 +11,13 @@ use futures::executor::block_on; use lightning::blinded_path::BlindedPath; use lightning::ln::features::InitFeatures; use lightning::ln::msgs::{Init, OnionMessageHandler}; -use lightning::ln::peer_handler::IgnoringMessageHandler; use lightning::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; use lightning::offers::merkle::SignError; use lightning::offers::offer::Offer; use lightning::offers::parse::{Bolt12ParseError, Bolt12SemanticError}; use lightning::onion_message::{ - CustomOnionMessageHandler, DefaultMessageRouter, Destination, MessageRouter, OffersMessage, - OffersMessageHandler, OnionMessagePath, OnionMessenger, SendError, + CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, + OnionMessagePath, OnionMessenger, SendError, }; use lightning::sign::EntropySource; use lightning::sign::NodeSigner; @@ -64,28 +61,15 @@ pub fn decode(offer_str: String) -> Result { } // request_invoice builds an invoice request and tries to send it to the offer maker. -pub async fn request_invoice( +pub async fn request_invoice( client: Client, + onion_messenger: &OMH, custom_msg_client: impl SendCustomMessage, - pubkey: PublicKey, offer: Offer, introduction_node: PublicKey, blinded_path: BlindedPath, ) -> Result<(), OfferError> { - let mut signer_clone = client.clone(); - let node_client = signer_clone.signer(); - let node_signer = LndNodeSigner::new(pubkey, node_client); - let messenger_utils = MessengerUtilities::new(); - let onion_messenger = OnionMessenger::new( - &messenger_utils, - &node_signer, - &messenger_utils, - &DefaultMessageRouter {}, - IgnoringMessageHandler {}, - IgnoringMessageHandler {}, - ); - - // Ensure that the introduction node we need to connect to actually supports onion messaging. + // Double check that the introduction node we need to connect to actually supports onion messaging. let onion_support = lookup_onion_support(&introduction_node.clone(), client.clone().lightning()).await; if !onion_support { @@ -162,10 +146,10 @@ pub async fn create_invoice_request( } // send_invoice_request sends the invoice request to node that created the offer we want to pay. -pub async fn send_invoice_request( +pub async fn send_invoice_request( network: Network, invoice_request: InvoiceRequest, - onion_messenger: impl OnionMessageHandler + SendOnion, + onion_messenger: &OMH, intermediate_nodes: Vec, blinded_path: BlindedPath, mut custom_msg_client: impl SendCustomMessage, diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index be2983d2..08a8ffec 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -4,11 +4,16 @@ use lndk; use bitcoin::secp256k1::PublicKey; use bitcoin::Network; use ldk_sample::node_api::Node as LdkNode; +use lightning::ln::peer_handler::IgnoringMessageHandler; use lightning::offers::offer::Quantity; +use lightning::onion_message::{DefaultMessageRouter, OnionMessenger}; +use lndk::lnd::LndNodeSigner; use lndk::lndk_offers::request_invoice; -use lndk::onion_messenger::CustomMessenger; +use lndk::onion_messenger::{CustomMessenger, InvoiceHandler, MessengerUtilities}; +use std::collections::VecDeque; use std::path::PathBuf; use std::str::FromStr; +use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; use tokio::select; use tokio::time::{sleep, timeout, Duration as TokioDuration}; @@ -141,6 +146,22 @@ async fn test_lndk_send_invoice_request() { ), }; + let mut signer_clone = lnd.client.clone().unwrap(); + let signer_client = signer_clone.signer(); + let node_signer = LndNodeSigner::new(lnd_pubkey.clone(), signer_client); + let messenger_utils = MessengerUtilities::new(); + let mut invoice_handler = InvoiceHandler { + invoices: Arc::new(Mutex::new(VecDeque::new())), + }; + let onion_messenger = OnionMessenger::new( + &messenger_utils, + &node_signer, + &messenger_utils, + &DefaultMessageRouter {}, + &mut invoice_handler, + IgnoringMessageHandler {}, + ); + let client = lnd.client.clone().unwrap(); let mut client_clone = lnd.client.clone().unwrap(); let custom_msg_client = CustomMessenger { @@ -152,7 +173,7 @@ async fn test_lndk_send_invoice_request() { panic!("lndk should not have completed first {:?}", val); }, // Make sure lndk successfully sends the invoice_request. - res = request_invoice(client, custom_msg_client, lnd_pubkey, offer, pubkey_2, blinded_path) => { + res = request_invoice(client, &onion_messenger, custom_msg_client, offer, pubkey_2, blinded_path) => { assert!(res.is_ok()); ldk1.stop().await; From 569139f1f60596520c45fce43e0989d5bf49c198 Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 8 Dec 2023 22:52:57 -0600 Subject: [PATCH 5/8] Add reply_path to invoice request We need to send the reply_path along with our invoice request so that the offer creator knows where to reply with the invoice. --- src/lndk_offers.rs | 12 +++++++++++- tests/integration_tests.rs | 8 ++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lndk_offers.rs b/src/lndk_offers.rs index d68dc0b2..ef48aa55 100644 --- a/src/lndk_offers.rs +++ b/src/lndk_offers.rs @@ -67,6 +67,7 @@ pub async fn request_invoice( custom_msg_client: impl SendCustomMessage, offer: Offer, introduction_node: PublicKey, + reply_path: BlindedPath, blinded_path: BlindedPath, ) -> Result<(), OfferError> { // Double check that the introduction node we need to connect to actually supports onion messaging. @@ -80,12 +81,14 @@ pub async fn request_invoice( create_invoice_request(client.clone(), offer, vec![], Network::Regtest, 20000) .await .expect("should build invoice request"); + send_invoice_request( Network::Regtest, invoice_request, onion_messenger, vec![introduction_node], blinded_path, + reply_path, custom_msg_client, ) .await @@ -152,6 +155,7 @@ pub async fn send_invoice_request( onion_messenger: &OMH, intermediate_nodes: Vec, blinded_path: BlindedPath, + reply_path: BlindedPath, mut custom_msg_client: impl SendCustomMessage, ) -> Result<(), OfferError> { // We need to make sure our local onion messenger is connected to the needed local peers in @@ -181,7 +185,7 @@ pub async fn send_invoice_request( let contents = OffersMessage::InvoiceRequest(invoice_request); onion_messenger - .send_onion(path, contents, None) + .send_onion(path, contents, Some(reply_path)) .map_err(OfferError::::SendError)?; let onion_message = onion_messenger @@ -471,6 +475,7 @@ mod tests { let invoice_request = get_invoice_request(); let blinded_path = get_blinded_path(); + let reply_path = get_blinded_path(); let pubkey = PublicKey::from_str(&get_pubkey()).unwrap(); assert!(send_invoice_request( @@ -479,6 +484,7 @@ mod tests { onion_mock, vec![pubkey], blinded_path, + reply_path, sender_mock ) .await @@ -506,6 +512,7 @@ mod tests { let invoice_request = get_invoice_request(); let blinded_path = get_blinded_path(); + let reply_path = get_blinded_path(); let pubkey = PublicKey::from_str(&get_pubkey()).unwrap(); assert!(send_invoice_request( @@ -514,6 +521,7 @@ mod tests { onion_mock, vec![pubkey], blinded_path, + reply_path, sender_mock ) .await @@ -543,6 +551,7 @@ mod tests { let invoice_request = get_invoice_request(); let blinded_path = get_blinded_path(); + let reply_path = get_blinded_path(); let pubkey = PublicKey::from_str(&get_pubkey()).unwrap(); assert!(send_invoice_request( @@ -551,6 +560,7 @@ mod tests { onion_mock, vec![pubkey], blinded_path, + reply_path, sender_mock ) .await diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 08a8ffec..56ff1e5e 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,9 +1,10 @@ mod common; use lndk; -use bitcoin::secp256k1::PublicKey; +use bitcoin::secp256k1::{PublicKey, Secp256k1}; use bitcoin::Network; use ldk_sample::node_api::Node as LdkNode; +use lightning::blinded_path::BlindedPath; use lightning::ln::peer_handler::IgnoringMessageHandler; use lightning::offers::offer::Quantity; use lightning::onion_message::{DefaultMessageRouter, OnionMessenger}; @@ -168,12 +169,15 @@ async fn test_lndk_send_invoice_request() { client: client_clone.lightning().to_owned(), }; let blinded_path = offer.paths()[0].clone(); + let secp_ctx = Secp256k1::new(); + let reply_path = + BlindedPath::new_for_message(&[pubkey_2, lnd_pubkey], &messenger_utils, &secp_ctx).unwrap(); select! { val = lndk::run(lndk_cfg) => { panic!("lndk should not have completed first {:?}", val); }, // Make sure lndk successfully sends the invoice_request. - res = request_invoice(client, &onion_messenger, custom_msg_client, offer, pubkey_2, blinded_path) => { + res = request_invoice(client, &onion_messenger, custom_msg_client, offer, pubkey_2, reply_path, blinded_path) => { assert!(res.is_ok()); ldk1.stop().await; From 1c6eeccc7629cf43ad92dae0a96e1b81b36e7af1 Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 8 Dec 2023 22:36:46 -0600 Subject: [PATCH 6/8] tests: add new_address and start LND API calls for tests --- tests/common/mod.rs | 49 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index dd13cebf..6c1e65cc 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -109,7 +109,7 @@ fn setup_test_dirs(test_name: &str) -> (PathBuf, PathBuf, PathBuf) { // BitcoindNode holds the tools we need to interact with a Bitcoind node. pub struct BitcoindNode { - node: BitcoinD, + pub node: BitcoinD, _data_dir: TempDir, zmq_block_port: u16, zmq_tx_port: u16, @@ -150,7 +150,8 @@ pub struct LndNode { _lnd_dir_tmp: TempDir, pub cert_path: String, pub macaroon_path: String, - _handle: Child, + args: [String; 20], + pub handle: Child, pub client: Option, } @@ -202,6 +203,7 @@ impl LndNode { format!("--tlscertpath={}", cert_path), format!("--tlskeypath={}", key_path), format!("--logdir={}", log_dir.display()), + format!("--debuglevel=debug,PEER=info"), format!("--bitcoind.rpcuser={}", connect_params.0.unwrap()), format!("--bitcoind.rpcpass={}", connect_params.1.unwrap()), format!( @@ -220,7 +222,7 @@ impl LndNode { // TODO: For Windows we might need to add ".exe" at the end. let cmd = Command::new("./lnd-itest") - .args(args) + .args(args.clone()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() @@ -231,11 +233,26 @@ impl LndNode { _lnd_dir_tmp: lnd_dir_binding, cert_path: cert_path, macaroon_path: macaroon_path, - _handle: cmd, + args: args, + handle: cmd, client: None, } } + pub async fn start(&mut self) { + let lnd_exe_dir = env::temp_dir().join(LNDK_TESTS_FOLDER).join("bin"); + env::set_current_dir(lnd_exe_dir).expect("couldn't set current directory"); + + let cmd = Command::new("./lnd-itest") + .args(self.args.clone()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to execute lnd command"); + + self.handle = cmd + } + // 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. @@ -326,4 +343,28 @@ impl LndNode { resp } + + // Get an on-chain bitcoin address connect to our LND node. + pub async fn new_address(&mut self) -> tonic_lnd::lnrpc::NewAddressResponse { + let addr_req = tonic_lnd::lnrpc::NewAddressRequest { + r#type: 4, // 4 is the TAPROOT_PUBKEY type. + ..Default::default() + }; + + let resp = if let Some(client) = self.client.clone() { + let make_request = || async { + client + .clone() + .lightning() + .new_address(addr_req.clone()) + .await + }; + let resp = test_utils::retry_async(make_request, String::from("new_address")); + resp.await.unwrap() + } else { + panic!("No client") + }; + + resp + } } From 888e8f44c67d87fee99875643c00ff7da83fd9ae Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 8 Dec 2023 22:37:33 -0600 Subject: [PATCH 7/8] offers: wait for invoice to be returned --- Cargo.lock | 3 +- Cargo.toml | 3 +- src/lndk_offers.rs | 34 ++++++++- tests/common/test_utils.rs | 4 +- tests/integration_tests.rs | 151 ++++++++++++++++++++++++++++++++++++- 5 files changed, 188 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93bc3a65..9893b2d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -947,7 +947,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "ldk-sample" version = "0.1.0" -source = "git+https://github.com/lndk-org/ldk-sample?branch=offers#848e562f0ef57c95e015454cc2c00bca1e315e29" +source = "git+https://github.com/lndk-org/ldk-sample?branch=offer-handling#cdc1b006694eb323a8a982d523ae5b40f5f857ad" dependencies = [ "base64 0.13.1", "bech32 0.8.1", @@ -1095,6 +1095,7 @@ dependencies = [ "rand_core 0.6.4", "tempfile", "tokio", + "tokio-stream", "tonic 0.8.3", "tonic_lnd", ] diff --git a/Cargo.toml b/Cargo.toml index 101af1b6..5d286638 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ rand_core = "0.6.4" log = "0.4.17" log4rs = { version = "1.2.0", features = ["file_appender"] } tokio = { version = "1.25.0", features = ["rt", "rt-multi-thread"] } +tokio-stream = { version = "0.1.14" } tonic = "0.8.3" tonic_lnd = { git = "https://github.com/orbitalturtle/tonic_lnd", branch = "update-signer-client" } hex = "0.4.3" @@ -34,7 +35,7 @@ bytes = "1.4.0" 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", branch = "offers" } +ldk-sample = { git = "https://github.com/lndk-org/ldk-sample", branch = "offer-handling" } mockall = "0.11.3" tempfile = "3.5.0" diff --git a/src/lndk_offers.rs b/src/lndk_offers.rs index ef48aa55..d5496890 100644 --- a/src/lndk_offers.rs +++ b/src/lndk_offers.rs @@ -10,7 +10,7 @@ use core::ops::Deref; use futures::executor::block_on; use lightning::blinded_path::BlindedPath; use lightning::ln::features::InitFeatures; -use lightning::ln::msgs::{Init, OnionMessageHandler}; +use lightning::ln::msgs::{Init, OnionMessage, OnionMessageHandler}; use lightning::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest}; use lightning::offers::merkle::SignError; use lightning::offers::offer::Offer; @@ -22,9 +22,12 @@ use lightning::onion_message::{ use lightning::sign::EntropySource; use lightning::sign::NodeSigner; use lightning::util::logger::Logger; +use lightning::util::ser::Readable; use std::error::Error; use std::fmt::Display; +use std::io::Cursor; use tokio::task; +use tokio_stream::StreamExt; use tonic_lnd::signrpc::{KeyLocator, SignMessageReq}; use tonic_lnd::tonic::Status; use tonic_lnd::Client; @@ -40,6 +43,8 @@ pub enum OfferError { DeriveKeyFailure(Status), /// Trouble sending an offers-related onion message along. SendError(SendError), + /// GetInvoiceError Indicates a failure to receive an invoice. + GetInvoiceError, } impl Display for OfferError { @@ -49,6 +54,7 @@ impl Display for OfferError { OfferError::SignError(e) => write!(f, "Error signing invoice request: {e:?}"), OfferError::DeriveKeyFailure(e) => write!(f, "Error signing invoice request: {e:?}"), OfferError::SendError(e) => write!(f, "Error sending onion message: {e:?}"), + OfferError::GetInvoiceError => write!(f, "Error retrieving invoice"), } } } @@ -198,6 +204,32 @@ pub async fn send_invoice_request( &mut custom_msg_client, ) .await; + + Ok(()) +} + +// After we've requested an invoice from the offer maker, wait for them to send us back an invoice. +pub async fn wait_for_invoice( + ln_client: &mut tonic_lnd::LightningClient, + onion_messenger: &OMH, +) -> Result<(), OfferError> { + let message_stream = ln_client + .subscribe_custom_messages(tonic_lnd::lnrpc::SubscribeCustomMessagesRequest {}) + .await + .expect("message subscription failed") + .into_inner(); + + let mut message_stream = message_stream.take(1); + if let Some(msg) = message_stream.next().await { + let message = msg.unwrap(); + if let Ok(onion_msg) = ::read(&mut Cursor::new(message.data)) { + let pubkey = PublicKey::from_slice(&message.peer).unwrap(); + onion_messenger.handle_onion_message(&pubkey, &onion_msg); + return Ok(()); + } else { + return Err(OfferError::GetInvoiceError); + } + } Ok(()) } diff --git a/tests/common/test_utils.rs b/tests/common/test_utils.rs index 0412b853..b3bf9e8f 100644 --- a/tests/common/test_utils.rs +++ b/tests/common/test_utils.rs @@ -19,11 +19,11 @@ where while retry_num < 3 { sleep(Duration::from_secs(3)).await; match f().await { - Err(_) => { + Err(e) => { println!("retrying {} call", func_name.clone()); retry_num += 1; if retry_num == 5 { - panic!("{} call failed after 3 retries", func_name); + panic!("{} call failed after 3 retries: {:?}", func_name, e); } continue; } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 56ff1e5e..e1af423d 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3,21 +3,24 @@ use lndk; use bitcoin::secp256k1::{PublicKey, Secp256k1}; use bitcoin::Network; +use bitcoincore_rpc::bitcoin::Network as RpcNetwork; +use bitcoincore_rpc::RpcApi; use ldk_sample::node_api::Node as LdkNode; use lightning::blinded_path::BlindedPath; use lightning::ln::peer_handler::IgnoringMessageHandler; use lightning::offers::offer::Quantity; use lightning::onion_message::{DefaultMessageRouter, OnionMessenger}; use lndk::lnd::LndNodeSigner; -use lndk::lndk_offers::request_invoice; +use lndk::lndk_offers::{request_invoice, wait_for_invoice}; use lndk::onion_messenger::{CustomMessenger, InvoiceHandler, MessengerUtilities}; use std::collections::VecDeque; +use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; -use tokio::select; use tokio::time::{sleep, timeout, Duration as TokioDuration}; +use tokio::{join, select}; async fn wait_to_receive_onion_message( ldk1: LdkNode, @@ -185,3 +188,147 @@ async fn test_lndk_send_invoice_request() { } } } + +#[tokio::test(flavor = "multi_thread")] +// Here we test that when the offer creator responds with an invoice, lndk is able to receive it. +async fn test_lndk_wait_for_invoice() { + let test_name = "lndk_wait_for_invoice"; + let (bitcoind, mut lnd, ldk1, ldk2, _lndk_dir) = + common::setup_test_infrastructure(test_name).await; + + // Here we'll produce a little network of channels: + // + // ldk1 <- ldk2 <- lnd + // + // ldk1 will be the offer creator, which will build a blinded route from ldk2 to ldk1. + let (pubkey, addr) = ldk1.get_node_info(); + let (pubkey_2, addr_2) = ldk2.get_node_info(); + + ldk1.connect_to_peer(pubkey_2, addr_2).await.unwrap(); + lnd.connect_to_peer(pubkey_2, addr_2).await; + + let lnd_info = lnd.get_info().await; + let lnd_pubkey = PublicKey::from_str(&lnd_info.identity_pubkey).unwrap(); + + // ldk1 won't send back the invoice unless we have channels in our network. + let ldk2_fund_addr = ldk2.bitcoind_client.get_new_address().await; + let lnd_fund_addr = lnd.new_address().await.address; + + // We need to convert funding addresses to the form that the bitcoincore_rpc library recognizes. + let ldk2_addr_string = ldk2_fund_addr.to_string(); + let ldk2_addr = bitcoincore_rpc::bitcoin::Address::from_str(&ldk2_addr_string) + .unwrap() + .require_network(RpcNetwork::Regtest) + .unwrap(); + let lnd_addr = bitcoincore_rpc::bitcoin::Address::from_str(&lnd_fund_addr) + .unwrap() + .require_network(RpcNetwork::Regtest) + .unwrap(); + let lnd_network_addr = lnd + .address + .replace("localhost", "127.0.0.1") + .replace("https://", ""); + + // Fund both of these nodes so we can open channels. + bitcoind + .node + .client + .generate_to_address(1, &ldk2_addr) + .unwrap(); + bitcoind + .node + .client + .generate_to_address(1, &lnd_addr) + .unwrap(); + + // Make sure the above funds are confirmed. + bitcoind + .node + .client + .generate_to_address(100, &ldk2_addr) + .unwrap(); + + ldk2.open_channel(pubkey, addr, 200000, 0, true) + .await + .unwrap(); + ldk2.open_channel( + lnd_pubkey, + SocketAddr::from_str(&lnd_network_addr).unwrap(), + 200000, + 100000, + false, + ) + .await + .unwrap(); + + bitcoind + .node + .client + .generate_to_address(100, &ldk2_addr) + .unwrap(); + + // TODO: Restarting the lnd node here isn't ideal. But for some reason after we call LDK's open_channel + // API call, LND blocks and is unable to receive any API requests. This might be a bug in ldk-sample or + // our ldk-sample implementation. + lnd.handle.kill().expect("lnd couldn't be killed"); + lnd.start().await; + lnd.connect_to_peer(pubkey_2, addr_2).await; + + let path_pubkeys = vec![pubkey_2, pubkey]; + let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60); + let offer = ldk1 + .create_offer( + &path_pubkeys, + Network::Regtest, + 20_000, + Quantity::One, + expiration, + ) + .await + .expect("should create offer"); + + let mut signer_clone = lnd.client.clone().unwrap(); + let signer_client = signer_clone.signer(); + let node_signer = LndNodeSigner::new(lnd_pubkey.clone(), signer_client); + let messenger_utils = MessengerUtilities::new(); + let mut invoice_handler = InvoiceHandler { + invoices: Arc::new(Mutex::new(VecDeque::new())), + }; + let onion_messenger = OnionMessenger::new( + &messenger_utils, + &node_signer, + &messenger_utils, + &DefaultMessageRouter {}, + &mut invoice_handler, + IgnoringMessageHandler {}, + ); + + let client = lnd.client.clone().unwrap(); + let mut client_clone = lnd.client.clone().unwrap(); + let custom_msg_client = CustomMessenger { + client: client_clone.lightning().to_owned(), + }; + let blinded_path = offer.paths()[0].clone(); + let secp_ctx = Secp256k1::new(); + let reply_path = + BlindedPath::new_for_message(&[pubkey_2, lnd_pubkey], &messenger_utils, &secp_ctx).unwrap(); + + let (res1, res2) = join!( + wait_for_invoice(client_clone.lightning(), &onion_messenger), + request_invoice( + client.clone(), + &onion_messenger, + custom_msg_client, + offer, + pubkey_2, + reply_path, + blinded_path + ) + ); + res1.unwrap(); + res2.unwrap(); + + // LND should have received the Bolt12Invoice. + let invoices = invoice_handler.invoices.lock().unwrap(); + assert!(invoices.len() == 1); +} From a87b987eff415aca163d752edf21693b886ce0cc Mon Sep 17 00:00:00 2001 From: Orbital Date: Fri, 8 Dec 2023 21:56:48 -0600 Subject: [PATCH 8/8] Add Default impl for MessengerUtilities to appease clippy --- src/onion_messenger.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/onion_messenger.rs b/src/onion_messenger.rs index 31db5b72..eecefbb5 100644 --- a/src/onion_messenger.rs +++ b/src/onion_messenger.rs @@ -66,6 +66,12 @@ impl MessengerUtilities { } } +impl Default for MessengerUtilities { + fn default() -> Self { + Self::new() + } +} + impl EntropySource for MessengerUtilities { // TODO: surface LDK's EntropySource and use instead. fn get_secure_random_bytes(&self) -> [u8; 32] {