Skip to content

Commit 07883fe

Browse files
committed
Make DefaultMessageRouter use the context to pad/compact paths
After much discussion in #3246 we mostly decided to allow downstream developers to override whatever decisions the `DefaultMessageRouter` makes regarding blinded path selection by providing easy overrides for the selected `OnionMessageRouter`. We did not, however, actually select good defaults for `DefaultMessageRouter`. Here we add those defaults, taking advantage of the `MessageContext` we're given to detect why we're building a blinded path and selecting blinding and compaction parameters based on it. Specifically, if the blinded path is not being built for an offers context, we always use a non-compact blinded path and always pad it to four hops (including the recipient). However, if the blinded path is being built for an `Offers` context which implies it might need to fit in a QR code (or, worse, a payment onion), we reduce our padding and try to build a compact blinded path if possible. We retain the `NodeIdMessageRouter` to disable compact blinded path creation but use the same path-padding heuristic as for `DefaultMessageRouter`.
1 parent b60ba66 commit 07883fe

File tree

3 files changed

+98
-54
lines changed

3 files changed

+98
-54
lines changed

lightning/src/ln/offers_tests.rs

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ use crate::offers::invoice_error::InvoiceError;
6060
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestVerifiedFromOffer};
6161
use crate::offers::nonce::Nonce;
6262
use crate::offers::parse::Bolt12SemanticError;
63-
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageSendInstructions, NodeIdMessageRouter, NullMessageRouter, PeeledOnion, PADDED_PATH_LENGTH};
63+
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageSendInstructions, NodeIdMessageRouter, NullMessageRouter, PeeledOnion, DUMMY_HOPS_PATH_LENGTH, QR_CODED_DUMMY_HOPS_PATH_LENGTH};
6464
use crate::onion_message::offers::OffersMessage;
6565
use crate::routing::gossip::{NodeAlias, NodeId};
6666
use crate::routing::router::{PaymentParameters, RouteParameters, RouteParametersConfig};
@@ -163,6 +163,20 @@ fn check_compact_path_introduction_node<'a, 'b, 'c>(
163163
&& matches!(path.introduction_node(), IntroductionNode::DirectedShortChannelId(..))
164164
}
165165

