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());
    }
}
