From e54bf563b52ee30a7903b49caacb9e593d0f3c69 Mon Sep 17 00:00:00 2001 From: Ryo Onodera Date: Thu, 23 Jan 2020 10:51:22 +0900 Subject: [PATCH] Avoid unsorted recent_blockhashes for determinism (#7918) * Avoid unsorted recent_blockhashes for determinism * Add a test: test_create_account_unsorted --- sdk/src/sysvar/recent_blockhashes.rs | 65 +++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/sdk/src/sysvar/recent_blockhashes.rs b/sdk/src/sysvar/recent_blockhashes.rs index 1f2feaef7..874a74d87 100644 --- a/sdk/src/sysvar/recent_blockhashes.rs +++ b/sdk/src/sysvar/recent_blockhashes.rs @@ -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 { + inner: BinaryHeap, +} + +impl Iterator for IntoIterSorted { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + self.inner.pop() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + 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 @@ -61,7 +89,8 @@ where I: IntoIterator, { 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) } @@ -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() { @@ -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); + } }