@@ -130,7 +130,7 @@ pub fn fail_blinded_htlc_backwards(
130130 let unblinded_node_updates = get_htlc_update_msgs ! ( nodes[ i] , nodes[ i-1 ] . node. get_our_node_id( ) ) ;
131131 assert_eq ! ( unblinded_node_updates. update_fail_htlcs. len( ) , 1 ) ;
132132 nodes[ i-1 ] . node . handle_update_fail_htlc (
133- nodes[ i] . node . get_our_node_id ( ) , & unblinded_node_updates. update_fail_htlcs [ i- 1 ]
133+ nodes[ i] . node . get_our_node_id ( ) , & unblinded_node_updates. update_fail_htlcs [ 0 ]
134134 ) ;
135135 do_commitment_signed_dance ( & nodes[ i-1 ] , & nodes[ i] , & unblinded_node_updates. commitment_signed , false , false ) ;
136136 } ,
@@ -2061,3 +2061,170 @@ fn test_successful_trampoline_single_hop_receive() {
20612061 pass_along_route ( & nodes[ 0 ] , & [ & [ & nodes[ 1 ] , & nodes[ 2 ] ] ] , amt_msat, payment_hash, payment_secret) ;
20622062 claim_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] , & nodes[ 2 ] ] , payment_preimage) ;
20632063}
2064+
2065+ #[ test]
2066+ #[ cfg( trampoline) ]
2067+ fn test_trampoline_blinded_decoding_failure ( ) {
2068+ // Simulate a payment failure of A (0) -> B (1) -> C(Trampoline (blinded forward)) (2)
2069+
2070+ const TOTAL_NODE_COUNT : usize = 3 ;
2071+ let secp_ctx = Secp256k1 :: new ( ) ;
2072+
2073+ let chanmon_cfgs = create_chanmon_cfgs ( TOTAL_NODE_COUNT ) ;
2074+ let node_cfgs = create_node_cfgs ( TOTAL_NODE_COUNT , & chanmon_cfgs) ;
2075+ let node_chanmgrs = create_node_chanmgrs ( TOTAL_NODE_COUNT , & node_cfgs, & vec ! [ None ; TOTAL_NODE_COUNT ] ) ;
2076+ let mut nodes = create_network ( TOTAL_NODE_COUNT , & node_cfgs, & node_chanmgrs) ;
2077+
2078+ let ( _, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
2079+ let ( _, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
2080+
2081+ for i in 0 ..TOTAL_NODE_COUNT { // connect all nodes' blocks
2082+ connect_blocks ( & nodes[ i] , ( TOTAL_NODE_COUNT as u32 ) * CHAN_CONFIRM_DEPTH + 1 - nodes[ i] . best_block_info ( ) . 1 ) ;
2083+ }
2084+
2085+ let alice_node_id = nodes[ 0 ] . node ( ) . get_our_node_id ( ) ;
2086+ let bob_node_id = nodes[ 1 ] . node ( ) . get_our_node_id ( ) ;
2087+ let carol_node_id = nodes[ 2 ] . node ( ) . get_our_node_id ( ) ;
2088+
2089+ let alice_bob_scid = nodes[ 0 ] . node ( ) . list_channels ( ) . iter ( ) . find ( |c| c. channel_id == chan_id_alice_bob) . unwrap ( ) . short_channel_id . unwrap ( ) ;
2090+ let bob_carol_scid = nodes[ 1 ] . node ( ) . list_channels ( ) . iter ( ) . find ( |c| c. channel_id == chan_id_bob_carol) . unwrap ( ) . short_channel_id . unwrap ( ) ;
2091+
2092+ let amt_msat = 1000 ;
2093+ let ( payment_preimage, payment_hash, _) = get_payment_preimage_hash ( & nodes[ 2 ] , Some ( amt_msat) , None ) ;
2094+ let payee_tlvs = blinded_path:: payment:: TrampolineForwardTlvs {
2095+ next_trampoline : alice_node_id,
2096+ payment_constraints : PaymentConstraints {
2097+ max_cltv_expiry : u32:: max_value ( ) ,
2098+ htlc_minimum_msat : amt_msat,
2099+ } ,
2100+ features : BlindedHopFeatures :: empty ( ) ,
2101+ payment_relay : PaymentRelay {
2102+ cltv_expiry_delta : 0 ,
2103+ fee_proportional_millionths : 0 ,
2104+ fee_base_msat : 0 ,
2105+ } ,
2106+ next_blinding_override : None ,
2107+ } ;
2108+
2109+ let carol_unblinded_tlvs = payee_tlvs. encode ( ) ;
2110+ let path = [ ( carol_node_id, WithoutLength ( & carol_unblinded_tlvs) ) ] ;
2111+ let carol_alice_trampoline_session_priv = secret_from_hex ( "a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03" ) ;
2112+ let carol_blinding_point = PublicKey :: from_secret_key ( & secp_ctx, & carol_alice_trampoline_session_priv) ;
2113+ let carol_blinded_hops = blinded_path:: utils:: construct_blinded_hops (
2114+ & secp_ctx, path. into_iter ( ) , & carol_alice_trampoline_session_priv
2115+ ) . unwrap ( ) ;
2116+
2117+ let route = Route {
2118+ paths : vec ! [ Path {
2119+ hops: vec![
2120+ // Bob
2121+ RouteHop {
2122+ pubkey: bob_node_id,
2123+ node_features: NodeFeatures :: empty( ) ,
2124+ short_channel_id: alice_bob_scid,
2125+ channel_features: ChannelFeatures :: empty( ) ,
2126+ fee_msat: 1000 ,
2127+ cltv_expiry_delta: 48 ,
2128+ maybe_announced_channel: false ,
2129+ } ,
2130+
2131+ // Carol
2132+ RouteHop {
2133+ pubkey: carol_node_id,
2134+ node_features: NodeFeatures :: empty( ) ,
2135+ short_channel_id: bob_carol_scid,
2136+ channel_features: ChannelFeatures :: empty( ) ,
2137+ fee_msat: 0 ,
2138+ cltv_expiry_delta: 48 ,
2139+ maybe_announced_channel: false ,
2140+ }
2141+ ] ,
2142+ blinded_tail: Some ( BlindedTail {
2143+ trampoline_hops: vec![
2144+ // Carol
2145+ TrampolineHop {
2146+ pubkey: carol_node_id,
2147+ node_features: Features :: empty( ) ,
2148+ fee_msat: amt_msat,
2149+ cltv_expiry_delta: 24 ,
2150+ } ,
2151+ ] ,
2152+ hops: carol_blinded_hops,
2153+ blinding_point: carol_blinding_point,
2154+ excess_final_cltv_expiry_delta: 39 ,
2155+ final_value_msat: amt_msat,
2156+ } )
2157+ } ] ,
2158+ route_params : None ,
2159+ } ;
2160+
2161+ nodes[ 0 ] . node . send_payment_with_route ( route. clone ( ) , payment_hash, RecipientOnionFields :: spontaneous_empty ( ) , PaymentId ( payment_hash. 0 ) ) . unwrap ( ) ;
2162+
2163+ let replacement_onion = {
2164+ // create a substitute onion where the last Trampoline hop is a forward
2165+ let trampoline_secret_key = secret_from_hex ( "0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799" ) ;
2166+ let prng_seed = secret_from_hex ( "fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9" ) ;
2167+ let recipient_onion_fields = RecipientOnionFields :: spontaneous_empty ( ) ;
2168+
2169+ let mut blinded_tail = route. paths [ 0 ] . blinded_tail . clone ( ) . unwrap ( ) ;
2170+
2171+ // append some dummy blinded hop so the intro hop looks like a forward
2172+ blinded_tail. hops . push ( BlindedHop {
2173+ blinded_node_id : alice_node_id,
2174+ encrypted_payload : vec ! [ ] ,
2175+ } ) ;
2176+
2177+ 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 ( ) ;
2178+
2179+ // pop the last dummy hop
2180+ trampoline_payloads. pop ( ) ;
2181+
2182+ let trampoline_onion_keys = onion_utils:: construct_trampoline_onion_keys ( & secp_ctx, & route. paths [ 0 ] . blinded_tail . as_ref ( ) . unwrap ( ) , & trampoline_secret_key) . unwrap ( ) ;
2183+ let trampoline_packet = onion_utils:: construct_trampoline_onion_packet (
2184+ trampoline_payloads,
2185+ trampoline_onion_keys,
2186+ prng_seed. secret_bytes ( ) ,
2187+ & payment_hash,
2188+ None ,
2189+ ) . unwrap ( ) ;
2190+
2191+ let outer_session_priv = secret_from_hex ( "e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677" ) ;
2192+
2193+ 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 ( ) ;
2194+ let outer_onion_keys = onion_utils:: construct_onion_keys ( & secp_ctx, & route. clone ( ) . paths [ 0 ] , & outer_session_priv) . unwrap ( ) ;
2195+ let outer_packet = onion_utils:: construct_onion_packet (
2196+ outer_payloads,
2197+ outer_onion_keys,
2198+ prng_seed. secret_bytes ( ) ,
2199+ & payment_hash,
2200+ ) . unwrap ( ) ;
2201+
2202+ outer_packet
2203+ } ;
2204+
2205+ check_added_monitors ! ( & nodes[ 0 ] , 1 ) ;
2206+
2207+ // Check that we've queued the HTLCs of the async keysend payment.
2208+ let mut events = nodes[ 0 ] . node . get_and_clear_pending_msg_events ( ) ;
2209+ assert_eq ! ( events. len( ) , 1 ) ;
2210+ let mut first_message_event = remove_first_msg_event_to_node ( & nodes[ 1 ] . node . get_our_node_id ( ) , & mut events) ;
2211+ let mut update_message = match first_message_event {
2212+ MessageSendEvent :: UpdateHTLCs { ref mut updates, .. } => {
2213+ assert_eq ! ( updates. update_add_htlcs. len( ) , 1 ) ;
2214+ updates. update_add_htlcs . get_mut ( 0 )
2215+ } ,
2216+ _ => panic ! ( )
2217+ } ;
2218+ update_message. map ( |msg| {
2219+ msg. onion_routing_packet = replacement_onion. clone ( ) ;
2220+ } ) ;
2221+
2222+ let route: & [ & Node ] = & [ & nodes[ 1 ] , & nodes[ 2 ] ] ;
2223+ let args = PassAlongPathArgs :: new ( & nodes[ 0 ] , route, amt_msat, payment_hash, first_message_event)
2224+ . with_payment_preimage ( payment_preimage)
2225+ . without_claimable_event ( )
2226+ . expect_failure ( HTLCDestination :: InvalidOnion ) ;
2227+ do_pass_along_path ( args) ;
2228+
2229+ fail_blinded_htlc_backwards ( payment_hash, 1 , & [ & nodes[ 0 ] , & nodes[ 1 ] , & nodes[ 2 ] ] , false ) ;
2230+ }
0 commit comments