@@ -2426,6 +2426,228 @@ fn test_trampoline_unblinded_receive_normal() {
2426
2426
do_test_trampoline_unblinded_receive ( false ) ;
2427
2427
}
2428
2428
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
+
2429
2651
#[ test]
2430
2652
fn test_trampoline_forward_rejection ( ) {
2431
2653
const TOTAL_NODE_COUNT : usize = 3 ;
0 commit comments