Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

offers: wait for offers invoice #84

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>(
orbitalturtle marked this conversation as resolved.
Show resolved Hide resolved
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");
Copy link
Collaborator Author

@orbitalturtle orbitalturtle Dec 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm FYI I found a fix for this -- it seems like the problem has something to do with mining a bunch of blocks while lnd is trying to initiate a channel open. I'll push up a fix for this soon so we can remove this restart.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that makes sense - some RPC calls will fail if we're not synced to chain. Easiest way will prob be to poll GetInfo to make sure that we're synced?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, I'll give that a try. Right now I just have some sleeps scattered throughout as a POC, which is definitely not ideal!

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);
}