Skip to content

Commit f1c4da6

Browse files
committed
f: add CLTV test
1 parent 0b34262 commit f1c4da6

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,6 +2426,228 @@ fn test_trampoline_unblinded_receive_normal() {
24262426
do_test_trampoline_unblinded_receive(false);
24272427
}
24282428

2429+
#[derive(PartialEq)]
2430+
enum TrampolineConstraintFailureScenarios {
2431+
TrampolineCLTVGreaterThanOnion,
2432+
#[allow(dead_code)]
2433+
// TODO: To test Amount grearter than onion we need the ability
2434+
// to forward Trampoline payments.
2435+
TrampolineAmountGreaterThanOnion,
2436+
}
2437+
2438+
fn do_test_trampoline_unblinded_receive_contraint_failure(failure_scenario: TrampolineConstraintFailureScenarios) {
2439+
// Simulate a payment of A (0) -> B (1) -> C(Trampoline) (2)
2440+
2441+
const TOTAL_NODE_COUNT: usize = 3;
2442+
let secp_ctx = Secp256k1::new();
2443+
2444+
let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT);
2445+
let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs);
2446+
let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]);
2447+
let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs);
2448+
2449+
let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2450+
let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
2451+
2452+
for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks
2453+
connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1);
2454+
}
2455+
2456+
let alice_node_id = nodes[0].node().get_our_node_id();
2457+
let bob_node_id = nodes[1].node().get_our_node_id();
2458+
let carol_node_id = nodes[2].node().get_our_node_id();
2459+
2460+
let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap();
2461+
let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap();
2462+
2463+
let amt_msat = 1000;
2464+
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
2465+
let payee_tlvs = blinded_path::payment::TrampolineForwardTlvs {
2466+
next_trampoline: alice_node_id,
2467+
payment_constraints: PaymentConstraints {
2468+
max_cltv_expiry: u32::max_value(),
2469+
htlc_minimum_msat: amt_msat,
2470+
},
2471+
features: BlindedHopFeatures::empty(),
2472+
payment_relay: PaymentRelay {
2473+
cltv_expiry_delta: 0,
2474+
fee_proportional_millionths: 0,
2475+
fee_base_msat: 0,
2476+
},
2477+
next_blinding_override: None,
2478+
};
2479+
2480+
let carol_unblinded_tlvs = payee_tlvs.encode();
2481+
let path = [((carol_node_id, None), WithoutLength(&carol_unblinded_tlvs))];
2482+
let carol_alice_trampoline_session_priv = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03");
2483+
let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &carol_alice_trampoline_session_priv);
2484+
let carol_blinded_hops = blinded_path::utils::construct_blinded_hops(
2485+
&secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv,
2486+
).unwrap();
2487+
2488+
let trampoline_cltv_value = if failure_scenario == TrampolineConstraintFailureScenarios::TrampolineCLTVGreaterThanOnion { 52 } else { 72 };
2489+
2490+
let route = Route {
2491+
paths: vec![Path {
2492+
hops: vec![
2493+
// Bob
2494+
RouteHop {
2495+
pubkey: bob_node_id,
2496+
node_features: NodeFeatures::empty(),
2497+
short_channel_id: alice_bob_scid,
2498+
channel_features: ChannelFeatures::empty(),
2499+
fee_msat: 1000,
2500+
cltv_expiry_delta: 48,
2501+
maybe_announced_channel: false,
2502+
},
2503+
2504+
// Carol
2505+
RouteHop {
2506+
pubkey: carol_node_id,
2507+
node_features: NodeFeatures::empty(),
2508+
short_channel_id: bob_carol_scid,
2509+
channel_features: ChannelFeatures::empty(),
2510+
fee_msat: 0, // no routing fees because it's the final hop
2511+
cltv_expiry_delta: 48,
2512+
maybe_announced_channel: false,
2513+
}
2514+
],
2515+
blinded_tail: Some(BlindedTail {
2516+
trampoline_hops: vec![
2517+
// Carol
2518+
TrampolineHop {
2519+
pubkey: carol_node_id,
2520+
node_features: Features::empty(),
2521+
fee_msat: amt_msat,
2522+
cltv_expiry_delta: trampoline_cltv_value,
2523+
},
2524+
],
2525+
hops: carol_blinded_hops,
2526+
blinding_point: carol_blinding_point,
2527+
excess_final_cltv_expiry_delta: 39,
2528+
final_value_msat: amt_msat,
2529+
})
2530+
}],
2531+
route_params: None,
2532+
};
2533+
2534+
let payment_id = PaymentId(payment_hash.0);
2535+
2536+
nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap();
2537+
2538+
let replacement_onion = {
2539+
// create a substitute onion where the last Trampoline hop is an unblinded receive, which we
2540+
// (deliberately) do not support out of the box, therefore necessitating this workaround
2541+
let trampoline_secret_key = secret_from_hex("0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799");
2542+
let prng_seed = secret_from_hex("fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9");
2543+
let recipient_onion_fields = RecipientOnionFields::spontaneous_empty();
2544+
2545+
let blinded_tail = route.paths[0].blinded_tail.clone().unwrap();
2546+
2547+
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();
2548+
// pop the last dummy hop
2549+
trampoline_payloads.pop();
2550+
trampoline_payloads.push(msgs::OutboundTrampolinePayload::Receive {
2551+
payment_data: Some(msgs::FinalOnionHopData {
2552+
payment_secret,
2553+
total_msat: amt_msat,
2554+
}),
2555+
sender_intended_htlc_amt_msat: amt_msat,
2556+
cltv_expiry_height: 104,
2557+
});
2558+
2559+
let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key);
2560+
let trampoline_packet = onion_utils::construct_trampoline_onion_packet(
2561+
trampoline_payloads,
2562+
trampoline_onion_keys,
2563+
prng_seed.secret_bytes(),
2564+
&payment_hash,
2565+
None,
2566+
).unwrap();
2567+
2568+
// Get the original inner session private key that the ChannelManager generated so we can
2569+
// re-use it for the outer session private key. This way HMAC validation in attributable
2570+
// errors do not makes the test fail.
2571+
let mut orig_inner_priv_bytes = [0u8; 32];
2572+
nodes[0].node.test_modify_pending_payment(&payment_id, |pmt| {
2573+
if let crate::ln::outbound_payment::PendingOutboundPayment::Retryable { session_privs, .. } = pmt {
2574+
orig_inner_priv_bytes = *session_privs.iter().next().unwrap();
2575+
}
2576+
});
2577+
let inner_session_priv = SecretKey::from_slice(&orig_inner_priv_bytes).unwrap();
2578+
2579+
// Derive the outer session private key from the inner one.
2580+
let outer_session_priv_hash = Sha256::hash(&inner_session_priv.secret_bytes());
2581+
let outer_session_priv = SecretKey::from_slice(&outer_session_priv_hash.to_byte_array()).unwrap();
2582+
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();
2583+
let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv);
2584+
let outer_packet = onion_utils::construct_onion_packet(
2585+
outer_payloads,
2586+
outer_onion_keys,
2587+
prng_seed.secret_bytes(),
2588+
&payment_hash,
2589+
).unwrap();
2590+
2591+
outer_packet
2592+
};
2593+
2594+
check_added_monitors!(&nodes[0], 1);
2595+
2596+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
2597+
assert_eq!(events.len(), 1);
2598+
let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
2599+
let mut update_message = match first_message_event {
2600+
MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => {
2601+
assert_eq!(updates.update_add_htlcs.len(), 1);
2602+
updates.update_add_htlcs.get_mut(0)
2603+
},
2604+
_ => panic!()
2605+
};
2606+
update_message.map(|msg| {
2607+
msg.onion_routing_packet = replacement_onion.clone();
2608+
});
2609+
2610+
let route: &[&Node] = &[&nodes[1], &nodes[2]];
2611+
let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event)
2612+
.with_payment_preimage(payment_preimage)
2613+
.without_claimable_event()
2614+
.expect_failure(HTLCHandlingFailureType::Receive { payment_hash });
2615+
2616+
do_pass_along_path(args);
2617+
{
2618+
let unblinded_node_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
2619+
nodes[1].node.handle_update_fail_htlc(
2620+
nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0]
2621+
);
2622+
do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false);
2623+
}
2624+
{
2625+
let unblinded_node_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
2626+
nodes[0].node.handle_update_fail_htlc(
2627+
nodes[1].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0]
2628+
);
2629+
do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false);
2630+
}
2631+
2632+
match failure_scenario {
2633+
TrampolineConstraintFailureScenarios::TrampolineAmountGreaterThanOnion => {
2634+
let payment_failed_conditions = PaymentFailedConditions::new()
2635+
.expected_htlc_error_data(LocalHTLCFailureReason::FinalIncorrectHTLCAmount, &[0, 0, 0, 0, 0, 0, 3, 232]);
2636+
expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions);
2637+
},
2638+
TrampolineConstraintFailureScenarios::TrampolineCLTVGreaterThanOnion => {
2639+
let payment_failed_conditions = PaymentFailedConditions::new()
2640+
.expected_htlc_error_data(LocalHTLCFailureReason::FinalIncorrectCLTVExpiry, &[0, 0, 0, 84]);
2641+
expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions);
2642+
}
2643+
}
2644+
}
2645+
2646+
#[test]
2647+
fn test_trampoline_enforced_constrait_cltv() {
2648+
do_test_trampoline_unblinded_receive_contraint_failure(TrampolineConstraintFailureScenarios::TrampolineCLTVGreaterThanOnion);
2649+
}
2650+
24292651
#[test]
24302652
fn test_trampoline_forward_rejection() {
24312653
const TOTAL_NODE_COUNT: usize = 3;

0 commit comments

Comments
 (0)