Skip to content

Implement Distinct RNG Sequences #259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 60 additions & 11 deletions simln-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ use bitcoin::Network;
use csv::WriterBuilder;
use lightning::ln::features::NodeFeatures;
use lightning::ln::PaymentHash;
use rand::rngs::StdRng;
use rand::{Rng, RngCore, SeedableRng};
use rand_chacha::ChaCha8Rng;
use random_activity::RandomActivityError;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use std::hash::{DefaultHasher, Hash, Hasher};
use std::marker::Send;
use std::path::PathBuf;
use std::sync::Mutex as StdMutex;
Expand Down Expand Up @@ -501,9 +501,23 @@ impl MutRng {
if let Some(seed) = seed_opt {
Self(Arc::new(StdMutex::new(ChaCha8Rng::seed_from_u64(seed))))
} else {
Self(Arc::new(StdMutex::new(StdRng::from_entropy())))
Self(Arc::new(StdMutex::new(ChaCha8Rng::from_entropy())))
}
}

/// Creates a new MutRng that is salted with a pubkey. This ensures that each node
/// gets a deterministic but different RNG sequence.
pub fn salted(&self, pubkey: &PublicKey, seed: u64) -> Self {
let mut hasher = DefaultHasher::new();

// Get pubkey bytes and concatenate with seed bytes
let mut combined = pubkey.serialize().to_vec();
combined.extend_from_slice(&seed.to_le_bytes());

combined.hash(&mut hasher);
let salt = hasher.finish();
Self(Arc::new(StdMutex::new(ChaCha8Rng::seed_from_u64(salt))))
}
}

/// Contains the configuration options for our simulation.
Expand All @@ -518,8 +532,8 @@ pub struct SimulationCfg {
activity_multiplier: f64,
/// Configurations for printing results to CSV. Results are not written if this option is None.
write_results: Option<WriteResults>,
/// Random number generator created from fixed seed.
seeded_rng: MutRng,
/// Optional seed for deterministic random number generation.
seed: Option<u64>,
}

impl SimulationCfg {
Expand All @@ -535,7 +549,7 @@ impl SimulationCfg {
expected_payment_msat,
activity_multiplier,
write_results,
seeded_rng: MutRng::new(seed),
seed,
}
}
}
Expand Down Expand Up @@ -932,12 +946,11 @@ impl Simulation {
active_nodes.insert(node_info.pubkey, (node_info, capacity));
}

// Create a base RNG from the seed for the network generator
let base_rng = MutRng::new(self.cfg.seed);
let network_generator = Arc::new(Mutex::new(
NetworkGraphView::new(
active_nodes.values().cloned().collect(),
self.cfg.seeded_rng.clone(),
)
.map_err(SimulationError::RandomActivityError)?,
NetworkGraphView::new(active_nodes.values().cloned().collect(), base_rng.clone())
.map_err(SimulationError::RandomActivityError)?,
));

log::info!(
Expand All @@ -946,6 +959,8 @@ impl Simulation {
);

for (node_info, capacity) in active_nodes.values() {
// Create a salted RNG for this node based on its pubkey
let salted_rng = base_rng.salted(&node_info.pubkey, self.cfg.seed.unwrap_or(0));
generators.push(ExecutorKit {
source_info: node_info.clone(),
network_generator: network_generator.clone(),
Expand All @@ -954,7 +969,7 @@ impl Simulation {
*capacity,
self.cfg.expected_payment_msat,
self.cfg.activity_multiplier,
self.cfg.seeded_rng.clone(),
salted_rng,
)
.map_err(SimulationError::RandomActivityError)?,
),
Expand Down Expand Up @@ -1528,6 +1543,40 @@ mod tests {
assert_ne!(rng_1.next_u64(), rng_2.next_u64())
}

#[test]
fn create_salted_mut_rng() {
let base_rng = MutRng::new(Some(42));

let (_, pk1) = test_utils::get_random_keypair();
let (_, pk2) = test_utils::get_random_keypair();

let salted_rng_1 = base_rng.salted(&pk1, 42);
let salted_rng_2 = base_rng.salted(&pk2, 42);

let mut seq1 = Vec::new();
let mut seq2 = Vec::new();

let mut rng1 = salted_rng_1.0.lock().unwrap();
let mut rng2 = salted_rng_2.0.lock().unwrap();

for _ in 0..10 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

seq1.push(rng1.next_u64());
seq2.push(rng2.next_u64());
}

assert_ne!(seq1, seq2);

let salted_rng1_again = base_rng.salted(&pk1, 42);
let mut rng1_again = salted_rng1_again.0.lock().unwrap();
let mut seq1_again = Vec::new();

for _ in 0..10 {
seq1_again.push(rng1_again.next_u64());
}

assert_eq!(seq1, seq1_again);
}

mock! {
pub Generator {}

Expand Down