@@ -2086,7 +2086,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) {
2086
2086
pubkey: carol_node_id,
2087
2087
node_features: Features :: empty( ) ,
2088
2088
fee_msat: amt_msat,
2089
- cltv_expiry_delta: 24 ,
2089
+ cltv_expiry_delta: 104 ,
2090
2090
} ,
2091
2091
] ,
2092
2092
hops: carol_blinded_hops,
@@ -2206,8 +2206,237 @@ fn test_trampoline_single_hop_receive() {
2206
2206
do_test_trampoline_single_hop_receive ( false ) ;
2207
2207
}
2208
2208
2209
+ fn do_test_trampoline_unblinded_receive ( underpay : bool ) {
2210
+ // Simulate a payment of A (0) -> B (1) -> C(Trampoline) (2)
2211
+
2212
+ const TOTAL_NODE_COUNT : usize = 3 ;
2213
+ let secp_ctx = Secp256k1 :: new ( ) ;
2214
+
2215
+ let chanmon_cfgs = create_chanmon_cfgs ( TOTAL_NODE_COUNT ) ;
2216
+ let node_cfgs = create_node_cfgs ( TOTAL_NODE_COUNT , & chanmon_cfgs) ;
2217
+ let node_chanmgrs = create_node_chanmgrs ( TOTAL_NODE_COUNT , & node_cfgs, & vec ! [ None ; TOTAL_NODE_COUNT ] ) ;
2218
+ let mut nodes = create_network ( TOTAL_NODE_COUNT , & node_cfgs, & node_chanmgrs) ;
2219
+
2220
+ let ( _, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
2221
+ let ( _, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
2222
+
2223
+ for i in 0 ..TOTAL_NODE_COUNT { // connect all nodes' blocks
2224
+ connect_blocks ( & nodes[ i] , ( TOTAL_NODE_COUNT as u32 ) * CHAN_CONFIRM_DEPTH + 1 - nodes[ i] . best_block_info ( ) . 1 ) ;
2225
+ }
2226
+
2227
+ let alice_node_id = nodes[ 0 ] . node ( ) . get_our_node_id ( ) ;
2228
+ let bob_node_id = nodes[ 1 ] . node ( ) . get_our_node_id ( ) ;
2229
+ let carol_node_id = nodes[ 2 ] . node ( ) . get_our_node_id ( ) ;
2230
+
2231
+ let alice_bob_scid = nodes[ 0 ] . node ( ) . list_channels ( ) . iter ( ) . find ( |c| c. channel_id == chan_id_alice_bob) . unwrap ( ) . short_channel_id . unwrap ( ) ;
2232
+ let bob_carol_scid = nodes[ 1 ] . node ( ) . list_channels ( ) . iter ( ) . find ( |c| c. channel_id == chan_id_bob_carol) . unwrap ( ) . short_channel_id . unwrap ( ) ;
2233
+
2234
+ let amt_msat = 1000 ;
2235
+ let ( payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash ( & nodes[ 2 ] , Some ( amt_msat) , None ) ;
2236
+ let payee_tlvs = blinded_path:: payment:: TrampolineForwardTlvs {
2237
+ next_trampoline : alice_node_id,
2238
+ payment_constraints : PaymentConstraints {
2239
+ max_cltv_expiry : u32:: max_value ( ) ,
2240
+ htlc_minimum_msat : amt_msat,
2241
+ } ,
2242
+ features : BlindedHopFeatures :: empty ( ) ,
2243
+ payment_relay : PaymentRelay {
2244
+ cltv_expiry_delta : 0 ,
2245
+ fee_proportional_millionths : 0 ,
2246
+ fee_base_msat : 0 ,
2247
+ } ,
2248
+ next_blinding_override : None ,
2249
+ } ;
2250
+
2251
+ let carol_unblinded_tlvs = payee_tlvs. encode ( ) ;
2252
+ let path = [ ( ( carol_node_id, None ) , WithoutLength ( & carol_unblinded_tlvs) ) ] ;
2253
+ let carol_alice_trampoline_session_priv = secret_from_hex ( "a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03" ) ;
2254
+ let carol_blinding_point = PublicKey :: from_secret_key ( & secp_ctx, & carol_alice_trampoline_session_priv) ;
2255
+ let carol_blinded_hops = blinded_path:: utils:: construct_blinded_hops (
2256
+ & secp_ctx, path. into_iter ( ) , & carol_alice_trampoline_session_priv,
2257
+ ) . unwrap ( ) ;
2258
+
2259
+ let route = Route {
2260
+ paths : vec ! [ Path {
2261
+ hops: vec![
2262
+ // Bob
2263
+ RouteHop {
2264
+ pubkey: bob_node_id,
2265
+ node_features: NodeFeatures :: empty( ) ,
2266
+ short_channel_id: alice_bob_scid,
2267
+ channel_features: ChannelFeatures :: empty( ) ,
2268
+ fee_msat: 1000 ,
2269
+ cltv_expiry_delta: 48 ,
2270
+ maybe_announced_channel: false ,
2271
+ } ,
2272
+
2273
+ // Carol
2274
+ RouteHop {
2275
+ pubkey: carol_node_id,
2276
+ node_features: NodeFeatures :: empty( ) ,
2277
+ short_channel_id: bob_carol_scid,
2278
+ channel_features: ChannelFeatures :: empty( ) ,
2279
+ fee_msat: 0 , // no routing fees because it's the final hop
2280
+ cltv_expiry_delta: 48 ,
2281
+ maybe_announced_channel: false ,
2282
+ }
2283
+ ] ,
2284
+ blinded_tail: Some ( BlindedTail {
2285
+ trampoline_hops: vec![
2286
+ // Carol
2287
+ TrampolineHop {
2288
+ pubkey: carol_node_id,
2289
+ node_features: Features :: empty( ) ,
2290
+ fee_msat: 0 , // no trampoline fee becuase we are receiving.
2291
+ cltv_expiry_delta: 72 , // blinded hop cltv to be used building the outer onion.
2292
+ } ,
2293
+ ] ,
2294
+ hops: carol_blinded_hops,
2295
+ blinding_point: carol_blinding_point,
2296
+ excess_final_cltv_expiry_delta: 39 ,
2297
+ final_value_msat: amt_msat,
2298
+ } )
2299
+ } ] ,
2300
+ route_params : None ,
2301
+ } ;
2302
+
2303
+ let payment_id = PaymentId ( payment_hash. 0 ) ;
2304
+
2305
+ nodes[ 0 ] . node . send_payment_with_route ( route. clone ( ) , payment_hash, RecipientOnionFields :: spontaneous_empty ( ) , PaymentId ( payment_hash. 0 ) ) . unwrap ( ) ;
2306
+
2307
+ let replacement_onion = {
2308
+ // create a substitute onion where the last Trampoline hop is an unblinded receive, which we
2309
+ // (deliberately) do not support out of the box, therefore necessitating this workaround
2310
+ let trampoline_secret_key = secret_from_hex ( "0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799" ) ;
2311
+ let prng_seed = secret_from_hex ( "fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9" ) ;
2312
+ let recipient_onion_fields = RecipientOnionFields :: spontaneous_empty ( ) ;
2313
+
2314
+ let blinded_tail = route. paths [ 0 ] . blinded_tail . clone ( ) . unwrap ( ) ;
2315
+
2316
+ 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 ( ) ;
2317
+ // pop the last dummy hop
2318
+ trampoline_payloads. pop ( ) ;
2319
+ let replacement_payload_amount = if underpay { amt_msat * 2 } else { amt_msat } ;
2320
+
2321
+ trampoline_payloads. push ( msgs:: OutboundTrampolinePayload :: Receive {
2322
+ payment_data : Some ( msgs:: FinalOnionHopData {
2323
+ payment_secret,
2324
+ total_msat : replacement_payload_amount,
2325
+ } ) ,
2326
+ sender_intended_htlc_amt_msat : replacement_payload_amount,
2327
+ // We will use the same cltv to the outer onion: 72 (blinded tail) + 32 (offset).
2328
+ cltv_expiry_height : 104 ,
2329
+ } ) ;
2330
+
2331
+ let trampoline_onion_keys = onion_utils:: construct_trampoline_onion_keys ( & secp_ctx, & route. paths [ 0 ] . blinded_tail . as_ref ( ) . unwrap ( ) , & trampoline_secret_key) ;
2332
+ let trampoline_packet = onion_utils:: construct_trampoline_onion_packet (
2333
+ trampoline_payloads,
2334
+ trampoline_onion_keys,
2335
+ prng_seed. secret_bytes ( ) ,
2336
+ & payment_hash,
2337
+ None ,
2338
+ ) . unwrap ( ) ;
2339
+
2340
+ // Get the original inner session private key that the ChannelManager generated so we can
2341
+ // re-use it for the outer session private key. This way HMAC validation in attributable
2342
+ // errors do not makes the test fail.
2343
+ let mut orig_inner_priv_bytes = [ 0u8 ; 32 ] ;
2344
+ nodes[ 0 ] . node . test_modify_pending_payment ( & payment_id, |pmt| {
2345
+ if let crate :: ln:: outbound_payment:: PendingOutboundPayment :: Retryable { session_privs, .. } = pmt {
2346
+ orig_inner_priv_bytes = * session_privs. iter ( ) . next ( ) . unwrap ( ) ;
2347
+ }
2348
+ } ) ;
2349
+ let inner_session_priv = SecretKey :: from_slice ( & orig_inner_priv_bytes) . unwrap ( ) ;
2350
+
2351
+ // Derive the outer session private key from the inner one.
2352
+ let outer_session_priv_hash = Sha256 :: hash ( & inner_session_priv. secret_bytes ( ) ) ;
2353
+ let outer_session_priv = SecretKey :: from_slice ( & outer_session_priv_hash. to_byte_array ( ) ) . unwrap ( ) ;
2354
+ 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 ( ) ;
2355
+ let outer_onion_keys = onion_utils:: construct_onion_keys ( & secp_ctx, & route. clone ( ) . paths [ 0 ] , & outer_session_priv) ;
2356
+ let outer_packet = onion_utils:: construct_onion_packet (
2357
+ outer_payloads,
2358
+ outer_onion_keys,
2359
+ prng_seed. secret_bytes ( ) ,
2360
+ & payment_hash,
2361
+ ) . unwrap ( ) ;
2362
+
2363
+ outer_packet
2364
+ } ;
2365
+
2366
+ check_added_monitors ! ( & nodes[ 0 ] , 1 ) ;
2367
+
2368
+ let mut events = nodes[ 0 ] . node . get_and_clear_pending_msg_events ( ) ;
2369
+ assert_eq ! ( events. len( ) , 1 ) ;
2370
+ let mut first_message_event = remove_first_msg_event_to_node ( & nodes[ 1 ] . node . get_our_node_id ( ) , & mut events) ;
2371
+ let mut update_message = match first_message_event {
2372
+ MessageSendEvent :: UpdateHTLCs { ref mut updates, .. } => {
2373
+ assert_eq ! ( updates. update_add_htlcs. len( ) , 1 ) ;
2374
+ updates. update_add_htlcs . get_mut ( 0 )
2375
+ } ,
2376
+ _ => panic ! ( )
2377
+ } ;
2378
+ update_message. map ( |msg| {
2379
+ msg. onion_routing_packet = replacement_onion. clone ( ) ;
2380
+ } ) ;
2381
+
2382
+ let route: & [ & Node ] = & [ & nodes[ 1 ] , & nodes[ 2 ] ] ;
2383
+ let args = PassAlongPathArgs :: new ( & nodes[ 0 ] , route, amt_msat, payment_hash, first_message_event) ;
2384
+
2385
+ let args = if underpay {
2386
+ args. with_payment_preimage ( payment_preimage)
2387
+ . without_claimable_event ( )
2388
+ . expect_failure ( HTLCHandlingFailureType :: Receive { payment_hash } )
2389
+ } else {
2390
+ args. with_payment_secret ( payment_secret)
2391
+ } ;
2392
+
2393
+ do_pass_along_path ( args) ;
2394
+
2395
+ if underpay {
2396
+ {
2397
+ let unblinded_node_updates = get_htlc_update_msgs ! ( nodes[ 2 ] , nodes[ 1 ] . node. get_our_node_id( ) ) ;
2398
+ nodes[ 1 ] . node . handle_update_fail_htlc (
2399
+ nodes[ 2 ] . node . get_our_node_id ( ) , & unblinded_node_updates. update_fail_htlcs [ 0 ]
2400
+ ) ;
2401
+ do_commitment_signed_dance ( & nodes[ 1 ] , & nodes[ 2 ] , & unblinded_node_updates. commitment_signed , true , false ) ;
2402
+ }
2403
+ {
2404
+ let unblinded_node_updates = get_htlc_update_msgs ! ( nodes[ 1 ] , nodes[ 0 ] . node. get_our_node_id( ) ) ;
2405
+ nodes[ 0 ] . node . handle_update_fail_htlc (
2406
+ nodes[ 1 ] . node . get_our_node_id ( ) , & unblinded_node_updates. update_fail_htlcs [ 0 ]
2407
+ ) ;
2408
+ do_commitment_signed_dance ( & nodes[ 0 ] , & nodes[ 1 ] , & unblinded_node_updates. commitment_signed , false , false ) ;
2409
+ }
2410
+ {
2411
+ let payment_failed_conditions = PaymentFailedConditions :: new ( )
2412
+ . expected_htlc_error_data ( LocalHTLCFailureReason :: FinalIncorrectHTLCAmount , & [ 0 , 0 , 0 , 0 , 0 , 0 , 3 , 232 ] ) ;
2413
+ expect_payment_failed_conditions ( & nodes[ 0 ] , payment_hash, false , payment_failed_conditions) ;
2414
+ }
2415
+ } else {
2416
+ claim_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] , & nodes[ 2 ] ] , payment_preimage) ;
2417
+ }
2418
+ }
2419
+
2420
+ #[ test]
2421
+ fn test_trampoline_unblinded_receive_underpay ( ) {
2422
+ do_test_trampoline_unblinded_receive ( true ) ;
2423
+ }
2424
+
2209
2425
#[ test]
2210
- fn test_trampoline_unblinded_receive ( ) {
2426
+ fn test_trampoline_unblinded_receive_normal ( ) {
2427
+ do_test_trampoline_unblinded_receive ( false ) ;
2428
+ }
2429
+
2430
+ #[ derive( PartialEq ) ]
2431
+ enum TrampolineConstraintFailureScenarios {
2432
+ TrampolineCLTVGreaterThanOnion ,
2433
+ #[ allow( dead_code) ]
2434
+ // TODO: To test amount greater than onion we need the ability
2435
+ // to forward Trampoline payments.
2436
+ TrampolineAmountGreaterThanOnion ,
2437
+ }
2438
+
2439
+ fn do_test_trampoline_unblinded_receive_constraint_failure ( failure_scenario : TrampolineConstraintFailureScenarios ) {
2211
2440
// Simulate a payment of A (0) -> B (1) -> C(Trampoline) (2)
2212
2441
2213
2442
const TOTAL_NODE_COUNT : usize = 3 ;
@@ -2257,6 +2486,15 @@ fn test_trampoline_unblinded_receive() {
2257
2486
& secp_ctx, path. into_iter ( ) , & carol_alice_trampoline_session_priv,
2258
2487
) . unwrap ( ) ;
2259
2488
2489
+ // We decide an arbitrary ctlv delta for the blinded hop that will be the only cltv delta
2490
+ // in the blinded tail.
2491
+ let blinded_hop_cltv = if failure_scenario == TrampolineConstraintFailureScenarios :: TrampolineCLTVGreaterThanOnion { 52 } else { 72 } ;
2492
+ // Then when building the trampoline hop we use an arbitrary cltv delta offset to be used
2493
+ // when re-building the outer trampoline onion.
2494
+ let starting_cltv_offset_trampoline = 32 ;
2495
+ // Finally we decide a forced cltv delta expiry for the trampoline hop itself.
2496
+ // This one will be compared against the outer onion ctlv delta.
2497
+ let forced_trampoline_cltv_delta = 104 ;
2260
2498
let route = Route {
2261
2499
paths : vec ! [ Path {
2262
2500
hops: vec![
@@ -2277,7 +2515,7 @@ fn test_trampoline_unblinded_receive() {
2277
2515
node_features: NodeFeatures :: empty( ) ,
2278
2516
short_channel_id: bob_carol_scid,
2279
2517
channel_features: ChannelFeatures :: empty( ) ,
2280
- fee_msat: 0 ,
2518
+ fee_msat: 0 , // no routing fees because it's the final hop
2281
2519
cltv_expiry_delta: 48 ,
2282
2520
maybe_announced_channel: false ,
2283
2521
}
@@ -2289,18 +2527,21 @@ fn test_trampoline_unblinded_receive() {
2289
2527
pubkey: carol_node_id,
2290
2528
node_features: Features :: empty( ) ,
2291
2529
fee_msat: amt_msat,
2292
- cltv_expiry_delta: 24 ,
2530
+ cltv_expiry_delta: blinded_hop_cltv , // blinded tail ctlv delta.
2293
2531
} ,
2294
2532
] ,
2295
2533
hops: carol_blinded_hops,
2296
2534
blinding_point: carol_blinding_point,
2535
+ // This will be ignored becase we force the cltv_expiry of the trampoline hop.
2297
2536
excess_final_cltv_expiry_delta: 39 ,
2298
2537
final_value_msat: amt_msat,
2299
2538
} )
2300
2539
} ] ,
2301
2540
route_params : None ,
2302
2541
} ;
2303
2542
2543
+ let payment_id = PaymentId ( payment_hash. 0 ) ;
2544
+
2304
2545
nodes[ 0 ] . node . send_payment_with_route ( route. clone ( ) , payment_hash, RecipientOnionFields :: spontaneous_empty ( ) , PaymentId ( payment_hash. 0 ) ) . unwrap ( ) ;
2305
2546
2306
2547
let replacement_onion = {
@@ -2311,18 +2552,17 @@ fn test_trampoline_unblinded_receive() {
2311
2552
let recipient_onion_fields = RecipientOnionFields :: spontaneous_empty ( ) ;
2312
2553
2313
2554
let blinded_tail = route. paths [ 0 ] . blinded_tail . clone ( ) . unwrap ( ) ;
2314
- 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 ( ) ;
2315
2555
2556
+ let ( mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils:: build_trampoline_onion_payloads ( & blinded_tail, amt_msat, & recipient_onion_fields, starting_cltv_offset_trampoline, & None ) . unwrap ( ) ;
2316
2557
// pop the last dummy hop
2317
2558
trampoline_payloads. pop ( ) ;
2318
-
2319
2559
trampoline_payloads. push ( msgs:: OutboundTrampolinePayload :: Receive {
2320
2560
payment_data : Some ( msgs:: FinalOnionHopData {
2321
2561
payment_secret,
2322
2562
total_msat : amt_msat,
2323
2563
} ) ,
2324
2564
sender_intended_htlc_amt_msat : amt_msat,
2325
- cltv_expiry_height : 104 ,
2565
+ cltv_expiry_height : forced_trampoline_cltv_delta ,
2326
2566
} ) ;
2327
2567
2328
2568
let trampoline_onion_keys = onion_utils:: construct_trampoline_onion_keys ( & secp_ctx, & route. paths [ 0 ] . blinded_tail . as_ref ( ) . unwrap ( ) , & trampoline_secret_key) ;
@@ -2334,10 +2574,20 @@ fn test_trampoline_unblinded_receive() {
2334
2574
None ,
2335
2575
) . unwrap ( ) ;
2336
2576
2337
- // Use a different session key to construct the replacement onion packet. Note that the sender isn't aware of
2338
- // this and won't be able to decode the fulfill hold times.
2339
- let outer_session_priv = secret_from_hex ( "e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677" ) ;
2577
+ // Get the original inner session private key that the ChannelManager generated so we can
2578
+ // re-use it for the outer session private key. This way HMAC validation in attributable
2579
+ // errors do not makes the test fail.
2580
+ let mut orig_inner_priv_bytes = [ 0u8 ; 32 ] ;
2581
+ nodes[ 0 ] . node . test_modify_pending_payment ( & payment_id, |pmt| {
2582
+ if let crate :: ln:: outbound_payment:: PendingOutboundPayment :: Retryable { session_privs, .. } = pmt {
2583
+ orig_inner_priv_bytes = * session_privs. iter ( ) . next ( ) . unwrap ( ) ;
2584
+ }
2585
+ } ) ;
2586
+ let inner_session_priv = SecretKey :: from_slice ( & orig_inner_priv_bytes) . unwrap ( ) ;
2340
2587
2588
+ // Derive the outer session private key from the inner one.
2589
+ let outer_session_priv_hash = Sha256 :: hash ( & inner_session_priv. secret_bytes ( ) ) ;
2590
+ let outer_session_priv = SecretKey :: from_slice ( & outer_session_priv_hash. to_byte_array ( ) ) . unwrap ( ) ;
2341
2591
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 ( ) ;
2342
2592
let outer_onion_keys = onion_utils:: construct_onion_keys ( & secp_ctx, & route. clone ( ) . paths [ 0 ] , & outer_session_priv) ;
2343
2593
let outer_packet = onion_utils:: construct_onion_packet (
@@ -2368,10 +2618,46 @@ fn test_trampoline_unblinded_receive() {
2368
2618
2369
2619
let route: & [ & Node ] = & [ & nodes[ 1 ] , & nodes[ 2 ] ] ;
2370
2620
let args = PassAlongPathArgs :: new ( & nodes[ 0 ] , route, amt_msat, payment_hash, first_message_event)
2371
- . with_payment_secret ( payment_secret) ;
2621
+ . with_payment_preimage ( payment_preimage)
2622
+ . without_claimable_event ( )
2623
+ . expect_failure ( HTLCHandlingFailureType :: Receive { payment_hash } ) ;
2624
+
2372
2625
do_pass_along_path ( args) ;
2626
+ {
2627
+ let unblinded_node_updates = get_htlc_update_msgs ! ( nodes[ 2 ] , nodes[ 1 ] . node. get_our_node_id( ) ) ;
2628
+ nodes[ 1 ] . node . handle_update_fail_htlc (
2629
+ nodes[ 2 ] . node . get_our_node_id ( ) , & unblinded_node_updates. update_fail_htlcs [ 0 ]
2630
+ ) ;
2631
+ do_commitment_signed_dance ( & nodes[ 1 ] , & nodes[ 2 ] , & unblinded_node_updates. commitment_signed , true , false ) ;
2632
+ }
2633
+ {
2634
+ let unblinded_node_updates = get_htlc_update_msgs ! ( nodes[ 1 ] , nodes[ 0 ] . node. get_our_node_id( ) ) ;
2635
+ nodes[ 0 ] . node . handle_update_fail_htlc (
2636
+ nodes[ 1 ] . node . get_our_node_id ( ) , & unblinded_node_updates. update_fail_htlcs [ 0 ]
2637
+ ) ;
2638
+ do_commitment_signed_dance ( & nodes[ 0 ] , & nodes[ 1 ] , & unblinded_node_updates. commitment_signed , false , false ) ;
2639
+ }
2373
2640
2374
- claim_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] , & nodes[ 2 ] ] , payment_preimage) ;
2641
+ match failure_scenario {
2642
+ TrampolineConstraintFailureScenarios :: TrampolineAmountGreaterThanOnion => {
2643
+ let expected_error_data = amt_msat. to_be_bytes ( ) ;
2644
+ let payment_failed_conditions = PaymentFailedConditions :: new ( )
2645
+ . expected_htlc_error_data ( LocalHTLCFailureReason :: FinalIncorrectHTLCAmount , & expected_error_data) ;
2646
+ expect_payment_failed_conditions ( & nodes[ 0 ] , payment_hash, false , payment_failed_conditions) ;
2647
+ } ,
2648
+ TrampolineConstraintFailureScenarios :: TrampolineCLTVGreaterThanOnion => {
2649
+ // The amount of the outer onion cltv delta plus the trampoline offset.
2650
+ let expected_error_data = ( blinded_hop_cltv + starting_cltv_offset_trampoline) . to_be_bytes ( ) ;
2651
+ let payment_failed_conditions = PaymentFailedConditions :: new ( )
2652
+ . expected_htlc_error_data ( LocalHTLCFailureReason :: FinalIncorrectCLTVExpiry , & expected_error_data) ;
2653
+ expect_payment_failed_conditions ( & nodes[ 0 ] , payment_hash, false , payment_failed_conditions) ;
2654
+ }
2655
+ }
2656
+ }
2657
+
2658
+ #[ test]
2659
+ fn test_trampoline_enforced_constraint_cltv ( ) {
2660
+ do_test_trampoline_unblinded_receive_constraint_failure ( TrampolineConstraintFailureScenarios :: TrampolineCLTVGreaterThanOnion ) ;
2375
2661
}
2376
2662
2377
2663
#[ test]
0 commit comments