Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Avoid unsorted recent_blockhashes for determinism #7918

Merged
merged 2 commits into from
Jan 23, 2020
Merged
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
65 changes: 63 additions & 2 deletions sdk/src/sysvar/recent_blockhashes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,34 @@ impl<'a> FromIterator<&'a Hash> for RecentBlockhashes {
}
}

// This is cherry-picked from HEAD of rust-lang's master (ref1) because it's
// a nightly-only experimental API.
// (binary_heap_into_iter_sorted [rustc issue #59278])
// Remove this and use the standard API once BinaryHeap::into_iter_sorted (ref2)
// is stabilized.
// ref1: https://github.com/rust-lang/rust/blob/2f688ac602d50129388bb2a5519942049096cbff/src/liballoc/collections/binary_heap.rs#L1149
// ref2: https://doc.rust-lang.org/std/collections/struct.BinaryHeap.html#into_iter_sorted.v

#[derive(Clone, Debug)]
pub struct IntoIterSorted<T> {
inner: BinaryHeap<T>,
}

impl<T: Ord> Iterator for IntoIterSorted<T> {
type Item = T;

#[inline]
fn next(&mut self) -> Option<T> {
self.inner.pop()
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let exact = self.inner.len();
(exact, Some(exact))
}
}

impl Sysvar for RecentBlockhashes {
fn size_of() -> usize {
// hard-coded so that we don't have to construct an empty
Expand All @@ -61,7 +89,8 @@ where
I: IntoIterator<Item = (u64, &'a Hash)>,
{
let sorted = BinaryHeap::from_iter(recent_blockhash_iter);
let recent_blockhash_iter = sorted.into_iter().take(MAX_ENTRIES).map(|(_, hash)| hash);
let sorted_iter = IntoIterSorted { inner: sorted };
let recent_blockhash_iter = sorted_iter.take(MAX_ENTRIES).map(|(_, hash)| hash);
let recent_blockhashes = RecentBlockhashes::from_iter(recent_blockhash_iter);
recent_blockhashes.to_account(account)
}
Expand All @@ -85,7 +114,9 @@ pub fn create_test_recent_blockhashes(start: usize) -> RecentBlockhashes {
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::Hash;
use crate::hash::HASH_BYTES;
use rand::seq::SliceRandom;
use rand::thread_rng;

#[test]
fn test_size_of() {
Expand Down Expand Up @@ -120,4 +151,34 @@ mod tests {
let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap();
assert_eq!(recent_blockhashes.len(), MAX_ENTRIES);
}

#[test]
fn test_create_account_unsorted() {
let mut unsorted_recent_blockhashes: Vec<_> = (0..MAX_ENTRIES)
.map(|i| {
(i as u64, {
// create hash with visibly recognizable ordering
let mut h = [0; HASH_BYTES];
h[HASH_BYTES - 1] = i as u8;
Hash::new(&h)
})
})
.collect();
unsorted_recent_blockhashes.shuffle(&mut thread_rng());

let account = create_account_with_data(
42,
unsorted_recent_blockhashes
.iter()
.map(|(i, hash)| (*i, hash)),
);
let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap();

let mut expected_recent_blockhashes: Vec<_> =
(unsorted_recent_blockhashes.into_iter().map(|(_, b)| b)).collect();
expected_recent_blockhashes.sort();
expected_recent_blockhashes.reverse();

assert_eq!(*recent_blockhashes, expected_recent_blockhashes);
}
}