Skip to content

Commit

Permalink
offers: send invoice request
Browse files Browse the repository at this point in the history
  • Loading branch information
orbitalturtle committed Feb 1, 2024
1 parent c6ab86e commit d932363
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 15 deletions.
73 changes: 67 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ 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::onion_messenger::MessengerUtilities;
use bitcoin::secp256k1::PublicKey;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{Error as Secp256k1Error, PublicKey};
use home::home_dir;
use lightning::blinded_path::BlindedPath;
use lightning::ln::peer_handler::IgnoringMessageHandler;
use lightning::offers::offer::Offer;
use lightning::onion_message::{
DefaultMessageRouter, OffersMessage, OffersMessageHandler, OnionMessenger, PendingOnionMessage,
DefaultMessageRouter, Destination, OffersMessage, OffersMessageHandler, OnionMessenger,
PendingOnionMessage,
};
use log::{error, info, LevelFilter};
use log4rs::append::console::ConsoleAppender;
Expand All @@ -26,6 +31,7 @@ use std::str::FromStr;
use std::sync::{Mutex, Once};
use tokio::sync::mpsc::{Receiver, Sender};
use tonic_lnd::lnrpc::GetInfoRequest;
use tonic_lnd::Client;
use triggered::{Listener, Trigger};

static INIT: Once = Once::new();
Expand Down Expand Up @@ -107,6 +113,7 @@ impl LndkOnionMessenger {
network_str = Some(chain.network.clone())
}
}

