use exonum_crypto::Hash;
use std::ops::{Bound, RangeBounds};
use super::{key::ProofListKey, tree_height_by_length, ListProof};
use crate::BinaryValue;
pub trait MerkleTree<V> {
fn len(&self) -> u64;
fn node(&self, position: ProofListKey) -> Hash;
fn values<'s>(&'s self, start_index: u64) -> Box<dyn Iterator<Item = V> + 's>;
fn merkle_root(&self) -> Hash {
let tree_height = tree_height_by_length(self.len());
self.node(ProofListKey::new(tree_height, 0))
}
}
pub trait BuildProof<V> {
fn create_proof(&self, index: u64) -> ListProof<V>;
fn create_range_proof(&self, indexes: impl RangeBounds<u64>) -> ListProof<V>;
}
impl<V, T> BuildProof<V> for T
where
V: BinaryValue,
T: MerkleTree<V>,
{
fn create_proof(&self, index: u64) -> ListProof<V> {
create_proof(self, index, index)
}
fn create_range_proof(&self, indexes: impl RangeBounds<u64>) -> ListProof<V> {
let from = match indexes.start_bound() {
Bound::Unbounded => 0_u64,
Bound::Included(from) => *from,
Bound::Excluded(from) => *from + 1,
};
let to = match indexes.end_bound() {
Bound::Unbounded => self.len(),
Bound::Included(to) => to.saturating_add(1),
Bound::Excluded(to) => *to,
};
if (from >= self.len() && indexes.end_bound() == Bound::Unbounded) || from == to {
return ListProof::empty(self.merkle_root(), self.len());
}
assert!(
to > from,
"Illegal range boundaries: the range start is {}, but the range end is {}",
from,
to
);
create_proof(self, from, to - 1)
}
}
fn create_proof<V: BinaryValue>(
tree: &impl MerkleTree<V>,
from: u64,
inclusive_to: u64,
) -> ListProof<V> {
let tree_len = tree.len();
let tree_height = tree_height_by_length(tree_len);
if from >= tree_len {
return ListProof::empty(tree.merkle_root(), tree_len);
}
let items = (from..=inclusive_to).zip(tree.values(from));
let mut proof = ListProof::new(items, tree_len);
let (mut left, mut right) = (from, inclusive_to);
let mut last_index_on_level = tree_len - 1;
for height in 1..tree_height {
if left % 2 == 1 {
let hash = tree.node(ProofListKey::new(height, left - 1));
proof.push_hash(height, left - 1, hash);
}
if right % 2 == 0 && right < last_index_on_level {
let hash = tree.node(ProofListKey::new(height, right + 1));
proof.push_hash(height, right + 1, hash);
}
left /= 2;
right /= 2;
last_index_on_level /= 2;
}
proof
}