use exonum_merkledb::{
proof_map_index::ProofPath, BinaryKey, Database, IndexAccess, MapProof, ProofMapIndex,
TemporaryDB,
};
use proptest::{prelude::*, test_runner::Config};
use std::{
collections::{BTreeMap, BTreeSet},
fmt::Debug,
ops::Range,
};
use crate::prop::{
array,
collection::{btree_map, vec},
};
use exonum_merkledb::{BinaryValue, ObjectHash};
const INDEX_NAME: &str = "index";
fn check_map_proof<T, K, V>(proof: MapProof<K, V>, key: Option<K>, table: &ProofMapIndex<T, K, V>)
where
T: IndexAccess,
K: BinaryKey + ObjectHash + PartialEq + Debug,
V: BinaryValue + ObjectHash + PartialEq + Debug,
{
let entries = key.map(|key| {
let value = table.get(&key).unwrap();
(key, value)
});
let proof = proof.check().unwrap();
assert_eq!(
proof.entries().collect::<Vec<_>>(),
entries
.iter()
.map(|&(ref k, ref v)| (k, v))
.collect::<Vec<_>>()
);
assert_eq!(proof.root_hash(), table.object_hash());
}
fn check_map_multiproof<T, K, V>(
proof: MapProof<K, V>,
keys: BTreeSet<K>,
table: &ProofMapIndex<T, K, V>,
) where
T: IndexAccess,
K: BinaryKey + ObjectHash + Clone + PartialEq + Debug,
V: BinaryValue + ObjectHash + Clone + PartialEq + Debug,
{
let (entries, missing_keys) = {
let mut entries: Vec<(K, V)> = Vec::new();
let mut missing_keys: Vec<K> = Vec::new();
for key in keys {
if table.contains(&key) {
let value = table.get(&key).unwrap();
entries.push((key, value));
} else {
missing_keys.push(key);
}
}
entries.sort_unstable_by(|&(ref x, _), &(ref y, _)| {
ProofPath::new(x).partial_cmp(&ProofPath::new(y)).unwrap()
});
missing_keys
.sort_unstable_by(|x, y| ProofPath::new(x).partial_cmp(&ProofPath::new(y)).unwrap());
(entries, missing_keys)
};
let unchecked_proof = proof.clone();
let proof = proof.check().unwrap();
assert_eq!(
proof.all_entries().collect::<Vec<_>>(),
unchecked_proof.all_entries_unchecked().collect::<Vec<_>>()
);
assert_eq!(proof.root_hash(), table.object_hash());
assert_eq!(missing_keys.iter().collect::<Vec<&_>>(), {
let mut actual_keys = proof.missing_keys().collect::<Vec<_>>();
actual_keys
.sort_unstable_by(|&x, &y| ProofPath::new(x).partial_cmp(&ProofPath::new(y)).unwrap());
actual_keys
});
assert_eq!(
entries
.iter()
.map(|&(ref k, ref v)| (k, v))
.collect::<Vec<_>>(),
{
let mut actual_entries = proof.entries().collect::<Vec<_>>();
actual_entries.sort_unstable_by(|&(x, _), &(y, _)| {
ProofPath::new(x).partial_cmp(&ProofPath::new(y)).unwrap()
});
actual_entries
}
);
}
fn index_data<S>(key_bytes: S, elements_len: Range<usize>) -> BoxedStrategy<BTreeMap<[u8; 32], u64>>
where
S: 'static + Strategy<Value = u8>,
{
btree_map(array::uniform32(key_bytes), any::<u64>(), elements_len).boxed()
}
fn data_to_db(data: BTreeMap<[u8; 32], u64>) -> TemporaryDB {
let db = TemporaryDB::new();
let fork = db.fork();
{
let mut table = ProofMapIndex::new(INDEX_NAME, &fork);
for (key, value) in data {
table.put(&key, value);
}
}
db.merge(fork.into_patch()).unwrap();
db
}
macro_rules! proof_map_tests {
(cases = $cases:expr,sizes = $sizes:expr,bytes = $bytes:expr) => {
proptest! {
#![proptest_config(Config::with_cases($cases))]
#[test]
fn proof_of_presence(
(key, ref db) in index_data($bytes, $sizes)
.prop_flat_map(|data| (0..data.len(), Just(data)))
.prop_map(|(index, data)| {
(*data.keys().nth(index).unwrap(), data_to_db(data))
})
) {
let snapshot = db.snapshot();
let table: ProofMapIndex<_, [u8; 32], u64> =
ProofMapIndex::new(INDEX_NAME, &snapshot);
let proof = table.get_proof(key);
check_map_proof(proof, Some(key), &table);
}
#[test]
fn proof_of_absence(
ref db in index_data($bytes, $sizes).prop_map(data_to_db),
key in array::uniform32($bytes)
) {
let snapshot = db.snapshot();
let table: ProofMapIndex<_, [u8; 32], u64> =
ProofMapIndex::new(INDEX_NAME, &snapshot);
prop_assume!(!table.contains(&key));
let proof = table.get_proof(key);
check_map_proof(proof, None, &table);
}
#[test]
fn multiproof_of_existing_elements(
(ref keys, ref db) in index_data($bytes, $sizes)
.prop_flat_map(|data| {
(vec(0..data.len(), data.len() / 5), Just(data))
})
.prop_map(|(indexes, data)| {
let keys: Vec<_> = indexes
.into_iter()
.map(|i| *data.keys().nth(i).unwrap())
.collect();
(keys, data_to_db(data))
})
) {
let snapshot = db.snapshot();
let table: ProofMapIndex<_, [u8; 32], u64> =
ProofMapIndex::new(INDEX_NAME, &snapshot);
let proof = table.get_multiproof(keys.clone());
let unique_keys: BTreeSet<_> = keys.iter().cloned().collect();
check_map_multiproof(proof, unique_keys, &table);
}
#[test]
fn multiproof_of_nonexisting_elements(
ref db in index_data($bytes, $sizes).prop_map(data_to_db),
ref keys in vec(array::uniform32($bytes), 20)
) {
let snapshot = db.snapshot();
let table: ProofMapIndex<_, [u8; 32], u64> =
ProofMapIndex::new(INDEX_NAME, &snapshot);
prop_assume!(keys.iter().all(|key| !table.contains(key)));
let proof = table.get_multiproof(keys.clone());
let unique_keys: BTreeSet<_> = keys.iter().cloned().collect();
check_map_multiproof(proof, unique_keys, &table);
}
#[test]
fn mixed_multiproof(
(ref keys, ref db) in index_data($bytes, $sizes)
.prop_flat_map(|data| {
(vec(0..data.len(), data.len() / 5), Just(data))
})
.prop_map(|(indexes, data)| {
let keys: Vec<_> = indexes
.into_iter()
.map(|i| *data.keys().nth(i).unwrap())
.collect();
(keys, data_to_db(data))
}),
ref absent_keys in vec(array::uniform32($bytes), 20)
) {
let snapshot = db.snapshot();
let table: ProofMapIndex<_, [u8; 32], u64> =
ProofMapIndex::new(INDEX_NAME, &snapshot);
let mut all_keys = keys.clone();
all_keys.extend_from_slice(absent_keys);
let proof = table.get_multiproof(all_keys.clone());
let unique_keys: BTreeSet<_> = all_keys.into_iter().collect();
check_map_multiproof(proof, unique_keys, &table);
}
}
};
}
mod small_index {
use super::*;
proof_map_tests!(
cases = Config::default().cases,
sizes = 10..100,
bytes = 0_u8..
);
}
mod small_index_skewed {
use super::*;
proof_map_tests!(
cases = Config::default().cases,
sizes = 10..100,
bytes = 0_u8..3
);
}
mod large_index {
use super::*;
proof_map_tests!(
cases = Config::default().cases >> 5,
sizes = 5_000..10_000,
bytes = 0_u8..
);
}
mod large_index_skewed {
use super::*;
proof_map_tests!(
cases = Config::default().cases >> 5,
sizes = 5_000..10_000,
bytes = 0_u8..3
);
}