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