Skip to content
Open
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
322 changes: 160 additions & 162 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions config_spec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ doc = "LND GRPC address, kindly note that the address must start with https:// i
name = "cert"
type = "std::path::PathBuf"
optional = false
doc = "The path to LND tls certificate file. Note: the abosolute tls certificate file path is required here."
doc = "The path to LND tls certificate file. Note: the absolute tls certificate file path is required here."


[[param]]
name = "macaroon"
type = "std::path::PathBuf"
optional = false
doc = "The path to LND macaroon file. Note: the abosolute macaroon file path is required here."
doc = "The path to LND macaroon file. Note: the absolute macaroon file path is required here."

[[param]]
name = "log_dir"
type = "String"
doc = "The path to the lndk log file"

[[param]]
name = "auto_connect"
type = "bool"
default = "false"
doc = "When starting up lndk, auto connects to some random nodes that support onion messaging to help forward onion messages."
17 changes: 16 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::lnd::{
features_support_onion_messages, get_lnd_client, string_to_network, LndCfg, LndNodeSigner,
};
use crate::lndk_offers::{OfferError, PayInvoiceParams};
use crate::onion_messenger::MessengerUtilities;
use crate::onion_messenger::{connect_to_onion_peers, MessengerUtilities};
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{Error as Secp256k1Error, PublicKey, Secp256k1};
use home::home_dir;
Expand Down Expand Up @@ -41,9 +41,13 @@ use triggered::{Listener, Trigger};

static INIT: Once = Once::new();

/// The number of peers we'll target to connect to in auto connect mode.
const NUM_PEER_TARGET: u8 = 3;

pub struct Cfg {
pub lnd: LndCfg,
pub log_dir: Option<String>,
pub auto_connect: bool,
pub signals: LifecycleSignals,
}

Expand Down Expand Up @@ -153,6 +157,17 @@ impl LndkOnionMessenger {
peer_support.insert(pubkey, onion_support);
}

// If the auto connect option is turned on and we aren't connected to the targeted number of peers
// that support onion messaging, we'll automatically connect to some.
if args.auto_connect && peer_support.len() < NUM_PEER_TARGET.into() {
let peer_num = NUM_PEER_TARGET - u8::try_from(peer_support.len()).unwrap();
connect_to_onion_peers(client.clone(), peer_num, peer_support.clone(), pubkey)
.await
.map_err(|e| {
error!("Could not connect to peers: {e}.");
})?;
}

// Create an onion messenger that depends on LND's signer client and consume related events.
let mut node_client = client.signer().clone();
let node_signer = LndNodeSigner::new(pubkey, &mut node_client);
Expand Down
10 changes: 7 additions & 3 deletions src/lnd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::path::PathBuf;
use tonic_lnd::lnrpc::{HtlcAttempt, LightningNode, ListPeersResponse, QueryRoutesResponse, Route};
use tonic_lnd::lnrpc::{
ChannelGraph, HtlcAttempt, LightningNode, ListPeersResponse, QueryRoutesResponse, Route,
};
use tonic_lnd::signrpc::KeyLocator;
use tonic_lnd::tonic::Status;
use tonic_lnd::{Client, ConnectError};

const ONION_MESSAGES_REQUIRED: u32 = 38;
pub(crate) const ONION_MESSAGES_REQUIRED: u32 = 38;
pub(crate) const ONION_MESSAGES_OPTIONAL: u32 = 39;

/// get_lnd_client connects to LND's grpc api using the config provided, blocking until a connection is established.
Expand Down Expand Up @@ -204,12 +206,14 @@ pub trait MessageSigner {
) -> Result<InvoiceRequest, OfferError<bitcoin::secp256k1::Error>>;
}

/// PeerConnector provides a layer of abstraction over the LND API for connecting to a peer.
/// PeerConnector provides a layer of abstraction over the LND API for browsing the network graph or connecting to
/// a peer.
#[async_trait]
pub trait PeerConnector {
async fn list_peers(&mut self) -> Result<ListPeersResponse, Status>;
async fn connect_peer(&mut self, node_id: String, addr: String) -> Result<(), Status>;
async fn get_node_info(&mut self, pub_key: String) -> Result<Option<LightningNode>, Status>;
async fn describe_graph(&mut self) -> Result<ChannelGraph, Status>;
}

