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

Commit 28f81bd

Browse files
mergify[bot]solana-grimes
authored andcommitted
Avoid unsorted recent_blockhashes for determinism (#7918) (#7936)
automerge
1 parent 1f4ae43 commit 28f81bd

File tree

1 file changed

+63
-2
lines changed

1 file changed

+63
-2
lines changed

sdk/src/sysvar/recent_blockhashes.rs

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,34 @@ impl<'a> FromIterator<&'a Hash> for RecentBlockhashes {
3838
}
3939
}
4040

41+
// This is cherry-picked from HEAD of rust-lang's master (ref1) because it's
42+
// a nightly-only experimental API.
43+
// (binary_heap_into_iter_sorted [rustc issue #59278])
44+
// Remove this and use the standard API once BinaryHeap::into_iter_sorted (ref2)
45+
// is stabilized.
46+
// ref1: https://github.com/rust-lang/rust/blob/2f688ac602d50129388bb2a5519942049096cbff/src/liballoc/collections/binary_heap.rs#L1149
47+
// ref2: https://doc.rust-lang.org/std/collections/struct.BinaryHeap.html#into_iter_sorted.v
48+
49+
#[derive(Clone, Debug)]
50+
pub struct IntoIterSorted<T> {
51+
inner: BinaryHeap<T>,
52+
}
53+
54+
impl<T: Ord> Iterator for IntoIterSorted<T> {
55+
type Item = T;
56+
57+
#[inline]
58+
fn next(&mut self) -> Option<T> {
59+
self.inner.pop()
60+
}
61+
62+
#[inline]
63+
fn size_hint(&self) -> (usize, Option<usize>) {
64+
let exact = self.inner.len();
65+
(exact, Some(exact))
66+
}
67+
}
68+
4169
impl Sysvar for RecentBlockhashes {
4270
fn biggest() -> Self {
4371
RecentBlockhashes(vec![Hash::default(); MAX_ENTRIES])
@@ -60,7 +88,8 @@ where
6088
I: IntoIterator<Item = (u64, &'a Hash)>,
6189
{
6290
let sorted = BinaryHeap::from_iter(recent_blockhash_iter);
63-
let recent_blockhash_iter = sorted.into_iter().take(MAX_ENTRIES).map(|(_, hash)| hash);
91+
let sorted_iter = IntoIterSorted { inner: sorted };
92+
let recent_blockhash_iter = sorted_iter.take(MAX_ENTRIES).map(|(_, hash)| hash);
6493
let recent_blockhashes = RecentBlockhashes::from_iter(recent_blockhash_iter);
6594
recent_blockhashes.to_account(account)
6695
}
@@ -84,7 +113,9 @@ pub fn create_test_recent_blockhashes(start: usize) -> RecentBlockhashes {
84113
#[cfg(test)]
85114
mod tests {
86115
use super::*;
87-
use crate::hash::Hash;
116+
use crate::hash::HASH_BYTES;
117+
use rand::seq::SliceRandom;
118+
use rand::thread_rng;
88119

89120
#[test]
90121
fn test_create_account_empty() {
@@ -110,4 +141,34 @@ mod tests {
110141
let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap();
111142
assert_eq!(recent_blockhashes.len(), MAX_ENTRIES);
112143
}
144+
145+
#[test]
146+
fn test_create_account_unsorted() {
147+
let mut unsorted_recent_blockhashes: Vec<_> = (0..MAX_ENTRIES)
148+
.map(|i| {
149+
(i as u64, {
150+
// create hash with visibly recognizable ordering
151+
let mut h = [0; HASH_BYTES];
152+
h[HASH_BYTES - 1] = i as u8;
153+
Hash::new(&h)
154+
})
155+
})
156+
.collect();
157+
unsorted_recent_blockhashes.shuffle(&mut thread_rng());
158+
159+
let account = create_account_with_data(
160+
42,
161+
unsorted_recent_blockhashes
162+
.iter()
163+
.map(|(i, hash)| (*i, hash)),
164+
);
165+
let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap();
166+
167+
let mut expected_recent_blockhashes: Vec<_> =
168+
(unsorted_recent_blockhashes.into_iter().map(|(_, b)| b)).collect();
169+
expected_recent_blockhashes.sort();
170+
expected_recent_blockhashes.reverse();
171+
172+
assert_eq!(*recent_blockhashes, expected_recent_blockhashes);
173+
}
113174
}

0 commit comments

Comments
 (0)