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/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 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/lndk_offers.rs b/src/lndk_offers.rs index 4c4644e7..d5496890 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; @@ -12,22 +10,24 @@ 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::peer_handler::IgnoringMessageHandler; +use lightning::ln::msgs::{Init, OnionMessage, OnionMessageHandler}; 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; 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; @@ -43,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 { @@ -52,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"), } } } @@ -64,28 +67,16 @@ 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, + reply_path: BlindedPath, 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 { @@ -96,12 +87,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 @@ -162,12 +155,13 @@ 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, + 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 @@ -197,7 +191,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 @@ -210,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(()) } @@ -487,6 +507,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( @@ -495,6 +516,7 @@ mod tests { onion_mock, vec![pubkey], blinded_path, + reply_path, sender_mock ) .await @@ -522,6 +544,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( @@ -530,6 +553,7 @@ mod tests { onion_mock, vec![pubkey], blinded_path, + reply_path, sender_mock ) .await @@ -559,6 +583,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( @@ -567,6 +592,7 @@ mod tests { onion_mock, vec![pubkey], blinded_path, + reply_path, sender_mock ) .await diff --git a/src/onion_messenger.rs b/src/onion_messenger.rs index 3e9eb374..eecefbb5 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::{ @@ -51,18 +54,24 @@ 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()), } } } +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] { @@ -699,6 +708,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::*; 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 + } } 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 be2983d2..e1af423d 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,17 +1,26 @@ mod common; use lndk; -use bitcoin::secp256k1::PublicKey; +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 lndk::lndk_offers::request_invoice; -use lndk::onion_messenger::CustomMessenger; +use lightning::onion_message::{DefaultMessageRouter, OnionMessenger}; +use lndk::lnd::LndNodeSigner; +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, @@ -141,18 +150,37 @@ 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 { 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, custom_msg_client, lnd_pubkey, 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; @@ -160,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); +}