pub use self::proof::{CheckedListProof, ListProof, ListProofError, ValidationError};
use exonum_crypto::Hash;
use std::{cmp, iter, marker::PhantomData, ops::RangeBounds};
use self::{
key::{ProofListKey, MAX_INDEX},
proof::HashedEntry,
proof_builder::{BuildProof, MerkleTree},
};
use crate::{
access::{Access, AccessError, FromAccess},
hash::HashTag,
indexes::iter::{Entries, IndexIterator, Values},
views::{IndexState, IndexType, RawAccess, RawAccessMut, View, ViewWithMetadata},
BinaryValue, IndexAddress, ObjectHash,
};
mod key;
mod proof;
mod proof_builder;
#[cfg(test)]
mod tests;
fn tree_height_by_length(len: u64) -> u8 {
if len == 0 {
0
} else {
len.next_power_of_two().trailing_zeros() as u8 + 1
}
}
#[derive(Debug)]
pub struct ProofListIndex<T: RawAccess, V> {
base: View<T>,
state: IndexState<T, u64>,
_v: PhantomData<V>,
}
impl<T, V> MerkleTree<V> for ProofListIndex<T, V>
where
T: RawAccess,
V: BinaryValue,
{
fn len(&self) -> u64 {
self.len()
}
fn node(&self, position: ProofListKey) -> Hash {
self.get_branch_unchecked(position)
}
fn merkle_root(&self) -> Hash {
self.get_branch(self.root_key()).unwrap_or_default()
}
fn values<'s>(&'s self, start_index: u64) -> Box<dyn Iterator<Item = V> + 's> {
Box::new(self.iter_from(start_index))
}
}
impl<T, V> FromAccess<T> for ProofListIndex<T::Base, V>
where
T: Access,
V: BinaryValue,
{
fn from_access(access: T, addr: IndexAddress) -> Result<Self, AccessError> {
let view = access.get_or_create_view(addr, IndexType::ProofList)?;
Ok(Self::new(view))
}
}
impl<T, V> ProofListIndex<T, V>
where
T: RawAccess,
V: BinaryValue,
{
pub(crate) fn new(view: ViewWithMetadata<T>) -> Self {
let (base, state) = view.into_parts();
Self {
base,
state,
_v: PhantomData,
}
}
fn has_branch(&self, key: ProofListKey) -> bool {
key.first_left_leaf_index() < self.len()
}
fn get_branch(&self, key: ProofListKey) -> Option<Hash> {
if self.has_branch(key) {
self.base.get(&key)
} else {
None
}
}
fn get_branch_unchecked(&self, key: ProofListKey) -> Hash {
debug_assert!(self.has_branch(key));
self.base.get(&key).unwrap()
}
fn root_key(&self) -> ProofListKey {
ProofListKey::new(self.height(), 0)
}
pub fn get(&self, index: u64) -> Option<V> {
if index > MAX_INDEX {
return None;
}
self.base.get(&ProofListKey::leaf(index))
}
pub fn last(&self) -> Option<V> {
match self.len() {
0 => None,
l => self.get(l - 1),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> u64 {
self.state.get().unwrap_or_default()
}
pub fn height(&self) -> u8 {
tree_height_by_length(self.len())
}
pub fn get_proof(&self, index: u64) -> ListProof<V> {
self.create_proof(index)
}
pub fn get_range_proof<R: RangeBounds<u64>>(&self, range: R) -> ListProof<V> {
self.create_range_proof(range)
}
pub fn iter(&self) -> Values<'_, V> {
self.index_iter(None).skip_keys()
}
pub fn iter_from(&self, from: u64) -> Values<'_, V> {
self.index_iter(Some(&from)).skip_keys()
}
}
impl<T, V> ProofListIndex<T, V>
where
T: RawAccessMut,
V: BinaryValue,
{
fn set_len(&mut self, len: u64) {
self.state.set(len)
}
fn update_range(&mut self, mut first_index: u64, mut last_index: u64) {
let mut last_index_on_height = self.len() - 1;
for height in 1..self.height() {
debug_assert!(first_index <= last_index);
debug_assert!(last_index <= last_index_on_height);
let mut index = first_index & !1;
let stop_index = cmp::min(last_index | 1, last_index_on_height);
while index < stop_index {
let key = ProofListKey::new(height, index);
let branch_hash = HashTag::hash_node(
&self.get_branch_unchecked(key),
&self.get_branch_unchecked(key.as_right()),
);
self.base.put(&key.parent(), branch_hash);
index += 2;
}
if stop_index % 2 == 0 {
let key = ProofListKey::new(height, stop_index);
let branch_hash = HashTag::hash_single_node(&self.get_branch_unchecked(key));
self.base.put(&key.parent(), branch_hash);
}
first_index /= 2;
last_index /= 2;
last_index_on_height /= 2;
}
debug_assert_eq!(first_index, 0);
debug_assert_eq!(last_index, 0);
debug_assert_eq!(last_index_on_height, 0);
}
fn remove_range(&mut self, mut old_last_index: u64, old_height: u8) {
let new_length = self.len();
let mut last_index = Some(new_length - 1);
let mut started_updating_hashes = false;
for height in 1..old_height {
for index in last_index.map_or(0, |i| i + 1)..=old_last_index {
self.base.remove(&ProofListKey::new(height, index));
}
if let Some(last_index) = last_index {
if last_index > 0 && last_index < old_last_index && last_index % 2 == 0 {
started_updating_hashes = true;
}
if started_updating_hashes && last_index > 0 {
let key = ProofListKey::new(height, last_index);
let hash = self.get_branch_unchecked(key);
let parent_hash = if key.is_left() {
HashTag::hash_single_node(&hash)
} else {
let left_sibling = self.get_branch_unchecked(key.as_left());
HashTag::hash_node(&left_sibling, &hash)
};
self.base.put(&key.parent(), parent_hash);
}
}
last_index = match last_index {
Some(0) | None => None,
Some(i) => Some(i / 2),
};
old_last_index /= 2;
}
}
pub fn push(&mut self, value: V) {
self.extend(iter::once(value));
}
pub fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = V>,
{
let old_list_len = self.len();
let mut new_list_len = old_list_len;
for value in iter {
self.base.put(
&ProofListKey::new(1, new_list_len),
HashTag::hash_leaf(&value.to_bytes()),
);
self.base.put(&ProofListKey::leaf(new_list_len), value);
new_list_len += 1;
}
if new_list_len == old_list_len {
return;
}
assert!(
new_list_len < MAX_INDEX + 1,
"Length of a `ProofListIndex` exceeding the maximum allowed value ({}). \
This should never happen in realistic scenarios. If you feel this is not a bug, \
open an issue on https://github.com/exonum/exonum and tell us your use case \
for such a large list.",
MAX_INDEX + 1
);
self.set_len(new_list_len);
self.update_range(old_list_len, new_list_len - 1);
}
pub fn set(&mut self, index: u64, value: V) {
if index >= self.len() {
panic!(
"Index out of bounds: the len is {} but the index is {}",
self.len(),
index
);
}
self.base.put(
&ProofListKey::new(1, index),
HashTag::hash_leaf(&value.to_bytes()),
);
self.base.put(&ProofListKey::leaf(index), value);
self.update_range(index, index);
}
pub fn truncate(&mut self, new_length: u64) {
if self.len() <= new_length {
return;
}
if new_length == 0 {
self.clear();
return;
}
let old_last_index = self.len() - 1;
let old_height = self.height();
self.set_len(new_length);
for index in new_length..=old_last_index {
self.base.remove(&ProofListKey::leaf(index));
}
self.remove_range(old_last_index, old_height);
}
pub fn pop(&mut self) -> Option<V> {
if self.is_empty() {
None
} else {
let last_element = self.get(self.len() - 1); self.truncate(self.len() - 1);
last_element
}
}
pub fn clear(&mut self) {
self.base.clear();
self.state.unset();
}
}
impl<T, V> ObjectHash for ProofListIndex<T, V>
where
T: RawAccess,
V: BinaryValue,
{
fn object_hash(&self) -> Hash {
HashTag::hash_list_node(self.len(), self.merkle_root())
}
}
impl<'a, T, V> IntoIterator for &'a ProofListIndex<T, V>
where
T: RawAccess,
V: BinaryValue,
{
type Item = V;
type IntoIter = Values<'a, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<T, V> IndexIterator for ProofListIndex<T, V>
where
T: RawAccess,
V: BinaryValue,
{
type Key = u64;
type Value = V;
fn index_iter(&self, from: Option<&u64>) -> Entries<'_, u64, V> {
Entries::with_prefix(&self.base, &0_u8, from)
}
}
#[cfg(feature = "with-protobuf")]
mod proto {
use anyhow::ensure;
use exonum_proto::ProtobufConvert;
use protobuf::RepeatedField;
use std::borrow::Cow;
use super::{
key::{HEIGHT_SHIFT, MAX_INDEX},
HashedEntry, ListProof, ProofListKey,
};
use crate::{
proto::{self, *},
BinaryValue,
};
impl ProtobufConvert for ProofListKey {
type ProtoStruct = proto::ProofListKey;
fn to_pb(&self) -> Self::ProtoStruct {
let mut key = proto::ProofListKey::new();
key.set_index(self.index());
key.set_height(self.height().into());
key
}
fn from_pb(pb: Self::ProtoStruct) -> anyhow::Result<Self> {
let index = pb.get_index();
let height = pb.get_height();
ensure!(index <= MAX_INDEX, "index is out of range");
ensure!(u64::from(height) <= HEIGHT_SHIFT, "height is out of range");
Ok(Self::new(height as u8, index))
}
}
#[test]
fn proof_list_key_errors() {
let mut bogus_key = proto::ProofListKey::new();
bogus_key.set_height(57);
let err = ProofListKey::from_pb(bogus_key).unwrap_err();
assert!(err.to_string().contains("height is out of range"));
let mut bogus_key = proto::ProofListKey::new();
bogus_key.set_height(3);
bogus_key.set_index(1_u64 << 57);
let err = ProofListKey::from_pb(bogus_key).unwrap_err();
assert!(err.to_string().contains("index is out of range"));
}
impl<V> ProtobufConvert for ListProof<V>
where
V: BinaryValue,
{
type ProtoStruct = proto::ListProof;
fn to_pb(&self) -> Self::ProtoStruct {
let mut list_proof = proto::ListProof::new();
list_proof.set_length(self.list_len());
let entries = self
.entries_unchecked()
.iter()
.map(|(index, value)| {
let mut entry = ListProofEntry::new();
entry.set_index(*index);
entry.set_value(value.to_bytes());
entry
})
.collect();
let proof = self
.proof_unchecked()
.iter()
.map(HashedEntry::to_pb)
.collect();
list_proof.set_proof(RepeatedField::from_vec(proof));
list_proof.set_entries(RepeatedField::from_vec(entries));
list_proof
}
fn from_pb(mut pb: Self::ProtoStruct) -> anyhow::Result<Self> {
let proof = pb
.take_proof()
.into_iter()
.map(HashedEntry::from_pb)
.collect::<anyhow::Result<_>>()?;
let entries = pb
.get_entries()
.iter()
.map(|entry| {
Ok((
entry.get_index(),
V::from_bytes(Cow::Borrowed(entry.get_value()))?,
))
})
.collect::<anyhow::Result<_>>()?;
Ok(Self::from_raw_parts(proof, entries, pb.get_length()))
}
}
}