@@ -200,6 +200,17 @@ struct PathBuildingHop {
200
200
/// A mirror of the same field in RouteGraphNode. Note that this is only used during the graph
201
201
/// walk and may be invalid thereafter.
202
202
path_htlc_minimum_msat : u64 ,
203
+ /// If we've already processed a node as the best node, we shouldn't process it again. Normally
204
+ /// we'd just ignore it if we did as all channels would have a higher new fee, but because we
205
+ /// may decrease the amounts in use as we walk the graph, the actual calculated fee may
206
+ /// decrease as well. Thus, we have to explicitly track which nodes have been processed and
207
+ /// avoid processing them again.
208
+ was_processed : bool ,
209
+ #[ cfg( test) ]
210
+ // In tests, we apply further sanity checks on cases where we skip nodes we already processed
211
+ // to ensure it is specifically in cases where the fee has gone down because of a decrease in
212
+ // valud_contribution_msat, which requires tracking it here.
213
+ value_contribution_msat : u64 ,
203
214
}
204
215
205
216
// Instantiated with a list of hops with correct data in them collected during path finding,
@@ -614,85 +625,114 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
614
625
total_fee_msat: u64 :: max_value( ) ,
615
626
htlc_minimum_msat: $directional_info. htlc_minimum_msat,
616
627
path_htlc_minimum_msat,
628
+ was_processed: false ,
629
+ #[ cfg( test) ]
630
+ value_contribution_msat,
617
631
}
618
632
} ) ;
619
633
620
- let mut hop_use_fee_msat = 0 ;
621
- let mut total_fee_msat = $next_hops_fee_msat;
622
-
623
- // Ignore hop_use_fee_msat for channel-from-us as we assume all channels-from-us
624
- // will have the same effective-fee
625
- if $src_node_id != * our_node_id {
626
- match compute_fees( amount_to_transfer_over_msat, $directional_info. fees) {
627
- // max_value means we'll always fail
628
- // the old_entry.total_fee_msat > total_fee_msat check
629
- None => total_fee_msat = u64 :: max_value( ) ,
630
- Some ( fee_msat) => {
631
- hop_use_fee_msat = fee_msat;
632
- total_fee_msat += hop_use_fee_msat;
633
- // When calculating the lowest inbound fees to a node, we
634
- // calculate fees here not based on the actual value we think
635
- // will flow over this channel, but on the minimum value that
636
- // we'll accept flowing over it. Otherwise we may later find a
637
- // different path to the source node that is more expensive,
638
- // but which we consider to be cheaper because we are capacity
639
- // constrained and the relative fee becomes lower.
640
- match compute_fees( minimal_value_contribution_msat, old_entry. src_lowest_inbound_fees)
641
- . map( |a| a. checked_add( total_fee_msat) ) {
642
- Some ( Some ( v) ) => {
643
- total_fee_msat = v;
644
- } ,
645
- _ => {
646
- total_fee_msat = u64 :: max_value( ) ;
647
- }
648
- } ;
649
- }
650
- }
634
+ #[ allow( unused_mut) ] // We only use the mut in cfg(test)
635
+ let mut should_process = !old_entry. was_processed;
636
+ #[ cfg( any( test, feature = "fuzztarget" ) ) ]
637
+ {
638
+ // In test/fuzzing builds, we do extra checks to make sure the skipping
639
+ // of already-seen nodes only happens in cases we expect (see below).
640
+ if !should_process { should_process = true ; }
651
641
}
652
642
653
- let new_graph_node = RouteGraphNode {
654
- pubkey: $src_node_id,
655
- lowest_fee_to_peer_through_node: total_fee_msat,
656
- lowest_fee_to_node: $next_hops_fee_msat as u64 + hop_use_fee_msat,
657
- value_contribution_msat: value_contribution_msat,
658
- path_htlc_minimum_msat,
659
- } ;
643
+ if should_process {
644
+ let mut hop_use_fee_msat = 0 ;
645
+ let mut total_fee_msat = $next_hops_fee_msat;
646
+
647
+ // Ignore hop_use_fee_msat for channel-from-us as we assume all channels-from-us
648
+ // will have the same effective-fee
649
+ if $src_node_id != * our_node_id {
650
+ match compute_fees( amount_to_transfer_over_msat, $directional_info. fees) {
651
+ // max_value means we'll always fail
652
+ // the old_entry.total_fee_msat > total_fee_msat check
653
+ None => total_fee_msat = u64 :: max_value( ) ,
654
+ Some ( fee_msat) => {
655
+ hop_use_fee_msat = fee_msat;
656
+ total_fee_msat += hop_use_fee_msat;
657
+ // When calculating the lowest inbound fees to a node, we
658
+ // calculate fees here not based on the actual value we think
659
+ // will flow over this channel, but on the minimum value that
660
+ // we'll accept flowing over it. Otherwise we may later find a
661
+ // different path to the source node that is more expensive,
662
+ // but which we consider to be cheaper because we are capacity
663
+ // constrained and the relative fee becomes lower.
664
+ match compute_fees( minimal_value_contribution_msat, old_entry. src_lowest_inbound_fees)
665
+ . map( |a| a. checked_add( total_fee_msat) ) {
666
+ Some ( Some ( v) ) => {
667
+ total_fee_msat = v;
668
+ } ,
669
+ _ => {
670
+ total_fee_msat = u64 :: max_value( ) ;
671
+ }
672
+ } ;
673
+ }
674
+ }
675
+ }
660
676
661
- // Update the way of reaching $src_node_id with the given $chan_id (from $dest_node_id),
662
- // if this way is cheaper than the already known
663
- // (considering the cost to "reach" this channel from the route destination,
664
- // the cost of using this channel,
665
- // and the cost of routing to the source node of this channel).
666
- // Also, consider that htlc_minimum_msat_difference, because we might end up
667
- // paying it. Consider the following exploit:
668
- // we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
669
- // and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
670
- // 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
671
- // by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
672
- // to this channel.
673
- // Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
674
- // but it may require additional tracking - we don't want to double-count
675
- // the fees included in $incl_fee_next_hops_htlc_minimum_msat, but also
676
- // can't use something that may decrease on future hops.
677
- let old_cost = cmp:: max( old_entry. total_fee_msat, old_entry. path_htlc_minimum_msat) ;
678
- let new_cost = cmp:: max( total_fee_msat, path_htlc_minimum_msat) ;
679
-
680
- if new_cost < old_cost {
681
- targets. push( new_graph_node) ;
682
- old_entry. next_hops_fee_msat = $next_hops_fee_msat;
683
- old_entry. hop_use_fee_msat = hop_use_fee_msat;
684
- old_entry. total_fee_msat = total_fee_msat;
685
- old_entry. route_hop = RouteHop {
686
- pubkey: $dest_node_id. clone( ) ,
687
- node_features: NodeFeatures :: empty( ) ,
688
- short_channel_id: $chan_id. clone( ) ,
689
- channel_features: $chan_features. clone( ) ,
690
- fee_msat: 0 , // This value will be later filled with hop_use_fee_msat of the following channel
691
- cltv_expiry_delta: $directional_info. cltv_expiry_delta as u32 ,
677
+ let new_graph_node = RouteGraphNode {
678
+ pubkey: $src_node_id,
679
+ lowest_fee_to_peer_through_node: total_fee_msat,
680
+ lowest_fee_to_node: $next_hops_fee_msat as u64 + hop_use_fee_msat,
681
+ value_contribution_msat: value_contribution_msat,
682
+ path_htlc_minimum_msat,
692
683
} ;
693
- old_entry. channel_fees = $directional_info. fees;
694
- old_entry. htlc_minimum_msat = $directional_info. htlc_minimum_msat;
695
- old_entry. path_htlc_minimum_msat = path_htlc_minimum_msat;
684
+
685
+ // Update the way of reaching $src_node_id with the given $chan_id (from $dest_node_id),
686
+ // if this way is cheaper than the already known
687
+ // (considering the cost to "reach" this channel from the route destination,
688
+ // the cost of using this channel,
689
+ // and the cost of routing to the source node of this channel).
690
+ // Also, consider that htlc_minimum_msat_difference, because we might end up
691
+ // paying it. Consider the following exploit:
692
+ // we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
693
+ // and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
694
+ // 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
695
+ // by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
696
+ // to this channel.
697
+ // Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
698
+ // but it may require additional tracking - we don't want to double-count
699
+ // the fees included in $incl_fee_next_hops_htlc_minimum_msat, but also
700
+ // can't use something that may decrease on future hops.
701
+ let old_cost = cmp:: max( old_entry. total_fee_msat, old_entry. path_htlc_minimum_msat) ;
702
+ let new_cost = cmp:: max( total_fee_msat, path_htlc_minimum_msat) ;
703
+
704
+ if !old_entry. was_processed && new_cost < old_cost {
705
+ targets. push( new_graph_node) ;
706
+ old_entry. next_hops_fee_msat = $next_hops_fee_msat;
707
+ old_entry. hop_use_fee_msat = hop_use_fee_msat;
708
+ old_entry. total_fee_msat = total_fee_msat;
709
+ old_entry. route_hop = RouteHop {
710
+ pubkey: $dest_node_id. clone( ) ,
711
+ node_features: NodeFeatures :: empty( ) ,
712
+ short_channel_id: $chan_id. clone( ) ,
713
+ channel_features: $chan_features. clone( ) ,
714
+ fee_msat: 0 , // This value will be later filled with hop_use_fee_msat of the following channel
715
+ cltv_expiry_delta: $directional_info. cltv_expiry_delta as u32 ,
716
+ } ;
717
+ old_entry. channel_fees = $directional_info. fees;
718
+ old_entry. htlc_minimum_msat = $directional_info. htlc_minimum_msat;
719
+ old_entry. path_htlc_minimum_msat = path_htlc_minimum_msat;
720
+ #[ cfg( test) ]
721
+ {
722
+ old_entry. value_contribution_msat = value_contribution_msat;
723
+ }
724
+ } else if old_entry. was_processed && new_cost < old_cost {
725
+ #[ cfg( test) ]
726
+ {
727
+ // If we're skipping processing a node which was previously
728
+ // processed even though we found another path to it with a
729
+ // cheaper fee, check that it was because the second path we
730
+ // found (which we are processing now) has a lower value
731
+ // contribution due to an HTLC minimum limit.
732
+ debug_assert!( path_htlc_minimum_msat < old_entry. path_htlc_minimum_msat) ;
733
+ debug_assert!( value_contribution_msat < old_entry. value_contribution_msat) ;
734
+ }
735
+ }
696
736
}
697
737
}
698
738
}
@@ -707,7 +747,19 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
707
747
// This data can later be helpful to optimize routing (pay lower fees).
708
748
macro_rules! add_entries_to_cheapest_to_target_node {
709
749
( $node: expr, $node_id: expr, $fee_to_target_msat: expr, $next_hops_value_contribution: expr, $incl_fee_next_hops_htlc_minimum_msat: expr ) => {
710
- if first_hops. is_some( ) {
750
+ let skip_node = if let Some ( elem) = dist. get_mut( $node_id) {
751
+ let v = elem. was_processed;
752
+ elem. was_processed = true ;
753
+ v
754
+ } else {
755
+ // Entries are added to dist in add_entry!() when there is a channel from a node.
756
+ // Because there are no channels from payee, it will not have a dist entry at this point.
757
+ // If we're processing any other node, it is always be the result of a channel from it.
758
+ assert_eq!( $node_id, payee) ;
759
+ false
760
+ } ;
761
+
762
+ if !skip_node && first_hops. is_some( ) {
711
763
if let Some ( & ( ref first_hop, ref features, ref outbound_capacity_msat) ) = first_hop_targets. get( & $node_id) {
712
764
add_entry!( first_hop, * our_node_id, $node_id, dummy_directional_info, Some ( outbound_capacity_msat / 1000 ) , features. to_context( ) , $fee_to_target_msat, $next_hops_value_contribution, $incl_fee_next_hops_htlc_minimum_msat) ;
713
765
}
@@ -720,7 +772,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
720
772
features = NodeFeatures :: empty( ) ;
721
773
}
722
774
723
- if !features. requires_unknown_bits( ) {
775
+ if !skip_node && ! features. requires_unknown_bits( ) {
724
776
for chan_id in $node. channels. iter( ) {
725
777
let chan = network. get_channels( ) . get( chan_id) . unwrap( ) ;
726
778
if !chan. features. requires_unknown_bits( ) {
@@ -940,6 +992,11 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
940
992
break ' path_construction;
941
993
}
942
994
995
+ // If we found a path back to the payee, we shouldn't try to process it again. This is
996
+ // the equivalent of the `elem.was_processed` check in
997
+ // add_entries_to_cheapest_to_target_node!() (see comment there for more info).
998
+ if pubkey == * payee { continue ' path_construction; }
999
+
943
1000
// Otherwise, since the current target node is not us,
944
1001
// keep "unrolling" the payment graph from payee to payer by
945
1002
// finding a way to reach the current target from the payer side.
0 commit comments