Skip to content

Commit 0364ea8

Browse files
authored
Merge pull request #3224 from ProvableHQ/fix/restore_past_deterministic_rng_behavior
[Fix] Restore past deterministic Rng behavior
2 parents 114082e + cc5c5c5 commit 0364ea8

3 files changed

Lines changed: 78 additions & 4 deletions

File tree

ledger/puzzle/epoch/src/merkle/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ use console::{
1919
types::Field,
2020
};
2121
use snarkvm_ledger_puzzle::PuzzleTrait;
22+
use snarkvm_utilities::rand::gen_range_inclusive_legacy;
2223

2324
use anyhow::Result;
2425
use core::marker::PhantomData;
25-
use rand::{RngExt, SeedableRng};
26+
use rand::SeedableRng;
2627
use rand_chacha::ChaChaRng;
2728

2829
#[cfg(not(feature = "serial"))]
@@ -73,7 +74,9 @@ impl<N: Network> MerklePuzzle<N> {
7374
// Seed a random number generator from the epoch hash.
7475
let mut epoch_rng = ChaChaRng::seed_from_u64(seed);
7576
// Sample a random number of leaves.
76-
Ok(epoch_rng.random_range(MIN_NUMBER_OF_LEAVES..=MAX_NUMBER_OF_LEAVES))
77+
let num_leaves = gen_range_inclusive_legacy(MIN_NUMBER_OF_LEAVES, MAX_NUMBER_OF_LEAVES, &mut epoch_rng);
78+
79+
Ok(num_leaves)
7780
}
7881
}
7982

@@ -91,6 +94,7 @@ mod tests {
9194
let puzzle = MerklePuzzle::<CurrentNetwork>::new();
9295
// Sample the number of leaves.
9396
let num_leaves = puzzle.num_leaves(epoch_hash).unwrap();
97+
assert_eq!(num_leaves, 102436);
9498
// Ensure the number of leaves is within the expected range.
9599
assert!((MIN_NUMBER_OF_LEAVES..=MAX_NUMBER_OF_LEAVES).contains(&num_leaves));
96100
}

ledger/puzzle/epoch/src/synthesis/helpers/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ use console::{
3737
program::LiteralType,
3838
};
3939
use snarkvm_synthesizer_program::Instruction;
40+
use snarkvm_utilities::choose_weighted_legacy;
4041

4142
use anyhow::Result;
4243
use indexmap::IndexSet;
43-
use rand::{SeedableRng, prelude::*};
44+
use rand::SeedableRng;
4445
use rand_chacha::ChaChaRng;
4546
use std::{collections::HashMap, str::FromStr};
4647

@@ -74,7 +75,7 @@ pub(crate) fn sample_instructions<N: Network>(
7475
}
7576

7677
// Initialize the instruction and selected literals.
77-
let (sequence, _) = instruction_set_weights.choose_weighted(&mut rng, |(_, weight)| *weight).cloned().unwrap();
78+
let sequence = choose_weighted_legacy(&instruction_set_weights, |(_, weight)| *weight, &mut rng).0.clone();
7879

7980
// Initialize a cache for the ephemeral registers.
8081
// This is a mapping from the locator to the one assigned to it in the instruction sequence.
@@ -342,6 +343,8 @@ pub(crate) mod tests {
342343
use console::{prelude::TestRng, program::Identifier};
343344
use snarkvm_synthesizer_program::Program;
344345

346+
use rand::RngExt;
347+
345348
type CurrentNetwork = console::network::MainnetV0;
346349

347350
const ITERATIONS: u64 = 25;

utilities/src/rand.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,70 @@ impl Drop for TestRng {
174174
println!("Called TestRng with seed {} {} times", self.seed, self.calls);
175175
}
176176
}
177+
178+
/// This impl is Lemire's method with approximate zone. It reproduces rand 0.8's
179+
/// `rng.gen_range(low..=high)` for `usize` on 64-bit. It mustn't be modified
180+
/// to maintain backwards compatibility. It is a direct reimplementation of
181+
/// `sample_single_inclusive` for a concrete type (`usize`) an inlined widening multiply
182+
/// from https://github.com/rust-random/rand/blob/937320c/src/distributions/uniform.rs.
183+
pub fn gen_range_inclusive_legacy(low: usize, high: usize, rng: &mut impl Rng) -> usize {
184+
debug_assert!(low <= high);
185+
186+
let range = high.wrapping_sub(low).wrapping_add(1);
187+
// The range is 0..=usize::MAX.
188+
if range == 0 {
189+
return rng.random::<u64>() as usize;
190+
}
191+
192+
// Approximate zone: conservative but avoids division.
193+
let zone = (range << range.leading_zeros()).wrapping_sub(1);
194+
195+
loop {
196+
let v = rng.next_u64() as usize;
197+
// Widening multiply: v * range as u128, split into (hi, lo).
198+
let wide = (v as u128) * (range as u128);
199+
let hi = (wide >> 64) as usize;
200+
let lo = wide as usize;
201+
if lo <= zone {
202+
return low.wrapping_add(hi);
203+
}
204+
}
205+
}
206+
207+
/// This impl reproduces rand 0.8's `slice.choose_weighted(rng, weight_fn)` for u16 weights.
208+
/// It mustn't be modified to maintain backwards compatibility. It is a direct, "collapsed"
209+
/// reimplementation of `WeightedIndex::new(weights).sample(rng)` specifically for `u16` weights
210+
/// from https://github.com/rust-random/rand/blob/937320c/src/distributions/weighted_index.rs.
211+
pub fn choose_weighted_legacy<'a, T, R: Rng>(slice: &'a [T], weight_fn: impl Fn(&T) -> u16, rng: &mut R) -> &'a T {
212+
// WeightedIndex::new.
213+
let mut iter = slice.iter();
214+
let first = iter.next().unwrap();
215+
let mut total: u16 = weight_fn(first);
216+
let mut cumulative: Vec<u16> = Vec::with_capacity(slice.len() - 1);
217+
for item in iter {
218+
cumulative.push(total);
219+
total += weight_fn(item);
220+
}
221+
assert!(total > 0);
222+
223+
// Uniform::new(0u16, total) -> new_inclusive(0, total - 1)
224+
// range as u16, then promoted to u32 for zone math.
225+
let range = total as u32;
226+
let ints_to_reject = (u32::MAX - range + 1) % range;
227+
let zone = u32::MAX - ints_to_reject;
228+
229+
// Uniform::sample (exact Lemire, u32 sample space).
230+
let chosen: u16 = loop {
231+
let v = rng.next_u32();
232+
let wide = (v as u64) * (range as u64);
233+
let hi = (wide >> 32) as u32;
234+
let lo = wide as u32;
235+
if lo <= zone {
236+
break hi as u16;
237+
}
238+
};
239+
240+
// Binary search (partition_point).
241+
let idx = cumulative.partition_point(|w| *w <= chosen);
242+
&slice[idx]
243+
}

0 commit comments

Comments
 (0)