Skip to content

Commit d2a84c0

Browse files
committed
Update DefaultMessageRouter to always create compact blinded paths
Reasoning: This change aligns `DefaultMessageRouter`'s default behavior with the most common and practical usage: generating compact blinded paths for BOLT12 offers. While configurability is important, most users won't read all documentation and will rely on defaults that "just work." In line with this PR's principle— "One `MessageRouter`, one type of `BlindedPath`"—the previous default would silently produce full blinded paths, even when a compact one would have been more appropriate. In typical setups (e.g., a user connected to a single LSP), the SCID alias remains stable for the channel's lifetime. Compact paths are not only sufficient in this case, but also result in smaller, more efficient offers. And if the alias becomes invalid, a pubkey-based path wouldn't help either—so compact remains the better default. In brief: This commit makes the default behavior match what users actually want. Thanks to [@TheBlueMatt](https://github.com/TheBlueMatt) for the original reasoning. **Discussion link:** [lightningdevkit#3246 (pull request review)](lightningdevkit#3246 (review))
1 parent ecfb791 commit d2a84c0

File tree

3 files changed

+52
-34
lines changed

3 files changed

+52
-34
lines changed

lightning-dns-resolver/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ mod test {
456456
#[tokio::test]
457457
async fn end_to_end_test() {
458458
let chanmon_cfgs = create_chanmon_cfgs(2);
459-
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
459+
let node_cfgs = create_node_cfgs_with_node_id_message_router(2, &chanmon_cfgs);
460460
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
461461
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
462462

lightning/src/ln/offers_tests.rs

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ fn resolve_introduction_node<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, path: &Blinded
149149
.unwrap()
150150
}
151151

152+
fn check_compact_path_introduction_node<'a, 'b, 'c>(
153+
path: &BlindedMessagePath,
154+
lookup_node: &Node<'a, 'b, 'c>,
155+
expected_introduction_node: PublicKey,
156+
) -> bool {
157+
let introduction_node_id = resolve_introduction_node(lookup_node, path);
158+
introduction_node_id == expected_introduction_node
159+
&& matches!(path.introduction_node(), IntroductionNode::DirectedShortChannelId(..))
160+
}
161+
152162
fn route_bolt12_payment<'a, 'b, 'c>(
153163
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice
154164
) {
@@ -406,7 +416,7 @@ fn creates_short_lived_offer() {
406416
#[test]
407417
fn creates_long_lived_offer() {
408418
let chanmon_cfgs = create_chanmon_cfgs(2);
409-
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
419+
let node_cfgs = create_node_cfgs_with_node_id_message_router(2, &chanmon_cfgs);
410420
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
411421
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
412422

@@ -470,7 +480,7 @@ fn creates_short_lived_refund() {
470480
#[test]
471481
fn creates_long_lived_refund() {
472482
let chanmon_cfgs = create_chanmon_cfgs(2);
473-
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
483+
let node_cfgs = create_node_cfgs_with_node_id_message_router(2, &chanmon_cfgs);
474484
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
475485
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
476486

@@ -539,7 +549,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
539549
assert_ne!(offer.issuer_signing_pubkey(), Some(alice_id));
540550
assert!(!offer.paths().is_empty());
541551
for path in offer.paths() {
542-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(bob_id));
552+
assert!(check_compact_path_introduction_node(&path, alice, bob_id));
543553
}
544554

545555
let payment_id = PaymentId([1; 32]);
@@ -569,7 +579,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
569579
});
570580
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
571581
assert_ne!(invoice_request.payer_signing_pubkey(), david_id);
572-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
582+
assert!(check_compact_path_introduction_node(&reply_path, bob, charlie_id));
573583

574584
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
575585
charlie.onion_messenger.handle_onion_message(alice_id, &onion_message);
@@ -588,10 +598,8 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
588598
// to Alice when she's handling the message. Therefore, either Bob or Charlie could
589599
// serve as the introduction node for the reply path back to Alice.
590600
assert!(
591-
matches!(
592-
reply_path.introduction_node(),
593-
&IntroductionNode::NodeId(node_id) if node_id == bob_id || node_id == charlie_id,
594-
)
601+
check_compact_path_introduction_node(&reply_path, david, bob_id) ||
602+
check_compact_path_introduction_node(&reply_path, david, charlie_id)
595603
);
596604

597605
route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
@@ -650,7 +658,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
650658
assert_ne!(refund.payer_signing_pubkey(), david_id);
651659
assert!(!refund.paths().is_empty());
652660
for path in refund.paths() {
653-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
661+
assert!(check_compact_path_introduction_node(&path, david, charlie_id));
654662
}
655663
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
656664

@@ -674,8 +682,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
674682
for path in invoice.payment_paths() {
675683
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(bob_id));
676684
}
677-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(bob_id));
678-
685+
assert!(check_compact_path_introduction_node(&reply_path, alice, bob_id));
679686

