Skip to content

Commit

Permalink
multi: send offer payment
Browse files Browse the repository at this point in the history
  • Loading branch information
orbitalturtle committed Jan 23, 2024
1 parent 9499d9a commit 0bad16d
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 18 deletions.
54 changes: 46 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ 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};
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::{
Expand Down Expand Up @@ -187,6 +188,7 @@ pub enum OfferState {

pub struct OfferHandler {
active_offers: Mutex<HashMap<String, OfferState>>,
active_invoices: Mutex<Vec<Bolt12Invoice>>,
pending_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
messenger_utils: MessengerUtilities,
messenger_state: RefCell<MessengerState>,
Expand All @@ -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),
Expand Down Expand Up @@ -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(())
}
Expand All @@ -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 {
Expand All @@ -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
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/lndk_offers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use bitcoin::network::constants::Network;
use bitcoin::secp256k1::schnorr::Signature;
use bitcoin::secp256k1::{Error as Secp256k1Error, PublicKey};
use futures::executor::block_on;
use lightning::blinded_path::BlindedPath;
use lightning::ln::channelmanager::PaymentId;
use lightning::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest};
use lightning::offers::merkle::SignError;
Expand Down
40 changes: 39 additions & 1 deletion tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::process::{Child, Command, Stdio};
use std::thread;
use std::{env, fs};
use tempfile::{tempdir, Builder, TempDir};
use tokio::time::Duration;
use tokio::time::{sleep, timeout, Duration};
use tonic_lnd::lnrpc::GetInfoRequest;
use tonic_lnd::Client;

Expand Down Expand Up @@ -351,4 +351,42 @@ impl LndNode {

resp
}

// wait_for_chain_sync waits until we're synced to chain according to the get_info response.
// We'll timeout if it takes too long.
pub async fn wait_for_chain_sync(&mut self) {
match timeout(Duration::from_secs(100), self.check_chain_sync()).await {
Err(_) => panic!("timeout before lnd synced to chain"),
_ => {}
};
}

pub async fn check_chain_sync(&mut self) {
loop {
let resp = self.get_info().await;
if resp.synced_to_chain {
return;
}
sleep(Duration::from_secs(2)).await;
}
}

// wait_for_lnd_sync waits until we're synced to graph according to the get_info response.
// We'll timeout if it takes too long.
pub async fn wait_for_graph_sync(&mut self) {
match timeout(Duration::from_secs(100), self.check_graph_sync()).await {
Err(_) => panic!("timeout before lnd synced to graph"),
_ => {}
};
}

pub async fn check_graph_sync(&mut self) {
loop {
let resp = self.get_info().await;
if resp.synced_to_graph {
return;
}
sleep(Duration::from_secs(2)).await;
}
}
}
73 changes: 64 additions & 9 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,26 +158,81 @@ 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();

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
Expand All @@ -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(),
Expand All @@ -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();
Expand Down

0 comments on commit 0bad16d

Please sign in to comment.