use std::collections::BTreeMap;
use itertools::Itertools;
use zebra_chain::{block::Height, parameters::Network};
use zebra_state::{RawBytes, ReadDisk, SaplingScannedDatabaseIndex, TransactionLocation, KV};
use crate::storage::{db::ScannerDb, Storage};
#[test]
fn test_database_format() {
let _init_guard = zebra_test::init();
for network in Network::iter() {
test_database_format_with_network(network);
}
}
fn test_database_format_with_network(network: Network) {
let mut net_suffix = network.to_string();
net_suffix.make_ascii_lowercase();
let mut storage = super::new_test_storage(&network);
let mut cf_names = storage.db.list_cf().expect("empty database is valid");
cf_names.sort();
insta::assert_ron_snapshot!("column_family_names", cf_names);
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_suffix("empty");
settings.bind(|| snapshot_raw_rocksdb_column_family_data(&storage.db, &cf_names));
settings.bind(|| snapshot_typed_result_data(&storage));
super::add_fake_keys(&mut storage);
settings.set_snapshot_suffix(format!("{net_suffix}_keys"));
settings.bind(|| snapshot_raw_rocksdb_column_family_data(&storage.db, &cf_names));
settings.bind(|| snapshot_typed_result_data(&storage));
for height in 0..=2 {
super::add_fake_results(&mut storage, &network, Height(height), true);
super::add_fake_results(&mut storage, &network, Height(height), false);
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_suffix(format!("{net_suffix}_{height}"));
settings.bind(|| snapshot_raw_rocksdb_column_family_data(&storage.db, &cf_names));
settings.bind(|| snapshot_typed_result_data(&storage));
}
}
fn snapshot_raw_rocksdb_column_family_data(db: &ScannerDb, original_cf_names: &[String]) {
let mut new_cf_names = db.list_cf().expect("empty database is valid");
new_cf_names.sort();
assert_eq!(
original_cf_names, new_cf_names,
"unexpected extra column families",
);
let mut empty_column_families = Vec::new();
for cf_name in original_cf_names {
let cf_handle = db
.cf_handle(cf_name)
.expect("RocksDB API provides correct names");
let cf_items: BTreeMap<RawBytes, RawBytes> = db.zs_items_in_range_ordered(&cf_handle, ..);
let cf_data: Vec<KV> = cf_items
.iter()
.map(|(key, value)| KV::new(key.raw_bytes(), value.raw_bytes()))
.collect();
if cf_name == "default" {
assert_eq!(cf_data.len(), 0, "default column family is never used");
} else if cf_data.is_empty() {
empty_column_families.push(format!("{cf_name}: no entries"));
} else {
insta::assert_ron_snapshot!(format!("{cf_name}_raw_data"), cf_data);
}
}
insta::assert_ron_snapshot!("empty_column_families", empty_column_families);
}
fn snapshot_typed_result_data(storage: &Storage) {
let sapling_keys_last_heights = storage.sapling_keys_last_heights();
insta::assert_ron_snapshot!(
"sapling_keys",
sapling_keys_last_heights,
{
"." => insta::sorted_redaction()
}
);
for (key_index, (sapling_key, last_height)) in
sapling_keys_last_heights.iter().sorted().enumerate()
{
let sapling_results = storage.sapling_results(sapling_key);
assert_eq!(sapling_results.keys().max(), Some(last_height));
for (height, results) in sapling_results.iter() {
let sapling_index_and_results =
storage.sapling_results_for_key_and_height(sapling_key, *height);
let sapling_results_for_height: Vec<_> = sapling_index_and_results
.values()
.flatten()
.cloned()
.collect();
assert_eq!(results, &sapling_results_for_height);
for (index, result) in sapling_index_and_results {
let index = SaplingScannedDatabaseIndex {
sapling_key: sapling_key.clone(),
tx_loc: TransactionLocation::from_parts(*height, index),
};
let sapling_result_for_index = storage.sapling_result_for_index(&index);
assert_eq!(result, sapling_result_for_index);
}
}
insta::assert_ron_snapshot!(format!("sapling_key_{key_index}_results"), sapling_results);
}
}