@@ -19,7 +19,7 @@ use ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures};
1919use ln:: msgs:: { DecodeError , ErrorAction , LightningError , MAX_VALUE_MSAT } ;
2020use routing:: scoring:: { ChannelUsage , Score } ;
2121use routing:: network_graph:: { DirectedChannelInfoWithUpdate , EffectiveCapacity , NetworkGraph , ReadOnlyNetworkGraph , NodeId , RoutingFees } ;
22- use util:: ser:: { Writeable , Readable } ;
22+ use util:: ser:: { Writeable , Readable , Writer } ;
2323use util:: logger:: { Level , Logger } ;
2424use util:: chacha20:: ChaCha20 ;
2525
@@ -151,8 +151,8 @@ impl Readable for Route {
151151
152152/// Parameters needed to find a [`Route`].
153153///
154- /// Passed to [`find_route`] and also provided in [`Event::PaymentPathFailed`] for retrying a failed
155- /// payment path.
154+ /// Passed to [`find_route`] and [`build_route_from_hops`], but also provided in
155+ /// [`Event::PaymentPathFailed`] for retrying a failed payment path.
156156///
157157/// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
158158#[ derive( Clone , Debug ) ]
@@ -676,16 +676,11 @@ pub fn find_route<L: Deref, S: Score>(
676676) -> Result < Route , LightningError >
677677where L :: Target : Logger {
678678 let network_graph = network. read_only ( ) ;
679- match get_route (
680- our_node_pubkey, & route_params. payment_params , & network_graph, first_hops, route_params. final_value_msat ,
681- route_params. final_cltv_expiry_delta , logger, scorer, random_seed_bytes
682- ) {
683- Ok ( mut route) => {
684- add_random_cltv_offset ( & mut route, & route_params. payment_params , & network_graph, random_seed_bytes) ;
685- Ok ( route)
686- } ,
687- Err ( err) => Err ( err) ,
688- }
679+ let mut route = get_route ( our_node_pubkey, & route_params. payment_params , & network_graph, first_hops,
680+ route_params. final_value_msat , route_params. final_cltv_expiry_delta , logger, scorer,
681+ random_seed_bytes) ?;
682+ add_random_cltv_offset ( & mut route, & route_params. payment_params , & network_graph, random_seed_bytes) ;
683+ Ok ( route)
689684}
690685
691686pub ( crate ) fn get_route < L : Deref , S : Score > (
@@ -1703,7 +1698,9 @@ where L::Target: Logger {
17031698// destination, if the remaining CLTV expiry delta exactly matches a feasible path in the network
17041699// graph. In order to improve privacy, this method obfuscates the CLTV expiry deltas along the
17051700// payment path by adding a randomized 'shadow route' offset to the final hop.
1706- fn add_random_cltv_offset ( route : & mut Route , payment_params : & PaymentParameters , network_graph : & ReadOnlyNetworkGraph , random_seed_bytes : & [ u8 ; 32 ] ) {
1701+ fn add_random_cltv_offset ( route : & mut Route , payment_params : & PaymentParameters ,
1702+ network_graph : & ReadOnlyNetworkGraph , random_seed_bytes : & [ u8 ; 32 ]
1703+ ) {
17071704 let network_channels = network_graph. channels ( ) ;
17081705 let network_nodes = network_graph. nodes ( ) ;
17091706
@@ -1785,10 +1782,84 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
17851782 }
17861783}
17871784
1785+ /// Construct a route from us (payer) to the target node (payee) via the given hops (which should
1786+ /// exclude the payer, but include the payee). This may be useful, e.g., for probing the chosen path.
1787+ ///
1788+ /// Re-uses logic from `find_route`, so the restrictions described there also apply here.
1789+ pub fn build_route_from_hops < L : Deref > (
1790+ our_node_pubkey : & PublicKey , hops : & [ PublicKey ] , route_params : & RouteParameters , network : & NetworkGraph ,
1791+ logger : L , random_seed_bytes : & [ u8 ; 32 ]
1792+ ) -> Result < Route , LightningError >
1793+ where L :: Target : Logger {
1794+ let network_graph = network. read_only ( ) ;
1795+ let mut route = build_route_from_hops_internal (
1796+ our_node_pubkey, hops, & route_params. payment_params , & network_graph,
1797+ route_params. final_value_msat , route_params. final_cltv_expiry_delta , logger, random_seed_bytes) ?;
1798+ add_random_cltv_offset ( & mut route, & route_params. payment_params , & network_graph, random_seed_bytes) ;
1799+ Ok ( route)
1800+ }
1801+
1802+ fn build_route_from_hops_internal < L : Deref > (
1803+ our_node_pubkey : & PublicKey , hops : & [ PublicKey ] , payment_params : & PaymentParameters ,
1804+ network_graph : & ReadOnlyNetworkGraph , final_value_msat : u64 , final_cltv_expiry_delta : u32 ,
1805+ logger : L , random_seed_bytes : & [ u8 ; 32 ]
1806+ ) -> Result < Route , LightningError > where L :: Target : Logger {
1807+
1808+ struct HopScorer {
1809+ our_node_id : NodeId ,
1810+ hop_ids : [ Option < NodeId > ; MAX_PATH_LENGTH_ESTIMATE as usize ] ,
1811+ }
1812+
1813+ impl Score for HopScorer {
1814+ fn channel_penalty_msat ( & self , _short_channel_id : u64 , source : & NodeId , target : & NodeId ,
1815+ _usage : ChannelUsage ) -> u64
1816+ {
1817+ let mut cur_id = self . our_node_id ;
1818+ for i in 0 ..self . hop_ids . len ( ) {
1819+ if let Some ( next_id) = self . hop_ids [ i] {
1820+ if cur_id == * source && next_id == * target {
1821+ return 0 ;
1822+ }
1823+ cur_id = next_id;
1824+ } else {
1825+ break ;
1826+ }
1827+ }
1828+ u64:: max_value ( )
1829+ }
1830+
1831+ fn payment_path_failed ( & mut self , _path : & [ & RouteHop ] , _short_channel_id : u64 ) { }
1832+
1833+ fn payment_path_successful ( & mut self , _path : & [ & RouteHop ] ) { }
1834+ }
1835+
1836+ impl < ' a > Writeable for HopScorer {
1837+ #[ inline]
1838+ fn write < W : Writer > ( & self , _w : & mut W ) -> Result < ( ) , io:: Error > {
1839+ unreachable ! ( ) ;
1840+ }
1841+ }
1842+
1843+ if hops. len ( ) > MAX_PATH_LENGTH_ESTIMATE . into ( ) {
1844+ return Err ( LightningError { err : "Cannot build a route exceeding the maximum path length." . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ;
1845+ }
1846+
1847+ let our_node_id = NodeId :: from_pubkey ( our_node_pubkey) ;
1848+ let mut hop_ids = [ None ; MAX_PATH_LENGTH_ESTIMATE as usize ] ;
1849+ for i in 0 ..hops. len ( ) {
1850+ hop_ids[ i] = Some ( NodeId :: from_pubkey ( & hops[ i] ) ) ;
1851+ }
1852+
1853+ let scorer = HopScorer { our_node_id, hop_ids } ;
1854+
1855+ get_route ( our_node_pubkey, payment_params, network_graph, None , final_value_msat,
1856+ final_cltv_expiry_delta, logger, & scorer, random_seed_bytes)
1857+ }
1858+
17881859#[ cfg( test) ]
17891860mod tests {
17901861 use routing:: network_graph:: { NetworkGraph , NetGraphMsgHandler , NodeId } ;
1791- use routing:: router:: { get_route, add_random_cltv_offset, default_node_features,
1862+ use routing:: router:: { get_route, build_route_from_hops_internal , add_random_cltv_offset, default_node_features,
17921863 PaymentParameters , Route , RouteHint , RouteHintHop , RouteHop , RoutingFees ,
17931864 DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA , MAX_PATH_LENGTH_ESTIMATE } ;
17941865 use routing:: scoring:: { ChannelUsage , Score } ;
@@ -5486,6 +5557,26 @@ mod tests {
54865557 assert ! ( path_plausibility. iter( ) . all( |x| * x) ) ;
54875558 }
54885559
5560+ #[ test]
5561+ fn builds_correct_path_from_hops ( ) {
5562+ let ( secp_ctx, network, _, _, logger) = build_graph ( ) ;
5563+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
5564+ let network_graph = network. read_only ( ) ;
5565+
5566+ let keys_manager = test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
5567+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
5568+
5569+ let payment_params = PaymentParameters :: from_node_id ( nodes[ 3 ] ) ;
5570+ let hops = [ nodes[ 1 ] , nodes[ 2 ] , nodes[ 4 ] , nodes[ 3 ] ] ;
5571+ let route = build_route_from_hops_internal ( & our_id, & hops, & payment_params,
5572+ & network_graph, 100 , 0 , Arc :: clone ( & logger) , & random_seed_bytes) . unwrap ( ) ;
5573+ let route_hop_pubkeys = route. paths [ 0 ] . iter ( ) . map ( |hop| hop. pubkey ) . collect :: < Vec < _ > > ( ) ;
5574+ assert_eq ! ( hops. len( ) , route. paths[ 0 ] . len( ) ) ;
5575+ for ( idx, hop_pubkey) in hops. iter ( ) . enumerate ( ) {
5576+ assert ! ( * hop_pubkey == route_hop_pubkeys[ idx] ) ;
5577+ }
5578+ }
5579+
54895580 #[ cfg( not( feature = "no-std" ) ) ]
54905581 pub ( super ) fn random_init_seed ( ) -> u64 {
54915582 // Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.
0 commit comments