Skip to content

Commit

Permalink
Add generate and verify logic for AncestryProof
Browse files Browse the repository at this point in the history
Co-authored-by: Robert Hambrock
  • Loading branch information
serban300 committed May 10, 2024
1 parent 2ea19f6 commit fbb3f7d
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 39 deletions.
38 changes: 33 additions & 5 deletions substrate/frame/merkle-mountain-range/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,6 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
mmr.generate_proof(leaf_indices)
}

/// Return the on-chain MMR root hash.
pub fn mmr_root() -> HashOf<T, I> {
RootHash::<T, I>::get()
}

/// Verify MMR proof for given `leaves`.
///
/// This method is safe to use within the runtime code.
Expand All @@ -393,4 +388,37 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Err(primitives::Error::Verify.log_debug("The proof is incorrect."))
}
}

pub fn generate_ancestry_proof(
prev_block_number: BlockNumberFor<T>,
best_known_block_number: Option<BlockNumberFor<T>>,
) -> Result<primitives::AncestryProof<HashOf<T, I>>, Error> {
// check whether best_known_block_number provided, else use current best block
let best_known_block_number =
best_known_block_number.unwrap_or_else(|| <frame_system::Pallet<T>>::block_number());

let leaf_count = Self::block_num_to_leaf_index(best_known_block_number)?.saturating_add(1);
let prev_leaf_count = Self::block_num_to_leaf_index(prev_block_number)?.saturating_add(1);

let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(leaf_count);
mmr.generate_ancestry_proof(prev_leaf_count)
}

pub fn verify_ancestry_proof(
ancestry_proof: primitives::AncestryProof<HashOf<T, I>>,
) -> Result<(), Error> {
let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> =
mmr::Mmr::new(ancestry_proof.leaf_count);
let is_valid = mmr.verify_ancestry_proof(ancestry_proof)?;
if is_valid {
Ok(())
} else {
Err(Error::Verify.log_debug("The ancestry proof is incorrect."))
}
}

/// Return the on-chain MMR root hash.
pub fn mmr_root() -> HashOf<T, I> {
RootHash::<T, I>::get()
}
}
63 changes: 62 additions & 1 deletion substrate/frame/merkle-mountain-range/src/mmr/mmr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
primitives::{self, Error, NodeIndex},
Config, HashOf, HashingOf,
};
use sp_mmr_primitives::{mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils};
use sp_mmr_primitives::{mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, LeafIndex};
use sp_std::prelude::*;

/// Stateless verification of the proof for a batch of leaves.
Expand Down Expand Up @@ -119,6 +119,44 @@ where
.map_err(|e| Error::Verify.log_debug(e))
}

pub fn verify_ancestry_proof(
&self,
ancestry_proof: primitives::AncestryProof<HashOf<T, I>>,
) -> Result<bool, Error> {
let prev_peaks_proof =
mmr_lib::NodeMerkleProof::<NodeOf<T, I, L>, Hasher<HashingOf<T, I>, L>>::new(
self.mmr.mmr_size(),
ancestry_proof
.items
.into_iter()
.map(|(index, hash)| (index, Node::Hash(hash)))
.collect(),
);

let raw_ancestry_proof = mmr_lib::AncestryProof::<
NodeOf<T, I, L>,
Hasher<HashingOf<T, I>, L>,
> {
prev_peaks: ancestry_proof
.prev_peaks
.into_iter()
.map(|hash| Node::Hash(hash))
.collect(),
prev_size: mmr_lib::helper::leaf_index_to_mmr_size(ancestry_proof.prev_leaf_count - 1),
proof: prev_peaks_proof,
};

let prev_root = mmr_lib::ancestry_proof::bagging_peaks_hashes::<
NodeOf<T, I, L>,
Hasher<HashingOf<T, I>, L>,
>(raw_ancestry_proof.prev_peaks.clone())
.map_err(|e| Error::Verify.log_debug(e))?;
let root = self.mmr.get_root().map_err(|e| Error::GetRoot.log_error(e))?;
raw_ancestry_proof
.verify_ancestor(root, prev_root)
.map_err(|e| Error::Verify.log_debug(e))
}

/// Return the internal size of the MMR (number of nodes).
#[cfg(test)]
pub fn size(&self) -> NodeIndex {
Expand Down Expand Up @@ -193,4 +231,27 @@ where
})
.map(|p| (leaves, p))
}

