Skip to content

Commit 9daf52c

Browse files
committed
Add support for salted rng for each node
remove seed from MutRng Add seed to SimulationCfg Add base seed to salt fix PR nits Consolidate salted seed functionality into new handle case when seed is null but pubkey is not formatting changes
1 parent 55cc91a commit 9daf52c

File tree

2 files changed

+67
-23
lines changed

2 files changed

+67
-23
lines changed

simln-lib/src/lib.rs

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ use bitcoin::Network;
77
use csv::WriterBuilder;
88
use lightning::ln::features::NodeFeatures;
99
use lightning::ln::PaymentHash;
10-
use rand::rngs::StdRng;
1110
use rand::{Rng, RngCore, SeedableRng};
1211
use rand_chacha::ChaCha8Rng;
1312
use random_activity::RandomActivityError;
1413
use serde::{Deserialize, Serialize};
1514
use std::collections::HashSet;
1615
use std::fmt::{Display, Formatter};
16+
use std::hash::{DefaultHasher, Hash, Hasher};
1717
use std::marker::Send;
1818
use std::path::PathBuf;
1919
use std::sync::Mutex as StdMutex;
@@ -527,16 +527,24 @@ type MutRngType = Arc<StdMutex<dyn RngCore + Send>>;
527527
struct MutRng(MutRngType);
528528

529529
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))))
540548
}
541549
}
542550

@@ -552,8 +560,8 @@ pub struct SimulationCfg {
552560
activity_multiplier: f64,
553561
/// Configurations for printing results to CSV. Results are not written if this option is None.
554562
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>,
557565
}
558566

559567
impl SimulationCfg {
@@ -569,7 +577,7 @@ impl SimulationCfg {
569577
expected_payment_msat,
570578
activity_multiplier,
571579
write_results,
572-
seeded_rng: MutRng::new(seed),
580+
seed,
573581
}
574582
}
575583
}
@@ -985,10 +993,11 @@ impl<C: Clock + 'static> Simulation<C> {
985993
active_nodes.insert(node_info.pubkey, (node_info, capacity));
986994
}
987995

996+
// Create a network generator with a shared RNG for all nodes.
988997
let network_generator = Arc::new(Mutex::new(
989998
NetworkGraphView::new(
990999
active_nodes.values().cloned().collect(),
991-
self.cfg.seeded_rng.clone(),
1000+
MutRng::new(self.cfg.seed.map(|seed| (seed, None))),
9921001
)
9931002
.map_err(SimulationError::RandomActivityError)?,
9941003
));
@@ -999,6 +1008,9 @@ impl<C: Clock + 'static> Simulation<C> {
9991008
);
10001009

10011010
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);
10021014
generators.push(ExecutorKit {
10031015
source_info: node_info.clone(),
10041016
network_generator: network_generator.clone(),
@@ -1007,7 +1019,7 @@ impl<C: Clock + 'static> Simulation<C> {
10071019
*capacity,
10081020
self.cfg.expected_payment_msat,
10091021
self.cfg.activity_multiplier,
1010-
self.cfg.seeded_rng.clone(),
1022+
salted_rng,
10111023
)
10121024
.map_err(SimulationError::RandomActivityError)?,
10131025
),
@@ -1040,7 +1052,7 @@ impl<C: Clock + 'static> Simulation<C> {
10401052

10411053
// Generate a consumer for the receiving end of the channel. It takes the event receiver that it'll pull
10421054
// events from and the results sender to report the events it has triggered for further monitoring.
1043-
// ce: consume event
1055+
// ce: consume event.
10441056
let ce_listener = self.shutdown_listener.clone();
10451057
let ce_shutdown = self.shutdown_trigger.clone();
10461058
let ce_output_sender = output_sender.clone();
@@ -1568,8 +1580,8 @@ mod tests {
15681580
let seeds = vec![u64::MIN, u64::MAX];
15691581

15701582
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)));
15731585

15741586
let mut rng_1 = mut_rng_1.0.lock().unwrap();
15751587
let mut rng_2 = mut_rng_2.0.lock().unwrap();
@@ -1589,6 +1601,38 @@ mod tests {
15891601
assert_ne!(rng_1.next_u64(), rng_2.next_u64())
15901602
}
15911603

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+
15921636
mock! {
15931637
pub Generator {}
15941638

simln-lib/src/random_activity.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ mod tests {
308308
#[test]
309309
fn test_new() {
310310
// Check that we need, at least, two nodes
311-
let rng = MutRng::new(Some(u64::MAX));
311+
let rng = MutRng::new(Some((u64::MAX, None)));
312312
for i in 0..2 {
313313
assert!(matches!(
314314
NetworkGraphView::new(create_nodes(i, 42 * (i as u64 + 1)), rng.clone()),
@@ -362,7 +362,7 @@ mod tests {
362362
nodes.extend(create_nodes(big_node_count, big_node_capacity));
363363
let big_node = nodes.last().unwrap().0.pubkey;
364364

365-
let rng = MutRng::new(Some(u64::MAX));
365+
let rng = MutRng::new(Some((u64::MAX, None)));
366366
let view = NetworkGraphView::new(nodes, rng).unwrap();
367367

368368
for _ in 0..10 {
@@ -380,7 +380,7 @@ mod tests {
380380
// For the payment activity generator to fail during construction either the provided capacity must fail validation or the exponential
381381
// distribution must fail building given the inputs. The former will be thoroughly tested in its own unit test, but we'll test some basic cases
382382
// here. Mainly, if the `capacity < expected_payment_amnt / 2`, the generator will fail building
383-
let rng = MutRng::new(Some(u64::MAX));
383+
let rng = MutRng::new(Some((u64::MAX, None)));
384384
let expected_payment = get_random_int(1, 100);
385385
assert!(RandomPaymentActivity::new(
386386
2 * expected_payment,
@@ -453,7 +453,7 @@ mod tests {
453453
// All of them will yield a sigma squared smaller than 0, which we have a sanity check for.
454454
let expected_payment = get_random_int(1, 100);
455455
let source_capacity = 2 * expected_payment;
456-
let rng = MutRng::new(Some(u64::MAX));
456+
let rng = MutRng::new(Some((u64::MAX, None)));
457457
let pag =
458458
RandomPaymentActivity::new(source_capacity, expected_payment, 1.0, rng).unwrap();
459459

0 commit comments

Comments
 (0)