Skip to content

Commit 0ff1d27

Browse files
committed
Pathfind Trampoline forward and construct onion [rephrase]
1 parent c6536a9 commit 0ff1d27

File tree

2 files changed

+429
-11
lines changed

2 files changed

+429
-11
lines changed

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,3 +2496,165 @@ fn test_trampoline_constraint_enforcement() {
24962496
expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions);
24972497
}
24982498
}
2499+
2500+
#[test]
2501+
fn test_unblinded_trampoline_forward() {
2502+
// Simulate a payment of A (0) -> B (1) -> C(Trampoline) (2) -> D(Trampoline(receive)) (3)
2503+
// trampoline hops C -> T0 (4) -> D
2504+
// make it fail at B, then at C's outer onion, then at C's inner onion
2505+
const TOTAL_NODE_COUNT: usize = 5;
2506+
let secp_ctx = Secp256k1::new();
2507+
2508+
let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT);
2509+
let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs);
2510+
let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]);
2511+
let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs);
2512+
2513+
let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2514+
let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
2515+
let (_, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 1_000_000, 0);
2516+
let (_, _, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 4, 3, 1_000_000, 0);
2517+
2518+
for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks
2519+
connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1);
2520+
}
2521+
2522+
let alice_node_id = nodes[0].node().get_our_node_id();
2523+
let bob_node_id = nodes[1].node().get_our_node_id();
2524+
let carol_node_id = nodes[2].node().get_our_node_id();
2525+
let dave_node_id = nodes[3].node().get_our_node_id();
2526+
2527+
let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap();
2528+
let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap();
2529+
2530+
let amt_msat = 1000;
2531+
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
2532+
2533+
let route = Route {
2534+
paths: vec![Path {
2535+
hops: vec![
2536+
// Bob
2537+
RouteHop {
2538+
pubkey: bob_node_id,
2539+
node_features: NodeFeatures::empty(),
2540+
short_channel_id: alice_bob_scid,
2541+
channel_features: ChannelFeatures::empty(),
2542+
fee_msat: 1000, // forwarding fee to Carol
2543+
cltv_expiry_delta: 48,
2544+
maybe_announced_channel: false,
2545+
},
2546+
2547+
// Carol
2548+
RouteHop {
2549+
pubkey: carol_node_id,
2550+
node_features: NodeFeatures::empty(),
2551+
short_channel_id: bob_carol_scid,
2552+
channel_features: ChannelFeatures::empty(),
2553+
fee_msat: 2000, // fee for the usage of the entire blinded path, including Trampoline
2554+
cltv_expiry_delta: 48,
2555+
maybe_announced_channel: false,
2556+
}
2557+
],
2558+
blinded_tail: Some(BlindedTail {
2559+
trampoline_hops: vec![
2560+
// Carol
2561+
TrampolineHop {
2562+
pubkey: carol_node_id,
2563+
node_features: Features::empty(),
2564+
fee_msat: amt_msat,
2565+
cltv_expiry_delta: 176, // let her cook
2566+
},
2567+
2568+
// Dave (recipient)
2569+
TrampolineHop {
2570+
pubkey: dave_node_id,
2571+
node_features: Features::empty(),
2572+
fee_msat: 0, // no need to charge a fee as the recipient
2573+
cltv_expiry_delta: 24,
2574+
},
2575+
],
2576+
hops: vec![
2577+
// Dave's blinded node id
2578+
BlindedHop {
2579+
blinded_node_id: pubkey_from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be"),
2580+
encrypted_payload: bytes_from_hex("0ccf3c8a58deaa603f657ee2a5ed9d604eb5c8ca1e5f801989afa8f3ea6d789bbdde2c7e7a1ef9ca8c38d2c54760febad8446d3f273ddb537569ef56613846ccd3aba78a"),
2581+
}
2582+
],
2583+
blinding_point: alice_node_id,
2584+
excess_final_cltv_expiry_delta: 39,
2585+
final_value_msat: amt_msat,
2586+
})
2587+
}],
2588+
route_params: None,
2589+
};
2590+
2591+
nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap();
2592+
2593+
let replacement_onion = {
2594+
// create a substitute onion where the last Trampoline hop is an unblinded receive, which we
2595+
// (deliberately) do not support out of the box, therefore necessitating this workaround
2596+
let trampoline_secret_key = secret_from_hex("0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799");
2597+
let prng_seed = secret_from_hex("fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9");
2598+
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
2599+
2600+
let blinded_tail = route.paths[0].blinded_tail.clone().unwrap();
2601+
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();
2602+
2603+
// pop the last dummy hop
2604+
trampoline_payloads.pop();
2605+
2606+
trampoline_payloads.push(msgs::OutboundTrampolinePayload::Receive {
2607+
payment_data: Some(msgs::FinalOnionHopData {
2608+
payment_secret,
2609+
total_msat: amt_msat,
2610+
}),
2611+
sender_intended_htlc_amt_msat: amt_msat,
2612+
cltv_expiry_height: 96,
2613+
});
2614+
2615+
let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key).unwrap();
2616+
let trampoline_packet = onion_utils::construct_trampoline_onion_packet(
2617+
trampoline_payloads,
2618+
trampoline_onion_keys,
2619+
prng_seed.secret_bytes(),
2620+
&payment_hash,
2621+
None,
2622+
).unwrap();
2623+
2624+
let outer_session_priv = secret_from_hex("e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677");
2625+
2626+
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();
2627+
let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv).unwrap();
2628+
let outer_packet = onion_utils::construct_onion_packet(
2629+
outer_payloads,
2630+
outer_onion_keys,
2631+
prng_seed.secret_bytes(),
2632+
&payment_hash,
2633+
).unwrap();
2634+
2635+
outer_packet
2636+
};
2637+
2638+
check_added_monitors!(&nodes[0], 1);
2639+
2640+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
2641+
assert_eq!(events.len(), 1);
2642+
let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
2643+
let mut update_message = match first_message_event {
2644+
MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => {
2645+
assert_eq!(updates.update_add_htlcs.len(), 1);
2646+
updates.update_add_htlcs.get_mut(0)
2647+
},
2648+
_ => panic!()
2649+
};
2650+
update_message.map(|msg| {
2651+
msg.onion_routing_packet = replacement_onion.clone();
2652+
});
2653+
2654+
let route: &[&Node] = &[&nodes[1], &nodes[2], &nodes[4], &nodes[3]];
2655+
let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event)
2656+
.with_payment_secret(payment_secret);
2657+
do_pass_along_path(args);
2658+
2659+
claim_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[4], &nodes[3]], payment_preimage);
2660+
}

0 commit comments

Comments
 (0)