680687
route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
681688
expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id);
@@ -708,7 +715,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
708715
assert_ne!(offer.issuer_signing_pubkey(), Some(alice_id));
709716
assert!(!offer.paths().is_empty());
710717
for path in offer.paths() {
711-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
718+
assert!(check_compact_path_introduction_node(&path, bob, alice_id));
712719
}
713720

714721
let payment_id = PaymentId([1; 32]);
@@ -730,7 +737,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
730737
});
731738
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
732739
assert_ne!(invoice_request.payer_signing_pubkey(), bob_id);
733-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(bob_id));
740+
assert!(check_compact_path_introduction_node(&reply_path, alice, bob_id));
734741

735742
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
736743
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);
@@ -742,7 +749,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
742749
for path in invoice.payment_paths() {
743750
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
744751
}
745-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(alice_id));
752+
assert!(check_compact_path_introduction_node(&reply_path, bob, alice_id));
746753

747754
route_bolt12_payment(bob, &[alice], &invoice);
748755
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
@@ -779,7 +786,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
779786
assert_ne!(refund.payer_signing_pubkey(), bob_id);
780787
assert!(!refund.paths().is_empty());
781788
for path in refund.paths() {
782-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(bob_id));
789+
assert!(check_compact_path_introduction_node(&path, alice, bob_id));
783790
}
784791
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
785792

@@ -798,7 +805,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
798805
for path in invoice.payment_paths() {
799806
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
800807
}
801-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(alice_id));
808+
assert!(check_compact_path_introduction_node(&reply_path, bob, alice_id));
802809

803810
route_bolt12_payment(bob, &[alice], &invoice);
804811
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
@@ -956,7 +963,7 @@ fn send_invoice_requests_with_distinct_reply_path() {
956963
assert_ne!(offer.issuer_signing_pubkey(), Some(alice_id));
957964
assert!(!offer.paths().is_empty());
958965
for path in offer.paths() {
959-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(bob_id));
966+
assert!(check_compact_path_introduction_node(&path, alice, bob_id));
960967
}
961968

962969
let payment_id = PaymentId([1; 32]);
@@ -975,7 +982,7 @@ fn send_invoice_requests_with_distinct_reply_path() {
975982
alice.onion_messenger.handle_onion_message(bob_id, &onion_message);
976983

977984
let (_, reply_path) = extract_invoice_request(alice, &onion_message);
978-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
985+
assert!(check_compact_path_introduction_node(&reply_path, alice, charlie_id));
979986

980987
// Send, extract and verify the second Invoice Request message
981988
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
@@ -985,7 +992,7 @@ fn send_invoice_requests_with_distinct_reply_path() {
985992
alice.onion_messenger.handle_onion_message(bob_id, &onion_message);
986993

987994
let (_, reply_path) = extract_invoice_request(alice, &onion_message);
988-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(nodes[6].node.get_our_node_id()));
995+
assert!(check_compact_path_introduction_node(&reply_path, alice, nodes[6].node.get_our_node_id()));
989996
}
990997

991998
/// This test checks that when multiple potential introduction nodes are available for the payee,
@@ -1040,7 +1047,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
10401047
.build().unwrap();
10411048
assert_ne!(refund.payer_signing_pubkey(), alice_id);
10421049
for path in refund.paths() {
1043-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(bob_id));
1050+
assert!(check_compact_path_introduction_node(&path, alice, bob_id));
10441051
}
10451052
expect_recent_payment!(alice, RecentPaymentDetails::AwaitingInvoice, payment_id);
10461053

@@ -1056,7 +1063,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
10561063
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
10571064

10581065
let (_, reply_path) = extract_invoice(alice, &onion_message);
1059-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
1066+
assert!(check_compact_path_introduction_node(&reply_path, alice, charlie_id));
10601067

10611068
// Send, extract and verify the second Invoice Request message
10621069
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
@@ -1065,7 +1072,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
10651072
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
10661073

10671074
let (_, reply_path) = extract_invoice(alice, &onion_message);
1068-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(nodes[6].node.get_our_node_id()));
1075+
assert!(check_compact_path_introduction_node(&reply_path, alice, nodes[6].node.get_our_node_id()));
10691076
}
10701077

10711078
/// Verifies that the invoice request message can be retried if it fails to reach the
@@ -1091,7 +1098,7 @@ fn creates_and_pays_for_offer_with_retry() {
10911098
assert_ne!(offer.issuer_signing_pubkey(), Some(alice_id));
10921099
assert!(!offer.paths().is_empty());
10931100
for path in offer.paths() {
1094-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
1101+
assert!(check_compact_path_introduction_node(&path, bob, alice_id));
10951102
}
10961103
let payment_id = PaymentId([1; 32]);
10971104
bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), RouteParametersConfig::default()).unwrap();
@@ -1119,7 +1126,7 @@ fn creates_and_pays_for_offer_with_retry() {
11191126
});
11201127
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
11211128
assert_ne!(invoice_request.payer_signing_pubkey(), bob_id);
1122-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(bob_id));
1129+
assert!(check_compact_path_introduction_node(&reply_path, alice, bob_id));
11231130
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
11241131
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);
11251132

