@@ -19,9 +19,9 @@ use lightning::ln::channel_state::{ChannelCounterparty, ChannelDetails, ChannelS
1919use lightning:: ln:: channelmanager;
2020use lightning:: ln:: msgs;
2121use lightning:: ln:: types:: ChannelId ;
22- use lightning:: routing:: gossip:: { NetworkGraph , RoutingFees } ;
22+ use lightning:: routing:: gossip:: { NetworkGraph , NodeId , RoutingFees } ;
2323use lightning:: routing:: router:: {
24- find_route, PaymentParameters , RouteHint , RouteHintHop , RouteParameters ,
24+ find_route, Payee , PaymentParameters , RouteHint , RouteHintHop , RouteParameters ,
2525} ;
2626use lightning:: routing:: scoring:: {
2727 ProbabilisticScorer , ProbabilisticScoringDecayParameters , ProbabilisticScoringFeeParameters ,
@@ -296,19 +296,97 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
296296 let final_value_msat = slice_to_be64( get_slice!( 8 ) ) ;
297297 let final_cltv_expiry_delta = slice_to_be32( get_slice!( 4 ) ) ;
298298 let route_params = $route_params( final_value_msat, final_cltv_expiry_delta, target) ;
299- let _ = find_route(
299+ let route = find_route(
300300 & our_pubkey,
301301 & route_params,
302302 & net_graph,
303- $first_hops
304- . map( |c| c. iter( ) . collect:: <Vec <_>>( ) )
303+ $first_hops. map( |c| c. iter( ) . collect:: <Vec <_>>( ) )
305304 . as_ref( )
306305 . map( |a| a. as_slice( ) ) ,
307306 & logger,
308307 & scorer,
309308 & ProbabilisticScoringFeeParameters :: default ( ) ,
310309 & random_seed_bytes,
311310 ) ;
311+ if let Ok ( route) = route {
312+ // If we generated a route, check that it is valid
313+ // TODO: Check CLTV deltas
314+ assert_eq!( route. route_params. as_ref( ) , Some ( & route_params) ) ;
315+ let graph = net_graph. read_only( ) ;
316+ let mut blinded_path_payment_amts = new_hash_map( ) ;
317+ let mut total_fee = 0 ;
318+ let mut total_sent = 0 ;
319+ for path in & route. paths {
320+ total_fee += path. fee_msat( ) ;
321+ total_sent += path. final_value_msat( ) ;
322+ let unblinded_recipient = path. hops. last( ) . expect( "No hops" ) . pubkey;
323+ let mut hops = path. hops. iter( ) . peekable( ) ;
324+ ' path_check: while let Some ( hop) = hops. next( ) {
325+ if let Some ( next_hop) = hops. peek( ) . cloned( ) {
326+ let amt_to_send: u64 = hops. clone( ) . map( |hop| hop. fee_msat) . sum( ) ;
327+ if let Payee :: Clear { route_hints, .. } = & route_params. payment_params. payee {
328+ // If we paid to an invoice with clear route hints, check
329+ // whether we pulled from a route hint first, and if not fall
330+ // back to searching through the public network graph.
331+ for hint in route_hints. iter( ) {
332+ let mut hint_hops = hint. 0 . iter( ) . peekable( ) ;
333+ while let Some ( hint_hop) = hint_hops. next( ) {
334+ let next_hint_hop_key = hint_hops. peek( )
335+ . map( |hint_hop| hint_hop. src_node_id)
336+ . unwrap_or( unblinded_recipient) ;
337+
338+ let matches_hint = hint_hop. src_node_id == hop. pubkey
339+ && hint_hop. short_channel_id == next_hop. short_channel_id
340+ && next_hint_hop_key == next_hop. pubkey;
341+ if matches_hint {
342+ let min_fee = amt_to_send
343+ * ( hint_hop. fees. proportional_millionths as u64 ) / 1_000_000
344+ + hint_hop. fees. base_msat as u64 ;
345+ assert!( min_fee <= hop. fee_msat) ;
346+ continue ' path_check;
347+ }
348+ }
349+ }
350+ }
351+ let chan = graph. channel( hop. short_channel_id) . expect( "No chan" ) ;
352+ assert!( chan. one_to_two. is_some( ) && chan. two_to_one. is_some( ) ) ;
353+ let fees = if chan. node_one == NodeId :: from_pubkey( & hop. pubkey) {
354+ chan. one_to_two. as_ref( ) . unwrap( ) . fees
355+ } else {
356+ chan. two_to_one. as_ref( ) . unwrap( ) . fees
357+ } ;
358+ let min_fee = amt_to_send * ( fees. proportional_millionths as u64 ) / 1_000_000 + fees. base_msat as u64 ;
359+ assert!( min_fee <= hop. fee_msat) ;
360+ } else {
361+ if let Payee :: Blinded { route_hints, .. } = & route_params. payment_params. payee {
362+ let tail = path. blinded_tail. as_ref( ) . expect( "No blinded path" ) ;
363+ if tail. hops. len( ) == 1 {
364+ // We don't consider the payinfo for one-hop blinded paths
365+ // since they're not "real" blinded paths.
366+ continue ;
367+ }
368+ let blinded_intro_amt = tail. final_value_msat + hop. fee_msat;
369+ // TODO: We should add some kind of coverage of trampoline hops
370+ assert!( tail. trampoline_hops. is_empty( ) ) ;
371+ let hint_filter = |hint: &&BlindedPaymentPath | {
372+ hint. blinded_hops( ) [ 0 ] . encrypted_payload == tail. hops[ 0 ] . encrypted_payload
373+ } ;
374+ let mut matching_hints = route_hints. iter( ) . filter( hint_filter) ;
375+ let matching_hint = matching_hints. next( ) . unwrap( ) ;
376+ assert!( matching_hints. next( ) . is_none( ) ) ;
377+ let key = & tail. hops[ 0 ] . encrypted_payload;
378+ let used = blinded_path_payment_amts. entry( key) . or_insert( 0u64 ) ;
379+ * used += blinded_intro_amt;
380+ assert!( * used <= matching_hint. payinfo. htlc_maximum_msat) ;
381+ assert!( blinded_intro_amt >= matching_hint. payinfo. htlc_minimum_msat) ;
382+ }
383+ break ;
384+ }
385+ }
386+ }
387+ assert!( total_sent >= final_value_msat) ;
388+ assert!( total_fee <= route_params. max_total_routing_fee_msat. unwrap_or( u64 :: MAX ) ) ;
389+ }
312390 }
313391 } ;
314392 }
@@ -383,7 +461,8 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
383461 let dummy_pk = PublicKey :: from_slice ( & [ 2 ; 33 ] ) . unwrap ( ) ;
384462 let last_hops: Vec < BlindedPaymentPath > = last_hops_unblinded
385463 . into_iter ( )
386- . map ( |hint| {
464+ . enumerate ( )
465+ . map ( |( hint_idx, hint) | {
387466 let hop = & hint. 0 [ 0 ] ;
388467 let payinfo = BlindedPayInfo {
389468 fee_base_msat : hop. fees . base_msat ,
@@ -398,7 +477,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
398477 for _ in 0 ..num_blinded_hops {
399478 blinded_hops. push ( BlindedHop {
400479 blinded_node_id : dummy_pk,
401- encrypted_payload : Vec :: new ( ) ,
480+ encrypted_payload : hint_idx . to_ne_bytes ( ) . to_vec ( ) ,
402481 } ) ;
403482 }
404483 BlindedPaymentPath :: from_raw (
0 commit comments