@@ -296,7 +296,6 @@ pub async fn create_simulation_with_network<P: for<'a> PathFinder<'a> + Clone +
296
296
) )
297
297
}
298
298
299
-
300
299
/// Parses the cli options provided and creates a simulation to be run, connecting to lightning nodes and validating
301
300
/// any activity described in the simulation file.
302
301
pub async fn create_simulation (
@@ -593,3 +592,228 @@ pub async fn get_validated_activities(
593
592
594
593
validate_activities ( activity. to_vec ( ) , activity_validation_params) . await
595
594
}
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