Skip to content

Commit

Permalink
offers: Build a reply path for invoice request
Browse files Browse the repository at this point in the history
  • Loading branch information
orbitalturtle committed Feb 1, 2024
1 parent ef332c8 commit 3e4cf01
Showing 1 changed file with 116 additions and 3 deletions.
119 changes: 116 additions & 3 deletions src/lndk_offers.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::lnd::{InvoicePayer, MessageSigner, PeerConnector};
use crate::lnd::{features_support_onion_messages, InvoicePayer, MessageSigner, PeerConnector};
use crate::OfferHandler;
use async_trait::async_trait;
use bitcoin::hashes::sha256::Hash;
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::schnorr::Signature;
use bitcoin::secp256k1::{Error as Secp256k1Error, PublicKey};
use bitcoin::secp256k1::{Error as Secp256k1Error, PublicKey, Secp256k1};
use futures::executor::block_on;
use lightning::blinded_path::BlindedPath;
use lightning::ln::channelmanager::PaymentId;
Expand All @@ -13,8 +13,10 @@ use lightning::offers::merkle::SignError;
use lightning::offers::offer::{Amount, Offer};
use lightning::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
use lightning::sign::EntropySource;
use log::error;
use std::error::Error;
use std::fmt::Display;
use std::str::FromStr;
use tokio::task;
use tonic_lnd::lnrpc::{
HtlcAttempt, LightningNode, ListPeersRequest, ListPeersResponse, QueryRoutesResponse, Route,
Expand Down Expand Up @@ -44,6 +46,10 @@ pub enum OfferError<Secp256k1Error> {
NodeAddressNotFound,
/// Unable to find or send to payment route.
RouteFailure(Status),
/// Cannot list peers.
ListPeersFailure(Status),
/// Failure to build a reply path.
BuildBlindedPathFailure,
}

impl Display for OfferError<Secp256k1Error> {
Expand All @@ -63,6 +69,8 @@ impl Display for OfferError<Secp256k1Error> {
OfferError::PeerConnectError(e) => write!(f, "Error connecting to peer: {e:?}"),
OfferError::NodeAddressNotFound => write!(f, "Couldn't get node address"),
OfferError::RouteFailure(e) => write!(f, "Error routing payment: {e:?}"),
OfferError::ListPeersFailure(e) => write!(f, "Error listing peers: {e:?}"),
OfferError::BuildBlindedPathFailure => write!(f, "Error building blinded path"),
}
}
}
Expand Down Expand Up @@ -208,6 +216,50 @@ impl OfferHandler {
.await
.unwrap()
}

// create_reply_path creates a blinded path to provide to the offer maker when requesting an
// invoice so they know where to send the invoice back to.
pub async fn create_reply_path(
&self,
mut connector: impl PeerConnector + std::marker::Send + 'static,
node_id: PublicKey,
) -> Result<BlindedPath, OfferError<Secp256k1Error>> {
// Find an introduction node for our blinded path.
let current_peers = connector.list_peers().await.map_err(|e| {
error!("Could not lookup current peers: {e}.");
OfferError::ListPeersFailure(e)
})?;

let mut intro_node = None;
for peer in current_peers.peers {
let pubkey = PublicKey::from_str(&peer.pub_key).unwrap();
let onion_support = features_support_onion_messages(&peer.features);
if onion_support {
intro_node = Some(pubkey);
}
}

let secp_ctx = Secp256k1::new();
if intro_node.is_none() {
Ok(
BlindedPath::one_hop_for_message(node_id, &self.messenger_utils, &secp_ctx)
.map_err(|_| {
error!("Could not create blinded path.");
OfferError::BuildBlindedPathFailure
})?,
)
} else {
Ok(BlindedPath::new_for_message(
&[intro_node.unwrap()],
&self.messenger_utils,
&secp_ctx,
)
.map_err(|_| {
error!("Could not create blinded path.");
OfferError::BuildBlindedPathFailure
}))?
}
}
}

// pay_invoice tries to pay the provided invoice.
Expand Down Expand Up @@ -388,11 +440,12 @@ impl InvoicePayer for Client {
mod tests {
use super::*;
use crate::MessengerUtilities;
use bitcoin::secp256k1::{Error as Secp256k1Error, KeyPair, Secp256k1, SecretKey};
use bitcoin::secp256k1::{Error as Secp256k1Error, KeyPair, SecretKey};
use core::convert::Infallible;
use lightning::offers::merkle::SignError;
use lightning::offers::offer::{OfferBuilder, Quantity};
use mockall::mock;
use std::collections::HashMap;
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use tokio::sync::mpsc;
Expand Down Expand Up @@ -749,4 +802,64 @@ mod tests {
.is_err()
);
}

#[tokio::test]
async fn test_create_reply_path() {
let mut connector_mock = MockTestPeerConnector::new();

connector_mock.expect_list_peers().returning(|| {
let feature = tonic_lnd::lnrpc::Feature {
..Default::default()
};
let mut feature_entry = HashMap::new();
feature_entry.insert(38, feature);

let peer = tonic_lnd::lnrpc::Peer {
pub_key: get_pubkey(),
features: feature_entry,
..Default::default()
};
Ok(ListPeersResponse { peers: vec![peer] })
});

let receiver_node_id = PublicKey::from_str(&get_pubkey()).unwrap();
let handler = OfferHandler::new();
assert!(handler
.create_reply_path(connector_mock, receiver_node_id)
.await
.is_ok())
}

#[tokio::test]
// Test that create_reply_path works fine when no suitable introduction node peer is found.
async fn test_create_reply_path_no_intro_node() {
let mut connector_mock = MockTestPeerConnector::new();

connector_mock
.expect_list_peers()
.returning(|| Ok(ListPeersResponse { peers: vec![] }));

let receiver_node_id = PublicKey::from_str(&get_pubkey()).unwrap();
let handler = OfferHandler::new();
assert!(handler
.create_reply_path(connector_mock, receiver_node_id)
.await
.is_ok())
}

#[tokio::test]
async fn test_create_reply_path_list_peers_error() {
let mut connector_mock = MockTestPeerConnector::new();

connector_mock
.expect_list_peers()
.returning(|| Err(Status::unknown("unknown error")));

let receiver_node_id = PublicKey::from_str(&get_pubkey()).unwrap();
let handler = OfferHandler::new();
assert!(handler
.create_reply_path(connector_mock, receiver_node_id)
.await
.is_err())
}
}

0 comments on commit 3e4cf01

Please sign in to comment.