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 all commits
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ itest:
@$(call print, "Building lnd for itests.")
git submodule update --init --recursive
cd lnd/cmd/lnd; $(GO_BUILD) -tags="peersrpc signrpc walletrpc dev" -o $(TMP_DIR)/lndk-tests/bin/lnd-itest$(EXEC_SUFFIX)
$(CARGO_TEST) -- -- test '*' --test-threads=1 --nocapture
$(CARGO_TEST) --test '*' -- --test-threads=1 --nocapture

4 changes: 2 additions & 2 deletions src/lnd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ pub(crate) fn features_support_onion_messages(
}

/// LndNodeSigner provides signing operations using LND's signer subserver.
pub(crate) struct LndNodeSigner<'a> {
pub struct LndNodeSigner<'a> {
pubkey: PublicKey,
secp_ctx: Secp256k1<secp256k1::All>,
signer: RefCell<&'a mut tonic_lnd::SignerClient>,
}

impl<'a> LndNodeSigner<'a> {
pub(crate) fn new(pubkey: PublicKey, signer: &'a mut tonic_lnd::SignerClient) -> Self {
pub fn new(pubkey: PublicKey, signer: &'a mut tonic_lnd::SignerClient) -> Self {
LndNodeSigner {
pubkey,
secp_ctx: Secp256k1::new(),
Expand Down
80 changes: 53 additions & 27 deletions src/lndk_offers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::lnd::{LndNodeSigner, MessageSigner, ONION_MESSAGES_OPTIONAL};
use crate::onion_messenger::{
lookup_onion_support, relay_outgoing_msg_event, MessengerUtilities, SendCustomMessage,
};
use crate::lnd::{MessageSigner, ONION_MESSAGES_OPTIONAL};
use crate::onion_messenger::{lookup_onion_support, relay_outgoing_msg_event, SendCustomMessage};
use async_trait::async_trait;
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::hashes::sha256::Hash;
Expand All @@ -12,22 +10,24 @@ 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::peer_handler::IgnoringMessageHandler;
use lightning::ln::msgs::{Init, OnionMessage, OnionMessageHandler};
use lightning::offers::invoice_request::{InvoiceRequest, UnsignedInvoiceRequest};
use lightning::offers::merkle::SignError;
use lightning::offers::offer::Offer;
use lightning::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use lightning::onion_message::{
CustomOnionMessageHandler, DefaultMessageRouter, Destination, MessageRouter, OffersMessage,
OffersMessageHandler, OnionMessagePath, OnionMessenger, SendError,
CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler,
OnionMessagePath, OnionMessenger, SendError,
};
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 @@ -43,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 @@ -52,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 All @@ -64,28 +67,16 @@ pub fn decode(offer_str: String) -> Result<Offer, Bolt12ParseError> {
}

// request_invoice builds an invoice request and tries to send it to the offer maker.
pub async fn request_invoice(
pub async fn request_invoice<OMH: OnionMessageHandler + SendOnion>(
client: Client,
onion_messenger: &OMH,
custom_msg_client: impl SendCustomMessage,
pubkey: PublicKey,
offer: Offer,
introduction_node: PublicKey,
reply_path: BlindedPath,
blinded_path: BlindedPath,
) -> Result<(), OfferError<Secp256k1Error>> {
let mut signer_clone = client.clone();
let node_client = signer_clone.signer();
let node_signer = LndNodeSigner::new(pubkey, node_client);
let messenger_utils = MessengerUtilities::new();
let onion_messenger = OnionMessenger::new(
&messenger_utils,
&node_signer,
&messenger_utils,
&DefaultMessageRouter {},
IgnoringMessageHandler {},
IgnoringMessageHandler {},
);

// Ensure that the introduction node we need to connect to actually supports onion messaging.
// Double check that the introduction node we need to connect to actually supports onion messaging.
let onion_support =
lookup_onion_support(&introduction_node.clone(), client.clone().lightning()).await;
if !onion_support {
Expand All @@ -96,12 +87,14 @@ pub async fn request_invoice(
create_invoice_request(client.clone(), offer, vec![], Network::Regtest, 20000)
.await
.expect("should build invoice request");

send_invoice_request(
Network::Regtest,
invoice_request,
onion_messenger,
vec![introduction_node],
blinded_path,
reply_path,
custom_msg_client,
)
.await
Expand Down Expand Up @@ -162,12 +155,13 @@ pub async fn create_invoice_request(
}

// send_invoice_request sends the invoice request to node that created the offer we want to pay.
pub async fn send_invoice_request(
pub async fn send_invoice_request<OMH: OnionMessageHandler + SendOnion>(
network: Network,
invoice_request: InvoiceRequest,
onion_messenger: impl OnionMessageHandler + SendOnion,
onion_messenger: &OMH,
intermediate_nodes: Vec<PublicKey>,
blinded_path: BlindedPath,
reply_path: BlindedPath,
mut custom_msg_client: impl SendCustomMessage,
) -> Result<(), OfferError<bitcoin::secp256k1::Error>> {
// We need to make sure our local onion messenger is connected to the needed local peers in
Expand Down Expand Up @@ -197,7 +191,7 @@ pub async fn send_invoice_request(
let contents = OffersMessage::InvoiceRequest(invoice_request);

onion_messenger
.send_onion(path, contents, None)
.send_onion(path, contents, Some(reply_path))
.map_err(OfferError::<Secp256k1Error>::SendError)?;

let onion_message = onion_messenger
Expand All @@ -210,6 +204,32 @@ pub async fn send_invoice_request(
&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,
orbitalturtle marked this conversation as resolved.
Show resolved Hide resolved
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 Expand Up @@ -487,6 +507,7 @@ mod tests {

let invoice_request = get_invoice_request();
let blinded_path = get_blinded_path();
let reply_path = get_blinded_path();
let pubkey = PublicKey::from_str(&get_pubkey()).unwrap();

assert!(send_invoice_request(
Expand All @@ -495,6 +516,7 @@ mod tests {
onion_mock,
vec![pubkey],
blinded_path,
reply_path,
sender_mock
)
.await
Expand Down Expand Up @@ -522,6 +544,7 @@ mod tests {

let invoice_request = get_invoice_request();
let blinded_path = get_blinded_path();
let reply_path = get_blinded_path();
let pubkey = PublicKey::from_str(&get_pubkey()).unwrap();

assert!(send_invoice_request(
Expand All @@ -530,6 +553,7 @@ mod tests {
onion_mock,
vec![pubkey],
blinded_path,
reply_path,
sender_mock
)
.await
Expand Down Expand Up @@ -559,6 +583,7 @@ mod tests {

let invoice_request = get_invoice_request();
let blinded_path = get_blinded_path();
let reply_path = get_blinded_path();
let pubkey = PublicKey::from_str(&get_pubkey()).unwrap();

assert!(send_invoice_request(
Expand All @@ -567,6 +592,7 @@ mod tests {
onion_mock,
vec![pubkey],
blinded_path,
reply_path,
sender_mock
)
.await
Expand Down
43 changes: 39 additions & 4 deletions src/onion_messenger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ use bitcoin::secp256k1::PublicKey;
use core::ops::Deref;
use lightning::ln::features::InitFeatures;
use lightning::ln::msgs::{Init, OnionMessage, OnionMessageHandler};
use lightning::offers::invoice::Bolt12Invoice;
use lightning::onion_message::{
CustomOnionMessageHandler, MessageRouter, OffersMessageHandler, OnionMessenger,
CustomOnionMessageHandler, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessenger,
PendingOnionMessage,
};
use lightning::sign::EntropySource;
use lightning::sign::NodeSigner;
Expand All @@ -19,12 +21,13 @@ use log::{debug, error, info, trace, warn};
use rand_chacha::ChaCha20Rng;
use rand_core::{RngCore, SeedableRng};
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::{HashMap, VecDeque};
use std::error::Error;
use std::fmt;
use std::io::Cursor;
use std::marker::Copy;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::{select, time, time::Duration, time::Interval};
use tonic_lnd::{
Expand All @@ -51,18 +54,24 @@ const DEFAULT_CALL_FREQUENCY: Duration = Duration::from_secs(1);
/// A refcell is used for entropy_source to provide interior mutability for ChaCha20Rng. We need a mutable reference
/// to be able to use the chacha library’s fill_bytes method, but the EntropySource interface in LDK is for an
/// immutable reference.
pub(crate) struct MessengerUtilities {
pub struct MessengerUtilities {
entropy_source: RefCell<ChaCha20Rng>,
}

impl MessengerUtilities {
pub(crate) fn new() -> Self {
pub fn new() -> Self {
MessengerUtilities {
entropy_source: RefCell::new(ChaCha20Rng::from_entropy()),
}
}
}

impl Default for MessengerUtilities {
fn default() -> Self {
Self::new()
}
}

impl EntropySource for MessengerUtilities {
// TODO: surface LDK's EntropySource and use instead.
fn get_secure_random_bytes(&self) -> [u8; 32] {
Expand Down Expand Up @@ -699,6 +708,32 @@ pub(crate) async fn relay_outgoing_msg_event(
}
}

// A simple offers message handler.
#[derive(Clone)]
pub struct InvoiceHandler {
pub invoices: Arc<Mutex<VecDeque<Bolt12Invoice>>>,
}

impl OffersMessageHandler for InvoiceHandler {
fn handle_message(&self, message: OffersMessage) -> Option<OffersMessage> {
match message {
OffersMessage::Invoice(invoice) => {
println!("Received an invoice offers message.");
self.invoices.lock().unwrap().push_back(invoice);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we track that we actually sent an invoice request for the offer, and discard the invoice if not?

None
}
_ => {
println!("Received an unsupported offers message.");
None
}
}
}

fn release_pending_messages(&self) -> Vec<PendingOnionMessage<OffersMessage>> {
vec![]
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading