Skip to content

Commit 07d7640

Browse files
committed
Test unblinded Trampoline receives
1 parent 863e2fb commit 07d7640

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

lightning/src/ln/blinded_payment_tests.rs

+169
Original file line numberDiff line numberDiff line change
@@ -2179,3 +2179,172 @@ fn test_trampoline_single_hop_receive() {
21792179
// Simulate a payment failure of A (0) -> B (1) -> C(Trampoline (blinded forward)) (2)
21802180
do_test_trampoline_single_hop_receive(false);
21812181
}
2182+
2183+
#[test]
2184+
#[cfg(trampoline)]
2185+
fn test_trampoline_unblinded_receive() {
2186+
// Simulate a payment of A (0) -> B (1) -> C(Trampoline) (2)
2187+
2188+
const TOTAL_NODE_COUNT: usize = 3;
2189+
let secp_ctx = Secp256k1::new();
2190+
2191+
let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT);
2192+
let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs);
2193+
let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]);
2194+
let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs);
2195+
2196+
let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2197+
let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
2198+
2199+
for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks
2200+
connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1);
2201+
}
2202+
2203+
let alice_node_id = nodes[0].node().get_our_node_id();
2204+
let bob_node_id = nodes[1].node().get_our_node_id();
2205+
let carol_node_id = nodes[2].node().get_our_node_id();
2206+
2207+
let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap();
2208+
let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap();
2209+
2210+
let amt_msat = 1000;
2211+
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
2212+
let payee_tlvs = blinded_path::payment::TrampolineForwardTlvs {
2213+
next_trampoline: alice_node_id,
2214+
payment_constraints: PaymentConstraints {
2215+
max_cltv_expiry: u32::max_value(),
2216+
htlc_minimum_msat: amt_msat,
2217+
},
2218+
features: BlindedHopFeatures::empty(),
2219+
payment_relay: PaymentRelay {
2220+
cltv_expiry_delta: 0,
2221+
fee_proportional_millionths: 0,
2222+
fee_base_msat: 0,
2223+
},
2224+
next_blinding_override: None,
2225+
};
2226+
2227+
let carol_unblinded_tlvs = payee_tlvs.encode();
2228+
let path = [(carol_node_id, WithoutLength(&carol_unblinded_tlvs))];
2229+
let carol_alice_trampoline_session_priv = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03");
2230+
let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &carol_alice_trampoline_session_priv);
2231+
let carol_blinded_hops = blinded_path::utils::construct_blinded_hops(
2232+
&secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv
2233+
).unwrap();
2234+
2235+
let route = Route {
2236+
paths: vec![Path {
2237+
hops: vec![
2238+
// Bob
2239+
RouteHop {
2240+
pubkey: bob_node_id,
2241+
node_features: NodeFeatures::empty(),
2242+
short_channel_id: alice_bob_scid,
2243+
channel_features: ChannelFeatures::empty(),
2244+
fee_msat: 1000,
2245+
cltv_expiry_delta: 48,
2246+
maybe_announced_channel: false,
2247+
},
2248+
2249+
// Carol
2250+
RouteHop {
2251+
pubkey: carol_node_id,
2252+
node_features: NodeFeatures::empty(),
2253+
short_channel_id: bob_carol_scid,
2254+
channel_features: ChannelFeatures::empty(),
2255+
fee_msat: 0,
2256+
cltv_expiry_delta: 48,
2257+
maybe_announced_channel: false,
2258+
}
2259+
],
2260+
blinded_tail: Some(BlindedTail {
2261+
trampoline_hops: vec![
2262+
// Carol
2263+
TrampolineHop {
2264+
pubkey: carol_node_id,
2265+
node_features: Features::empty(),
2266+
fee_msat: amt_msat,
2267+
cltv_expiry_delta: 24,
2268+
},
2269+
],
2270+
hops: carol_blinded_hops,
2271+
blinding_point: carol_blinding_point,
2272+
excess_final_cltv_expiry_delta: 39,
2273+
final_value_msat: amt_msat,
2274+
})
2275+
}],
2276+
route_params: None,
2277+
};
2278+
2279+
nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap();
2280+
2281+
let replacement_onion = {
2282+
// create a substitute onion where the last Trampoline hop is an unblinded receive, which we
2283+
// (deliberately) do not support out of the box, therefore necessitating this workaround
2284+
let trampoline_secret_key = secret_from_hex("0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799");
2285+
let prng_seed = secret_from_hex("fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9");
2286+
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
2287+
2288+
let blinded_tail = route.paths[0].blinded_tail.clone().unwrap();
2289+
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();
2290+
2291+
// pop the last dummy hop
2292+
trampoline_payloads.pop();
2293+
2294+
trampoline_payloads.push(msgs::OutboundTrampolinePayload::Receive {
2295+
payment_data: Some(msgs::FinalOnionHopData {
2296+
payment_secret,
2297+
total_msat: amt_msat,
2298+
}),
2299+
sender_intended_htlc_amt_msat: amt_msat,
2300+
cltv_expiry_height: 104,
2301+
});
2302+
2303+
let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key).unwrap();
2304+
let trampoline_packet = onion_utils::construct_trampoline_onion_packet(
2305+
trampoline_payloads,
2306+
trampoline_onion_keys,
2307+
prng_seed.secret_bytes(),
2308+
&payment_hash,
2309+
None,
2310+
).unwrap();
2311+
2312+
let outer_session_priv = secret_from_hex("e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677");
2313+
2314+
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();
2315+
let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv).unwrap();
2316+
let outer_packet = onion_utils::construct_onion_packet(
2317+
outer_payloads,
2318+
outer_onion_keys,
2319+
prng_seed.secret_bytes(),
2320+
&payment_hash,
2321+
).unwrap();
2322+
2323+
outer_packet
2324+
};
2325+
2326+
check_added_monitors!(&nodes[0], 1);
2327+
2328+
// Check that we've queued the HTLCs of the async keysend payment.
2329+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
2330+
assert_eq!(events.len(), 1);
2331+
let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
2332+
let mut update_message = match first_message_event {
2333+
MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => {
2334+
assert_eq!(updates.update_add_htlcs.len(), 1);
2335+
updates.update_add_htlcs.get_mut(0)
2336+
},
2337+
_ => panic!()
2338+
};
2339+
update_message.map(|msg| {
2340+
msg.onion_routing_packet = replacement_onion.clone();
2341+
});
2342+
2343+
let route: &[&Node] = &[&nodes[1], &nodes[2]];
2344+
let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event)
2345+
.with_payment_secret(payment_secret);
2346+
do_pass_along_path(args);
2347+
2348+
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
2349+
}
2350+