/// InvoicePayer provides a layer of abstraction over the LND API for paying for a BOLT 12 invoice.
Expand Down
16 changes: 14 additions & 2 deletions src/lndk_offers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use std::str::FromStr;
use tokio::sync::mpsc::Receiver;
use tokio::task;
use tonic_lnd::lnrpc::{
GetInfoRequest, HtlcAttempt, LightningNode, ListPeersRequest, ListPeersResponse,
ChannelGraph, GetInfoRequest, HtlcAttempt, LightningNode, ListPeersRequest, ListPeersResponse,
QueryRoutesResponse, Route,
};
use tonic_lnd::routerrpc::TrackPaymentRequest;
Expand Down Expand Up @@ -217,7 +217,7 @@ impl OfferHandler {
/// 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. We try to find a peer that we're
/// connected to with onion messaging support that we can use to form a blinded path,
/// otherwise we creae a blinded path directly to ourselves.
/// otherwise we create a blinded path directly to ourselves.
pub async fn create_reply_path(
&self,
mut connector: impl PeerConnector + std::marker::Send + 'static,
Expand Down Expand Up @@ -438,6 +438,17 @@ impl PeerConnector for Client {
.await
.map(|resp| resp.into_inner().node)
}

async fn describe_graph(&mut self) -> Result<ChannelGraph, Status> {
let req = tonic_lnd::lnrpc::ChannelGraphRequest {
include_unannounced: false,
};

self.lightning()
.describe_graph(req)
.await
.map(|resp| resp.into_inner())
}
}

#[async_trait]
Expand Down Expand Up @@ -664,6 +675,7 @@ mod tests {
async fn list_peers(&mut self) -> Result<ListPeersResponse, Status>;
async fn get_node_info(&mut self, pub_key: String) -> Result<Option<LightningNode>, Status>;
async fn connect_peer(&mut self, node_id: String, addr: String) -> Result<(), Status>;
async fn describe_graph(&mut self) -> Result<ChannelGraph, Status>;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ async fn main() -> Result<(), ()> {
let args = Cfg {
lnd: lnd_args,
log_dir: config.log_dir,
auto_connect: config.auto_connect,
signals,
};

Expand Down
139 changes: 134 additions & 5 deletions src/onion_messenger.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::clock::TokioClock;
use crate::lnd::{features_support_onion_messages, ONION_MESSAGES_OPTIONAL};
use crate::lnd::{
features_support_onion_messages, PeerConnector, ONION_MESSAGES_OPTIONAL,
ONION_MESSAGES_REQUIRED,
};
use crate::rate_limit::{RateLimiter, TokenLimiter};
use crate::{LifecycleSignals, LndkOnionMessenger};
use async_trait::async_trait;
Expand Down Expand Up @@ -31,7 +34,7 @@ use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::{select, time, time::Duration, time::Interval};
use tonic_lnd::{
lnrpc::peer_event::EventType::PeerOffline, lnrpc::peer_event::EventType::PeerOnline,
lnrpc::CustomMessage, lnrpc::PeerEvent, lnrpc::SendCustomMessageRequest,
lnrpc::ChannelGraph, lnrpc::CustomMessage, lnrpc::PeerEvent, lnrpc::SendCustomMessageRequest,
lnrpc::SendCustomMessageResponse, tonic::Status, LightningClient,
};
use triggered::Listener;
Expand Down Expand Up @@ -299,6 +302,65 @@ async fn lookup_onion_support(pubkey: &PublicKey, client: &mut tonic_lnd::Lightn
}
}

/// connect_to_onion_peers picks a few random nodes from the network graph that support onion messenging, then
Copy link
Contributor

Choose a reason for hiding this comment

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

messenging -> messaging or messages ?

/// connects to them.
pub(crate) async fn connect_to_onion_peers(
mut connector: impl PeerConnector + std::marker::Send + 'static,
target_peers: u8,
current_peers: HashMap<PublicKey, bool>,
lnd_key: PublicKey,
) -> Result<(), Status> {
let graph = connector.describe_graph().await?;

let onion_peers = get_onion_peers(graph, target_peers, current_peers, lnd_key).await;
//for i in 0..onion_peers.len() {
for peer in onion_peers.iter() {
connector
.connect_peer(peer.0.clone(), peer.1.clone())
.await?
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the connect peer API return an error if LND fails to connect to a peer?
In my testing, LNDK doesn't print error logs on connection failure.

}

Ok(())
}

/// get_onion_peers picks a few onion-messenging-supporting nodes from the network graph.
Copy link
Contributor

Choose a reason for hiding this comment

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

messenging -> messaging or messages ?

async fn get_onion_peers(
graph: ChannelGraph,
target_peers: u8,
current_peers: HashMap<PublicKey, bool>,
lnd_key: PublicKey,
) -> Vec<(String, String)> {
let mut onion_peers = Vec::new();
let mut num = 0;
for node in graph.nodes.iter() {
if node.features.contains_key(&ONION_MESSAGES_REQUIRED)
|| node.features.contains_key(&ONION_MESSAGES_OPTIONAL)
{
// We don't want to connect to ourself.
if node.pub_key == lnd_key.to_string() {
continue;
}

if current_peers
.get(&PublicKey::from_str(&node.pub_key).unwrap())
.is_some()
{
continue;
}

if !node.addresses.is_empty() {
onion_peers.push((node.pub_key.clone(), node.addresses[0].addr.clone()));
num += 1;
}
if num >= target_peers {
break;
}
}
}

onion_peers
}

#[derive(Debug)]
/// ProducerError represents the exit of a producing loop.
enum ProducerError {
Expand Down Expand Up @@ -589,14 +651,14 @@ async fn consume_messenger_events(
.map_err(|_| ConsumerError::OnionMessengerFailure)?;

// In addition to keeping the onion messenger up to date with the latest peers, we need to keep our
// local version up to date so we send outgoing OMs all of our peers.
// local version up to date so we send outgoing onion messages to all of our peers.
rate_limiter.peer_connected(pubkey);
}
MessengerEvents::PeerDisconnected(pubkey) => {
onion_messenger.peer_disconnected(&pubkey);

// In addition to keeping the onion messenger up to date with the latest peers, we need to keep our
// local version up to date so we send outgoing OMs to our correct peers.
// local version up to date so we send outgoing onion messages to our correct peers.
rate_limiter.peer_disconnected(pubkey);
}
MessengerEvents::IncomingMessage(pubkey, onion_message) => {
Expand Down Expand Up @@ -650,7 +712,7 @@ impl SendCustomMessage for CustomMessenger {
}
}

/// produce_outgoing_message_events is produce for producing outgoing message events at a regular interval.
/// produce_outgoing_message_events produces outgoing message events at a regular interval.
///
/// Note that this function *must* send an exit error to the Sender provided on all exit-cases, so that upstream
/// consumers know to exit as well. Failures related to sending events are an exception, as failure to send indicates
Expand Down Expand Up @@ -728,6 +790,7 @@ mod tests {
use mockall::mock;
use std::io::Cursor;
use tokio::sync::mpsc::channel;
use tonic_lnd::lnrpc::{Feature, LightningNode, NodeAddress};

/// Produces an OnionMessage that can be used for tests. We need to manually write individual bytes because onion
/// messages in LDK can only be created using read/write impls that deal with raw bytes (since some other fields
Expand Down Expand Up @@ -1129,4 +1192,70 @@ mod tests {
.await
.is_ok());
}

#[tokio::test]
async fn test_get_onion_peers() {
let feature_value = Feature {
..Default::default()
};
let mut features_optional = HashMap::new();
features_optional.insert(ONION_MESSAGES_OPTIONAL, feature_value.clone());
let mut features_required = HashMap::new();
features_required.insert(ONION_MESSAGES_REQUIRED, feature_value);

let address = NodeAddress {
network: String::from("tcp"),
addr: String::from("http://127.0.0.1:9735"),
};
let mut addresses = Vec::new();
addresses.push(address);

let mut nodes = Vec::new();
let node1 = LightningNode {
features: features_optional,
addresses: addresses.clone(),
pub_key: pubkey(2).to_string(),
..Default::default()
};
let node2 = LightningNode {
features: features_required,
addresses: addresses.clone(),
pub_key: pubkey(3).to_string(),
..Default::default()
};
let node3 = LightningNode {
features: HashMap::new(),
addresses: addresses,
..Default::default()
};
nodes.push(node1);
nodes.push(node2.clone());
nodes.push(node3);

let graph = ChannelGraph {
nodes: nodes.clone(),
..Default::default()
};

let current_peers = HashMap::new();
let pk_1 = pubkey(1);

// Make sure get_onion_peers correctly only identifies the two nodes that advertise onion messaging support.
let onion_peers = get_onion_peers(graph, 3, current_peers.clone(), pk_1.clone()).await;
assert!(onion_peers.len() == 2);

// Now let's add a couple more onion-message-supporting nodes to our graph. If there's four such nodes
// in the graph, make sure get_onion_peers only grabs three of them (since that's how many we request
// by default).
nodes.push(node2.clone());
nodes.push(node2);

let graph = ChannelGraph {
nodes: nodes,
..Default::default()
};

let onion_peers = get_onion_peers(graph, 3, current_peers, pk_1).await;
assert!(onion_peers.len() == 3);
}
}
27 changes: 25 additions & 2 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use bitcoind::{get_available_port, BitcoinD, Conf, ConnectParams};
use chrono::Utc;
use ldk_sample::config::LdkUserInfo;
use ldk_sample::node_api::Node as LdkNode;
use ldk_sample::start_ldk;
use lightning::util::logger::Level;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf;
Expand Down Expand Up @@ -67,8 +68,8 @@ pub async fn setup_test_infrastructure(
node_num: 2,
};

let ldk1 = ldk_sample::start_ldk(ldk1_config, test_name).await;
let ldk2 = ldk_sample::start_ldk(ldk2_config, test_name).await;
let ldk1 = start_ldk(ldk1_config, test_name).await;
let ldk2 = start_ldk(ldk2_config, test_name).await;

(bitcoind, lnd, ldk1, ldk2, lndk_test_dir)
}
Expand Down Expand Up @@ -360,6 +361,28 @@ impl LndNode {
resp
}

pub async fn list_peers(&mut self) -> tonic_lnd::lnrpc::ListPeersResponse {
let list_req = tonic_lnd::lnrpc::ListPeersRequest {
..Default::default()
};

let resp = if let Some(client) = self.client.clone() {
let make_request = || async {
client
.clone()
.lightning()
.list_peers(list_req.clone())
.await
};
let resp = test_utils::retry_async(make_request, String::from("list_peers"));
resp.await.unwrap()
} else {
panic!("No client")
};

resp
}

// wait_for_chain_sync waits until we're synced to chain according to the get_info response.
// We'll timeout if it takes too long.
pub async fn wait_for_chain_sync(&mut self) {
Expand Down
Loading