use super::{
accumulator::InMemoryAccumulator, position::Position, verify_transaction_info,
MerkleTreeInternalNode, SparseMerkleInternalNode, SparseMerkleLeafNode,
};
use crate::{
ledger_info::LedgerInfo,
transaction::{TransactionInfo, Version},
};
use anyhow::{bail, ensure, format_err, Context, Result};
#[cfg(any(test, feature = "fuzzing"))]
use aptos_crypto::hash::TestOnlyHasher;
use aptos_crypto::{
hash::{
CryptoHash, CryptoHasher, EventAccumulatorHasher, TransactionAccumulatorHasher,
SPARSE_MERKLE_PLACEHOLDER_HASH,
},
HashValue,
};
#[cfg(any(test, feature = "fuzzing"))]
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
#[derive(Clone, Serialize, Deserialize)]
pub struct AccumulatorProof<H> {
siblings: Vec<HashValue>,
phantom: PhantomData<H>,
}
pub type LeafCount = u64;
pub const MAX_ACCUMULATOR_PROOF_DEPTH: usize = 63;
pub const MAX_ACCUMULATOR_LEAVES: LeafCount = 1 << MAX_ACCUMULATOR_PROOF_DEPTH;
impl<H> AccumulatorProof<H>
where
H: CryptoHasher,
{
pub fn new(siblings: Vec<HashValue>) -> Self {
AccumulatorProof {
siblings,
phantom: PhantomData,
}
}
pub fn siblings(&self) -> &[HashValue] {
&self.siblings
}
pub fn verify(
&self,
expected_root_hash: HashValue,
element_hash: HashValue,
element_index: u64,
) -> Result<()> {
ensure!(
self.siblings.len() <= MAX_ACCUMULATOR_PROOF_DEPTH,
"Accumulator proof has more than {} ({}) siblings.",
MAX_ACCUMULATOR_PROOF_DEPTH,
self.siblings.len()
);
let actual_root_hash = self
.siblings
.iter()
.fold(
(element_hash, element_index),
|(hash, index), sibling_hash| {
(
if index % 2 == 0 {
MerkleTreeInternalNode::<H>::new(hash, *sibling_hash).hash()
} else {
MerkleTreeInternalNode::<H>::new(*sibling_hash, hash).hash()
},
index / 2,
)
},
)
.0;
ensure!(
actual_root_hash == expected_root_hash,
"Root hashes do not match. Actual root hash: {:x}. Expected root hash: {:x}.",
actual_root_hash,
expected_root_hash
);
Ok(())
}
}
impl<H> std::fmt::Debug for AccumulatorProof<H> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AccumulatorProof {{ siblings: {:?} }}", self.siblings)
}
}
impl<H> PartialEq for AccumulatorProof<H> {
fn eq(&self, other: &Self) -> bool {
self.siblings == other.siblings
}
}
impl<H> Eq for AccumulatorProof<H> {}
pub type TransactionAccumulatorProof = AccumulatorProof<TransactionAccumulatorHasher>;
pub type EventAccumulatorProof = AccumulatorProof<EventAccumulatorHasher>;
#[cfg(any(test, feature = "fuzzing"))]
pub type TestAccumulatorProof = AccumulatorProof<TestOnlyHasher>;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SparseMerkleProof {
leaf: Option<SparseMerkleLeafNode>,
siblings: Vec<HashValue>,
}
impl SparseMerkleProof {
pub fn new(leaf: Option<SparseMerkleLeafNode>, siblings: Vec<HashValue>) -> Self {
SparseMerkleProof { leaf, siblings }
}
pub fn leaf(&self) -> Option<SparseMerkleLeafNode> {
self.leaf
}
pub fn siblings(&self) -> &[HashValue] {
&self.siblings
}
pub fn verify<V: CryptoHash>(
&self,
expected_root_hash: HashValue,
element_key: HashValue,
element_value: Option<&V>,
) -> Result<()> {
self.verify_by_hash(
expected_root_hash,
element_key,
element_value.map(|v| v.hash()),
)
}
pub fn verify_by_hash(
&self,
expected_root_hash: HashValue,
element_key: HashValue,
element_hash: Option<HashValue>,
) -> Result<()> {
ensure!(
self.siblings.len() <= HashValue::LENGTH_IN_BITS,
"Sparse Merkle Tree proof has more than {} ({}) siblings.",
HashValue::LENGTH_IN_BITS,
self.siblings.len(),
);
match (element_hash, self.leaf) {
(Some(hash), Some(leaf)) => {
ensure!(
element_key == leaf.key,
"Keys do not match. Key in proof: {:x}. Expected key: {:x}.",
leaf.key,
element_key
);
ensure!(
hash == leaf.value_hash,
"Value hashes do not match. Value hash in proof: {:x}. \
Expected value hash: {:x}",
leaf.value_hash,
hash,
);
}
(Some(_hash), None) => bail!("Expected inclusion proof. Found non-inclusion proof."),
(None, Some(leaf)) => {
ensure!(
element_key != leaf.key,
"Expected non-inclusion proof, but key exists in proof.",
);
ensure!(
element_key.common_prefix_bits_len(leaf.key) >= self.siblings.len(),
"Key would not have ended up in the subtree where the provided key in proof \
is the only existing key, if it existed. So this is not a valid \
non-inclusion proof.",
);
}
(None, None) => {
}
}
let current_hash = self
.leaf
.map_or(*SPARSE_MERKLE_PLACEHOLDER_HASH, |leaf| leaf.hash());
let actual_root_hash = self
.siblings
.iter()
.zip(
element_key
.iter_bits()
.rev()
.skip(HashValue::LENGTH_IN_BITS - self.siblings.len()),
)
.fold(current_hash, |hash, (sibling_hash, bit)| {
if bit {
SparseMerkleInternalNode::new(*sibling_hash, hash).hash()
} else {
SparseMerkleInternalNode::new(hash, *sibling_hash).hash()
}
});
ensure!(
actual_root_hash == expected_root_hash,
"Root hashes do not match. Actual root hash: {:x}. Expected root hash: {:x}.",
actual_root_hash,
expected_root_hash,
);
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TransactionAccumulatorSummary(InMemoryAccumulator<TransactionAccumulatorHasher>);
impl TransactionAccumulatorSummary {
pub fn new(accumulator: InMemoryAccumulator<TransactionAccumulatorHasher>) -> Result<Self> {
ensure!(
!accumulator.is_empty(),
"empty accumulator: we can't verify consistency proofs from an empty accumulator",
);
Ok(Self(accumulator))
}
pub fn version(&self) -> Version {
self.0.version()
}
pub fn root_hash(&self) -> HashValue {
self.0.root_hash()
}
pub fn verify_consistency(&self, ledger_info: &LedgerInfo) -> Result<()> {
ensure!(
ledger_info.version() == self.version(),
"ledger info and accumulator must be at the same version: \
ledger info version={}, accumulator version={}",
ledger_info.version(),
self.version(),
);
ensure!(
ledger_info.transaction_accumulator_hash() == self.root_hash(),
"ledger info root hash and accumulator root hash must match: \
ledger info root hash={}, accumulator root hash={}",
ledger_info.transaction_accumulator_hash(),
self.root_hash(),
);
Ok(())
}
pub fn try_from_genesis_proof(
genesis_proof: AccumulatorConsistencyProof,
target_version: Version,
) -> Result<Self> {
let num_txns = target_version.saturating_add(1);
Ok(Self(InMemoryAccumulator::new(
genesis_proof.into_subtrees(),
num_txns,
)?))
}
pub fn try_extend_with_proof(
&self,
consistency_proof: &AccumulatorConsistencyProof,
target_li: &LedgerInfo,
) -> Result<Self> {
ensure!(
target_li.version() >= self.0.version(),
"target ledger info version ({}) must be newer than our current accumulator \
summary version ({})",
target_li.version(),
self.0.version(),
);
let num_new_txns = target_li.version() - self.0.version();
let new_accumulator = Self(
self.0
.append_subtrees(consistency_proof.subtrees(), num_new_txns)?,
);
new_accumulator
.verify_consistency(target_li)
.context("accumulator is not consistent with the target ledger info after applying consistency proof")?;
Ok(new_accumulator)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct AccumulatorConsistencyProof {
subtrees: Vec<HashValue>,
}
impl AccumulatorConsistencyProof {
pub fn new(subtrees: Vec<HashValue>) -> Self {
Self { subtrees }
}
pub fn is_empty(&self) -> bool {
self.subtrees.is_empty()
}
pub fn subtrees(&self) -> &[HashValue] {
&self.subtrees
}
pub fn into_subtrees(self) -> Vec<HashValue> {
self.subtrees
}
}
#[derive(Clone, Deserialize, Serialize)]
pub struct AccumulatorRangeProof<H> {
left_siblings: Vec<HashValue>,
right_siblings: Vec<HashValue>,
phantom: PhantomData<H>,
}
impl<H> AccumulatorRangeProof<H>
where
H: CryptoHasher,
{
pub fn new(left_siblings: Vec<HashValue>, right_siblings: Vec<HashValue>) -> Self {
Self {
left_siblings,
right_siblings,
phantom: PhantomData,
}
}
pub fn new_empty() -> Self {
Self::new(vec![], vec![])
}
pub fn left_siblings(&self) -> &Vec<HashValue> {
&self.left_siblings
}
pub fn right_siblings(&self) -> &Vec<HashValue> {
&self.right_siblings
}
pub fn verify(
&self,
expected_root_hash: HashValue,
first_leaf_index: Option<u64>,
leaf_hashes: &[HashValue],
) -> Result<()> {
if first_leaf_index.is_none() {
ensure!(
leaf_hashes.is_empty(),
"first_leaf_index indicated empty list while leaf_hashes is not empty.",
);
ensure!(
self.left_siblings.is_empty() && self.right_siblings.is_empty(),
"No siblings are needed.",
);
return Ok(());
}
ensure!(
self.left_siblings.len() <= MAX_ACCUMULATOR_PROOF_DEPTH,
"Proof has more than {} ({}) left siblings.",
MAX_ACCUMULATOR_PROOF_DEPTH,
self.left_siblings.len(),
);
ensure!(
self.right_siblings.len() <= MAX_ACCUMULATOR_PROOF_DEPTH,
"Proof has more than {} ({}) right siblings.",
MAX_ACCUMULATOR_PROOF_DEPTH,
self.right_siblings.len(),
);
ensure!(
!leaf_hashes.is_empty(),
"leaf_hashes is empty while first_leaf_index indicated non-empty list.",
);
let mut left_sibling_iter = self.left_siblings.iter().peekable();
let mut right_sibling_iter = self.right_siblings.iter().peekable();
let mut first_pos = Position::from_leaf_index(
first_leaf_index.expect("first_leaf_index should not be None."),
);
let mut current_hashes = leaf_hashes.to_vec();
let mut parent_hashes = vec![];
while current_hashes.len() > 1
|| left_sibling_iter.peek().is_some()
|| right_sibling_iter.peek().is_some()
{
let mut children_iter = current_hashes.iter();
if first_pos.is_right_child() {
let left_hash = *left_sibling_iter.next().ok_or_else(|| {
format_err!("First child is a right child, but missing sibling on the left.")
})?;
let right_hash = *children_iter.next().expect("The first leaf must exist.");
parent_hashes.push(MerkleTreeInternalNode::<H>::new(left_hash, right_hash).hash());
}
let mut children_iter = children_iter.as_slice().chunks_exact(2);
for chunk in children_iter.by_ref() {
let left_hash = chunk[0];
let right_hash = chunk[1];
parent_hashes.push(MerkleTreeInternalNode::<H>::new(left_hash, right_hash).hash());
}
let remainder = children_iter.remainder();
assert!(remainder.len() <= 1);
if !remainder.is_empty() {
let left_hash = remainder[0];
let right_hash = *right_sibling_iter.next().ok_or_else(|| {
format_err!("Last child is a left child, but missing sibling on the right.")
})?;
parent_hashes.push(MerkleTreeInternalNode::<H>::new(left_hash, right_hash).hash());
}
first_pos = first_pos.parent();
current_hashes.clear();
std::mem::swap(&mut current_hashes, &mut parent_hashes);
}
ensure!(
current_hashes[0] == expected_root_hash,
"Root hashes do not match. Actual root hash: {:x}. Expected root hash: {:x}.",
current_hashes[0],
expected_root_hash,
);
Ok(())
}
}
impl<H> std::fmt::Debug for AccumulatorRangeProof<H> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"AccumulatorRangeProof {{ left_siblings: {:?}, right_siblings: {:?} }}",
self.left_siblings, self.right_siblings,
)
}
}
impl<H> PartialEq for AccumulatorRangeProof<H> {
fn eq(&self, other: &Self) -> bool {
self.left_siblings == other.left_siblings && self.right_siblings == other.right_siblings
}
}
impl<H> Eq for AccumulatorRangeProof<H> {}
pub type TransactionAccumulatorRangeProof = AccumulatorRangeProof<TransactionAccumulatorHasher>;
#[cfg(any(test, feature = "fuzzing"))]
pub type TestAccumulatorRangeProof = AccumulatorRangeProof<TestOnlyHasher>;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct SparseMerkleRangeProof {
right_siblings: Vec<HashValue>,
}
impl SparseMerkleRangeProof {
pub fn new(right_siblings: Vec<HashValue>) -> Self {
Self { right_siblings }
}
pub fn right_siblings(&self) -> &[HashValue] {
&self.right_siblings
}
pub fn verify(
&self,
expected_root_hash: HashValue,
rightmost_known_leaf: SparseMerkleLeafNode,
left_siblings: Vec<HashValue>,
) -> Result<()> {
let num_siblings = left_siblings.len() + self.right_siblings.len();
let mut left_sibling_iter = left_siblings.iter();
let mut right_sibling_iter = self.right_siblings().iter();
let mut current_hash = rightmost_known_leaf.hash();
for bit in rightmost_known_leaf
.key()
.iter_bits()
.rev()
.skip(HashValue::LENGTH_IN_BITS - num_siblings)
{
let (left_hash, right_hash) = if bit {
(
*left_sibling_iter
.next()
.ok_or_else(|| format_err!("Missing left sibling."))?,
current_hash,
)
} else {
(
current_hash,
*right_sibling_iter
.next()
.ok_or_else(|| format_err!("Missing right sibling."))?,
)
};
current_hash = SparseMerkleInternalNode::new(left_hash, right_hash).hash();
}
ensure!(
current_hash == expected_root_hash,
"Root hashes do not match. Actual root hash: {:x}. Expected root hash: {:x}.",
current_hash,
expected_root_hash,
);
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct TransactionInfoWithProof {
pub ledger_info_to_transaction_info_proof: TransactionAccumulatorProof,
pub transaction_info: TransactionInfo,
}
impl TransactionInfoWithProof {
pub fn new(
ledger_info_to_transaction_info_proof: TransactionAccumulatorProof,
transaction_info: TransactionInfo,
) -> Self {
Self {
ledger_info_to_transaction_info_proof,
transaction_info,
}
}
pub fn ledger_info_to_transaction_info_proof(&self) -> &TransactionAccumulatorProof {
&self.ledger_info_to_transaction_info_proof
}
pub fn transaction_info(&self) -> &TransactionInfo {
&self.transaction_info
}
pub fn verify(&self, ledger_info: &LedgerInfo, transaction_version: Version) -> Result<()> {
verify_transaction_info(
ledger_info,
transaction_version,
&self.transaction_info,
&self.ledger_info_to_transaction_info_proof,
)?;
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct TransactionInfoListWithProof {
pub ledger_info_to_transaction_infos_proof: TransactionAccumulatorRangeProof,
pub transaction_infos: Vec<TransactionInfo>,
}
impl TransactionInfoListWithProof {
pub fn new(
ledger_info_to_transaction_infos_proof: TransactionAccumulatorRangeProof,
transaction_infos: Vec<TransactionInfo>,
) -> Self {
Self {
ledger_info_to_transaction_infos_proof,
transaction_infos,
}
}
pub fn new_empty() -> Self {
Self::new(AccumulatorRangeProof::new_empty(), vec![])
}
pub fn verify(
&self,
ledger_info: &LedgerInfo,
first_transaction_info_version: Option<Version>,
) -> Result<()> {
let txn_info_hashes: Vec<_> = self
.transaction_infos
.iter()
.map(CryptoHash::hash)
.collect();
self.ledger_info_to_transaction_infos_proof.verify(
ledger_info.transaction_accumulator_hash(),
first_transaction_info_version,
&txn_info_hashes,
)
}
pub fn verify_extends_ledger(
&self,
num_txns_in_ledger: LeafCount,
root_hash: HashValue,
first_transaction_info_version: Option<Version>,
) -> Result<usize> {
if let Some(first_version) = first_transaction_info_version {
ensure!(
first_version <= num_txns_in_ledger,
"Transaction list too new. Expected version: {}. First transaction version: {}.",
num_txns_in_ledger,
first_version
);
let num_overlap_txns = (num_txns_in_ledger - first_version) as usize;
if num_overlap_txns > self.transaction_infos.len() {
return Ok(self.transaction_infos.len());
}
let overlap_txn_infos = &self.transaction_infos[..num_overlap_txns];
let frozen_subtree_roots_from_proof = self
.ledger_info_to_transaction_infos_proof
.left_siblings()
.iter()
.rev()
.cloned()
.collect::<Vec<_>>();
let accu_from_proof = InMemoryAccumulator::<TransactionAccumulatorHasher>::new(
frozen_subtree_roots_from_proof,
first_version,
)?
.append(
&overlap_txn_infos
.iter()
.map(CryptoHash::hash)
.collect::<Vec<_>>()[..],
);
ensure!(
accu_from_proof.root_hash() == root_hash,
"Fork happens because the current synced_trees doesn't match the txn list provided."
);
Ok(num_overlap_txns)
} else {
ensure!(self.transaction_infos.is_empty());
Ok(0)
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AccumulatorExtensionProof<H> {
frozen_subtree_roots: Vec<HashValue>,
num_leaves: LeafCount,
leaves: Vec<HashValue>,
hasher: PhantomData<H>,
}
impl<H: CryptoHasher> AccumulatorExtensionProof<H> {
pub fn new(
frozen_subtree_roots: Vec<HashValue>,
num_leaves: LeafCount,
leaves: Vec<HashValue>,
) -> Self {
Self {
frozen_subtree_roots,
num_leaves,
leaves,
hasher: PhantomData,
}
}
pub fn verify(&self, original_root: HashValue) -> anyhow::Result<InMemoryAccumulator<H>> {
let original_tree =
InMemoryAccumulator::<H>::new(self.frozen_subtree_roots.clone(), self.num_leaves)?;
ensure!(
original_tree.root_hash() == original_root,
"Root hashes do not match. Actual root hash: {:x}. Expected root hash: {:x}.",
original_tree.root_hash(),
original_root
);
Ok(original_tree.append(self.leaves.as_slice()))
}
}