@@ -1391,7 +1398,7 @@ fn fails_authentication_when_handling_invoice_request() {
13911398
assert_ne!(offer.issuer_signing_pubkey(), Some(alice_id));
13921399
assert!(!offer.paths().is_empty());
13931400
for path in offer.paths() {
1394-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(bob_id));
1401+
assert!(check_compact_path_introduction_node(&path, alice, bob_id));
13951402
}
13961403

13971404
let invalid_path = alice.node
@@ -1400,7 +1407,7 @@ fn fails_authentication_when_handling_invoice_request() {
14001407
.build().unwrap()
14011408
.paths().first().unwrap()
14021409
.clone();
1403-
assert_eq!(invalid_path.introduction_node(), &IntroductionNode::NodeId(bob_id));
1410+
assert!(check_compact_path_introduction_node(&invalid_path, alice, bob_id));
14041411

14051412
// Send the invoice request directly to Alice instead of using a blinded path.
14061413
let payment_id = PaymentId([1; 32]);
@@ -1421,7 +1428,7 @@ fn fails_authentication_when_handling_invoice_request() {
14211428
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
14221429
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
14231430
assert_ne!(invoice_request.payer_signing_pubkey(), david_id);
1424-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
1431+
assert!(check_compact_path_introduction_node(&reply_path, david, charlie_id));
14251432

14261433
assert_eq!(alice.onion_messenger.next_onion_message_for_peer(charlie_id), None);
14271434

@@ -1451,7 +1458,7 @@ fn fails_authentication_when_handling_invoice_request() {
14511458
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
14521459
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
14531460
assert_ne!(invoice_request.payer_signing_pubkey(), david_id);
1454-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
1461+
assert!(check_compact_path_introduction_node(&reply_path, david, charlie_id));
14551462

14561463
assert_eq!(alice.onion_messenger.next_onion_message_for_peer(charlie_id), None);
14571464
}
@@ -1502,7 +1509,7 @@ fn fails_authentication_when_handling_invoice_for_offer() {
15021509
assert_ne!(offer.issuer_signing_pubkey(), Some(alice_id));
15031510
assert!(!offer.paths().is_empty());
15041511
for path in offer.paths() {
1505-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(bob_id));
1512+
assert!(check_compact_path_introduction_node(&path, alice, bob_id));
15061513
}
15071514

15081515
// Initiate an invoice request, but abandon tracking it.
@@ -1553,7 +1560,7 @@ fn fails_authentication_when_handling_invoice_for_offer() {
15531560
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
15541561
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
15551562
assert_ne!(invoice_request.payer_signing_pubkey(), david_id);
1556-
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
1563+
assert!(check_compact_path_introduction_node(&reply_path, david, charlie_id));
15571564

15581565
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
15591566
charlie.onion_messenger.handle_onion_message(alice_id, &onion_message);
@@ -1610,7 +1617,7 @@ fn fails_authentication_when_handling_invoice_for_refund() {
16101617
assert_ne!(refund.payer_signing_pubkey(), david_id);
16111618
assert!(!refund.paths().is_empty());
16121619
for path in refund.paths() {
1613-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
1620+
assert!(check_compact_path_introduction_node(&path, david, charlie_id));
16141621
}
16151622
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
16161623

@@ -1644,7 +1651,7 @@ fn fails_authentication_when_handling_invoice_for_refund() {
16441651
assert_ne!(refund.payer_signing_pubkey(), david_id);
16451652
assert!(!refund.paths().is_empty());
16461653
for path in refund.paths() {
1647-
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
1654+
assert!(check_compact_path_introduction_node(&path, david, charlie_id));
16481655
}
16491656

16501657
let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();

lightning/src/onion_message/messenger.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,17 @@ pub trait MessageRouter {
521521
}
522522

523523
/// A [`MessageRouter`] that can only route to a directly connected [`Destination`].
524+
///
525+
/// [`DefaultMessageRouter`] constructs compact [`BlindedMessagePath`]s on a best-effort basis.
526+
/// That is, if appropriate SCID information is available for the intermediate peers, it will
527+
/// default to creating compact paths.
528+
///
529+
/// # Compact Blinded Paths
530+
///
531+
/// Compact blinded paths use short channel IDs (SCIDs) instead of pubkeys, resulting in smaller
532+
/// serialization. This is particularly useful when encoding data into space-constrained formats
533+
/// such as QR codes. The SCID is communicated via a [`MessageForwardNode`], but may be `None`
534+
/// to allow for graceful degradation.
524535
///
525536
/// # Privacy
526537
///
@@ -686,7 +697,7 @@ where
686697
peers.into_iter(),
687698
&self.entropy_source,
688699
secp_ctx,
689-
false,
700+
true,
690701
)
691702
}
692703

0 commit comments

Comments
 (0)