Skip to content

Commit

Permalink
offers: wait for invoice to be returned
Browse files Browse the repository at this point in the history
  • Loading branch information
orbitalturtle committed Dec 9, 2023
1 parent 2585b12 commit 0aba17b
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 7 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"

Expand Down
34 changes: 33 additions & 1 deletion src/lndk_offers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -40,6 +43,8 @@ pub enum OfferError<Secp256k1Error> {
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<Secp256k1Error> {
Expand All @@ -49,6 +54,7 @@ impl Display for OfferError<Secp256k1Error> {
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"),
}
}
}
Expand Down Expand Up @@ -198,6 +204,32 @@ pub async fn send_invoice_request<OMH: OnionMessageHandler + SendOnion>(
&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<OMH: OnionMessageHandler + SendOnion>(
ln_client: &mut tonic_lnd::LightningClient,
onion_messenger: &OMH,
) -> Result<(), OfferError<Secp256k1Error>> {
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) = <OnionMessage as Readable>::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(())
}

Expand Down
4 changes: 2 additions & 2 deletions tests/common/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
151 changes: 149 additions & 2 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}

0 comments on commit 0aba17b

Please sign in to comment.