solana/runtime/src/accounts_hash.rs

1560 lines
59 KiB
Rust

use log::*;
use rayon::prelude::*;
use solana_measure::measure::Measure;
use solana_sdk::{
clock::Slot,
hash::{Hash, Hasher},
pubkey::Pubkey,
};
use std::{convert::TryInto, sync::Mutex};
pub const ZERO_RAW_LAMPORTS_SENTINEL: u64 = std::u64::MAX;
pub const MERKLE_FANOUT: usize = 16;
#[derive(Debug, Default)]
pub struct HashStats {
pub scan_time_total_us: u64,
pub zeros_time_total_us: u64,
pub hash_time_total_us: u64,
pub sort_time_total_us: u64,
pub flatten_time_total_us: u64,
pub pre_scan_flatten_time_total_us: u64,
pub hash_total: usize,
pub unreduced_entries: usize,
pub num_snapshot_storage: usize,
}
impl HashStats {
fn log(&mut self) {
let total_time_us = self.scan_time_total_us
+ self.zeros_time_total_us
+ self.hash_time_total_us
+ self.sort_time_total_us
+ self.flatten_time_total_us
+ self.pre_scan_flatten_time_total_us;
datapoint_info!(
"calculate_accounts_hash_without_index",
("accounts_scan", self.scan_time_total_us, i64),
("eliminate_zeros", self.zeros_time_total_us, i64),
("hash", self.hash_time_total_us, i64),
("sort", self.sort_time_total_us, i64),
("hash_total", self.hash_total, i64),
("flatten", self.flatten_time_total_us, i64),
("unreduced_entries", self.unreduced_entries as i64, i64),
(
"num_snapshot_storage",
self.num_snapshot_storage as i64,
i64
),
(
"pre_scan_flatten",
self.pre_scan_flatten_time_total_us as i64,
i64
),
("total", total_time_us as i64, i64),
);
}
}
#[derive(Default, Debug, PartialEq, Clone)]
pub struct CalculateHashIntermediate {
pub version: u64,
pub hash: Hash,
pub lamports: u64,
pub slot: Slot,
pub pubkey: Pubkey,
}
impl CalculateHashIntermediate {
pub fn new(version: u64, hash: Hash, lamports: u64, slot: Slot, pubkey: Pubkey) -> Self {
Self {
version,
hash,
lamports,
slot,
pubkey,
}
}
}
#[derive(Default, Debug)]
pub struct CumulativeOffset {
pub index: Vec<usize>,
pub start_offset: usize,
}
impl CumulativeOffset {
pub fn new(index: Vec<usize>, start_offset: usize) -> CumulativeOffset {
Self {
index,
start_offset,
}
}
}
// Allow retreiving &[start..end] from a logical src: Vec<T>, where src is really Vec<Vec<T>> (or later Vec<Vec<Vec<T>>>)
// This model prevents callers from having to flatten which saves both working memory and time.
#[derive(Default, Debug)]
pub struct CumulativeOffsets {
cumulative_offsets: Vec<CumulativeOffset>,
total_count: usize,
}
impl CumulativeOffsets {
pub fn from_raw<T>(raw: &[Vec<T>]) -> CumulativeOffsets {
let mut total_count: usize = 0;
let cumulative_offsets: Vec<_> = raw
.iter()
.enumerate()
.filter_map(|(i, v)| {
let len = v.len();
if len > 0 {
let result = CumulativeOffset::new(vec![i], total_count);
total_count += len;
Some(result)
} else {
None
}
})
.collect();
Self {
cumulative_offsets,
total_count,
}
}
pub fn from_raw_2d<T>(raw: &[Vec<Vec<T>>]) -> CumulativeOffsets {
let mut total_count: usize = 0;
let mut cumulative_offsets = Vec::with_capacity(0);
for (i, v_outer) in raw.iter().enumerate() {
for (j, v) in v_outer.iter().enumerate() {
let len = v.len();
if len > 0 {
if cumulative_offsets.is_empty() {
// the first inner, non-empty vector we find gives us an approximate rectangular shape
cumulative_offsets = Vec::with_capacity(raw.len() * v_outer.len());
}
cumulative_offsets.push(CumulativeOffset::new(vec![i, j], total_count));
total_count += len;
}
}
}
Self {
cumulative_offsets,
total_count,
}
}
// return the biggest slice possible that starts at 'start'
pub fn get_slice<'a, T>(&self, raw: &'a [Vec<T>], start: usize) -> &'a [T] {
// This could be binary search, but we expect a small number of vectors.
for i in (0..self.cumulative_offsets.len()).into_iter().rev() {
let index = &self.cumulative_offsets[i];
if start >= index.start_offset {
let start = start - index.start_offset;
const DIMENSION: usize = 0;
return &raw[index.index[DIMENSION]][start..];
}
}
panic!(
"get_slice didn't find: {}, len: {}",
start, self.total_count
);
}
// return the biggest slice possible that starts at 'start'
pub fn get_slice_2d<'a, T>(&self, raw: &'a [Vec<Vec<T>>], start: usize) -> &'a [T] {
// This could be binary search, but we expect a small number of vectors.
for i in (0..self.cumulative_offsets.len()).into_iter().rev() {
let index = &self.cumulative_offsets[i];
if start >= index.start_offset {
let start = start - index.start_offset;
const DIMENSION_0: usize = 0;
const DIMENSION_1: usize = 1;
return &raw[index.index[DIMENSION_0]][index.index[DIMENSION_1]][start..];
}
}
panic!(
"get_slice didn't find: {}, len: {}",
start, self.total_count
);
}
}
#[derive(Debug)]
pub struct AccountsHash {
pub dummy: i32,
}
impl AccountsHash {
pub fn calculate_hash(hashes: Vec<Vec<Hash>>) -> (Hash, usize) {
let cumulative_offsets = CumulativeOffsets::from_raw(&hashes);
let hash_total = cumulative_offsets.total_count;
let result = AccountsHash::compute_merkle_root_from_slices(
hash_total,
MERKLE_FANOUT,
None,
|start: usize| cumulative_offsets.get_slice(&hashes, start),
);
(result, hash_total)
}
pub fn compute_merkle_root(hashes: Vec<(Pubkey, Hash)>, fanout: usize) -> Hash {
Self::compute_merkle_root_loop(hashes, fanout, |t| t.1)
}
// this function avoids an infinite recursion compiler error
pub fn compute_merkle_root_recurse(hashes: Vec<Hash>, fanout: usize) -> Hash {
Self::compute_merkle_root_loop(hashes, fanout, |t: &Hash| *t)
}
pub fn div_ceil(x: usize, y: usize) -> usize {
let mut result = x / y;
if x % y != 0 {
result += 1;
}
result
}
// For the first iteration, there could be more items in the tuple than just hash and lamports.
// Using extractor allows us to avoid an unnecessary array copy on the first iteration.
pub fn compute_merkle_root_loop<T, F>(hashes: Vec<T>, fanout: usize, extractor: F) -> Hash
where
F: Fn(&T) -> Hash + std::marker::Sync,
T: std::marker::Sync,
{
if hashes.is_empty() {
return Hasher::default().result();
}
let mut time = Measure::start("time");
let total_hashes = hashes.len();
let chunks = Self::div_ceil(total_hashes, fanout);
let result: Vec<_> = (0..chunks)
.into_par_iter()
.map(|i| {
let start_index = i * fanout;
let end_index = std::cmp::min(start_index + fanout, total_hashes);
let mut hasher = Hasher::default();
for item in hashes.iter().take(end_index).skip(start_index) {
let h = extractor(&item);
hasher.hash(h.as_ref());
}
hasher.result()
})
.collect();
time.stop();
debug!("hashing {} {}", total_hashes, time);
if result.len() == 1 {
result[0]
} else {
Self::compute_merkle_root_recurse(result, fanout)
}
}
// This function is designed to allow hashes to be located in multiple, perhaps multiply deep vecs.
// The caller provides a function to return a slice from the source data.
pub fn compute_merkle_root_from_slices<'a, F>(
total_hashes: usize,
fanout: usize,
max_levels_per_pass: Option<usize>,
get_hashes: F,
) -> Hash
where
F: Fn(usize) -> &'a [Hash] + std::marker::Sync,
{
if total_hashes == 0 {
return Hasher::default().result();
}
let mut time = Measure::start("time");
const THREE_LEVEL_OPTIMIZATION: usize = 3; // this '3' is dependent on the code structure below where we manually unroll
let target = fanout.pow(THREE_LEVEL_OPTIMIZATION as u32);
// Only use the 3 level optimization if we have at least 4 levels of data.
// Otherwise, we'll be serializing a parallel operation.
let threshold = target * fanout;
let three_level = max_levels_per_pass.unwrap_or(usize::MAX) >= THREE_LEVEL_OPTIMIZATION
&& total_hashes >= threshold;
let num_hashes_per_chunk = if three_level { target } else { fanout };
let chunks = Self::div_ceil(total_hashes, num_hashes_per_chunk);
// initial fetch - could return entire slice
let data: &[Hash] = get_hashes(0);
let data_len = data.len();
let result: Vec<_> = (0..chunks)
.into_par_iter()
.map(|i| {
let start_index = i * num_hashes_per_chunk;
let end_index = std::cmp::min(start_index + num_hashes_per_chunk, total_hashes);
let mut hasher = Hasher::default();
let mut data_index = start_index;
let mut data = data;
let mut data_len = data_len;
if !three_level {
// 1 group of fanout
// The result of this loop is a single hash value from fanout input hashes.
for i in start_index..end_index {
if data_index >= data_len {
// fetch next slice
data = get_hashes(i);
data_len = data.len();
data_index = 0;
}
hasher.hash(data[data_index].as_ref());
data_index += 1;
}
} else {
// hash 3 levels of fanout simultaneously.
// The result of this loop is a single hash value from fanout^3 input hashes.
let mut i = start_index;
while i < end_index {
let mut hasher_j = Hasher::default();
for _j in 0..fanout {
let mut hasher_k = Hasher::default();
let end = std::cmp::min(end_index - i, fanout);
for _k in 0..end {
if data_index >= data_len {
// fetch next slice
data = get_hashes(i);
data_len = data.len();
data_index = 0;
}
hasher_k.hash(data[data_index].as_ref());
data_index += 1;
i += 1;
}
hasher_j.hash(hasher_k.result().as_ref());
if i >= end_index {
break;
}
}
hasher.hash(hasher_j.result().as_ref());
}
}
hasher.result()
})
.collect();
time.stop();
debug!("hashing {} {}", total_hashes, time);
if result.len() == 1 {
result[0]
} else {
Self::compute_merkle_root_recurse(result, fanout)
}
}
pub fn accumulate_account_hashes(mut hashes: Vec<(Pubkey, Hash)>) -> Hash {
Self::sort_hashes_by_pubkey(&mut hashes);
Self::compute_merkle_root_loop(hashes, MERKLE_FANOUT, |i| i.1)
}
pub fn sort_hashes_by_pubkey(hashes: &mut Vec<(Pubkey, Hash)>) {
hashes.par_sort_unstable_by(|a, b| a.0.cmp(&b.0));
}
fn flatten_hash_intermediate<T>(
data_sections_by_pubkey: Vec<Vec<Vec<T>>>,
stats: &mut HashStats,
) -> Vec<Vec<T>>
where
T: Clone,
{
// flatten this:
// vec: just a level of hierarchy
// vec: 1 vec per PUBKEY_BINS_FOR_CALCULATING_HASHES
// vec: Intermediate data whose pubkey belongs in this division
// into this:
// vec: 1 vec per PUBKEY_BINS_FOR_CALCULATING_HASHES
// vec: Intermediate data whose pubkey belongs in this division
let mut flatten_time = Measure::start("flatten");
let mut data_by_pubkey: Vec<Vec<T>> = vec![];
let mut raw_len = 0;
for mut outer in data_sections_by_pubkey {
let outer_len = outer.len();
for pubkey_index in 0..outer_len {
let this_len = outer[pubkey_index].len();
if this_len == 0 {
continue;
}
raw_len += this_len;
let mut data = vec![];
std::mem::swap(&mut data, &mut outer[pubkey_index]);
if data_by_pubkey.len() <= pubkey_index {
data_by_pubkey.extend(vec![vec![]; pubkey_index - data_by_pubkey.len() + 1]);
}
data_by_pubkey[pubkey_index].extend(data);
}
}
flatten_time.stop();
stats.flatten_time_total_us += flatten_time.as_us();
stats.unreduced_entries = raw_len;
data_by_pubkey
}
pub fn compare_two_hash_entries(
a: &CalculateHashIntermediate,
b: &CalculateHashIntermediate,
) -> std::cmp::Ordering {
// note partial_cmp only returns None with floating point comparisons
match a.pubkey.partial_cmp(&b.pubkey).unwrap() {
std::cmp::Ordering::Equal => match b.slot.partial_cmp(&a.slot).unwrap() {
std::cmp::Ordering::Equal => b.version.partial_cmp(&a.version).unwrap(),
other => other,
},
other => other,
}
}
fn sort_hash_intermediate(
data_by_pubkey: Vec<Vec<CalculateHashIntermediate>>,
stats: &mut HashStats,
) -> Vec<Vec<CalculateHashIntermediate>> {
// sort each PUBKEY_DIVISION vec
let mut sort_time = Measure::start("sort");
let sorted_data_by_pubkey: Vec<Vec<_>> = data_by_pubkey
.into_par_iter()
.map(|mut pk_range| {
pk_range.par_sort_unstable_by(Self::compare_two_hash_entries);
pk_range
})
.collect();
sort_time.stop();
stats.sort_time_total_us += sort_time.as_us();
sorted_data_by_pubkey
}
pub fn checked_cast_for_capitalization(balance: u128) -> u64 {
balance
.try_into()
.expect("overflow is detected while summing capitalization")
}
fn de_dup_and_eliminate_zeros(
sorted_data_by_pubkey: Vec<Vec<CalculateHashIntermediate>>,
stats: &mut HashStats,
) -> (Vec<Vec<Vec<Hash>>>, u64) {
// 1. eliminate zero lamport accounts
// 2. pick the highest slot or (slot = and highest version) of each pubkey
// 3. produce this output:
// vec: PUBKEY_BINS_FOR_CALCULATING_HASHES in pubkey order
// vec: sorted sections from parallelism, in pubkey order
// vec: individual hashes in pubkey order
let mut zeros = Measure::start("eliminate zeros");
let overall_sum = Mutex::new(0u64);
const CHUNKS: usize = 10;
let hashes: Vec<Vec<Vec<Hash>>> = sorted_data_by_pubkey
.into_par_iter()
.map(|pubkey_division| {
let (hashes, sum) = Self::de_dup_accounts_in_parallel(&pubkey_division, CHUNKS);
let mut overall = overall_sum.lock().unwrap();
*overall = Self::checked_cast_for_capitalization(sum as u128 + *overall as u128);
hashes
})
.collect();
zeros.stop();
stats.zeros_time_total_us += zeros.as_us();
let sum = *overall_sum.lock().unwrap();
(hashes, sum)
}
// 1. eliminate zero lamport accounts
// 2. pick the highest slot or (slot = and highest version) of each pubkey
// 3. produce this output:
// vec: sorted sections from parallelism, in pubkey order
// vec: individual hashes in pubkey order
fn de_dup_accounts_in_parallel(
pubkey_division: &[CalculateHashIntermediate],
chunk_count: usize,
) -> (Vec<Vec<Hash>>, u64) {
let len = pubkey_division.len();
let max = if len > chunk_count {
std::cmp::max(chunk_count, 1)
} else {
1
};
let chunk_size = len / max;
let overall_sum = Mutex::new(0u64);
let hashes: Vec<Vec<Hash>> = (0..max)
.into_par_iter()
.map(|chunk_index| {
let mut start_index = chunk_index * chunk_size;
let mut end_index = start_index + chunk_size;
if chunk_index == max - 1 {
end_index = len;
}
let is_first_slice = chunk_index == 0;
if !is_first_slice {
// note that this causes all regions after region 0 to have 1 item that overlaps with the previous region
start_index -= 1;
}
let (result, sum) = Self::de_dup_accounts_from_stores(
chunk_index == 0,
&pubkey_division[start_index..end_index],
);
let mut overall = overall_sum.lock().unwrap();
*overall = Self::checked_cast_for_capitalization(sum + *overall as u128);
result
})
.collect();
let sum = *overall_sum.lock().unwrap();
(hashes, sum)
}
fn de_dup_accounts_from_stores(
is_first_slice: bool,
slice: &[CalculateHashIntermediate],
) -> (Vec<Hash>, u128) {
let len = slice.len();
let mut result: Vec<Hash> = Vec::with_capacity(len);
let mut sum: u128 = 0;
if len > 0 {
let mut i = 0;
// look_for_first_key means the first key we find in our slice may be a
// continuation of accounts belonging to a key that started in the last slice.
// so, look_for_first_key=true means we have to find the first key different than
// the first key we encounter in our slice. Note that if this is true,
// our slice begins one index prior to the 'actual' start of our logical range.
let mut look_for_first_key = !is_first_slice;
'outer: loop {
// at start of loop, item at 'i' is the first entry for a given pubkey - unless look_for_first
let now = &slice[i];
let last = now.pubkey;
if !look_for_first_key && now.lamports != ZERO_RAW_LAMPORTS_SENTINEL {
// first entry for this key that starts in our slice
result.push(now.hash);
sum += now.lamports as u128;
}
for (k, now) in slice.iter().enumerate().skip(i + 1) {
if now.pubkey != last {
i = k;
look_for_first_key = false;
continue 'outer;
} else {
let prev = &slice[k - 1];
assert!(
!(prev.slot == now.slot
&& prev.version == now.version
&& (prev.hash != now.hash || prev.lamports != now.lamports)),
"Conflicting store data. Pubkey: {}, Slot: {}, Version: {}, Hashes: {}, {}, Lamports: {}, {}", now.pubkey, now.slot, now.version, prev.hash, now.hash, prev.lamports, now.lamports
);
}
}
break; // ran out of items in our slice, so our slice is done
}
}
(result, sum)
}
fn flatten_hashes_and_hash(
hashes: Vec<Vec<Vec<Hash>>>,
fanout: usize,
stats: &mut HashStats,
) -> Hash {
let mut hash_time = Measure::start("flat2");
let offsets = CumulativeOffsets::from_raw_2d(&hashes);
let get_slice = |start: usize| -> &[Hash] { offsets.get_slice_2d(&hashes, start) };
let hash = AccountsHash::compute_merkle_root_from_slices(
offsets.total_count,
fanout,
None,
get_slice,
);
hash_time.stop();
stats.hash_time_total_us += hash_time.as_us();
stats.hash_total = offsets.total_count;
hash
}
// input:
// vec: unordered, created by parallelism
// vec: [0..bins] - where bins are pubkey ranges
// vec: [..] - items which fin in the containing bin, unordered within this vec
// so, assumption is middle vec is bins sorted by pubkey
pub fn rest_of_hash_calculation(
data_sections_by_pubkey: Vec<Vec<Vec<CalculateHashIntermediate>>>,
mut stats: &mut HashStats,
) -> (Hash, u64) {
let outer = Self::flatten_hash_intermediate(data_sections_by_pubkey, &mut stats);
let sorted_data_by_pubkey = Self::sort_hash_intermediate(outer, &mut stats);
let (hashes, total_lamports) =
Self::de_dup_and_eliminate_zeros(sorted_data_by_pubkey, &mut stats);
let hash = Self::flatten_hashes_and_hash(hashes, MERKLE_FANOUT, &mut stats);
stats.log();
(hash, total_lamports)
}
}
#[cfg(test)]
pub mod tests {
// TODO: all the bank tests are bank specific, issue: 2194
use super::*;
use std::str::FromStr;
#[test]
fn test_accountsdb_div_ceil() {
assert_eq!(AccountsHash::div_ceil(10, 3), 4);
assert_eq!(AccountsHash::div_ceil(0, 1), 0);
assert_eq!(AccountsHash::div_ceil(0, 5), 0);
assert_eq!(AccountsHash::div_ceil(9, 3), 3);
assert_eq!(AccountsHash::div_ceil(9, 9), 1);
}
#[test]
#[should_panic(expected = "attempt to divide by zero")]
fn test_accountsdb_div_ceil_fail() {
assert_eq!(AccountsHash::div_ceil(10, 0), 0);
}
#[test]
fn test_accountsdb_rest_of_hash_calculation() {
solana_logger::setup();
let mut account_maps: Vec<CalculateHashIntermediate> = Vec::new();
let key = Pubkey::new(&[11u8; 32]);
let hash = Hash::new(&[1u8; 32]);
let val = CalculateHashIntermediate::new(0, hash, 88, Slot::default(), key);
account_maps.push(val);
// 2nd key - zero lamports, so will be removed
let key = Pubkey::new(&[12u8; 32]);
let hash = Hash::new(&[2u8; 32]);
let val = CalculateHashIntermediate::new(
0,
hash,
ZERO_RAW_LAMPORTS_SENTINEL,
Slot::default(),
key,
);
account_maps.push(val);
let result = AccountsHash::rest_of_hash_calculation(
vec![vec![account_maps.clone()]],
&mut HashStats::default(),
);
let expected_hash = Hash::from_str("8j9ARGFv4W2GfML7d3sVJK2MePwrikqYnu6yqer28cCa").unwrap();
assert_eq!((result.0, result.1), (expected_hash, 88));
// 3rd key - with pubkey value before 1st key so it will be sorted first
let key = Pubkey::new(&[10u8; 32]);
let hash = Hash::new(&[2u8; 32]);
let val = CalculateHashIntermediate::new(0, hash, 20, Slot::default(), key);
account_maps.push(val);
let result = AccountsHash::rest_of_hash_calculation(
vec![vec![account_maps.clone()]],
&mut HashStats::default(),
);
let expected_hash = Hash::from_str("EHv9C5vX7xQjjMpsJMzudnDTzoTSRwYkqLzY8tVMihGj").unwrap();
assert_eq!((result.0, result.1), (expected_hash, 108));
// 3rd key - with later slot
let key = Pubkey::new(&[10u8; 32]);
let hash = Hash::new(&[99u8; 32]);
let val = CalculateHashIntermediate::new(0, hash, 30, Slot::default() + 1, key);
account_maps.push(val);
let result = AccountsHash::rest_of_hash_calculation(
vec![vec![account_maps]],
&mut HashStats::default(),
);
let expected_hash = Hash::from_str("7NNPg5A8Xsg1uv4UFm6KZNwsipyyUnmgCrznP6MBWoBZ").unwrap();
assert_eq!((result.0, result.1), (expected_hash, 118));
}
#[test]
fn test_accountsdb_de_dup_accounts_zero_chunks() {
let (hashes, lamports) =
AccountsHash::de_dup_accounts_in_parallel(&[CalculateHashIntermediate::default()], 0);
assert_eq!(vec![vec![Hash::default()]], hashes);
assert_eq!(lamports, 0);
}
#[test]
fn test_accountsdb_de_dup_accounts_empty() {
solana_logger::setup();
let (hashes, lamports) = AccountsHash::de_dup_and_eliminate_zeros(
vec![vec![], vec![]],
&mut HashStats::default(),
);
assert_eq!(
vec![vec![Hash::default(); 0], vec![]],
hashes.into_iter().flatten().collect::<Vec<_>>()
);
assert_eq!(lamports, 0);
let (hashes, lamports) =
AccountsHash::de_dup_and_eliminate_zeros(vec![], &mut HashStats::default());
let empty: Vec<Vec<Vec<Hash>>> = Vec::default();
assert_eq!(empty, hashes);
assert_eq!(lamports, 0);
let (hashes, lamports) = AccountsHash::de_dup_accounts_in_parallel(&[], 1);
assert_eq!(
vec![Hash::default(); 0],
hashes.into_iter().flatten().collect::<Vec<_>>()
);
assert_eq!(lamports, 0);
let (hashes, lamports) = AccountsHash::de_dup_accounts_in_parallel(&[], 2);
assert_eq!(
vec![Hash::default(); 0],
hashes.into_iter().flatten().collect::<Vec<_>>()
);
assert_eq!(lamports, 0);
}
#[test]
fn test_accountsdb_de_dup_accounts_from_stores() {
solana_logger::setup();
let key_a = Pubkey::new(&[1u8; 32]);
let key_b = Pubkey::new(&[2u8; 32]);
let key_c = Pubkey::new(&[3u8; 32]);
const COUNT: usize = 6;
const VERSION: u64 = 0;
let hashes: Vec<_> = (0..COUNT)
.into_iter()
.map(|i| Hash::new(&[i as u8; 32]))
.collect();
// create this vector
// abbbcc
let keys = [key_a, key_b, key_b, key_b, key_c, key_c];
let accounts: Vec<_> = hashes
.into_iter()
.zip(keys.iter())
.enumerate()
.map(|(i, (hash, key))| {
CalculateHashIntermediate::new(
VERSION,
hash,
(i + 1) as u64,
u64::MAX - i as u64,
*key,
)
})
.collect();
type ExpectedType = (String, bool, u64, String);
let expected:Vec<ExpectedType> = vec![
// ("key/lamports key2/lamports ...",
// is_first_slice
// result lamports
// result hashes)
// "a5" = key_a, 5 lamports
("a1", false, 0, "[]"),
("a1b2", false, 2, "[4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi]"),
("a1b2b3", false, 2, "[4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi]"),
("a1b2b3b4", false, 2, "[4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi]"),
("a1b2b3b4c5", false, 7, "[4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi, GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq]"),
("b2", false, 0, "[]"),
("b2b3", false, 0, "[]"),
("b2b3b4", false, 0, "[]"),
("b2b3b4c5", false, 5, "[GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq]"),
("b3", false, 0, "[]"),
("b3b4", false, 0, "[]"),
("b3b4c5", false, 5, "[GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq]"),
("b4", false, 0, "[]"),
("b4c5", false, 5, "[GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq]"),
("c5", false, 0, "[]"),
("a1", true, 1, "[11111111111111111111111111111111]"),
("a1b2", true, 3, "[11111111111111111111111111111111, 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi]"),
("a1b2b3", true, 3, "[11111111111111111111111111111111, 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi]"),
("a1b2b3b4", true, 3, "[11111111111111111111111111111111, 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi]"),
("a1b2b3b4c5", true, 8, "[11111111111111111111111111111111, 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi, GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq]"),
("b2", true, 2, "[4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi]"),
("b2b3", true, 2, "[4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi]"),
("b2b3b4", true, 2, "[4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi]"),
("b2b3b4c5", true, 7, "[4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi, GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq]"),
("b3", true, 3, "[8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR]"),
("b3b4", true, 3, "[8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR]"),
("b3b4c5", true, 8, "[8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR, GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq]"),
("b4", true, 4, "[CktRuQ2mttgRGkXJtyksdKHjUdc2C4TgDzyB98oEzy8]"),
("b4c5", true, 9, "[CktRuQ2mttgRGkXJtyksdKHjUdc2C4TgDzyB98oEzy8, GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq]"),
("c5", true, 5, "[GgBaCs3NCBuZN12kCJgAW63ydqohFkHEdfdEXBPzLHq]"),
].into_iter().map(|item| {
let result: ExpectedType = (
item.0.to_string(),
item.1,
item.2,
item.3.to_string(),
);
result
}).collect();
let mut expected_index = 0;
for first_slice in 0..2 {
for start in 0..COUNT {
for end in start + 1..COUNT {
let is_first_slice = first_slice == 1;
let accounts = accounts.clone();
let slice = &accounts[start..end];
let result = AccountsHash::de_dup_accounts_from_stores(is_first_slice, slice);
let (hashes2, lamports2) = AccountsHash::de_dup_accounts_in_parallel(slice, 1);
let (hashes3, lamports3) = AccountsHash::de_dup_accounts_in_parallel(slice, 2);
let (hashes4, lamports4) = AccountsHash::de_dup_and_eliminate_zeros(
vec![slice.to_vec()],
&mut HashStats::default(),
);
let (hashes5, lamports5) = AccountsHash::de_dup_and_eliminate_zeros(
vec![slice.to_vec(), slice.to_vec()],
&mut HashStats::default(),
);
let (hashes6, lamports6) = AccountsHash::de_dup_and_eliminate_zeros(
vec![vec![], slice.to_vec()],
&mut HashStats::default(),
);
assert_eq!(
hashes2.iter().flatten().collect::<Vec<_>>(),
hashes3.iter().flatten().collect::<Vec<_>>()
);
let expected2 = hashes2.clone().into_iter().flatten().collect::<Vec<_>>();
assert_eq!(
expected2,
hashes4
.into_iter()
.flatten()
.into_iter()
.flatten()
.collect::<Vec<_>>()
);
assert_eq!(
vec![expected2.clone(), expected2.clone()],
hashes5.into_iter().flatten().collect::<Vec<_>>()
);
assert_eq!(
vec![vec![], expected2.clone()],
hashes6.into_iter().flatten().collect::<Vec<_>>()
);
assert_eq!(lamports2, lamports3);
assert_eq!(lamports2, lamports4);
assert_eq!(lamports2 * 2, lamports5);
assert_eq!(lamports2, lamports6);
let hashes: Vec<_> = hashes2.into_iter().flatten().collect();
let human_readable = slice
.iter()
.map(|v| {
let mut s = (if v.pubkey == key_a {
"a"
} else if v.pubkey == key_b {
"b"
} else {
"c"
})
.to_string();
s.push_str(&v.lamports.to_string());
s
})
.collect::<String>();
let hash_result_as_string = format!("{:?}", result.0);
let packaged_result: ExpectedType = (
human_readable,
is_first_slice,
result.1 as u64,
hash_result_as_string,
);
if is_first_slice {
// the parallel version always starts with 'first slice'
assert_eq!(
result.0, hashes,
"description: {:?}, expected index: {}",
packaged_result, expected_index
);
assert_eq!(
result.1 as u64, lamports2,
"description: {:?}, expected index: {}",
packaged_result, expected_index
);
}
assert_eq!(expected[expected_index], packaged_result);
// for generating expected results
// error!("{:?},", packaged_result);
expected_index += 1;
}
}
}
for first_slice in 0..2 {
let result = AccountsHash::de_dup_accounts_from_stores(first_slice == 1, &[]);
assert_eq!((vec![Hash::default(); 0], 0), result);
}
}
#[test]
fn test_accountsdb_flatten_hashes_and_hash() {
solana_logger::setup();
const COUNT: usize = 4;
let hashes: Vec<_> = (0..COUNT)
.into_iter()
.map(|i| Hash::new(&[(i) as u8; 32]))
.collect();
let expected =
AccountsHash::compute_merkle_root_loop(hashes.clone(), MERKLE_FANOUT, |i| *i);
assert_eq!(
AccountsHash::flatten_hashes_and_hash(
vec![vec![hashes.clone()]],
MERKLE_FANOUT,
&mut HashStats::default()
),
expected,
);
for in_first in 1..COUNT - 1 {
assert_eq!(
AccountsHash::flatten_hashes_and_hash(
vec![vec![
hashes.clone()[0..in_first].to_vec(),
hashes.clone()[in_first..COUNT].to_vec()
]],
MERKLE_FANOUT,
&mut HashStats::default()
),
expected
);
}
}
#[test]
fn test_sort_hash_intermediate() {
solana_logger::setup();
let mut stats = HashStats::default();
let key = Pubkey::new_unique();
let hash = Hash::new_unique();
let val = CalculateHashIntermediate::new(1, hash, 1, 1, key);
// slot same, version <
let hash2 = Hash::new_unique();
let val2 = CalculateHashIntermediate::new(0, hash2, 4, 1, key);
let val3 = CalculateHashIntermediate::new(3, hash2, 4, 1, key);
let val4 = CalculateHashIntermediate::new(4, hash2, 4, 1, key);
let src = vec![vec![val2.clone()], vec![val.clone()]];
let result = AccountsHash::sort_hash_intermediate(src.clone(), &mut stats);
assert_eq!(result, src);
let src = vec![
vec![val2.clone(), val.clone()],
vec![val3.clone(), val4.clone()],
];
let sorted = vec![vec![val, val2], vec![val4, val3]];
let result = AccountsHash::sort_hash_intermediate(src, &mut stats);
assert_eq!(result, sorted);
let src = vec![vec![]];
let result = AccountsHash::sort_hash_intermediate(src.clone(), &mut stats);
assert_eq!(result, src);
let src = vec![];
let result = AccountsHash::sort_hash_intermediate(src.clone(), &mut stats);
assert_eq!(result, src);
}
#[test]
fn test_accountsdb_compare_two_hash_entries() {
solana_logger::setup();
let key = Pubkey::new_unique();
let hash = Hash::new_unique();
let val = CalculateHashIntermediate::new(1, hash, 1, 1, key);
// slot same, version <
let hash2 = Hash::new_unique();
let val2 = CalculateHashIntermediate::new(0, hash2, 4, 1, key);
assert_eq!(
std::cmp::Ordering::Less,
AccountsHash::compare_two_hash_entries(&val, &val2)
);
let list = vec![val.clone(), val2.clone()];
let mut list_bkup = list.clone();
list_bkup.sort_by(AccountsHash::compare_two_hash_entries);
let list = AccountsHash::sort_hash_intermediate(vec![list], &mut HashStats::default());
assert_eq!(list, vec![list_bkup]);
let list = vec![val2, val.clone()]; // reverse args
let mut list_bkup = list.clone();
list_bkup.sort_by(AccountsHash::compare_two_hash_entries);
let list = AccountsHash::sort_hash_intermediate(vec![list], &mut HashStats::default());
assert_eq!(list, vec![list_bkup]);
// slot same, vers =
let hash3 = Hash::new_unique();
let val3 = CalculateHashIntermediate::new(1, hash3, 2, 1, key);
assert_eq!(
std::cmp::Ordering::Equal,
AccountsHash::compare_two_hash_entries(&val, &val3)
);
// slot same, vers >
let hash4 = Hash::new_unique();
let val4 = CalculateHashIntermediate::new(2, hash4, 6, 1, key);
assert_eq!(
std::cmp::Ordering::Greater,
AccountsHash::compare_two_hash_entries(&val, &val4)
);
// slot >, version <
let hash5 = Hash::new_unique();
let val5 = CalculateHashIntermediate::new(0, hash5, 8, 2, key);
assert_eq!(
std::cmp::Ordering::Greater,
AccountsHash::compare_two_hash_entries(&val, &val5)
);
}
#[test]
fn test_accountsdb_remove_zero_balance_accounts() {
solana_logger::setup();
let key = Pubkey::new_unique();
let hash = Hash::new_unique();
let mut account_maps: Vec<CalculateHashIntermediate> = Vec::new();
let val = CalculateHashIntermediate::new(0, hash, 1, Slot::default(), key);
account_maps.push(val.clone());
let result = AccountsHash::de_dup_accounts_from_stores(true, &account_maps[..]);
assert_eq!(result, (vec![val.hash], val.lamports as u128));
// zero original lamports, higher version
let val = CalculateHashIntermediate::new(
1,
hash,
ZERO_RAW_LAMPORTS_SENTINEL,
Slot::default(),
key,
);
account_maps.insert(0, val); // has to be before other entry since sort order matters
let result = AccountsHash::de_dup_accounts_from_stores(true, &account_maps[..]);
assert_eq!(result, (vec![], 0));
}
#[test]
fn test_accountsdb_cumulative_offsets1_d() {
let input = vec![vec![0, 1], vec![], vec![2, 3, 4], vec![]];
let cumulative = CumulativeOffsets::from_raw(&input);
let src: Vec<_> = input.clone().into_iter().flatten().collect();
let len = src.len();
assert_eq!(cumulative.total_count, len);
assert_eq!(cumulative.cumulative_offsets.len(), 2); // 2 non-empty vectors
const DIMENSION: usize = 0;
assert_eq!(cumulative.cumulative_offsets[0].index[DIMENSION], 0);
assert_eq!(cumulative.cumulative_offsets[1].index[DIMENSION], 2);
assert_eq!(cumulative.cumulative_offsets[0].start_offset, 0);
assert_eq!(cumulative.cumulative_offsets[1].start_offset, 2);
for start in 0..len {
let slice = cumulative.get_slice(&input, start);
let len = slice.len();
assert!(len > 0);
assert_eq!(&src[start..(start + len)], slice);
}
let input = vec![vec![], vec![0, 1], vec![], vec![2, 3, 4], vec![]];
let cumulative = CumulativeOffsets::from_raw(&input);
let src: Vec<_> = input.clone().into_iter().flatten().collect();
let len = src.len();
assert_eq!(cumulative.total_count, len);
assert_eq!(cumulative.cumulative_offsets.len(), 2); // 2 non-empty vectors
assert_eq!(cumulative.cumulative_offsets[0].index[DIMENSION], 1);
assert_eq!(cumulative.cumulative_offsets[1].index[DIMENSION], 3);
assert_eq!(cumulative.cumulative_offsets[0].start_offset, 0);
assert_eq!(cumulative.cumulative_offsets[1].start_offset, 2);
for start in 0..len {
let slice = cumulative.get_slice(&input, start);
let len = slice.len();
assert!(len > 0);
assert_eq!(&src[start..(start + len)], slice);
}
let input: Vec<Vec<u32>> = vec![vec![]];
let cumulative = CumulativeOffsets::from_raw(&input);
let src: Vec<_> = input.into_iter().flatten().collect();
let len = src.len();
assert_eq!(cumulative.total_count, len);
assert_eq!(cumulative.cumulative_offsets.len(), 0); // 2 non-empty vectors
}
#[test]
fn test_accountsdb_cumulative_offsets2_d() {
let input = vec![vec![vec![0, 1], vec![], vec![2, 3, 4], vec![]]];
let cumulative = CumulativeOffsets::from_raw_2d(&input);
let src: Vec<_> = input
.clone()
.into_iter()
.flatten()
.into_iter()
.flatten()
.collect();
let len = src.len();
assert_eq!(cumulative.total_count, len);
assert_eq!(cumulative.cumulative_offsets.len(), 2); // 2 non-empty vectors
const DIMENSION_0: usize = 0;
const DIMENSION_1: usize = 1;
assert_eq!(cumulative.cumulative_offsets[0].index[DIMENSION_0], 0);
assert_eq!(cumulative.cumulative_offsets[0].index[DIMENSION_1], 0);
assert_eq!(cumulative.cumulative_offsets[1].index[DIMENSION_0], 0);
assert_eq!(cumulative.cumulative_offsets[1].index[DIMENSION_1], 2);
assert_eq!(cumulative.cumulative_offsets[0].start_offset, 0);
assert_eq!(cumulative.cumulative_offsets[1].start_offset, 2);
for start in 0..len {
let slice = cumulative.get_slice_2d(&input, start);
let len = slice.len();
assert!(len > 0);
assert_eq!(&src[start..(start + len)], slice);
}
let input = vec![vec![vec![], vec![0, 1], vec![], vec![2, 3, 4], vec![]]];
let cumulative = CumulativeOffsets::from_raw_2d(&input);
let src: Vec<_> = input
.clone()
.into_iter()
.flatten()
.into_iter()
.flatten()
.collect();
let len = src.len();
assert_eq!(cumulative.total_count, len);
assert_eq!(cumulative.cumulative_offsets.len(), 2); // 2 non-empty vectors
assert_eq!(cumulative.cumulative_offsets[0].index[DIMENSION_0], 0);
assert_eq!(cumulative.cumulative_offsets[0].index[DIMENSION_1], 1);
assert_eq!(cumulative.cumulative_offsets[1].index[DIMENSION_0], 0);
assert_eq!(cumulative.cumulative_offsets[1].index[DIMENSION_1], 3);
assert_eq!(cumulative.cumulative_offsets[0].start_offset, 0);
assert_eq!(cumulative.cumulative_offsets[1].start_offset, 2);
for start in 0..len {
let slice = cumulative.get_slice_2d(&input, start);
let len = slice.len();
assert!(len > 0);
assert_eq!(&src[start..(start + len)], slice);
}
let input: Vec<Vec<Vec<u32>>> = vec![vec![]];
let cumulative = CumulativeOffsets::from_raw_2d(&input);
let src: Vec<_> = input.into_iter().flatten().collect();
let len = src.len();
assert_eq!(cumulative.total_count, len);
assert_eq!(cumulative.cumulative_offsets.len(), 0); // 2 non-empty vectors
let input = vec![
vec![vec![0, 1]],
vec![vec![]],
vec![vec![], vec![2, 3, 4], vec![]],
];
let cumulative = CumulativeOffsets::from_raw_2d(&input);
let src: Vec<_> = input
.clone()
.into_iter()
.flatten()
.into_iter()
.flatten()
.collect();
let len = src.len();
assert_eq!(cumulative.total_count, len);
assert_eq!(cumulative.cumulative_offsets.len(), 2); // 2 non-empty vectors
assert_eq!(cumulative.cumulative_offsets[0].index[DIMENSION_0], 0);
assert_eq!(cumulative.cumulative_offsets[0].index[DIMENSION_1], 0);
assert_eq!(cumulative.cumulative_offsets[1].index[DIMENSION_0], 2);
assert_eq!(cumulative.cumulative_offsets[1].index[DIMENSION_1], 1);
assert_eq!(cumulative.cumulative_offsets[0].start_offset, 0);
assert_eq!(cumulative.cumulative_offsets[1].start_offset, 2);
for start in 0..len {
let slice = cumulative.get_slice_2d(&input, start);
let len = slice.len();
assert!(len > 0);
assert_eq!(&src[start..(start + len)], slice);
}
}
#[test]
fn test_accountsdb_flatten_hash_intermediate() {
solana_logger::setup();
let test = vec![vec![vec![CalculateHashIntermediate::new(
1,
Hash::new_unique(),
2,
3,
Pubkey::new_unique(),
)]]];
let mut stats = HashStats::default();
let result = AccountsHash::flatten_hash_intermediate(test.clone(), &mut stats);
assert_eq!(result, test[0]);
assert_eq!(stats.unreduced_entries, 1);
let mut stats = HashStats::default();
let result = AccountsHash::flatten_hash_intermediate(
vec![vec![vec![CalculateHashIntermediate::default(); 0]]],
&mut stats,
);
assert_eq!(result.iter().flatten().count(), 0);
assert_eq!(stats.unreduced_entries, 0);
let test = vec![
vec![vec![
CalculateHashIntermediate::new(1, Hash::new_unique(), 2, 3, Pubkey::new_unique()),
CalculateHashIntermediate::new(8, Hash::new_unique(), 9, 10, Pubkey::new_unique()),
]],
vec![vec![CalculateHashIntermediate::new(
4,
Hash::new_unique(),
5,
6,
Pubkey::new_unique(),
)]],
];
let mut stats = HashStats::default();
let result = AccountsHash::flatten_hash_intermediate(test.clone(), &mut stats);
let expected = test
.into_iter()
.flatten()
.into_iter()
.flatten()
.collect::<Vec<_>>();
assert_eq!(result.into_iter().flatten().collect::<Vec<_>>(), expected);
assert_eq!(stats.unreduced_entries, expected.len());
}
#[test]
fn test_accountsdb_flatten_hash_intermediate2() {
solana_logger::setup();
// data is ordered:
// vec: just a level of hierarchy
// vec: 1 vec per PUBKEY_BINS_FOR_CALCULATING_HASHES
// vec: Intermediate data whose pubkey belongs in this division
let binned_data = vec![
vec![vec![1, 2], vec![3, 4], vec![], vec![5]],
vec![vec![], vec![11, 12]],
];
let mut combined: Vec<Vec<Vec<i32>>> = vec![vec![]];
binned_data.iter().enumerate().for_each(|(bin, v)| {
v.iter()
.enumerate()
.for_each(|(dimension0, v): (usize, &Vec<i32>)| {
while combined.len() <= dimension0 {
combined.push(vec![]);
}
let vec: &mut Vec<Vec<i32>> = &mut combined[dimension0];
while vec.len() <= bin {
vec.push(vec![]);
}
vec[bin].extend(v.clone());
});
});
let mut stats = HashStats::default();
let result = AccountsHash::flatten_hash_intermediate(combined, &mut stats);
assert_eq!(
result,
binned_data
.clone()
.into_iter()
.map(|x| x.into_iter().flatten().collect::<Vec<_>>())
.collect::<Vec<_>>()
);
assert_eq!(
stats.unreduced_entries,
binned_data
.into_iter()
.flatten()
.into_iter()
.flatten()
.count()
);
let src = vec![vec![vec![0]]];
let result = AccountsHash::flatten_hash_intermediate(src, &mut stats);
assert_eq!(result, vec![vec![0]]);
let src = vec![vec![vec![0], vec![1]]];
let result = AccountsHash::flatten_hash_intermediate(src, &mut stats);
assert_eq!(result, vec![vec![0], vec![1]]);
let src = vec![vec![vec![]], vec![vec![], vec![1]]];
let result = AccountsHash::flatten_hash_intermediate(src, &mut stats);
assert_eq!(result, vec![vec![], vec![1]]);
let src: Vec<Vec<Vec<i32>>> = vec![vec![vec![], vec![]]];
let result = AccountsHash::flatten_hash_intermediate(src, &mut stats);
let expected: Vec<Vec<i32>> = vec![];
assert_eq!(result, expected);
let src: Vec<Vec<Vec<i32>>> = vec![vec![vec![], vec![]], vec![vec![], vec![]]];
let result = AccountsHash::flatten_hash_intermediate(src, &mut stats);
assert_eq!(result, expected);
let src: Vec<Vec<Vec<i32>>> = vec![vec![vec![], vec![]], vec![vec![]]];
let result = AccountsHash::flatten_hash_intermediate(src, &mut stats);
assert_eq!(result, expected);
let src: Vec<Vec<Vec<i32>>> = vec![vec![], vec![vec![]]];
let result = AccountsHash::flatten_hash_intermediate(src, &mut stats);
let expected: Vec<Vec<i32>> = vec![];
assert_eq!(result, expected);
}
fn test_hashing_larger(hashes: Vec<(Pubkey, Hash)>, fanout: usize) -> Hash {
let result = AccountsHash::compute_merkle_root(hashes.clone(), fanout);
let reduced: Vec<_> = hashes.iter().map(|x| x.1).collect();
let result2 = test_hashing(reduced, fanout);
assert_eq!(result, result2, "len: {}", hashes.len());
result
}
fn test_hashing(hashes: Vec<Hash>, fanout: usize) -> Hash {
let temp: Vec<_> = hashes.iter().map(|h| (Pubkey::default(), *h)).collect();
let result = AccountsHash::compute_merkle_root(temp, fanout);
let reduced: Vec<_> = hashes.clone();
let result2 =
AccountsHash::compute_merkle_root_from_slices(hashes.len(), fanout, None, |start| {
&reduced[start..]
});
assert_eq!(result, result2, "len: {}", hashes.len());
let result2 =
AccountsHash::compute_merkle_root_from_slices(hashes.len(), fanout, Some(1), |start| {
&reduced[start..]
});
assert_eq!(result, result2, "len: {}", hashes.len());
let reduced2: Vec<_> = hashes.iter().map(|x| vec![*x]).collect();
let result2 = AccountsHash::flatten_hashes_and_hash(
vec![reduced2],
fanout,
&mut HashStats::default(),
);
assert_eq!(result, result2, "len: {}", hashes.len());
let max = std::cmp::min(reduced.len(), fanout * 2);
for left in 0..max {
for right in left + 1..max {
let src = vec![
vec![reduced[0..left].to_vec(), reduced[left..right].to_vec()],
vec![reduced[right..].to_vec()],
];
let result2 =
AccountsHash::flatten_hashes_and_hash(src, fanout, &mut HashStats::default());
assert_eq!(result, result2);
}
}
result
}
#[test]
fn test_accountsdb_compute_merkle_root_large() {
solana_logger::setup();
// handle fanout^x -1, +0, +1 for a few 'x's
const FANOUT: usize = 3;
let mut hash_counts: Vec<_> = (1..6)
.map(|x| {
let mark = FANOUT.pow(x);
vec![mark - 1, mark, mark + 1]
})
.flatten()
.collect();
// saturate the test space for threshold to threshold + target
// this hits right before we use the 3 deep optimization and all the way through all possible partial last chunks
let target = FANOUT.pow(3);
let threshold = target * FANOUT;
hash_counts.extend(threshold - 1..=threshold + target);
for hash_count in hash_counts {
let hashes: Vec<_> = (0..hash_count)
.into_iter()
.map(|_| Hash::new_unique())
.collect();
test_hashing(hashes, FANOUT);
}
}
#[test]
fn test_accountsdb_compute_merkle_root() {
solana_logger::setup();
let expected_results = vec![
(0, 0, "GKot5hBsd81kMupNCXHaqbhv3huEbxAFMLnpcX2hniwn", 0),
(0, 1, "8unXKJYTxrR423HgQxbDmx29mFri1QNrzVKKDxEfc6bj", 0),
(0, 2, "6QfkevXLLqbfAaR1kVjvMLFtEXvNUVrpmkwXqgsYtCFW", 1),
(0, 3, "G3FrJd9JrXcMiqChTSfvEdBL2sCPny3ebiUy9Xxbn7a2", 3),
(0, 4, "G3sZXHhwoCFuNyWy7Efffr47RBW33ibEp7b2hqNDmXdu", 6),
(0, 5, "78atJJYpokAPKMJwHxUW8SBDvPkkSpTBV7GiB27HwosJ", 10),
(0, 6, "7c9SM2BmCRVVXdrEdKcMK91MviPqXqQMd8QAb77tgLEy", 15),
(0, 7, "3hsmnZPhf22UvBLiZ4dVa21Qsdh65CCrtYXsb8MxoVAa", 21),
(0, 8, "5bwXUiC6RCRhb8fqvjvUXT6waU25str3UXA3a6Aq1jux", 28),
(0, 9, "3NNtQKH6PaYpCnFBtyi2icK9eYX3YM5pqA3SKaXtUNzu", 36),
(1, 0, "GKot5hBsd81kMupNCXHaqbhv3huEbxAFMLnpcX2hniwn", 0),
(1, 1, "4GWVCsnEu1iRyxjAB3F7J7C4MMvcoxFWtP9ihvwvDgxY", 0),
(1, 2, "8ML8Te6Uw2mipFr2v9sMZDcziXzhVqJo2qeMJohg1CJx", 1),
(1, 3, "AMEuC3AgqAeRBGBhSfTmuMdfbAiXJnGmKv99kHmcAE1H", 3),
(1, 4, "HEnDuJLHpsQfrApimGrovTqPEF6Vkrx2dKFr3BDtYzWx", 6),
(1, 5, "6rH69iP2yM1o565noZN1EqjySW4PhYUskz3c5tXePUfV", 10),
(1, 6, "7qEQMEXdfSPjbZ3q4cuuZwebDMvTvuaQ3dBiHoDUKo9a", 15),
(1, 7, "GDJz7LSKYjqqz6ujCaaQRJRmQ7TLNCwYJhdT84qT4qwk", 21),
(1, 8, "HT9krPLVTo3rr5WZQBQFrbqWs8SbYScXfnt8EVuobboM", 28),
(1, 9, "8y2pMgqMdRsvqw6BQXm6wtz3qxGPss72i6H6gVpPyeda", 36),
];
let mut expected_index = 0;
let start = 0;
let default_fanout = 2;
// test 0..3 recursions (at fanout = 2) and 1 item remainder. The internals have 1 special case first loop and subsequent loops are the same types.
let iterations = default_fanout * default_fanout * default_fanout + 2;
for pass in 0..2 {
let fanout = if pass == 0 {
default_fanout
} else {
MERKLE_FANOUT
};
for count in start..iterations {
let mut input: Vec<_> = (0..count)
.map(|i| {
let key = Pubkey::new(&[(pass * iterations + count) as u8; 32]);
let hash = Hash::new(&[(pass * iterations + count + i + 1) as u8; 32]);
(key, hash)
})
.collect();
let result = if pass == 0 {
test_hashing_larger(input.clone(), fanout)
} else {
// this sorts inside
let early_result = AccountsHash::accumulate_account_hashes(
input.iter().map(|i| (i.0, i.1)).collect::<Vec<_>>(),
);
AccountsHash::sort_hashes_by_pubkey(&mut input);
let result = AccountsHash::compute_merkle_root(input.clone(), fanout);
assert_eq!(early_result, result);
result
};
// compare against captured, expected results for hash (and lamports)
assert_eq!(
(
pass,
count,
&*(result.to_string()),
expected_results[expected_index].3
), // we no longer calculate lamports
expected_results[expected_index]
);
expected_index += 1;
}
}
}
#[test]
#[should_panic(expected = "overflow is detected while summing capitalization")]
fn test_accountsdb_lamport_overflow() {
solana_logger::setup();
let offset = 2;
let input = vec![
CalculateHashIntermediate::new(
0,
Hash::new_unique(),
u64::MAX - offset,
0,
Pubkey::new_unique(),
),
CalculateHashIntermediate::new(
0,
Hash::new_unique(),
offset + 1,
0,
Pubkey::new_unique(),
),
];
AccountsHash::de_dup_accounts_in_parallel(&input, 1);
}
#[test]
#[should_panic(expected = "overflow is detected while summing capitalization")]
fn test_accountsdb_lamport_overflow2() {
solana_logger::setup();
let offset = 2;
let input = vec![
vec![CalculateHashIntermediate::new(
0,
Hash::new_unique(),
u64::MAX - offset,
0,
Pubkey::new_unique(),
)],
vec![CalculateHashIntermediate::new(
0,
Hash::new_unique(),
offset + 1,
0,
Pubkey::new_unique(),
)],
];
AccountsHash::de_dup_and_eliminate_zeros(input, &mut HashStats::default());
}
}