@@ -2496,3 +2496,165 @@ fn test_trampoline_constraint_enforcement() {
2496
2496
expect_payment_failed_conditions ( & nodes[ 0 ] , payment_hash, false , payment_failed_conditions) ;
2497
2497
}
2498
2498
}
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