Skip to content

Commit 43e821c

Browse files
committed
Introduce a test to verify reply path diversification
1 parent 9b571f2 commit 43e821c

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed

lightning/src/ln/offers_tests.rs

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,190 @@ fn pays_for_refund_without_blinded_paths() {
865865
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
866866
}
867867

868+
/// This test checks that when multiple potential introduction nodes are available for the payer,
869+
/// multiple `invoice_request` messages are sent for the offer, each with a different `reply_path`.
870+
#[test]
871+
fn send_invoice_requests_with_distinct_reply_path() {
872+
let mut accept_forward_cfg = test_default_channel_config();
873+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
874+
875+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
876+
features.set_onion_messages_optional();
877+
features.set_route_blinding_optional();
878+
879+
let chanmon_cfgs = create_chanmon_cfgs(7);
880+
let node_cfgs = create_node_cfgs(7, &chanmon_cfgs);
881+
882+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
883+
884+
let node_chanmgrs = create_node_chanmgrs(
885+
7, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None, None]
886+
);
887+
let nodes = create_network(7, &node_cfgs, &node_chanmgrs);
888+
889+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
890+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
891+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
892+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
893+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
894+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
895+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
896+
897+
// Introduce another potential introduction node, node[6], as a candidate
898+
create_unannounced_chan_between_nodes_with_value(&nodes, 3, 6, 10_000_000, 1_000_000_000);
899+
create_announced_chan_between_nodes_with_value(&nodes, 2, 6, 10_000_000, 1_000_000_000);
900+
create_announced_chan_between_nodes_with_value(&nodes, 4, 6, 10_000_000, 1_000_000_000);
901+
create_announced_chan_between_nodes_with_value(&nodes, 5, 6, 10_000_000, 1_000_000_000);
902+
903+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
904+
let alice_id = alice.node.get_our_node_id();
905+
let bob_id = bob.node.get_our_node_id();
906+
let charlie_id = charlie.node.get_our_node_id();
907+
let david_id = david.node.get_our_node_id();
908+
909+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5], &nodes[6]]);
910+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
911+
912+
let offer = alice.node
913+
.create_offer_builder(None)
914+
.unwrap()
915+
.amount_msats(10_000_000)
916+
.build().unwrap();
917+
assert_ne!(offer.signing_pubkey(), Some(alice_id));
918+
assert!(!offer.paths().is_empty());
919+
for path in offer.paths() {
920+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
921+
}
922+
923+
let payment_id = PaymentId([1; 32]);
924+
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
925+
.unwrap();
926+
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
927+
connect_peers(david, bob);
928+
929+
// Send, extract and verify the first Invoice Request message
930+
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
931+
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
932+
933+
connect_peers(alice, charlie);
934+
935+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
936+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
937+
938+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
939+
assert_eq!(invoice_request.amount_msats(), None);
940+
assert_ne!(invoice_request.payer_id(), david_id);
941+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
942+
943+
// Send, extract and verify the second Invoice Request message
944+
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
945+
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
946+
947+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
948+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
949+
950+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
951+
952+
assert_eq!(invoice_request.amount_msats(), None);
953+
assert_ne!(invoice_request.payer_id(), david_id);
954+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(nodes[6].node.get_our_node_id()));
955+
}
956+
957+
/// This test checks that when multiple potential introduction nodes are available for the payee,
958+
/// multiple `Invoice` messages are sent for the Refund, each with a different `reply_path`.
959+
#[test]
960+
fn send_invoice_with_distinct_reply_path() {
961+
let mut accept_forward_cfg = test_default_channel_config();
962+
accept_forward_cfg.accept_forwards_to_priv_channels = true;
963+
964+
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
965+
features.set_onion_messages_optional();
966+
features.set_route_blinding_optional();
967+
968+
let chanmon_cfgs = create_chanmon_cfgs(7);
969+
let node_cfgs = create_node_cfgs(7, &chanmon_cfgs);
970+
971+
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
972+
973+
let node_chanmgrs = create_node_chanmgrs(
974+
7, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None, None]
975+
);
976+
let nodes = create_network(7, &node_cfgs, &node_chanmgrs);
977+
978+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
979+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
980+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
981+
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
982+
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
983+
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
984+
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
985+
986+
// Introduce another potential introduction node, node[6], as a candidate
987+
create_unannounced_chan_between_nodes_with_value(&nodes, 3, 6, 10_000_000, 1_000_000_000);
988+
create_announced_chan_between_nodes_with_value(&nodes, 2, 6, 10_000_000, 1_000_000_000);
989+
create_announced_chan_between_nodes_with_value(&nodes, 4, 6, 10_000_000, 1_000_000_000);
990+
create_announced_chan_between_nodes_with_value(&nodes, 5, 6, 10_000_000, 1_000_000_000);
991+
992+
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
993+
let alice_id = alice.node.get_our_node_id();
994+
let bob_id = bob.node.get_our_node_id();
995+
let charlie_id = charlie.node.get_our_node_id();
996+
let david_id = david.node.get_our_node_id();
997+
998+
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5], &nodes[6]]);
999+
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
1000+
1001+
let absolute_expiry = Duration::from_secs(u64::MAX);
1002+
let payment_id = PaymentId([1; 32]);
1003+
let refund = alice.node
1004+
.create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None)
1005+
.unwrap()
1006+
.build().unwrap();
1007+
assert_eq!(refund.amount_msats(), 10_000_000);
1008+
assert_eq!(refund.absolute_expiry(), Some(absolute_expiry));
1009+
assert_ne!(refund.payer_id(), alice_id);
1010+
for path in refund.paths() {
1011+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
1012+
}
1013+
expect_recent_payment!(alice, RecentPaymentDetails::AwaitingInvoice, payment_id);
1014+
1015+
let expected_invoice = david.node.request_refund_payment(&refund).unwrap();
1016+
1017+
connect_peers(david, bob);
1018+
1019+
// Send, extract and verify the first Invoice Request message
1020+
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1021+
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
1022+
1023+
connect_peers(alice, charlie);
1024+
1025+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1026+
1027+
let (invoice, reply_path) = extract_invoice(alice, &onion_message);
1028+
assert_eq!(invoice, expected_invoice);
1029+
1030+
assert_eq!(invoice.amount_msats(), 10_000_000);
1031+
assert_ne!(invoice.signing_pubkey(), david_id);
1032+
assert!(!invoice.payment_paths().is_empty());
1033+
1034+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(charlie_id));
1035+
1036+
// Send, extract and verify the second Invoice Request message
1037+
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1038+
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
1039+
1040+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1041+
1042+
let (invoice, reply_path) = extract_invoice(alice, &onion_message);
1043+
assert_eq!(invoice, expected_invoice);
1044+
1045+
assert_eq!(invoice.amount_msats(), 10_000_000);
1046+
assert_ne!(invoice.signing_pubkey(), david_id);
1047+
assert!(!invoice.payment_paths().is_empty());
1048+
1049+
assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(nodes[6].node.get_our_node_id()));
1050+
}
1051+
8681052
/// Fails creating an offer when a blinded path cannot be created without exposing the node's id.
8691053
#[test]
8701054
fn fails_creating_offer_without_blinded_paths() {

0 commit comments

Comments
 (0)