166+
fn check_dummy_hopd_path_length<'a, 'b, 'c>(
167+
path: &BlindedMessagePath,
168+
lookup_node: &Node<'a, 'b, 'c>,
169+
expected_introduction_node: PublicKey,
170+
expected_path_length: usize,
171+
) -> bool {
172+
let introduction_node_id = resolve_introduction_node(lookup_node, path);
173+
let first_hop_len = path.blinded_hops().first().unwrap().encrypted_payload.len();
174+
let hops = path.blinded_hops();
175+
introduction_node_id == expected_introduction_node
176+
&& hops.len() == expected_path_length
177+
&& hops.iter().take(hops.len() - 1).all(|hop| hop.encrypted_payload.len() == first_hop_len)
178+
}
179+
166180
fn route_bolt12_payment<'a, 'b, 'c>(
167181
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice
168182
) {
@@ -455,7 +469,7 @@ fn check_dummy_hop_pattern_in_offer() {
455469
let bob_id = bob.node.get_our_node_id();
456470

457471
// Case 1: DefaultMessageRouter → uses compact blinded paths (via SCIDs)
458-
// Expected: No dummy hops; each path contains only the recipient.
472+
// Expected: Padded to QR_CODED_DUMMY_HOPS_PATH_LENGTH for QR code size optimization
459473
let default_router = DefaultMessageRouter::new(alice.network_graph, alice.keys_manager);
460474

461475
let compact_offer = alice.node
@@ -467,8 +481,8 @@ fn check_dummy_hop_pattern_in_offer() {
467481

468482
for path in compact_offer.paths() {
469483
assert_eq!(
470-
path.blinded_hops().len(), 1,
471-
"Compact paths must include only the recipient"
484+
path.blinded_hops().len(), QR_CODED_DUMMY_HOPS_PATH_LENGTH,
485+
"Compact offer paths are padded to QR_CODED_DUMMY_HOPS_PATH_LENGTH"
472486
);
473487
}
474488

@@ -480,10 +494,10 @@ fn check_dummy_hop_pattern_in_offer() {
480494

481495
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
482496
assert_ne!(invoice_request.payer_signing_pubkey(), bob_id);
483-
assert!(check_compact_path_introduction_node(&reply_path, alice, bob_id));
497+
assert!(check_dummy_hopd_path_length(&reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH));
484498

485499
// Case 2: NodeIdMessageRouter → uses node ID-based blinded paths
486-
// Expected: 0 to MAX_DUMMY_HOPS_COUNT dummy hops, followed by recipient.
500+
// Expected: Also padded to QR_CODED_DUMMY_HOPS_PATH_LENGTH for QR code size optimization
487501
let node_id_router = NodeIdMessageRouter::new(alice.network_graph, alice.keys_manager);
488502

489503
let padded_offer = alice.node
@@ -492,7 +506,7 @@ fn check_dummy_hop_pattern_in_offer() {
492506
.build().unwrap();
493507

494508
assert!(!padded_offer.paths().is_empty());
495-
assert!(padded_offer.paths().iter().all(|path| path.blinded_hops().len() == PADDED_PATH_LENGTH));
509+
assert!(padded_offer.paths().iter().all(|path| path.blinded_hops().len() == QR_CODED_DUMMY_HOPS_PATH_LENGTH));
496510

497511
let payment_id = PaymentId([2; 32]);
498512
bob.node.pay_for_offer(&padded_offer, None, payment_id, Default::default()).unwrap();
@@ -502,7 +516,7 @@ fn check_dummy_hop_pattern_in_offer() {
502516

503517
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
504518
assert_ne!(invoice_request.payer_signing_pubkey(), bob_id);
505-
assert!(check_compact_path_introduction_node(&reply_path, alice, bob_id));
519+
assert!(check_dummy_hopd_path_length(&reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH));
506520
}
507521

508522
/// Checks that blinded paths are compact for short-lived offers.
@@ -687,7 +701,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
687701
});
688702
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
689703
assert_ne!(invoice_request.payer_signing_pubkey(), david_id);
690-
assert!(check_compact_path_introduction_node(&reply_path, bob, charlie_id));
704+
assert!(check_dummy_hopd_path_length(&reply_path, bob, charlie_id, DUMMY_HOPS_PATH_LENGTH));
691705

692706
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
693707
charlie.onion_messenger.handle_onion_message(alice_id, &onion_message);
@@ -706,8 +720,8 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
706720
// to Alice when she's handling the message. Therefore, either Bob or Charlie could
707721
// serve as the introduction node for the reply path back to Alice.
708722
assert!(
709-
check_compact_path_introduction_node(&reply_path, david, bob_id) ||
710-
check_compact_path_introduction_node(&reply_path, david, charlie_id)
723+
check_dummy_hopd_path_length(&reply_path, david, bob_id, DUMMY_HOPS_PATH_LENGTH) ||
724+
check_dummy_hopd_path_length(&reply_path, david, charlie_id, DUMMY_HOPS_PATH_LENGTH)
711725
);
712726

713727
route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
@@ -790,7 +804,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
790804
for path in invoice.payment_paths() {
791805
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(bob_id));
792806
}
793-
assert!(check_compact_path_introduction_node(&reply_path, alice, bob_id));
807+
assert!(check_dummy_hopd_path_length(&reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH));
794808

795809
route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
796810
expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id);
@@ -845,7 +859,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
845859
});
846860
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
847861
assert_ne!(invoice_request.payer_signing_pubkey(), bob_id);
848-
assert!(check_compact_path_introduction_node(&reply_path, alice, bob_id));
862+
assert!(check_dummy_hopd_path_length(&reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH));
849863

850864
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
851865
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);
@@ -857,7 +871,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
857871
for path in invoice.payment_paths() {
858872
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
859873
}
860-
assert!(check_compact_path_introduction_node(&reply_path, bob, alice_id));
874+
assert!(check_dummy_hopd_path_length(&reply_path, bob, alice_id, DUMMY_HOPS_PATH_LENGTH));
861875

862876
route_bolt12_payment(bob, &[alice], &invoice);
863877
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
@@ -913,7 +927,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
913927
for path in invoice.payment_paths() {
914928
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
915929
}
916-
assert!(check_compact_path_introduction_node(&reply_path, bob, alice_id));
930+
assert!(check_dummy_hopd_path_length(&reply_path, bob, alice_id, DUMMY_HOPS_PATH_LENGTH));
917931

