@@ -296,7 +296,6 @@ pub async fn create_simulation_with_network<P: for<'a> PathFinder<'a> + Clone +
296296 ) )
297297}
298298
299-
300299/// Parses the cli options provided and creates a simulation to be run, connecting to lightning nodes and validating
301300/// any activity described in the simulation file.
302301pub async fn create_simulation (
@@ -593,3 +592,228 @@ pub async fn get_validated_activities(
593592
594593 validate_activities ( activity. to_vec ( ) , activity_validation_params) . await
595594}
595+
596+ #[ cfg( test) ]
597+ mod tests {
598+ use super :: * ;
599+ use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , SecretKey } ;
600+ use lightning:: routing:: gossip:: NetworkGraph ;
601+ use lightning:: routing:: router:: { find_route, PaymentParameters , Route , RouteParameters } ;
602+ use lightning:: routing:: scoring:: { ProbabilisticScorer , ProbabilisticScoringDecayParameters } ;
603+ use rand:: RngCore ;
604+ use simln_lib:: clock:: SystemClock ;
605+ use simln_lib:: sim_node:: {
606+ ln_node_from_graph, populate_network_graph, PathFinder , SimGraph , WrappedLog ,
607+ } ;
608+ use simln_lib:: SimulationError ;
609+ use std:: sync:: Arc ;
610+ use tokio:: sync:: Mutex ;
611+ use tokio_util:: task:: TaskTracker ;
612+
613+ /// Gets a key pair generated in a pseudorandom way.
614+ fn get_random_keypair ( ) -> ( SecretKey , PublicKey ) {
615+ let secp = Secp256k1 :: new ( ) ;
616+ let mut rng = rand:: thread_rng ( ) ;
617+ let mut bytes = [ 0u8 ; 32 ] ;
618+ rng. fill_bytes ( & mut bytes) ;
619+ let secret_key = SecretKey :: from_slice ( & bytes) . expect ( "Failed to create secret key" ) ;
620+ let public_key = PublicKey :: from_secret_key ( & secp, & secret_key) ;
621+ ( secret_key, public_key)
622+ }
623+
624+ /// Helper function to create simulated channels for testing
625+ fn create_simulated_channels ( num_channels : usize , capacity_msat : u64 ) -> Vec < SimulatedChannel > {
626+ let mut channels = Vec :: new ( ) ;
627+ for i in 0 ..num_channels {
628+ let ( _node1_sk, node1_pubkey) = get_random_keypair ( ) ;
629+ let ( _node2_sk, node2_pubkey) = get_random_keypair ( ) ;
630+
631+ let channel = SimulatedChannel :: new (
632+ capacity_msat,
633+ ShortChannelID :: from ( i as u64 ) ,
634+ ChannelPolicy {
635+ pubkey : node1_pubkey,
636+ max_htlc_count : 483 ,
637+ max_in_flight_msat : capacity_msat / 2 ,
638+ min_htlc_size_msat : 1000 ,
639+ max_htlc_size_msat : capacity_msat / 2 ,
640+ cltv_expiry_delta : 144 ,
641+ base_fee : 1000 ,
642+ fee_rate_prop : 100 ,
643+ } ,
644+ ChannelPolicy {
645+ pubkey : node2_pubkey,
646+ max_htlc_count : 483 ,
647+ max_in_flight_msat : capacity_msat / 2 ,
648+ min_htlc_size_msat : 1000 ,
649+ max_htlc_size_msat : capacity_msat / 2 ,
650+ cltv_expiry_delta : 144 ,
651+ base_fee : 1000 ,
652+ fee_rate_prop : 100 ,
653+ } ,
654+ ) ;
655+ channels. push ( channel) ;
656+ }
657+ channels
658+ }
659+
660+ /// A pathfinder that always fails to find a path
661+ #[ derive( Clone ) ]
662+ pub struct AlwaysFailPathFinder ;
663+
664+ impl < ' a > PathFinder < ' a > for AlwaysFailPathFinder {
665+ fn find_route (
666+ & self ,
667+ _source : & PublicKey ,
668+ _dest : PublicKey ,
669+ _amount_msat : u64 ,
670+ _pathfinding_graph : & NetworkGraph < & ' a WrappedLog > ,
671+ _scorer : & ProbabilisticScorer < Arc < NetworkGraph < & ' a WrappedLog > > , & ' a WrappedLog > ,
672+ ) -> Result < Route , SimulationError > {
673+ Err ( SimulationError :: SimulatedNetworkError (
674+ "No route found" . to_string ( ) ,
675+ ) )
676+ }
677+ }
678+
679+ /// A pathfinder that only returns single-hop paths
680+ #[ derive( Clone ) ]
681+ pub struct SingleHopOnlyPathFinder ;
682+
683+ impl < ' a > PathFinder < ' a > for SingleHopOnlyPathFinder {
684+ fn find_route (
685+ & self ,
686+ source : & PublicKey ,
687+ dest : PublicKey ,
688+ amount_msat : u64 ,
689+ pathfinding_graph : & NetworkGraph < & ' a WrappedLog > ,
690+ scorer : & ProbabilisticScorer < Arc < NetworkGraph < & ' a WrappedLog > > , & ' a WrappedLog > ,
691+ ) -> Result < Route , SimulationError > {
692+ // Try to find a direct route only (single hop)
693+ let route_params = RouteParameters {
694+ payment_params : PaymentParameters :: from_node_id ( dest, 0 )
695+ . with_max_total_cltv_expiry_delta ( u32:: MAX )
696+ . with_max_path_count ( 1 )
697+ . with_max_channel_saturation_power_of_half ( 1 ) ,
698+ final_value_msat : amount_msat,
699+ max_total_routing_fee_msat : None ,
700+ } ;
701+
702+ // Try to find a route - if it fails or has more than one hop, return an error
703+ match find_route (
704+ source,
705+ & route_params,
706+ pathfinding_graph,
707+ None ,
708+ & WrappedLog { } ,
709+ scorer,
710+ & Default :: default ( ) ,
711+ & [ 0 ; 32 ] ,
712+ ) {
713+ Ok ( route) => {
714+ // Check if the route has exactly one hop
715+ if route. paths . len ( ) == 1 && route. paths [ 0 ] . hops . len ( ) == 1 {
716+ Ok ( route)
717+ } else {
718+ Err ( SimulationError :: SimulatedNetworkError (
719+ "No direct route found" . to_string ( ) ,
720+ ) )
721+ }
722+ } ,
723+ Err ( e) => Err ( SimulationError :: SimulatedNetworkError ( e. err ) ) ,
724+ }
725+ }
726+ }
727+
728+ #[ tokio:: test]
729+ async fn test_always_fail_pathfinder ( ) {
730+ let channels = create_simulated_channels ( 3 , 1_000_000_000 ) ;
731+ let routing_graph =
732+ Arc :: new ( populate_network_graph ( channels. clone ( ) , Arc :: new ( SystemClock { } ) ) . unwrap ( ) ) ;
733+
734+ let pathfinder = AlwaysFailPathFinder ;
735+ let source = channels[ 0 ] . get_node_1_pubkey ( ) ;
736+ let dest = channels[ 2 ] . get_node_2_pubkey ( ) ;
737+
738+ let scorer = ProbabilisticScorer :: new (
739+ ProbabilisticScoringDecayParameters :: default ( ) ,
740+ routing_graph. clone ( ) ,
741+ & WrappedLog { } ,
742+ ) ;
743+
744+ let result = pathfinder. find_route ( & source, dest, 100_000 , & routing_graph, & scorer) ;
745+
746+ // Should always fail
747+ assert ! ( result. is_err( ) ) ;
748+ }
749+
750+ #[ tokio:: test]
751+ async fn test_single_hop_only_pathfinder ( ) {
752+ let channels = create_simulated_channels ( 3 , 1_000_000_000 ) ;
753+ let routing_graph =
754+ Arc :: new ( populate_network_graph ( channels. clone ( ) , Arc :: new ( SystemClock { } ) ) . unwrap ( ) ) ;
755+
756+ let pathfinder = SingleHopOnlyPathFinder ;
757+ let source = channels[ 0 ] . get_node_1_pubkey ( ) ;
758+
759+ let scorer = ProbabilisticScorer :: new (
760+ ProbabilisticScoringDecayParameters :: default ( ) ,
761+ routing_graph. clone ( ) ,
762+ & WrappedLog { } ,
763+ ) ;
764+
765+ // Test direct connection (should work)
766+ let direct_dest = channels[ 0 ] . get_node_2_pubkey ( ) ;
767+ let result = pathfinder. find_route ( & source, direct_dest, 100_000 , & routing_graph, & scorer) ;
768+
769+ if result. is_ok ( ) {
770+ let route = result. unwrap ( ) ;
771+ assert_eq ! ( route. paths[ 0 ] . hops. len( ) , 1 ) ; // Only one hop
772+ }
773+
774+ // Test indirect connection (should fail)
775+ let indirect_dest = channels[ 2 ] . get_node_2_pubkey ( ) ;
776+ let _result =
777+ pathfinder. find_route ( & source, indirect_dest, 100_000 , & routing_graph, & scorer) ;
778+
779+ // May fail because no direct route exists
780+ // (depends on your test network topology)
781+ }
782+
783+ /// Test that different pathfinders produce different behavior in payments
784+ #[ tokio:: test]
785+ async fn test_pathfinder_affects_payment_behavior ( ) {
786+ let channels = create_simulated_channels ( 3 , 1_000_000_000 ) ;
787+ let ( shutdown_trigger, shutdown_listener) = triggered:: trigger ( ) ;
788+ let sim_graph = Arc :: new ( Mutex :: new (
789+ SimGraph :: new (
790+ channels. clone ( ) ,
791+ TaskTracker :: new ( ) ,
792+ Vec :: new ( ) ,
793+ HashMap :: new ( ) , // Empty custom records
794+ ( shutdown_trigger. clone ( ) , shutdown_listener. clone ( ) ) ,
795+ )
796+ . unwrap ( ) ,
797+ ) ) ;
798+ let routing_graph =
799+ Arc :: new ( populate_network_graph ( channels. clone ( ) , Arc :: new ( SystemClock { } ) ) . unwrap ( ) ) ;
800+
801+ // Create nodes with different pathfinders
802+ let nodes_default = ln_node_from_graph (
803+ sim_graph. clone ( ) ,
804+ routing_graph. clone ( ) ,
805+ simln_lib:: sim_node:: DefaultPathFinder ,
806+ )
807+ . await ;
808+
809+ let nodes_fail = ln_node_from_graph (
810+ sim_graph. clone ( ) ,
811+ routing_graph. clone ( ) ,
812+ AlwaysFailPathFinder ,
813+ )
814+ . await ;
815+
816+ // Both should create the same structure
817+ assert_eq ! ( nodes_default. len( ) , nodes_fail. len( ) ) ;
818+ }
819+ }
0 commit comments