lightning/src/ln/msgs.rs

+19
Original file line numberDiff line numberDiff line change
@@ -2209,6 +2209,15 @@ mod fuzzy_internal_msgs {
22092209
/// The node id to which the trampoline node must find a route.
22102210
outgoing_node_id: PublicKey,
22112211
},
2212+
#[cfg(all(test, trampoline))]
2213+
/// LDK does not support making Trampoline payments to unblinded recipients. However, for
2214+
/// the purpose of testing our ability to receive them, we make this variant available in a
2215+
/// testing environment.
2216+
Receive {
2217+
payment_data: Option<FinalOnionHopData>,
2218+
sender_intended_htlc_amt_msat: u64,
2219+
cltv_expiry_height: u32,
2220+
},
22122221
#[allow(unused)]
22132222
/// This is the last Trampoline hop, whereupon the Trampoline forward mechanism is exited,
22142223
/// and payment data is relayed using non-Trampoline blinded hops
@@ -3185,6 +3194,16 @@ impl<'a> Writeable for OutboundTrampolinePayload<'a> {
31853194
(14, outgoing_node_id, required)
31863195
});
31873196
},
3197+
#[cfg(all(test, trampoline))]
3198+
Self::Receive {
3199+
ref payment_data, sender_intended_htlc_amt_msat, cltv_expiry_height,
3200+
} => {
3201+
_encode_varint_length_prefixed_tlv!(w, {
3202+
(2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required),
3203+
(4, HighZeroBytesDroppedBigSize(*cltv_expiry_height), required),
3204+
(8, payment_data, option)
3205+
});
3206+
},
31883207
Self::LegacyBlindedPathEntry { amt_to_forward, outgoing_cltv_value, payment_paths, invoice_features } => {
31893208
let mut blinded_path_serialization = [0u8; 2048]; // Fixed-length buffer on the stack
31903209
let serialization_length = {

0 commit comments

Comments
 (0)