918932
route_bolt12_payment(bob, &[alice], &invoice);
919933
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
@@ -1059,6 +1073,7 @@ fn send_invoice_requests_with_distinct_reply_path() {
10591073
let bob_id = bob.node.get_our_node_id();
10601074
let charlie_id = charlie.node.get_our_node_id();
10611075
let david_id = david.node.get_our_node_id();
1076+
let frank_id = nodes[6].node.get_our_node_id();
10621077

10631078
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5], &nodes[6]]);
10641079
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
@@ -1089,7 +1104,7 @@ fn send_invoice_requests_with_distinct_reply_path() {
10891104
alice.onion_messenger.handle_onion_message(bob_id, &onion_message);
10901105

10911106
let (_, reply_path) = extract_invoice_request(alice, &onion_message);
1092-
assert!(check_compact_path_introduction_node(&reply_path, alice, charlie_id));
1107+
assert!(check_dummy_hopd_path_length(&reply_path, alice, charlie_id, DUMMY_HOPS_PATH_LENGTH));
10931108

10941109
// Send, extract and verify the second Invoice Request message
10951110
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
@@ -1099,7 +1114,7 @@ fn send_invoice_requests_with_distinct_reply_path() {
10991114
alice.onion_messenger.handle_onion_message(bob_id, &onion_message);
11001115

11011116
let (_, reply_path) = extract_invoice_request(alice, &onion_message);
1102-
assert!(check_compact_path_introduction_node(&reply_path, alice, nodes[6].node.get_our_node_id()));
1117+
assert!(check_dummy_hopd_path_length(&reply_path, alice, frank_id, DUMMY_HOPS_PATH_LENGTH));
11031118
}
11041119

11051120
/// This test checks that when multiple potential introduction nodes are available for the payee,
@@ -1170,7 +1185,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
11701185
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
11711186

11721187
let (_, reply_path) = extract_invoice(alice, &onion_message);
1173-
assert!(check_compact_path_introduction_node(&reply_path, alice, charlie_id));
1188+
assert!(check_dummy_hopd_path_length(&reply_path, alice, charlie_id, DUMMY_HOPS_PATH_LENGTH));
11741189

11751190
// Send, extract and verify the second Invoice Request message
11761191
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
@@ -1179,7 +1194,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
11791194
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
11801195

11811196
let (_, reply_path) = extract_invoice(alice, &onion_message);
1182-
assert!(check_compact_path_introduction_node(&reply_path, alice, nodes[6].node.get_our_node_id()));
1197+
assert!(check_dummy_hopd_path_length(&reply_path, alice, nodes[6].node.get_our_node_id(), DUMMY_HOPS_PATH_LENGTH));
11831198
}
11841199

11851200
/// Verifies that the invoice request message can be retried if it fails to reach the
@@ -1233,7 +1248,7 @@ fn creates_and_pays_for_offer_with_retry() {
12331248
});
12341249
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
12351250
assert_ne!(invoice_request.payer_signing_pubkey(), bob_id);
1236-
assert!(check_compact_path_introduction_node(&reply_path, alice, bob_id));
1251+
assert!(check_dummy_hopd_path_length(&reply_path, alice, bob_id, DUMMY_HOPS_PATH_LENGTH));
12371252
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
12381253
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);
12391254

@@ -1534,7 +1549,7 @@ fn fails_authentication_when_handling_invoice_request() {
15341549
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
15351550
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
15361551
assert_ne!(invoice_request.payer_signing_pubkey(), david_id);
1537-
assert!(check_compact_path_introduction_node(&reply_path, david, charlie_id));
1552+
assert!(check_dummy_hopd_path_length(&reply_path, david, charlie_id, DUMMY_HOPS_PATH_LENGTH));
15381553

15391554
assert_eq!(alice.onion_messenger.next_onion_message_for_peer(charlie_id), None);
15401555

@@ -1563,7 +1578,7 @@ fn fails_authentication_when_handling_invoice_request() {
15631578
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
15641579
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
15651580
assert_ne!(invoice_request.payer_signing_pubkey(), david_id);
1566-
assert!(check_compact_path_introduction_node(&reply_path, david, charlie_id));
1581+
assert!(check_dummy_hopd_path_length(&reply_path, david, charlie_id, DUMMY_HOPS_PATH_LENGTH));
15671582

15681583
assert_eq!(alice.onion_messenger.next_onion_message_for_peer(charlie_id), None);
15691584
}
@@ -1663,7 +1678,7 @@ fn fails_authentication_when_handling_invoice_for_offer() {
16631678
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
16641679
assert_eq!(invoice_request.amount_msats(), Some(10_000_000));
16651680
assert_ne!(invoice_request.payer_signing_pubkey(), david_id);
1666-
assert!(check_compact_path_introduction_node(&reply_path, david, charlie_id));
1681+
assert!(check_dummy_hopd_path_length(&reply_path, david, charlie_id, DUMMY_HOPS_PATH_LENGTH));
16671682

16681683
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
16691684
charlie.onion_messenger.handle_onion_message(alice_id, &onion_message);

lightning/src/offers/flow.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use crate::onion_message::async_payments::{
5252
StaticInvoicePersisted,
5353
};
5454
use crate::onion_message::messenger::{
55-
Destination, MessageRouter, MessageSendInstructions, Responder, PADDED_PATH_LENGTH,
55+
Destination, MessageRouter, MessageSendInstructions, Responder, DUMMY_HOPS_PATH_LENGTH,
5656
};
5757
use crate::onion_message::offers::OffersMessage;
5858
use crate::onion_message::packet::OnionMessageContents;
@@ -1312,7 +1312,7 @@ where
13121312
prev_outbound_scid_alias,
13131313
htlc_id,
13141314
});
1315-
let num_dummy_hops = PADDED_PATH_LENGTH.saturating_sub(1);
1315+
let num_dummy_hops = DUMMY_HOPS_PATH_LENGTH.saturating_sub(1);
13161316
BlindedMessagePath::new_with_dummy_hops(
13171317
&[],
13181318
self.get_our_node_id(),

0 commit comments

Comments
 (0)