Skip to content

Commit 0a6886e

Browse files
committed
Compact a BlindedPath's introduction node
Add a method to BlindedPath that given a network graph will compact the IntroductionNode as the DirectedShortChannelId variant. Call this method from DefaultMessageRouter so that Offer paths use the compact representation (along with reply paths). This leaves payment paths in Bolt12Invoice using the NodeId variant, as the compact representation isn't as useful there.
1 parent 4956ade commit 0a6886e

File tree

3 files changed

+68
-11
lines changed

3 files changed

+68
-11
lines changed

lightning/src/blinded_path/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::offers::invoice::BlindedPayInfo;
2121
use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
2222
use crate::sign::EntropySource;
2323
use crate::util::ser::{Readable, Writeable, Writer};
24+
use crate::util::scid_utils;
2425

2526
use crate::io;
2627
use crate::prelude::*;
@@ -217,6 +218,35 @@ impl BlindedPath {
217218
},
218219
}
219220
}
221+
222+
/// Attempts to a use a compact representation for the [`IntroductionNode`] by using a directed
223+
/// short channel id from a channel in `network_graph` leading to the introduction node.
224+
///
225+
/// While this may result in a smaller encoding, there is a trade off in that the path may
226+
/// become invalid if the channel is closed or hasn't been propagated via gossip. Therefore,
227+
/// calling this may not be suitable for long-lived blinded paths.
228+
pub fn use_compact_introduction_node(&mut self, network_graph: &ReadOnlyNetworkGraph) {
229+
if let IntroductionNode::NodeId(pubkey) = &self.introduction_node {
230+
let node_id = NodeId::from_pubkey(pubkey);
231+
if let Some(node_info) = network_graph.node(&node_id) {
232+
if let Some((scid, channel_info)) = node_info
233+
.channels
234+
.iter()
235+
.filter_map(|scid| network_graph.channel(*scid).map(|info| (*scid, info)))
236+
.min_by_key(|(scid, _)| scid_utils::block_from_scid(*scid))
237+
{
238+
let direction = if node_id == channel_info.node_one {
239+
Direction::NodeOne
240+
} else {
241+
debug_assert_eq!(node_id, channel_info.node_two);
242+
Direction::NodeTwo
243+
};
244+
self.introduction_node =
245+
IntroductionNode::DirectedShortChannelId(direction, scid);
246+
}
247+
}
248+
}
249+
}
220250
}
221251

222252
impl Writeable for BlindedPath {

lightning/src/ln/offers_tests.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
//! blinded paths are used.
4242
4343
use bitcoin::network::constants::Network;
44+
use bitcoin::secp256k1::PublicKey;
4445
use core::time::Duration;
4546
use crate::blinded_path::{BlindedPath, IntroductionNode};
4647
use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext};
@@ -133,6 +134,12 @@ fn announce_node_address<'a, 'b, 'c>(
133134
}
134135
}
135136

137+
fn resolve_introduction_node<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, path: &BlindedPath) -> PublicKey {
138+
path.public_introduction_node_id(&node.network_graph.read_only())
139+
.and_then(|node_id| node_id.as_pubkey().ok())
140+
.unwrap()
141+
}
142+
136143
fn route_bolt12_payment<'a, 'b, 'c>(
137144
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice
138145
) {
@@ -273,8 +280,9 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
273280
assert_ne!(offer.signing_pubkey(), Some(bob_id));
274281
assert!(!offer.paths().is_empty());
275282
for path in offer.paths() {
276-
assert_ne!(path.introduction_node, IntroductionNode::NodeId(bob_id));
277-
assert_ne!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
283+
let introduction_node_id = resolve_introduction_node(david, &path);
284+
assert_ne!(introduction_node_id, bob_id);
285+
assert_ne!(introduction_node_id, charlie_id);
278286
}
279287

280288
// Use a one-hop blinded path when Bob is announced and all his peers are Tor-only.
@@ -288,7 +296,8 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
288296
assert_ne!(offer.signing_pubkey(), Some(bob_id));
289297
assert!(!offer.paths().is_empty());
290298
for path in offer.paths() {
291-
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
299+
let introduction_node_id = resolve_introduction_node(david, &path);
300+
assert_eq!(introduction_node_id, bob_id);
292301
}
293302
}
294303