if network_str.is_none() {
error!("lnd node is not connected to bitcoin network as expected");
return Err(());
Expand Down Expand Up @@ -175,19 +182,73 @@ enum OfferState {
}

pub struct OfferHandler {
_active_offers: Mutex<HashMap<String, OfferState>>,
active_offers: Mutex<HashMap<String, OfferState>>,
pending_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
// This channel will close once the onion messenger has successfully started up.
_started: RefCell<Receiver<u32>>,
started: RefCell<Receiver<u32>>,
}

impl OfferHandler {
pub fn new(started: Receiver<u32>) -> Self {
OfferHandler {
_active_offers: Mutex::new(HashMap::new()),
active_offers: Mutex::new(HashMap::new()),
pending_messages: Mutex::new(Vec::new()),
_started: RefCell::new(started),
started: RefCell::new(started),
}
}

/// Adds an offer to be paid with the amount specified. May only be called once for a single offer.
#[allow(clippy::await_holding_refcell_ref)]
pub async fn pay_offer(
&self,
offer: Offer,
amount: Option<u64>,
network: Network,
client: Client,
blinded_path: BlindedPath,
reply_path: Option<BlindedPath>,
) -> Result<(), OfferError<Secp256k1Error>> {
// Wait for onion messenger to give us the signal that it's ready. Once the onion messenger drops
// the channel sender, recv will return None and we'll stop blocking here.
while (self.started.borrow_mut().recv().await).is_some() {
println!("Error: we shouldn't receive any messages on this channel");
}

let validated_amount = validate_amount(&offer, amount).await?;

// For now we connect directly to the introduction node of the blinded path so we don't need any
// intermediate nodes here. In the future we'll query for a full path to the introduction node for
// better sender privacy.
connect_to_peer(client.clone(), blinded_path.introduction_node_id).await?;

let offer_id = offer.clone().to_string();
{
let mut active_offers = self.active_offers.lock().unwrap();
if active_offers.contains_key(&offer_id.clone()) {
return Err(OfferError::AlreadyProcessing);
}
active_offers.insert(offer.to_string().clone(), OfferState::OfferAdded);
}

let invoice_request = self
.create_invoice_request(client.clone(), offer, vec![], network, validated_amount)
.await?;

let contents = OffersMessage::InvoiceRequest(invoice_request);
let pending_message = PendingOnionMessage {
contents,
destination: Destination::BlindedPath(blinded_path.clone()),
reply_path,
};

let mut pending_messages = self.pending_messages.lock().unwrap();
pending_messages.push(pending_message);
std::mem::drop(pending_messages);

let mut active_offers = self.active_offers.lock().unwrap();
active_offers.insert(offer_id, OfferState::InvoiceRequestSent);

Ok(())
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/lnd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub(crate) fn get_lnd_client(cfg: LndCfg) -> Result<Client, ConnectError> {
}

/// LndCfg specifies the configuration required to connect to LND's grpc client.
#[derive(Clone)]
pub struct LndCfg {
address: String,
cert: PathBuf,
Expand Down Expand Up @@ -186,7 +187,7 @@ pub(crate) fn string_to_network(network_str: &str) -> Result<Network, NetworkPar

/// MessageSigner provides a layer of abstraction over the LND API for message signing.
#[async_trait]
pub(crate) trait MessageSigner {
pub trait MessageSigner {
async fn derive_key(&mut self, key_loc: KeyLocator) -> Result<Vec<u8>, Status>;
async fn sign_message(
&mut self,
Expand Down
14 changes: 9 additions & 5 deletions src/lndk_offers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,8 @@ pub fn decode(offer_str: String) -> Result<Offer, Bolt12ParseError> {
}

impl OfferHandler {
#[allow(dead_code)]
// create_invoice_request builds and signs an invoice request, the first step in the BOLT 12 process of paying an offer.
pub(crate) async fn create_invoice_request(
pub async fn create_invoice_request(
&self,
mut signer: impl MessageSigner + std::marker::Send + 'static,
offer: Offer,
Expand Down Expand Up @@ -290,6 +289,8 @@ mod tests {
use mockall::mock;
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use tokio::sync::mpsc;
use tokio::sync::mpsc::{Receiver, Sender};
use tonic_lnd::lnrpc::NodeAddress;

fn get_offer() -> String {
Expand Down Expand Up @@ -359,7 +360,8 @@ mod tests {
});

let offer = decode(get_offer()).unwrap();
let handler = OfferHandler::new();
let (_, rx): (Sender<u32>, Receiver<u32>) = mpsc::channel(1);
let handler = OfferHandler::new(rx);
assert!(handler
.create_invoice_request(signer_mock, offer, vec![], Network::Regtest, 10000)
.await
Expand All @@ -382,7 +384,8 @@ mod tests {
});

let offer = decode(get_offer()).unwrap();
let handler = OfferHandler::new();
let (_, rx): (Sender<u32>, Receiver<u32>) = mpsc::channel(1);
let handler = OfferHandler::new(rx);
assert!(handler
.create_invoice_request(signer_mock, offer, vec![], Network::Regtest, 10000)
.await
Expand All @@ -405,7 +408,8 @@ mod tests {
.returning(|_, _, _| Err(Status::unknown("error testing")));

let offer = decode(get_offer()).unwrap();
let handler = OfferHandler::new();
let (_, rx): (Sender<u32>, Receiver<u32>) = mpsc::channel(1);
let handler = OfferHandler::new(rx);
assert!(handler
.create_invoice_request(signer_mock, offer, vec![], Network::Regtest, 10000)
.await
Expand Down
2 changes: 1 addition & 1 deletion tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ pub struct LndNode {
pub cert_path: String,
pub macaroon_path: String,
_handle: Child,
client: Option<Client>,
pub client: Option<Client>,
}

impl LndNode {
Expand Down
92 changes: 90 additions & 2 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
mod common;
use lndk;

use bitcoin::secp256k1::PublicKey;
use bitcoin::secp256k1::{PublicKey, Secp256k1};
use bitcoin::Network;
use chrono::Utc;
use ldk_sample::node_api::Node as LdkNode;
use lightning::blinded_path::BlindedPath;
use lightning::offers::offer::Quantity;
use lndk::onion_messenger::MessengerUtilities;
use lndk::Signals;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::SystemTime;
use tokio::select;
use tokio::sync::mpsc;
use tokio::sync::mpsc::{Receiver, Sender};
Expand Down Expand Up @@ -36,7 +41,6 @@ async fn wait_to_receive_onion_message(
async fn check_for_message(ldk: LdkNode) -> LdkNode {
loop {
if ldk.onion_message_handler.messages.lock().unwrap().len() == 1 {
println!("MESSAGE: {:?}", ldk.onion_message_handler.messages);
return ldk;
}
sleep(Duration::from_secs(2)).await;
Expand Down Expand Up @@ -102,3 +106,87 @@ 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) =
common::setup_test_infrastructure(test_name).await;

// Here we'll produce a little network path:
//
// 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_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 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");

// 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(),
PathBuf::from_str(&lnd.cert_path).unwrap(),
PathBuf::from_str(&lnd.macaroon_path).unwrap(),
);
let (tx, rx): (Sender<u32>, Receiver<u32>) = mpsc::channel(1);
let signals = Signals {
shutdown: shutdown.clone(),
listener,
started: tx,
};

let lndk_cfg = lndk::Cfg {
lnd: lnd_cfg,
log_dir: Some(
lndk_dir
.join(format!("lndk-logs.txt"))
.to_str()
.unwrap()
.to_string(),
),
signals,
};

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

// Make sure lndk successfully sends the invoice_request.
let handler = lndk::OfferHandler::new(rx);
let messenger = lndk::LndkOnionMessenger::new(handler);
select! {
val = messenger.run(lndk_cfg) => {
panic!("lndk should not have completed first {:?}", val);
},
// We wait for ldk2 to receive the onion message.
res = messenger.offer_handler.pay_offer(offer.clone(), Some(20_000), Network::Regtest, client.clone(), blinded_path.clone(), Some(reply_path)) => {
assert!(res.is_ok());
shutdown.trigger();
ldk1.stop().await;
ldk2.stop().await;
}
}
}

0 comments on commit d932363

Please sign in to comment.