pub fn generate_ancestry_proof(
&self,
prev_leaf_count: LeafIndex,
) -> Result<primitives::AncestryProof<HashOf<T, I>>, Error> {
let prev_mmr_size = NodesUtils::new(prev_leaf_count).size();
let raw_ancestry_proof = self
.mmr
.gen_ancestry_proof(prev_mmr_size)
.map_err(|e| Error::GenerateProof.log_error(e))?;

Ok(primitives::AncestryProof {
prev_peaks: raw_ancestry_proof.prev_peaks.into_iter().map(|p| p.hash()).collect(),
prev_leaf_count,
leaf_count: self.leaves,
items: raw_ancestry_proof
.proof
.proof_items()
.iter()
.map(|(index, item)| (*index, item.hash()))
.collect(),
})
}
}
82 changes: 50 additions & 32 deletions substrate/frame/merkle-mountain-range/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,43 +516,40 @@ fn should_verify() {
});
}

#[test]
fn should_verify_batch_proofs() {
fn generate_and_verify_batch_proof(
ext: &mut sp_io::TestExternalities,
block_numbers: &Vec<u64>,
blocks_to_add: usize,
) {
let (leaves, proof) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(block_numbers.to_vec(), None).unwrap()
});
fn generate_and_verify_batch_proof(
ext: &mut sp_io::TestExternalities,
block_numbers: &Vec<u64>,
blocks_to_add: usize,
) {
let (leaves, proof) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(block_numbers.to_vec(), None).unwrap()
});

let max_block_number = ext.execute_with(|| frame_system::Pallet::<Test>::block_number());
let min_block_number = block_numbers.iter().max().unwrap();
let max_block_number = ext.execute_with(|| frame_system::Pallet::<Test>::block_number());
let min_block_number = block_numbers.iter().max().unwrap();

// generate all possible historical proofs for the given blocks
let historical_proofs = (*min_block_number..=max_block_number)
.map(|best_block| {
ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(block_numbers.to_vec(), Some(best_block))
.unwrap()
})
// generate all possible historical proofs for the given blocks
let historical_proofs = (*min_block_number..=max_block_number)
.map(|best_block| {
ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(block_numbers.to_vec(), Some(best_block))
.unwrap()
})
.collect::<Vec<_>>();

ext.execute_with(|| {
add_blocks(blocks_to_add);
// then
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proof), Ok(()));
historical_proofs.iter().for_each(|(leaves, proof)| {
assert_eq!(
crate::Pallet::<Test>::verify_leaves(leaves.clone(), proof.clone()),
Ok(())
);
});
})
}
.collect::<Vec<_>>();

ext.execute_with(|| {
add_blocks(blocks_to_add);
// then
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proof), Ok(()));
historical_proofs.iter().for_each(|(leaves, proof)| {
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves.clone(), proof.clone()), Ok(()));
});
})
}

#[test]
fn should_verify_batch_proofs() {
let _ = env_logger::try_init();

use itertools::Itertools;
Expand Down Expand Up @@ -790,3 +787,24 @@ fn does_not_panic_when_generating_historical_proofs() {
);
});
}

#[test]
fn generating_and_verifying_ancestry_proofs_works_correctly() {
let _ = env_logger::try_init();
let mut ext = new_test_ext();
ext.execute_with(|| add_blocks(500));
ext.persist_offchain_overlay();
register_offchain_ext(&mut ext);

ext.execute_with(|| {
// Check that generating and verifying ancestry proofs works correctly
// for each previous block
for prev_block_number in 1..501 {
let proof = Pallet::<Test>::generate_ancestry_proof(prev_block_number, None).unwrap();
Pallet::<Test>::verify_ancestry_proof(proof).unwrap();
}

// Check that we can't generate ancestry proofs for a future block.
assert_eq!(Pallet::<Test>::generate_ancestry_proof(501, None), Err(Error::GenerateProof));
});
}
16 changes: 15 additions & 1 deletion substrate/primitives/merkle-mountain-range/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,24 @@ pub struct LeafProof<Hash> {
pub leaf_indices: Vec<LeafIndex>,
/// Number of leaves in MMR, when the proof was generated.
pub leaf_count: NodeIndex,
/// Proof elements (hashes of siblings of inner nodes on the path to the leaf).
/// Proof elements (hashes of siblings of inner nodes on the path to the leafs).
pub items: Vec<Hash>,
}

/// An MMR ancestry proof for a prior mmr root.
#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)]
pub struct AncestryProof<Hash> {
/// Peaks of the ancestor's mmr
pub prev_peaks: Vec<Hash>,
/// Number of leaves in the ancestor's MMR.
pub prev_leaf_count: u64,
/// Number of leaves in MMR, when the proof was generated.
pub leaf_count: NodeIndex,
/// Proof elements
/// (positions and hashes of siblings of inner nodes on the path to the previous peaks).
pub items: Vec<(u64, Hash)>,
}

/// Merkle Mountain Range operation error.
#[cfg_attr(feature = "std", derive(thiserror::Error))]
#[derive(RuntimeDebug, codec::Encode, codec::Decode, PartialEq, Eq, TypeInfo)]
Expand Down

0 comments on commit fbb3f7d

Please sign in to comment.