Skip to content

Commit 7d1745e

Browse files
committed
BlindedPath with unannounced introduction node
When creating blinded paths for receiving onion messages, allow using the recipient's only peer as the introduction node when the recipient is unannounced. This allows for sending messages without going through an intermediary, which is useful for testing or when only connected to an LSP with an empty NetworkGraph.
1 parent 2498864 commit 7d1745e

File tree

4 files changed

+60
-23
lines changed

4 files changed

+60
-23
lines changed

lightning/src/blinded_path/message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use core::mem;
3030
use core::ops::Deref;
3131

3232
/// An intermediate node, and possibly a short channel id leading to the next node.
33-
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
33+
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
3434
pub struct ForwardNode {
3535
/// This node's pubkey.
3636
pub node_id: PublicKey,

lightning/src/ln/offers_tests.rs

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -950,43 +950,75 @@ fn pays_bolt12_invoice_asynchronously() {
950950
);
951951
}
952952

953-
/// Fails creating an offer when a blinded path cannot be created without exposing the node's id.
953+
/// Checks that an offer can be created using an unannounced node as a blinded path's introduction
954+
/// node. This is only preferred if there are no other options which may indicated either the offer
955+
/// is intended for the unannounced node or that the node is actually announced (e.g., an LSP) but
956+
/// the recipient doesn't have a network graph.
954957
#[test]
955-
fn fails_creating_offer_without_blinded_paths() {
958+
fn creates_offer_with_blinded_path_using_unannounced_introduction_node() {
956959
let chanmon_cfgs = create_chanmon_cfgs(2);
957960
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
958961
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
959962
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
960963

961964
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
962965

963-
match nodes[0].node.create_offer_builder(None) {
964-
Ok(_) => panic!("Expected error"),
965-
Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths),
966+
let alice = &nodes[0];
967+
let alice_id = alice.node.get_our_node_id();
968+
let bob = &nodes[1];
969+
let bob_id = bob.node.get_our_node_id();
970+
971+
let offer = alice.node
972+
.create_offer_builder(None).unwrap()
973+
.amount_msats(10_000_000)
974+
.build().unwrap();
975+
assert_ne!(offer.signing_pubkey(), Some(alice_id));
976+
assert!(!offer.paths().is_empty());
977+
for path in offer.paths() {
978+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
966979
}
980+
981+
let payment_id = PaymentId([1; 32]);
982+
bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap();
983+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
984+
985+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
986+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
987+
988+
let (_, reply_path) = extract_invoice_request(alice, &onion_message);
989+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(alice_id));
967990
}
968991

969-
/// Fails creating a refund when a blinded path cannot be created without exposing the node's id.
992+
/// Checks that a refund can be created using an unannounced node as a blinded path's introduction
993+
/// node. This is only preferred if there are no other options which may indicated either the refund
994+
/// is intended for the unannounced node or that the node is actually announced (e.g., an LSP) but
995+
/// the sender doesn't have a network graph.
970996
#[test]
971-
fn fails_creating_refund_without_blinded_paths() {
997+
fn creates_refund_with_blinded_path_using_unannounced_introduction_node() {
972998
let chanmon_cfgs = create_chanmon_cfgs(2);
973999
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
9741000
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
9751001
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
9761002

9771003
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
9781004

1005+
let alice = &nodes[0];
1006+
let alice_id = alice.node.get_our_node_id();
1007+
let bob = &nodes[1];
1008+
let bob_id = bob.node.get_our_node_id();
1009+
9791010
let absolute_expiry = Duration::from_secs(u64::MAX);
9801011
let payment_id = PaymentId([1; 32]);
981-
982-
match nodes[0].node.create_refund_builder(
983-
10_000, absolute_expiry, payment_id, Retry::Attempts(0), None
984-
) {
985-
Ok(_) => panic!("Expected error"),
986-
Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths),
1012+
let refund = bob.node
1013+
.create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None)
1014+
.unwrap()
1015+
.build().unwrap();
1016+
assert_ne!(refund.payer_id(), bob_id);
1017+
assert!(!refund.paths().is_empty());
1018+
for path in refund.paths() {
1019+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
9871020
}
988-
989-
assert!(nodes[0].node.list_recent_payments().is_empty());
1021+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
9901022
}
9911023

9921024
/// Fails creating or paying an offer when a blinded path cannot be created because no peers are
@@ -1165,8 +1197,7 @@ fn fails_sending_invoice_with_unsupported_chain_for_refund() {
11651197
}
11661198
}
11671199

1168-
/// Fails creating an invoice request when a blinded reply path cannot be created without exposing
1169-
/// the node's id.
1200+
/// Fails creating an invoice request when a blinded reply path cannot be created.
11701201
#[test]
11711202
fn fails_creating_invoice_request_without_blinded_reply_path() {
11721203
let chanmon_cfgs = create_chanmon_cfgs(6);
@@ -1183,7 +1214,7 @@ fn fails_creating_invoice_request_without_blinded_reply_path() {
11831214
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
11841215

11851216
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
1186-
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1217+
disconnect_peers(david, &[bob, charlie, &nodes[4], &nodes[5]]);
11871218

11881219
let offer = alice.node
11891220
.create_offer_builder(None).unwrap()

lightning/src/onion_message/functional_tests.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,9 @@ fn async_response_with_reply_path_fails() {
492492
let path_id = Some([2; 32]);
493493
let reply_path = BlindedPath::new_for_message(&[], bob.node_id, &*bob.entropy_source, &secp_ctx).unwrap();
494494

495-
// Alice tries to asynchronously respond to Bob, but fails because the nodes are unannounced.
496-
// Therefore, the reply_path cannot be used for the response.
495+
// Alice tries to asynchronously respond to Bob, but fails because the nodes are unannounced and
496+
// disconnected. Thus, a reply path could no be created for the response.
497+
disconnect_peers(alice, bob);
497498
let responder = Responder::new(reply_path, path_id);
498499
alice.custom_message_handler.expect_message_and_response(message.clone());
499500
let response_instruction = alice.custom_message_handler.handle_custom_message(message, Some(responder));

lightning/src/onion_message/messenger.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ where
489489
}
490490

491491
fn create_blinded_paths_from_iter<
492-
I: Iterator<Item = ForwardNode>,
492+
I: ExactSizeIterator<Item = ForwardNode>,
493493
T: secp256k1::Signing + secp256k1::Verification
494494
>(
495495
&self, recipient: PublicKey, peers: I, secp_ctx: &Secp256k1<T>, compact_paths: bool
@@ -505,13 +505,18 @@ where
505505
let is_recipient_announced =
506506
network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient));
507507

508+
let has_one_peer = peers.len() == 1;
508509
let mut peer_info = peers
509-
// Limit to peers with announced channels
510+
// Limit to peers with announced channels unless the recipient is unannounced.
510511
.filter_map(|peer|
511512
network_graph
512513
.node(&NodeId::from_pubkey(&peer.node_id))
513514
.filter(|info| info.channels.len() >= MIN_PEER_CHANNELS)
514515
.map(|info| (peer, info.is_tor_only(), info.channels.len()))
516+
// Allow messages directly with the only peer when unannounced.
517+
.or_else(|| (!is_recipient_announced && has_one_peer)
518+
.then(|| (peer, false, 0))
519+
)
515520
)
516521
// Exclude Tor-only nodes when the recipient is announced.
517522
.filter(|(_, is_tor_only, _)| !(*is_tor_only && is_recipient_announced))

0 commit comments

Comments
 (0)