diff --git a/src/lib.rs b/src/lib.rs index 0fab65ae..22276ec7 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::{ @@ -34,6 +35,7 @@ use std::collections::HashMap; use std::str::FromStr; use std::sync::{Mutex, Once}; use tokio::sync::mpsc::{Receiver, Sender}; +use tokio::time::{sleep, Duration}; use tonic_lnd::lnrpc::GetInfoRequest; use tonic_lnd::Client; use triggered::{Listener, Trigger}; @@ -191,6 +193,7 @@ enum OfferState { pub struct OfferHandler { active_offers: Mutex>, + active_invoices: Mutex>, pending_messages: Mutex>>, pub messenger_utils: MessengerUtilities, expanded_key: ExpandedKey, @@ -206,6 +209,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(), expanded_key, @@ -263,14 +267,40 @@ 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(), OfferState::InvoiceRequestSent); + let invoice = self.wait_for_invoice().await; + let payment_hash = invoice.payment_hash(); + let path_info = invoice.payment_paths()[0].clone(); + + 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(()) + // 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; + } } } @@ -287,13 +317,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 we // already processed an invoice for this payment. - 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 58f79738..67035ea4 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -112,19 +112,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(); @@ -132,6 +131,59 @@ 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 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 @@ -145,7 +197,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(), @@ -171,9 +222,9 @@ async fn test_lndk_send_invoice_request() { signals, }; + let messenger_utils = MessengerUtilities::new(); let client = lnd.client.clone().unwrap(); let blinded_path = offer.paths()[0].clone(); - let messenger_utils = MessengerUtilities::new(); let secp_ctx = Secp256k1::new(); let reply_path = BlindedPath::new_for_message(&[pubkey_2, lnd_pubkey], &messenger_utils, &secp_ctx).unwrap();