@@ -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:: {
2727ProbabilisticScorer , ProbabilisticScoringDecayParameters , ProbabilisticScoringFeeParameters ,
@@ -296,7 +296,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
296296let final_value_msat = slice_to_be64( get_slice!( 8 ) ) ;
297297let final_cltv_expiry_delta = slice_to_be32( get_slice!( 4 ) ) ;
298298let 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,
@@ -309,6 +309,91 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
309309& ProbabilisticScoringFeeParameters :: default ( ) ,
310310& random_seed_bytes,
311311) ;
312+ if let Ok ( route) = route {
313+ // If we generated a route, check that it is valid
314+ // TODO: Check CLTV deltas
315+ assert_eq!( route. route_params. as_ref( ) , Some ( & route_params) ) ;
316+ let graph = net_graph. read_only( ) ;
317+ let mut blinded_path_payment_amts = new_hash_map( ) ;
318+ let mut total_fee = 0 ;
319+ let mut total_sent = 0 ;
320+ for path in & route. paths {
321+ total_fee += path. fee_msat( ) ;
322+ total_sent += path. final_value_msat( ) ;
323+ let unblinded_recipient = path. hops. last( ) . expect( "No hops" ) . pubkey;
324+ let mut hops = path. hops. iter( ) . peekable( ) ;
325+ let payee = & route_params. payment_params. payee;
326+ ' path_check: while let Some ( hop) = hops. next( ) {
327+ if let Some ( next) = hops. peek( ) . cloned( ) {
328+ let amt_sent: u64 = hops. clone( ) . map( |hop| hop. fee_msat) . sum( ) ;
329+ if let Payee :: Clear { route_hints, .. } = payee {
330+ // If we paid to an invoice with clear route hints, check
331+ // whether we pulled from a route hint first, and if not fall
332+ // back to searching through the public network graph.
333+ for hint_path in route_hints. iter( ) {
334+ let mut hint_hops = hint_path. 0 . iter( ) . peekable( ) ;
335+ while let Some ( hint) = hint_hops. next( ) {
336+ let next_hint_hop_key = hint_hops
337+ . peek( )
338+ . map( |hint_hop| hint_hop. src_node_id)
339+ . unwrap_or( unblinded_recipient) ;
340+
341+ let matches_hint = hint. src_node_id == hop. pubkey
342+ && hint. short_channel_id == next. short_channel_id
343+ && next_hint_hop_key == next. pubkey;
344+ let prop = hint. fees. proportional_millionths as u128 ;
345+ let base = hint. fees. base_msat as u128 ;
346+ let min_fee = amt_sent as u128 * prop / 1000000 + base;
347+ if matches_hint {
348+ assert!( min_fee <= hop. fee_msat as u128 ) ;
349+ continue ' path_check;
350+ }
351+ }
352+ }
353+ }
354+ let chan = graph. channel( hop. short_channel_id) . expect( "No chan" ) ;
355+ assert!( chan. one_to_two. is_some( ) && chan. two_to_one. is_some( ) ) ;
356+ let fees = if chan. node_one == NodeId :: from_pubkey( & hop. pubkey) {
357+ chan. one_to_two. as_ref( ) . unwrap( ) . fees
358+ } else {
359+ chan. two_to_one. as_ref( ) . unwrap( ) . fees
360+ } ;
361+ let prop_fee = fees. proportional_millionths as u128 ;
362+ let base_fee = fees. base_msat as u128 ;
363+ let min_fee = amt_sent as u128 * prop_fee / 1_000_000 + base_fee;
364+ assert!( min_fee <= hop. fee_msat as u128 ) ;
365+ } else {
366+ if let Payee :: Blinded { route_hints, .. } = payee {
367+ let tail = path. blinded_tail. as_ref( ) . expect( "No blinded path" ) ;
368+ if tail. hops. len( ) == 1 {
369+ // We don't consider the payinfo for one-hop blinded paths
370+ // since they're not "real" blinded paths.
371+ continue ;
372+ }
373+ // TODO: We should add some kind of coverage of trampoline hops
374+ assert!( tail. trampoline_hops. is_empty( ) ) ;
375+ let hint_filter = |hint: &&BlindedPaymentPath | {
376+ // We store a unique counter in each encrypted_payload.
377+ let hint_id = & hint. blinded_hops( ) [ 0 ] . encrypted_payload;
378+ * hint_id == tail. hops[ 0 ] . encrypted_payload
379+ } ;
380+ let mut matching_hints = route_hints. iter( ) . filter( hint_filter) ;
381+ let used_hint = matching_hints. next( ) . unwrap( ) ;
382+ assert!( matching_hints. next( ) . is_none( ) ) ;
383+ let key = & tail. hops[ 0 ] . encrypted_payload;
384+ let used = blinded_path_payment_amts. entry( key) . or_insert( 0u64 ) ;
385+ let blind_intro_amt = tail. final_value_msat + hop. fee_msat;
386+ * used += blind_intro_amt;
387+ assert!( * used <= used_hint. payinfo. htlc_maximum_msat) ;
388+ assert!( blind_intro_amt >= used_hint. payinfo. htlc_minimum_msat) ;
389+ }
390+ break ;
391+ }
392+ }
393+ }
394+ assert!( total_sent >= final_value_msat) ;
395+ assert!( total_fee <= route_params. max_total_routing_fee_msat. unwrap( ) ) ;
396+ }
312397}
313398} ;
314399}
@@ -383,7 +468,8 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
383468let dummy_pk = PublicKey :: from_slice ( & [ 2 ; 33 ] ) . unwrap ( ) ;
384469let last_hops: Vec < BlindedPaymentPath > = last_hops_unblinded
385470. into_iter ( )
386- . map ( |hint| {
471+ . enumerate ( )
472+ . map ( |( hint_idx, hint) | {
387473let hop = & hint. 0 [ 0 ] ;
388474let payinfo = BlindedPayInfo {
389475fee_base_msat : hop. fees . base_msat ,
@@ -398,7 +484,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
398484for _ in 0 ..num_blinded_hops {
399485blinded_hops. push ( BlindedHop {
400486blinded_node_id : dummy_pk,
401- encrypted_payload : Vec :: new ( ) ,
487+ encrypted_payload : hint_idx . to_ne_bytes ( ) . to_vec ( ) ,
402488} ) ;
403489}
404490BlindedPaymentPath :: from_raw (
0 commit comments