From 2b72d7c674bc2f32a6ee19af31f86a8b3c26bac0 Mon Sep 17 00:00:00 2001 From: Orbital Date: Tue, 23 Jan 2024 14:34:54 -0600 Subject: [PATCH] multi: send offer payment --- src/lib.rs | 54 +++++++++++++++++++++++----- tests/integration_tests.rs | 73 +++++++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 986671d0..73bfe3b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ mod rate_limit; use crate::lnd::{ features_support_onion_messages, get_lnd_client, string_to_network, LndCfg, LndNodeSigner, }; -use crate::lndk_offers::{connect_to_peer, validate_amount, OfferError}; +use crate::lndk_offers::{connect_to_peer, pay_invoice, validate_amount, OfferError}; use crate::onion_messenger::MessengerUtilities; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{Error as Secp256k1Error, PublicKey, Secp256k1}; @@ -16,6 +16,7 @@ use home::home_dir; use lightning::blinded_path::BlindedPath; use lightning::ln::inbound_payment::ExpandedKey; use lightning::ln::peer_handler::IgnoringMessageHandler; +use lightning::offers::invoice::Bolt12Invoice; use lightning::offers::invoice_error::InvoiceError; use lightning::offers::offer::Offer; use lightning::onion_message::messenger::{ @@ -187,6 +188,7 @@ pub enum OfferState { pub struct OfferHandler { active_offers: Mutex>, + active_invoices: Mutex>, pending_messages: Mutex>>, pub messenger_utils: MessengerUtilities, messenger_state: RefCell, @@ -201,6 +203,7 @@ impl OfferHandler { OfferHandler { active_offers: Mutex::new(HashMap::new()), + active_invoices: Mutex::new(Vec::new()), pending_messages: Mutex::new(Vec::new()), messenger_utils: MessengerUtilities::new(), messenger_state: RefCell::new(MessengerState::Starting), @@ -258,12 +261,27 @@ impl OfferHandler { reply_path, }; - let mut pending_messages = self.pending_messages.lock().unwrap(); - pending_messages.push(pending_message); - std::mem::drop(pending_messages); + { + let mut pending_messages = self.pending_messages.lock().unwrap(); + pending_messages.push(pending_message); - let mut active_offers = self.active_offers.lock().unwrap(); - active_offers.insert(offer.to_string().clone(), OfferState::InvoiceRequestSent); + let mut active_offers = self.active_offers.lock().unwrap(); + active_offers.insert(offer.to_string().clone(), OfferState::InvoiceRequestSent); + } + + let invoice = self.wait_for_invoice().await; + let payment_hash = invoice.payment_hash(); + let path_info = invoice.payment_paths()[0].clone(); + + let _ = pay_invoice( + client, + path_info.1, + path_info.0.cltv_expiry_delta, + path_info.0.fee_base_msat, + payment_hash.0, + amount.unwrap(), + ) + .await; Ok(()) } @@ -279,6 +297,19 @@ impl OfferHandler { }; } } + + // wait_for_invoice waits for the offer creator to respond with an invoice. + pub async fn wait_for_invoice(&self) -> Bolt12Invoice { + loop { + { + let mut active_invoices = self.active_invoices.lock().unwrap(); + if active_invoices.len() == 1 { + return active_invoices.pop().unwrap(); + } + } + sleep(Duration::from_secs(2)).await; + } + } } impl OffersMessageHandler for OfferHandler { @@ -293,13 +324,20 @@ impl OffersMessageHandler for OfferHandler { match invoice.verify(&self.expanded_key, secp_ctx) { // TODO: Eventually we can use the returned payment id below to check if this // payment has been sent twice. - Ok(_payment_id) => Some(OffersMessage::Invoice(invoice)), + Ok(_payment_id) => { + let mut active_invoices = self.active_invoices.lock().unwrap(); + active_invoices.push(invoice.clone()); + Some(OffersMessage::Invoice(invoice)) + } Err(()) => Some(OffersMessage::InvoiceError(InvoiceError::from_string( String::from("invoice verification failure"), ))), } } - OffersMessage::InvoiceError(_error) => None, + OffersMessage::InvoiceError(error) => { + log::error!("Invoice error received: {}", error); + None + } } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 5170c58f..8b270bac 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -158,19 +158,18 @@ async fn test_lndk_forwards_onion_message() { } #[tokio::test(flavor = "multi_thread")] -// Here we test the beginning of the BOLT 12 offers flow. We show that lndk successfully builds an -// invoice_request and sends it. -async fn test_lndk_send_invoice_request() { - let test_name = "lndk_send_invoice_request"; - let (_bitcoind, mut lnd, ldk1, ldk2, lndk_dir) = +// Here we test that we're able to fully pay an offer. +async fn test_lndk_pay_offer() { + let test_name = "lndk_pay_offer"; + let (bitcoind, mut lnd, ldk1, ldk2, lndk_dir) = common::setup_test_infrastructure(test_name).await; - // Here we'll produce a little network path: + // Here we'll produce a little network of channels: // - // ldk1 <-> ldk2 <-> lnd + // ldk1 <- ldk2 <- lnd // // ldk1 will be the offer creator, which will build a blinded route from ldk2 to ldk1. - let (pubkey, _) = ldk1.get_node_info(); + let (pubkey, addr) = ldk1.get_node_info(); let (pubkey_2, addr_2) = ldk2.get_node_info(); let lnd_info = lnd.get_info().await; let lnd_pubkey = PublicKey::from_str(&lnd_info.identity_pubkey).unwrap(); @@ -178,6 +177,62 @@ async fn test_lndk_send_invoice_request() { 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(); + + 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 = bitcoind::bitcoincore_rpc::bitcoin::Address::from_str(&ldk2_addr_string) + .unwrap() + .require_network(RpcNetwork::Regtest) + .unwrap(); + let lnd_addr = bitcoind::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, open the channels, and synchronize the network. + bitcoind + .node + .client + .generate_to_address(6, &lnd_addr) + .unwrap(); + + lnd.wait_for_chain_sync().await; + + ldk2.open_channel(pubkey, addr, 200000, 0, false) + .await + .unwrap(); + + lnd.wait_for_graph_sync().await; + + ldk2.open_channel( + lnd_pubkey, + SocketAddr::from_str(&lnd_network_addr).unwrap(), + 200000, + 10000000, + true, + ) + .await + .unwrap(); + + lnd.wait_for_graph_sync().await; + + bitcoind + .node + .client + .generate_to_address(20, &ldk2_addr) + .unwrap(); + + lnd.wait_for_chain_sync().await; + let path_pubkeys = vec![pubkey_2, pubkey]; let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60); let offer = ldk1 @@ -191,7 +246,6 @@ async fn test_lndk_send_invoice_request() { .await .expect("should create offer"); - // Now we'll spin up lndk, which should forward the invoice request to ldk2. let (shutdown, listener) = triggered::trigger(); let lnd_cfg = lndk::lnd::LndCfg::new( lnd.address.clone(), @@ -211,6 +265,7 @@ async fn test_lndk_send_invoice_request() { listener, }; + let messenger_utils = MessengerUtilities::new(); let client = lnd.client.clone().unwrap(); let blinded_path = offer.paths()[0].clone(); let messenger_utils = MessengerUtilities::new();