Skip to content

Commit 7012f46

Browse files
committed
f: test blinded forward success
1 parent a2f5be0 commit 7012f46

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2697,3 +2697,189 @@ fn test_unblinded_trampoline_forward() {
26972697
do_test_unblinded_trampoline_forward(true);
26982698
do_test_unblinded_trampoline_forward(false);
26992699
}
2700+
2701+
#[test]
2702+
fn test_blinded_trampoline_forward() {
2703+
// Simulate a payment of A (0) -> B (1) -> C(Trampoline (blinded forward)) (2) -> D(Trampoline(blinded receive)) (3)
2704+
// trampoline hops C -> T0 (4) -> D
2705+
// make it fail at B, then at C's outer onion, then at C's inner onion
2706+
const TOTAL_NODE_COUNT: usize = 5;
2707+
let secp_ctx = Secp256k1::new();
2708+
2709+
let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT);
2710+
let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs);
2711+
let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]);
2712+
let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs);
2713+
2714+
let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2715+
let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
2716+
let (_, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 1_000_000, 0);
2717+
let (_, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 4, 3, 1_000_000, 0);
2718+
2719+
for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks
2720+
connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1);
2721+
}
2722+
2723+
let alice_node_id = nodes[0].node().get_our_node_id();
2724+
let bob_node_id = nodes[1].node().get_our_node_id();
2725+
let carol_node_id = nodes[2].node().get_our_node_id();
2726+
let dave_node_id = nodes[3].node().get_our_node_id();
2727+
2728+
let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap();
2729+
let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap();
2730+
2731+
let amt_msat = 1000;
2732+
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
2733+
2734+
let alice_carol_trampoline_shared_secret = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03");
2735+
let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &alice_carol_trampoline_shared_secret);
2736+
2737+
let forwarding_tlvs = blinded_path::payment::TrampolineForwardTlvs {
2738+
next_trampoline: dave_node_id,
2739+
payment_relay: PaymentRelay {
2740+
cltv_expiry_delta: 224,
2741+
fee_proportional_millionths: 0,
2742+
fee_base_msat: 2000,
2743+
},
2744+
payment_constraints: PaymentConstraints {
2745+
max_cltv_expiry: u32::max_value(),
2746+
htlc_minimum_msat: amt_msat
2747+
},
2748+
features: BlindedHopFeatures::empty(),
2749+
next_blinding_override: None,
2750+
};
2751+
let carol_unblinded_tlvs = forwarding_tlvs.encode();
2752+
2753+
let payee_tlvs = UnauthenticatedReceiveTlvs {
2754+
payment_secret,
2755+
payment_constraints: PaymentConstraints {
2756+
max_cltv_expiry: u32::max_value(),
2757+
htlc_minimum_msat: amt_msat,
2758+
},
2759+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
2760+
};
2761+
2762+
let nonce = Nonce([42u8; 16]);
2763+
let expanded_key = nodes[3].keys_manager.get_inbound_payment_key();
2764+
let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);
2765+
let dave_unblinded_tlvs = payee_tlvs.encode();
2766+
2767+
let path = [(carol_node_id, WithoutLength(&carol_unblinded_tlvs)), (dave_node_id, WithoutLength(&dave_unblinded_tlvs))];
2768+
let blinded_hops = blinded_path::utils::construct_blinded_hops(
2769+
&secp_ctx, path.into_iter(), &alice_carol_trampoline_shared_secret,
2770+
).unwrap();
2771+
2772+
let route = Route {
2773+
paths: vec![Path {
2774+
hops: vec![
2775+
// Bob
2776+
RouteHop {
2777+
pubkey: bob_node_id,
2778+
node_features: NodeFeatures::empty(),
2779+
short_channel_id: alice_bob_scid,
2780+
channel_features: ChannelFeatures::empty(),
2781+
fee_msat: 1000, // forwarding fee to Carol
2782+
cltv_expiry_delta: 48,
2783+
maybe_announced_channel: false,
2784+
},
2785+
2786+
// Carol
2787+
RouteHop {
2788+
pubkey: carol_node_id,
2789+
node_features: NodeFeatures::empty(),
2790+
short_channel_id: bob_carol_scid,
2791+
channel_features: ChannelFeatures::empty(),
2792+
fee_msat: 2000, // fee for the usage of the entire blinded path, including Trampoline
2793+
cltv_expiry_delta: 48,
2794+
maybe_announced_channel: false,
2795+
}
2796+
],
2797+
blinded_tail: Some(BlindedTail {
2798+
trampoline_hops: vec![
2799+
// Carol
2800+
TrampolineHop {
2801+
pubkey: carol_node_id,
2802+
node_features: Features::empty(),
2803+
fee_msat: amt_msat,
2804+
cltv_expiry_delta: 176, // let her cook
2805+
},
2806+
],
2807+
hops: blinded_hops,
2808+
blinding_point: carol_blinding_point,
2809+
excess_final_cltv_expiry_delta: 39,
2810+
final_value_msat: amt_msat,
2811+
})
2812+
}],
2813+
route_params: None,
2814+
};
2815+
2816+
nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap();
2817+
2818+
let replacement_onion = {
2819+
// create a substitute onion where the last Trampoline hop is an unblinded receive, which we
2820+
// (deliberately) do not support out of the box, therefore necessitating this workaround
2821+
let trampoline_secret_key = secret_from_hex("0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799");
2822+
let prng_seed = secret_from_hex("fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9");
2823+
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
2824+
2825+
let blinded_tail = route.paths[0].blinded_tail.clone().unwrap();
2826+
let (mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap();
2827+
2828+
// pop the last dummy hop
2829+
trampoline_payloads.pop();
2830+
2831+
trampoline_payloads.push(msgs::OutboundTrampolinePayload::Receive {
2832+
payment_data: Some(msgs::FinalOnionHopData {
2833+
payment_secret,
2834+
total_msat: amt_msat,
2835+
}),
2836+
sender_intended_htlc_amt_msat: amt_msat,
2837+
cltv_expiry_height: 96,
2838+
});
2839+
2840+
let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key).unwrap();
2841+
let trampoline_packet = onion_utils::construct_trampoline_onion_packet(
2842+
trampoline_payloads,
2843+
trampoline_onion_keys,
2844+
prng_seed.secret_bytes(),
2845+
&payment_hash,
2846+
None,
2847+
).unwrap();
2848+
2849+
let outer_session_priv = secret_from_hex("e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677");
2850+
2851+
let (outer_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap();
2852+
let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv).unwrap();
2853+
let outer_packet = onion_utils::construct_onion_packet(
2854+
outer_payloads,
2855+
outer_onion_keys,
2856+
prng_seed.secret_bytes(),
2857+
&payment_hash,
2858+
).unwrap();
2859+
2860+
outer_packet
2861+
};
2862+
2863+
check_added_monitors!(&nodes[0], 1);
2864+
2865+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
2866+
assert_eq!(events.len(), 1);
2867+
let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
2868+
let mut update_message = match first_message_event {
2869+
MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => {
2870+
assert_eq!(updates.update_add_htlcs.len(), 1);
2871+
updates.update_add_htlcs.get_mut(0)
2872+
},
2873+
_ => panic!()
2874+
};
2875+
update_message.map(|msg| {
2876+
// don't replace the onion, just let it forward in peace
2877+
// msg.onion_routing_packet = replacement_onion.clone();
2878+
});
2879+
2880+
let route: &[&Node] = &[&nodes[1], &nodes[2], &nodes[4], &nodes[3]];
2881+
let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event)
2882+
.with_payment_secret(payment_secret);
2883+
do_pass_along_path(args);
2884+
claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[4], &nodes[3]], payment_preimage);
2885+
}

0 commit comments

Comments
 (0)