@@ -7,13 +7,13 @@ use bitcoin::Network;
7
7
use csv:: WriterBuilder ;
8
8
use lightning:: ln:: features:: NodeFeatures ;
9
9
use lightning:: ln:: PaymentHash ;
10
- use rand:: rngs:: StdRng ;
11
10
use rand:: { Rng , RngCore , SeedableRng } ;
12
11
use rand_chacha:: ChaCha8Rng ;
13
12
use random_activity:: RandomActivityError ;
14
13
use serde:: { Deserialize , Serialize } ;
15
14
use std:: collections:: HashSet ;
16
15
use std:: fmt:: { Display , Formatter } ;
16
+ use std:: hash:: { DefaultHasher , Hash , Hasher } ;
17
17
use std:: marker:: Send ;
18
18
use std:: path:: PathBuf ;
19
19
use std:: sync:: Mutex as StdMutex ;
@@ -527,16 +527,24 @@ type MutRngType = Arc<StdMutex<dyn RngCore + Send>>;
527
527
struct MutRng ( MutRngType ) ;
528
528
529
529
impl MutRng {
530
- /// Creates a new MutRng given an optional `u64` argument. If `seed_opt` is `Some`,
531
- /// random activity generation in the simulator occurs near-deterministically.
532
- /// If it is `None`, activity generation is truly random, and based on a
533
- /// non-deterministic source of entropy.
534
- pub fn new ( seed_opt : Option < u64 > ) -> Self {
535
- if let Some ( seed) = seed_opt {
536
- Self ( Arc :: new ( StdMutex :: new ( ChaCha8Rng :: seed_from_u64 ( seed) ) ) )
537
- } else {
538
- Self ( Arc :: new ( StdMutex :: new ( StdRng :: from_entropy ( ) ) ) )
539
- }
530
+ /// Creates a new MutRng given an optional `u64` seed and optional pubkey. If `seed_opt` is `Some`, random activity
531
+ /// generation in the simulator occurs near-deterministically.
532
+ /// If it is `None`, activity generation is truly random, and based on a non-deterministic source of entropy.
533
+ /// If a pubkey is provided, it will be used to salt the seed to ensure each node gets a deterministic but
534
+ /// different RNG sequence.
535
+ pub fn new ( seed_opt : Option < ( u64 , Option < & PublicKey > ) > ) -> Self {
536
+ let seed = match seed_opt {
537
+ Some ( ( seed, Some ( pubkey) ) ) => {
538
+ let mut hasher = DefaultHasher :: new ( ) ;
539
+ let mut combined = pubkey. serialize ( ) . to_vec ( ) ;
540
+ combined. extend_from_slice ( & seed. to_le_bytes ( ) ) ;
541
+ combined. hash ( & mut hasher) ;
542
+ hasher. finish ( )
543
+ } ,
544
+ Some ( ( seed, None ) ) => seed,
545
+ None => rand:: random ( ) ,
546
+ } ;
547
+ Self ( Arc :: new ( StdMutex :: new ( ChaCha8Rng :: seed_from_u64 ( seed) ) ) )
540
548
}
541
549
}
542
550
@@ -552,8 +560,8 @@ pub struct SimulationCfg {
552
560
activity_multiplier : f64 ,
553
561
/// Configurations for printing results to CSV. Results are not written if this option is None.
554
562
write_results : Option < WriteResults > ,
555
- /// Random number generator created from fixed seed .
556
- seeded_rng : MutRng ,
563
+ /// Optional seed for deterministic random number generation .
564
+ seed : Option < u64 > ,
557
565
}
558
566
559
567
impl SimulationCfg {
@@ -569,7 +577,7 @@ impl SimulationCfg {
569
577
expected_payment_msat,
570
578
activity_multiplier,
571
579
write_results,
572
- seeded_rng : MutRng :: new ( seed) ,
580
+ seed,
573
581
}
574
582
}
575
583
}
@@ -985,10 +993,11 @@ impl<C: Clock + 'static> Simulation<C> {
985
993
active_nodes. insert ( node_info. pubkey , ( node_info, capacity) ) ;
986
994
}
987
995
996
+ // Create a network generator with a shared RNG for all nodes.
988
997
let network_generator = Arc :: new ( Mutex :: new (
989
998
NetworkGraphView :: new (
990
999
active_nodes. values ( ) . cloned ( ) . collect ( ) ,
991
- self . cfg . seeded_rng . clone ( ) ,
1000
+ MutRng :: new ( self . cfg . seed . map ( |seed| ( seed , None ) ) ) ,
992
1001
)
993
1002
. map_err ( SimulationError :: RandomActivityError ) ?,
994
1003
) ) ;
@@ -999,6 +1008,9 @@ impl<C: Clock + 'static> Simulation<C> {
999
1008
) ;
1000
1009
1001
1010
for ( node_info, capacity) in active_nodes. values ( ) {
1011
+ // Create a salted RNG for this node based on its pubkey.
1012
+ let seed_opt = self . cfg . seed . map ( |seed| ( seed, Some ( & node_info. pubkey ) ) ) ;
1013
+ let salted_rng = MutRng :: new ( seed_opt) ;
1002
1014
generators. push ( ExecutorKit {
1003
1015
source_info : node_info. clone ( ) ,
1004
1016
network_generator : network_generator. clone ( ) ,
@@ -1007,7 +1019,7 @@ impl<C: Clock + 'static> Simulation<C> {
1007
1019
* capacity,
1008
1020
self . cfg . expected_payment_msat ,
1009
1021
self . cfg . activity_multiplier ,
1010
- self . cfg . seeded_rng . clone ( ) ,
1022
+ salted_rng ,
1011
1023
)
1012
1024
. map_err ( SimulationError :: RandomActivityError ) ?,
1013
1025
) ,
@@ -1040,7 +1052,7 @@ impl<C: Clock + 'static> Simulation<C> {
1040
1052
1041
1053
// Generate a consumer for the receiving end of the channel. It takes the event receiver that it'll pull
1042
1054
// events from and the results sender to report the events it has triggered for further monitoring.
1043
- // ce: consume event
1055
+ // ce: consume event.
1044
1056
let ce_listener = self . shutdown_listener . clone ( ) ;
1045
1057
let ce_shutdown = self . shutdown_trigger . clone ( ) ;
1046
1058
let ce_output_sender = output_sender. clone ( ) ;
@@ -1568,8 +1580,8 @@ mod tests {
1568
1580
let seeds = vec ! [ u64 :: MIN , u64 :: MAX ] ;
1569
1581
1570
1582
for seed in seeds {
1571
- let mut_rng_1 = MutRng :: new ( Some ( seed) ) ;
1572
- let mut_rng_2 = MutRng :: new ( Some ( seed) ) ;
1583
+ let mut_rng_1 = MutRng :: new ( Some ( ( seed, None ) ) ) ;
1584
+ let mut_rng_2 = MutRng :: new ( Some ( ( seed, None ) ) ) ;
1573
1585
1574
1586
let mut rng_1 = mut_rng_1. 0 . lock ( ) . unwrap ( ) ;
1575
1587
let mut rng_2 = mut_rng_2. 0 . lock ( ) . unwrap ( ) ;
@@ -1589,6 +1601,38 @@ mod tests {
1589
1601
assert_ne ! ( rng_1. next_u64( ) , rng_2. next_u64( ) )
1590
1602
}
1591
1603
1604
+ #[ test]
1605
+ fn create_salted_mut_rng ( ) {
1606
+ let ( _, pk1) = test_utils:: get_random_keypair ( ) ;
1607
+ let ( _, pk2) = test_utils:: get_random_keypair ( ) ;
1608
+
1609
+ let salted_rng_1 = MutRng :: new ( Some ( ( 42 , Some ( & pk1) ) ) ) ;
1610
+ let salted_rng_2 = MutRng :: new ( Some ( ( 42 , Some ( & pk2) ) ) ) ;
1611
+
1612
+ let mut seq1 = Vec :: new ( ) ;
1613
+ let mut seq2 = Vec :: new ( ) ;
1614
+
1615
+ let mut rng1 = salted_rng_1. 0 . lock ( ) . unwrap ( ) ;
1616
+ let mut rng2 = salted_rng_2. 0 . lock ( ) . unwrap ( ) ;
1617
+
1618
+ for _ in 0 ..10 {
1619
+ seq1. push ( rng1. next_u64 ( ) ) ;
1620
+ seq2. push ( rng2. next_u64 ( ) ) ;
1621
+ }
1622
+
1623
+ assert_ne ! ( seq1, seq2) ;
1624
+
1625
+ let salted_rng1_again = MutRng :: new ( Some ( ( 42 , Some ( & pk1) ) ) ) ;
1626
+ let mut rng1_again = salted_rng1_again. 0 . lock ( ) . unwrap ( ) ;
1627
+ let mut seq1_again = Vec :: new ( ) ;
1628
+
1629
+ for _ in 0 ..10 {
1630
+ seq1_again. push ( rng1_again. next_u64 ( ) ) ;
1631
+ }
1632
+
1633
+ assert_eq ! ( seq1, seq1_again) ;
1634
+ }
1635
+
1592
1636
mock ! {
1593
1637
pub Generator { }
1594
1638
0 commit comments