@@ -338,7 +347,8 @@ fn prefers_more_connected_nodes_in_blinded_paths() {
338347
assert_ne!(offer.signing_pubkey(), Some(bob_id));
339348
assert!(!offer.paths().is_empty());
340349
for path in offer.paths() {
341-
assert_eq!(path.introduction_node, IntroductionNode::NodeId(nodes[4].node.get_our_node_id()));
350+
let introduction_node_id = resolve_introduction_node(david, &path);
351+
assert_eq!(introduction_node_id, nodes[4].node.get_our_node_id());
342352
}
343353
}
344354

@@ -388,7 +398,9 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
388398
assert_ne!(offer.signing_pubkey(), Some(alice_id));
389399
assert!(!offer.paths().is_empty());
390400
for path in offer.paths() {
391-
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
401+
let introduction_node_id = resolve_introduction_node(david, &path);
402+
assert_eq!(introduction_node_id, bob_id);
403+
assert!(matches!(path.introduction_node, IntroductionNode::DirectedShortChannelId(..)));
392404
}
393405

394406
let payment_id = PaymentId([1; 32]);
@@ -415,9 +427,11 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
415427
payer_note_truncated: None,
416428
},
417429
});
430+
let introduction_node_id = resolve_introduction_node(alice, &reply_path);
418431
assert_eq!(invoice_request.amount_msats(), None);
419432
assert_ne!(invoice_request.payer_id(), david_id);
420-
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
433+
assert_eq!(introduction_node_id, charlie_id);
434+
assert!(matches!(reply_path.introduction_node, IntroductionNode::DirectedShortChannelId(..)));
421435

422436
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
423437
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
@@ -489,7 +503,9 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
489503
assert_ne!(refund.payer_id(), david_id);
490504
assert!(!refund.paths().is_empty());
491505
for path in refund.paths() {
492-
assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
506+
let introduction_node_id = resolve_introduction_node(alice, &path);
507+
assert_eq!(introduction_node_id, charlie_id);
508+
assert!(matches!(path.introduction_node, IntroductionNode::DirectedShortChannelId(..)));
493509
}
494510
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
495511

@@ -545,7 +561,9 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
545561
assert_ne!(offer.signing_pubkey(), Some(alice_id));
546562
assert!(!offer.paths().is_empty());
547563
for path in offer.paths() {
548-
assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
564+
let introduction_node_id = resolve_introduction_node(bob, &path);
565+
assert_eq!(introduction_node_id, alice_id);
566+
assert!(matches!(path.introduction_node, IntroductionNode::DirectedShortChannelId(..)));
549567
}
550568

551569
let payment_id = PaymentId([1; 32]);
@@ -564,9 +582,11 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
564582
payer_note_truncated: None,
565583
},
566584
});
585+
let introduction_node_id = resolve_introduction_node(alice, &reply_path);
567586
assert_eq!(invoice_request.amount_msats(), None);
568587
assert_ne!(invoice_request.payer_id(), bob_id);
569-
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(bob_id));
588+
assert_eq!(introduction_node_id, bob_id);
589+
assert!(matches!(reply_path.introduction_node, IntroductionNode::DirectedShortChannelId(..)));
570590

571591
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
572592
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
@@ -614,7 +634,9 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
614634
assert_ne!(refund.payer_id(), bob_id);
615635
assert!(!refund.paths().is_empty());
616636
for path in refund.paths() {
617-
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
637+
let introduction_node_id = resolve_introduction_node(alice, &path);
638+
assert_eq!(introduction_node_id, bob_id);
639+
assert!(matches!(path.introduction_node, IntroductionNode::DirectedShortChannelId(..)));
618640
}
619641
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
620642

lightning/src/onion_message/messenger.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ where
445445
.take(MAX_PATHS)
446446
.collect::<Result<Vec<_>, _>>();
447447

448-
match paths {
448+
let mut paths = match paths {
449449
Ok(paths) if !paths.is_empty() => Ok(paths),
450450
_ => {
451451
if is_recipient_announced {
@@ -455,7 +455,12 @@ where
455455
Err(())
456456
}
457457
},
458+
}?;
459+
for path in &mut paths {
460+
path.use_compact_introduction_node(&network_graph);
458461
}
462+
463+
Ok(paths)
459464
}
460465
}
461466

0 commit comments

Comments
 (0)