mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Update MMR Runtime API with functionality to generate MMR proof for a series of leaf indices (#10635)
* updated mmr rpc api with functions for batch generation of proof * update code comments * fix build errors * added tests to mmr-rpc * add tests to pallet-mmr * update comments * minor comment fix * remove unused variables * fix rust doc errors * refactor mmr runtime api * fix tests * minor fix * minor fix * fix node-runtime * revert to initial api * impl from proof fot batchproof * minor fix * minor fix * use explicit functions to convert btw batch proof and single proof * minor fix * add new variant to mmr error * fmt * update conversion to single leaf proof * fix style nit Co-authored-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
@@ -1832,8 +1832,12 @@ impl_runtime_apis! {
|
||||
fn generate_proof(leaf_index: pallet_mmr::primitives::LeafIndex)
|
||||
-> Result<(mmr::EncodableOpaqueLeaf, mmr::Proof<mmr::Hash>), mmr::Error>
|
||||
{
|
||||
Mmr::generate_proof(leaf_index)
|
||||
.map(|(leaf, proof)| (mmr::EncodableOpaqueLeaf::from_leaf(&leaf), proof))
|
||||
Mmr::generate_batch_proof(vec![leaf_index]).and_then(|(leaves, proof)|
|
||||
Ok((
|
||||
mmr::EncodableOpaqueLeaf::from_leaf(&leaves[0]),
|
||||
mmr::BatchProof::into_single_leaf_proof(proof)?
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_proof(leaf: mmr::EncodableOpaqueLeaf, proof: mmr::Proof<mmr::Hash>)
|
||||
@@ -1843,7 +1847,7 @@ impl_runtime_apis! {
|
||||
.into_opaque_leaf()
|
||||
.try_decode()
|
||||
.ok_or(mmr::Error::Verify)?;
|
||||
Mmr::verify_leaf(leaf, proof)
|
||||
Mmr::verify_leaves(vec![leaf], mmr::Proof::into_batch_proof(proof))
|
||||
}
|
||||
|
||||
fn verify_proof_stateless(
|
||||
@@ -1852,12 +1856,38 @@ impl_runtime_apis! {
|
||||
proof: mmr::Proof<mmr::Hash>
|
||||
) -> Result<(), mmr::Error> {
|
||||
let node = mmr::DataOrHash::Data(leaf.into_opaque_leaf());
|
||||
pallet_mmr::verify_leaf_proof::<mmr::Hashing, _>(root, node, proof)
|
||||
pallet_mmr::verify_leaves_proof::<mmr::Hashing, _>(root, vec![node], mmr::Proof::into_batch_proof(proof))
|
||||
}
|
||||
|
||||
fn mmr_root() -> Result<mmr::Hash, mmr::Error> {
|
||||
Ok(Mmr::mmr_root())
|
||||
}
|
||||
|
||||
fn generate_batch_proof(leaf_indices: Vec<pallet_mmr::primitives::LeafIndex>)
|
||||
-> Result<(Vec<mmr::EncodableOpaqueLeaf>, mmr::BatchProof<mmr::Hash>), mmr::Error>
|
||||
{
|
||||
Mmr::generate_batch_proof(leaf_indices)
|
||||
.map(|(leaves, proof)| (leaves.into_iter().map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf)).collect(), proof))
|
||||
}
|
||||
|
||||
fn verify_batch_proof(leaves: Vec<mmr::EncodableOpaqueLeaf>, proof: mmr::BatchProof<mmr::Hash>)
|
||||
-> Result<(), mmr::Error>
|
||||
{
|
||||
let leaves = leaves.into_iter().map(|leaf|
|
||||
leaf.into_opaque_leaf()
|
||||
.try_decode()
|
||||
.ok_or(mmr::Error::Verify)).collect::<Result<Vec<mmr::Leaf>, mmr::Error>>()?;
|
||||
Mmr::verify_leaves(leaves, proof)
|
||||
}
|
||||
|
||||
fn verify_batch_proof_stateless(
|
||||
root: mmr::Hash,
|
||||
leaves: Vec<mmr::EncodableOpaqueLeaf>,
|
||||
proof: mmr::BatchProof<mmr::Hash>
|
||||
) -> Result<(), mmr::Error> {
|
||||
let nodes = leaves.into_iter().map(|leaf|mmr::DataOrHash::Data(leaf.into_opaque_leaf())).collect();
|
||||
pallet_mmr::verify_leaves_proof::<mmr::Hashing, _>(root, nodes, proof)
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_session::SessionKeys<Block> for Runtime {
|
||||
|
||||
@@ -39,7 +39,9 @@ use beefy_primitives::{
|
||||
crypto::AuthorityId, BeefyApi, ConsensusLog, MmrRootHash, ValidatorSet, BEEFY_ENGINE_ID,
|
||||
KEY_TYPE as BeefyKeyType,
|
||||
};
|
||||
use sp_mmr_primitives::{EncodableOpaqueLeaf, Error as MmrError, LeafIndex, MmrApi, Proof};
|
||||
use sp_mmr_primitives::{
|
||||
BatchProof, EncodableOpaqueLeaf, Error as MmrError, LeafIndex, MmrApi, Proof,
|
||||
};
|
||||
|
||||
use sp_api::{ApiRef, ProvideRuntimeApi};
|
||||
use sp_consensus::BlockOrigin;
|
||||
@@ -259,6 +261,22 @@ macro_rules! create_test_api {
|
||||
fn mmr_root() -> Result<MmrRootHash, MmrError> {
|
||||
Ok($mmr_root)
|
||||
}
|
||||
|
||||
fn generate_batch_proof(_leaf_indices: Vec<LeafIndex>) -> Result<(Vec<EncodableOpaqueLeaf>, BatchProof<MmrRootHash>), MmrError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn verify_batch_proof(_leaves: Vec<EncodableOpaqueLeaf>, _proof: BatchProof<MmrRootHash>) -> Result<(), MmrError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn verify_batch_proof_stateless(
|
||||
_root: MmrRootHash,
|
||||
_leaves: Vec<EncodableOpaqueLeaf>,
|
||||
_proof: BatchProof<MmrRootHash>
|
||||
) -> Result<(), MmrError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ serde = { version = "1.0.136", features = ["derive"] }
|
||||
sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" }
|
||||
sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" }
|
||||
sp-core = { version = "6.0.0", path = "../../../primitives/core" }
|
||||
sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/merkle-mountain-range" }
|
||||
sp-mmr-primitives = { version = "4.0.0-dev", path = "../../../primitives/merkle-mountain-range" }
|
||||
sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize};
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_core::Bytes;
|
||||
use sp_mmr_primitives::{Error as MmrError, LeafIndex, Proof};
|
||||
use sp_mmr_primitives::{BatchProof, Error as MmrError, LeafIndex, Proof};
|
||||
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
|
||||
|
||||
pub use sp_mmr_primitives::MmrApi as MmrRuntimeApi;
|
||||
@@ -57,6 +57,34 @@ impl<BlockHash> LeafProof<BlockHash> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieved MMR leaves and their proof.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LeafBatchProof<BlockHash> {
|
||||
/// Block hash the proof was generated for.
|
||||
pub block_hash: BlockHash,
|
||||
/// SCALE-encoded vector of `LeafData`.
|
||||
pub leaves: Bytes,
|
||||
/// SCALE-encoded proof data. See [sp_mmr_primitives::BatchProof].
|
||||
pub proof: Bytes,
|
||||
}
|
||||
|
||||
impl<BlockHash> LeafBatchProof<BlockHash> {
|
||||
/// Create new `LeafBatchProof` from a given vector of `Leaf` and a
|
||||
/// [sp_mmr_primitives::BatchProof].
|
||||
pub fn new<Leaf, MmrHash>(
|
||||
block_hash: BlockHash,
|
||||
leaves: Vec<Leaf>,
|
||||
proof: BatchProof<MmrHash>,
|
||||
) -> Self
|
||||
where
|
||||
Leaf: Encode,
|
||||
MmrHash: Encode,
|
||||
{
|
||||
Self { block_hash, leaves: Bytes(leaves.encode()), proof: Bytes(proof.encode()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// MMR RPC methods.
|
||||
#[rpc]
|
||||
pub trait MmrApi<BlockHash> {
|
||||
@@ -74,6 +102,23 @@ pub trait MmrApi<BlockHash> {
|
||||
leaf_index: LeafIndex,
|
||||
at: Option<BlockHash>,
|
||||
) -> Result<LeafProof<BlockHash>>;
|
||||
|
||||
/// Generate MMR proof for the given leaf indices.
|
||||
///
|
||||
/// This method calls into a runtime with MMR pallet included and attempts to generate
|
||||
/// MMR proof for a set of leaves at the given `leaf_indices`.
|
||||
/// Optionally, a block hash at which the runtime should be queried can be specified.
|
||||
///
|
||||
/// Returns the leaves and a proof for these leaves (compact encoding, i.e. hash of
|
||||
/// the leaves). Both parameters are SCALE-encoded.
|
||||
/// The order of entries in the `leaves` field of the returned struct
|
||||
/// is the same as the order of the entries in `leaf_indices` supplied
|
||||
#[rpc(name = "mmr_generateBatchProof")]
|
||||
fn generate_batch_proof(
|
||||
&self,
|
||||
leaf_indices: Vec<LeafIndex>,
|
||||
at: Option<BlockHash>,
|
||||
) -> Result<LeafBatchProof<BlockHash>>;
|
||||
}
|
||||
|
||||
/// An implementation of MMR specific RPC methods.
|
||||
@@ -117,6 +162,28 @@ where
|
||||
|
||||
Ok(LeafProof::new(block_hash, leaf, proof))
|
||||
}
|
||||
|
||||
fn generate_batch_proof(
|
||||
&self,
|
||||
leaf_indices: Vec<LeafIndex>,
|
||||
at: Option<<Block as BlockT>::Hash>,
|
||||
) -> Result<LeafBatchProof<<Block as BlockT>::Hash>> {
|
||||
let api = self.client.runtime_api();
|
||||
let block_hash = at.unwrap_or_else(||
|
||||
// If the block hash is not supplied assume the best block.
|
||||
self.client.info().best_hash);
|
||||
|
||||
let (leaves, proof) = api
|
||||
.generate_batch_proof_with_context(
|
||||
&BlockId::hash(block_hash),
|
||||
sp_core::ExecutionContext::OffchainCall(None),
|
||||
leaf_indices,
|
||||
)
|
||||
.map_err(runtime_error_into_rpc_error)?
|
||||
.map_err(mmr_error_into_rpc_error)?;
|
||||
|
||||
Ok(LeafBatchProof::new(block_hash, leaves, proof))
|
||||
}
|
||||
}
|
||||
|
||||
const RUNTIME_ERROR: i64 = 8000;
|
||||
@@ -179,6 +246,28 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serialize_leaf_batch_proof() {
|
||||
// given
|
||||
let leaf = vec![1_u8, 2, 3, 4];
|
||||
let proof = BatchProof {
|
||||
leaf_indices: vec![1],
|
||||
leaf_count: 9,
|
||||
items: vec![H256::repeat_byte(1), H256::repeat_byte(2)],
|
||||
};
|
||||
|
||||
let leaf_proof = LeafBatchProof::new(H256::repeat_byte(0), vec![leaf], proof);
|
||||
|
||||
// when
|
||||
let actual = serde_json::to_string(&leaf_proof).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
actual,
|
||||
r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaves":"0x041001020304","proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_deserialize_leaf_proof() {
|
||||
// given
|
||||
@@ -205,4 +294,31 @@ mod tests {
|
||||
// then
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_deserialize_leaf_batch_proof() {
|
||||
// given
|
||||
let expected = LeafBatchProof {
|
||||
block_hash: H256::repeat_byte(0),
|
||||
leaves: Bytes(vec![vec![1_u8, 2, 3, 4]].encode()),
|
||||
proof: Bytes(
|
||||
BatchProof {
|
||||
leaf_indices: vec![1],
|
||||
leaf_count: 9,
|
||||
items: vec![H256::repeat_byte(1), H256::repeat_byte(2)],
|
||||
}
|
||||
.encode(),
|
||||
),
|
||||
};
|
||||
|
||||
// when
|
||||
let actual: LeafBatchProof<H256> = serde_json::from_str(r#"{
|
||||
"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"leaves":"0x041001020304",
|
||||
"proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"
|
||||
}"#).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ mod tests;
|
||||
|
||||
pub use pallet::*;
|
||||
pub use sp_mmr_primitives::{self as primitives, Error, LeafDataProvider, LeafIndex, NodeIndex};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// The most common use case for MMRs is to store historical block hashes,
|
||||
/// so that any point in time in the future we can receive a proof about some past
|
||||
@@ -228,22 +229,23 @@ type LeafOf<T, I> = <<T as Config<I>>::LeafData as primitives::LeafDataProvider>
|
||||
/// Hashing used for the pallet.
|
||||
pub(crate) type HashingOf<T, I> = <T as Config<I>>::Hashing;
|
||||
|
||||
/// Stateless MMR proof verification.
|
||||
/// Stateless MMR proof verification for batch of leaves.
|
||||
///
|
||||
/// This function can be used to verify received MMR proof (`proof`)
|
||||
/// for given leaf data (`leaf`) against a known MMR root hash (`root`).
|
||||
///
|
||||
/// The verification does not require any storage access.
|
||||
pub fn verify_leaf_proof<H, L>(
|
||||
/// This function can be used to verify received MMR [primitives::BatchProof] (`proof`)
|
||||
/// for given leaves set (`leaves`) against a known MMR root hash (`root`).
|
||||
/// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the
|
||||
/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the
|
||||
/// [primitives::BatchProof].
|
||||
pub fn verify_leaves_proof<H, L>(
|
||||
root: H::Output,
|
||||
leaf: mmr::Node<H, L>,
|
||||
proof: primitives::Proof<H::Output>,
|
||||
leaves: Vec<mmr::Node<H, L>>,
|
||||
proof: primitives::BatchProof<H::Output>,
|
||||
) -> Result<(), primitives::Error>
|
||||
where
|
||||
H: traits::Hash,
|
||||
L: primitives::FullLeaf,
|
||||
{
|
||||
let is_valid = mmr::verify_leaf_proof::<H, L>(root, leaf, proof)?;
|
||||
let is_valid = mmr::verify_leaves_proof::<H, L>(root, leaves, proof)?;
|
||||
if is_valid {
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -255,29 +257,36 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
fn offchain_key(pos: NodeIndex) -> sp_std::prelude::Vec<u8> {
|
||||
(T::INDEXING_PREFIX, pos).encode()
|
||||
}
|
||||
|
||||
/// Generate a MMR proof for the given `leaf_index`.
|
||||
/// Generate a MMR proof for the given `leaf_indices`.
|
||||
///
|
||||
/// Note this method can only be used from an off-chain context
|
||||
/// (Offchain Worker or Runtime API call), since it requires
|
||||
/// all the leaves to be present.
|
||||
/// It may return an error or panic if used incorrectly.
|
||||
pub fn generate_proof(
|
||||
leaf_index: LeafIndex,
|
||||
) -> Result<(LeafOf<T, I>, primitives::Proof<<T as Config<I>>::Hash>), primitives::Error> {
|
||||
pub fn generate_batch_proof(
|
||||
leaf_indices: Vec<NodeIndex>,
|
||||
) -> Result<
|
||||
(Vec<LeafOf<T, I>>, primitives::BatchProof<<T as Config<I>>::Hash>),
|
||||
primitives::Error,
|
||||
> {
|
||||
let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(Self::mmr_leaves());
|
||||
mmr.generate_proof(leaf_index)
|
||||
mmr.generate_batch_proof(leaf_indices)
|
||||
}
|
||||
|
||||
/// Verify MMR proof for given `leaf`.
|
||||
/// Return the on-chain MMR root hash.
|
||||
pub fn mmr_root() -> <T as Config<I>>::Hash {
|
||||
Self::mmr_root_hash()
|
||||
}
|
||||
|
||||
/// Verify MMR proof for given `leaves`.
|
||||
///
|
||||
/// This method is safe to use within the runtime code.
|
||||
/// It will return `Ok(())` if the proof is valid
|
||||
/// and an `Err(..)` if MMR is inconsistent (some leaves are missing)
|
||||
/// or the proof is invalid.
|
||||
pub fn verify_leaf(
|
||||
leaf: LeafOf<T, I>,
|
||||
proof: primitives::Proof<<T as Config<I>>::Hash>,
|
||||
pub fn verify_leaves(
|
||||
leaves: Vec<LeafOf<T, I>>,
|
||||
proof: primitives::BatchProof<<T as Config<I>>::Hash>,
|
||||
) -> Result<(), primitives::Error> {
|
||||
if proof.leaf_count > Self::mmr_leaves() ||
|
||||
proof.leaf_count == 0 ||
|
||||
@@ -288,16 +297,11 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
}
|
||||
|
||||
let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(proof.leaf_count);
|
||||
let is_valid = mmr.verify_leaf_proof(leaf, proof)?;
|
||||
let is_valid = mmr.verify_leaves_proof(leaves, proof)?;
|
||||
if is_valid {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(primitives::Error::Verify.log_debug("The proof is incorrect."))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the on-chain MMR root hash.
|
||||
pub fn mmr_root() -> <T as Config<I>>::Hash {
|
||||
Self::mmr_root_hash()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,27 +24,39 @@ use crate::{
|
||||
primitives::{self, Error, NodeIndex},
|
||||
Config, HashingOf,
|
||||
};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use sp_std::vec;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// Stateless verification of the leaf proof.
|
||||
pub fn verify_leaf_proof<H, L>(
|
||||
/// Stateless verification of the proof for a batch of leaves.
|
||||
/// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the
|
||||
/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the
|
||||
/// [primitives::BatchProof]
|
||||
pub fn verify_leaves_proof<H, L>(
|
||||
root: H::Output,
|
||||
leaf: Node<H, L>,
|
||||
proof: primitives::Proof<H::Output>,
|
||||
leaves: Vec<Node<H, L>>,
|
||||
proof: primitives::BatchProof<H::Output>,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
H: sp_runtime::traits::Hash,
|
||||
L: primitives::FullLeaf,
|
||||
{
|
||||
let size = NodesUtils::new(proof.leaf_count).size();
|
||||
let leaf_position = mmr_lib::leaf_index_to_pos(proof.leaf_index);
|
||||
|
||||
if leaves.len() != proof.leaf_indices.len() {
|
||||
return Err(Error::Verify.log_debug("Proof leaf_indices not same length with leaves"))
|
||||
}
|
||||
|
||||
let leaves_and_position_data = proof
|
||||
.leaf_indices
|
||||
.into_iter()
|
||||
.map(|index| mmr_lib::leaf_index_to_pos(index))
|
||||
.zip(leaves.into_iter())
|
||||
.collect();
|
||||
|
||||
let p = mmr_lib::MerkleProof::<Node<H, L>, Hasher<H, L>>::new(
|
||||
size,
|
||||
proof.items.into_iter().map(Node::Hash).collect(),
|
||||
);
|
||||
p.verify(Node::Hash(root), vec![(leaf_position, leaf)])
|
||||
p.verify(Node::Hash(root), leaves_and_position_data)
|
||||
.map_err(|e| Error::Verify.log_debug(e))
|
||||
}
|
||||
|
||||
@@ -76,19 +88,32 @@ where
|
||||
Self { mmr: mmr_lib::MMR::new(size, Default::default()), leaves }
|
||||
}
|
||||
|
||||
/// Verify proof of a single leaf.
|
||||
pub fn verify_leaf_proof(
|
||||
/// Verify proof for a set of leaves.
|
||||
/// Note, the leaves should be sorted such that corresponding leaves and leaf indices have
|
||||
/// the same position in both the `leaves` vector and the `leaf_indices` vector contained in the
|
||||
/// [primitives::BatchProof]
|
||||
pub fn verify_leaves_proof(
|
||||
&self,
|
||||
leaf: L,
|
||||
proof: primitives::Proof<<T as Config<I>>::Hash>,
|
||||
leaves: Vec<L>,
|
||||
proof: primitives::BatchProof<<T as Config<I>>::Hash>,
|
||||
) -> Result<bool, Error> {
|
||||
let p = mmr_lib::MerkleProof::<NodeOf<T, I, L>, Hasher<HashingOf<T, I>, L>>::new(
|
||||
self.mmr.mmr_size(),
|
||||
proof.items.into_iter().map(Node::Hash).collect(),
|
||||
);
|
||||
let position = mmr_lib::leaf_index_to_pos(proof.leaf_index);
|
||||
|
||||
if leaves.len() != proof.leaf_indices.len() {
|
||||
return Err(Error::Verify.log_debug("Proof leaf_indices not same length with leaves"))
|
||||
}
|
||||
|
||||
let leaves_positions_and_data = proof
|
||||
.leaf_indices
|
||||
.into_iter()
|
||||
.map(|index| mmr_lib::leaf_index_to_pos(index))
|
||||
.zip(leaves.into_iter().map(|leaf| Node::Data(leaf)))
|
||||
.collect();
|
||||
let root = self.mmr.get_root().map_err(|e| Error::GetRoot.log_error(e))?;
|
||||
p.verify(root, vec![(position, Node::Data(leaf))])
|
||||
p.verify(root, leaves_positions_and_data)
|
||||
.map_err(|e| Error::Verify.log_debug(e))
|
||||
}
|
||||
|
||||
@@ -134,29 +159,36 @@ where
|
||||
I: 'static,
|
||||
L: primitives::FullLeaf + codec::Decode,
|
||||
{
|
||||
/// Generate a proof for given leaf index.
|
||||
/// Generate a proof for given leaf indices.
|
||||
///
|
||||
/// Proof generation requires all the nodes (or their hashes) to be available in the storage.
|
||||
/// (i.e. you can't run the function in the pruned storage).
|
||||
pub fn generate_proof(
|
||||
pub fn generate_batch_proof(
|
||||
&self,
|
||||
leaf_index: NodeIndex,
|
||||
) -> Result<(L, primitives::Proof<<T as Config<I>>::Hash>), Error> {
|
||||
let position = mmr_lib::leaf_index_to_pos(leaf_index);
|
||||
leaf_indices: Vec<NodeIndex>,
|
||||
) -> Result<(Vec<L>, primitives::BatchProof<<T as Config<I>>::Hash>), Error> {
|
||||
let positions = leaf_indices
|
||||
.iter()
|
||||
.map(|index| mmr_lib::leaf_index_to_pos(*index))
|
||||
.collect::<Vec<_>>();
|
||||
let store = <Storage<OffchainStorage, T, I, L>>::default();
|
||||
let leaf = match mmr_lib::MMRStore::get_elem(&store, position) {
|
||||
Ok(Some(Node::Data(leaf))) => leaf,
|
||||
e => return Err(Error::LeafNotFound.log_debug(e)),
|
||||
};
|
||||
let leaves = positions
|
||||
.iter()
|
||||
.map(|pos| match mmr_lib::MMRStore::get_elem(&store, *pos) {
|
||||
Ok(Some(Node::Data(leaf))) => Ok(leaf),
|
||||
e => Err(Error::LeafNotFound.log_debug(e)),
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
let leaf_count = self.leaves;
|
||||
self.mmr
|
||||
.gen_proof(vec![position])
|
||||
.gen_proof(positions)
|
||||
.map_err(|e| Error::GenerateProof.log_error(e))
|
||||
.map(|p| primitives::Proof {
|
||||
leaf_index,
|
||||
.map(|p| primitives::BatchProof {
|
||||
leaf_indices,
|
||||
leaf_count,
|
||||
items: p.proof_items().iter().map(|x| x.hash()).collect(),
|
||||
})
|
||||
.map(|p| (leaf, p))
|
||||
.map(|p| (leaves, p))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub mod utils;
|
||||
use sp_mmr_primitives::{DataOrHash, FullLeaf};
|
||||
use sp_runtime::traits;
|
||||
|
||||
pub use self::mmr::{verify_leaf_proof, Mmr};
|
||||
pub use self::mmr::{verify_leaves_proof, Mmr};
|
||||
|
||||
/// Node type for runtime `T`.
|
||||
pub type NodeOf<T, I, L> = Node<<T as crate::Config<I>>::Hashing, L>;
|
||||
|
||||
@@ -23,7 +23,7 @@ use sp_core::{
|
||||
offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt},
|
||||
H256,
|
||||
};
|
||||
use sp_mmr_primitives::{Compact, Proof};
|
||||
use sp_mmr_primitives::{BatchProof, Compact};
|
||||
|
||||
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
|
||||
frame_system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
|
||||
@@ -225,16 +225,18 @@ fn should_generate_proofs_correctly() {
|
||||
// when generate proofs for all leaves
|
||||
let proofs = (0_u64..crate::NumberOfLeaves::<Test>::get())
|
||||
.into_iter()
|
||||
.map(|leaf_index| crate::Pallet::<Test>::generate_proof(leaf_index).unwrap())
|
||||
.map(|leaf_index| {
|
||||
crate::Pallet::<Test>::generate_batch_proof(vec![leaf_index]).unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
proofs[0],
|
||||
(
|
||||
Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),)),
|
||||
Proof {
|
||||
leaf_index: 0,
|
||||
vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))],
|
||||
BatchProof {
|
||||
leaf_indices: vec![0],
|
||||
leaf_count: 7,
|
||||
items: vec![
|
||||
hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"),
|
||||
@@ -247,9 +249,9 @@ fn should_generate_proofs_correctly() {
|
||||
assert_eq!(
|
||||
proofs[4],
|
||||
(
|
||||
Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),)),
|
||||
Proof {
|
||||
leaf_index: 4,
|
||||
vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))],
|
||||
BatchProof {
|
||||
leaf_indices: vec![4],
|
||||
leaf_count: 7,
|
||||
items: vec![
|
||||
hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"),
|
||||
@@ -262,9 +264,9 @@ fn should_generate_proofs_correctly() {
|
||||
assert_eq!(
|
||||
proofs[6],
|
||||
(
|
||||
Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),)),
|
||||
Proof {
|
||||
leaf_index: 6,
|
||||
vec![Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),))],
|
||||
BatchProof {
|
||||
leaf_indices: vec![6],
|
||||
leaf_count: 7,
|
||||
items: vec![
|
||||
hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"),
|
||||
@@ -276,6 +278,37 @@ fn should_generate_proofs_correctly() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_generate_batch_proof_correctly() {
|
||||
let _ = env_logger::try_init();
|
||||
let mut ext = new_test_ext();
|
||||
// given
|
||||
ext.execute_with(|| init_chain(7));
|
||||
ext.persist_offchain_overlay();
|
||||
|
||||
// Try to generate proofs now. This requires the offchain extensions to be present
|
||||
// to retrieve full leaf data.
|
||||
register_offchain_ext(&mut ext);
|
||||
ext.execute_with(|| {
|
||||
// when generate proofs for all leaves
|
||||
let (.., proof) = crate::Pallet::<Test>::generate_batch_proof(vec![0, 4, 5]).unwrap();
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
proof,
|
||||
BatchProof {
|
||||
leaf_indices: vec![0, 4, 5],
|
||||
leaf_count: 7,
|
||||
items: vec![
|
||||
hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"),
|
||||
hex("cb24f4614ad5b2a5430344c99545b421d9af83c46fd632d70a332200884b4d46"),
|
||||
hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c"),
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_verify() {
|
||||
let _ = env_logger::try_init();
|
||||
@@ -289,15 +322,40 @@ fn should_verify() {
|
||||
// Try to generate proof now. This requires the offchain extensions to be present
|
||||
// to retrieve full leaf data.
|
||||
register_offchain_ext(&mut ext);
|
||||
let (leaf, proof5) = ext.execute_with(|| {
|
||||
let (leaves, proof5) = ext.execute_with(|| {
|
||||
// when
|
||||
crate::Pallet::<Test>::generate_proof(5).unwrap()
|
||||
crate::Pallet::<Test>::generate_batch_proof(vec![5]).unwrap()
|
||||
});
|
||||
|
||||
ext.execute_with(|| {
|
||||
init_chain(7);
|
||||
// then
|
||||
assert_eq!(crate::Pallet::<Test>::verify_leaf(leaf, proof5), Ok(()));
|
||||
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proof5), Ok(()));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_verify_batch_proof() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
// Start off with chain initialisation and storing indexing data off-chain
|
||||
// (MMR Leafs)
|
||||
let mut ext = new_test_ext();
|
||||
ext.execute_with(|| init_chain(7));
|
||||
ext.persist_offchain_overlay();
|
||||
|
||||
// Try to generate proof now. This requires the offchain extensions to be present
|
||||
// to retrieve full leaf data.
|
||||
register_offchain_ext(&mut ext);
|
||||
let (leaves, proof) = ext.execute_with(|| {
|
||||
// when
|
||||
crate::Pallet::<Test>::generate_batch_proof(vec![0, 4, 5]).unwrap()
|
||||
});
|
||||
|
||||
ext.execute_with(|| {
|
||||
init_chain(7);
|
||||
// then
|
||||
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proof), Ok(()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -314,16 +372,49 @@ fn verification_should_be_stateless() {
|
||||
// Try to generate proof now. This requires the offchain extensions to be present
|
||||
// to retrieve full leaf data.
|
||||
register_offchain_ext(&mut ext);
|
||||
let (leaf, proof5) = ext.execute_with(|| {
|
||||
let (leaves, proof5) = ext.execute_with(|| {
|
||||
// when
|
||||
crate::Pallet::<Test>::generate_proof(5).unwrap()
|
||||
crate::Pallet::<Test>::generate_batch_proof(vec![5]).unwrap()
|
||||
});
|
||||
let root = ext.execute_with(|| crate::Pallet::<Test>::mmr_root_hash());
|
||||
|
||||
// Verify proof without relying on any on-chain data.
|
||||
let leaf = crate::primitives::DataOrHash::Data(leaf);
|
||||
let leaf = crate::primitives::DataOrHash::Data(leaves[0].clone());
|
||||
assert_eq!(
|
||||
crate::verify_leaf_proof::<<Test as Config>::Hashing, _>(root, leaf, proof5),
|
||||
crate::verify_leaves_proof::<<Test as Config>::Hashing, _>(root, vec![leaf], proof5),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_verify_batch_proof_statelessly() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
// Start off with chain initialisation and storing indexing data off-chain
|
||||
// (MMR Leafs)
|
||||
let mut ext = new_test_ext();
|
||||
ext.execute_with(|| init_chain(7));
|
||||
ext.persist_offchain_overlay();
|
||||
|
||||
// Try to generate proof now. This requires the offchain extensions to be present
|
||||
// to retrieve full leaf data.
|
||||
register_offchain_ext(&mut ext);
|
||||
let (leaves, proof) = ext.execute_with(|| {
|
||||
// when
|
||||
crate::Pallet::<Test>::generate_batch_proof(vec![0, 4, 5]).unwrap()
|
||||
});
|
||||
let root = ext.execute_with(|| crate::Pallet::<Test>::mmr_root_hash());
|
||||
|
||||
// Verify proof without relying on any on-chain data.
|
||||
assert_eq!(
|
||||
crate::verify_leaves_proof::<<Test as Config>::Hashing, _>(
|
||||
root,
|
||||
leaves
|
||||
.into_iter()
|
||||
.map(|leaf| crate::primitives::DataOrHash::Data(leaf))
|
||||
.collect(),
|
||||
proof
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
@@ -340,10 +431,10 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() {
|
||||
|
||||
ext.execute_with(|| {
|
||||
// when
|
||||
let (leaf, proof5) = crate::Pallet::<Test>::generate_proof(5).unwrap();
|
||||
let (leaves, proof5) = crate::Pallet::<Test>::generate_batch_proof(vec![5]).unwrap();
|
||||
new_block();
|
||||
|
||||
// then
|
||||
assert_eq!(crate::Pallet::<Test>::verify_leaf(leaf, proof5), Ok(()));
|
||||
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proof5), Ok(()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
|
||||
use sp_debug_derive::RuntimeDebug;
|
||||
use sp_runtime::traits;
|
||||
use sp_std::fmt;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use sp_std::prelude::Vec;
|
||||
use sp_std::{fmt, vec};
|
||||
|
||||
/// A type to describe node position in the MMR (node index).
|
||||
pub type NodeIndex = u64;
|
||||
@@ -351,6 +351,38 @@ impl_leaf_data_for_tuple!(A:0, B:1, C:2);
|
||||
impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3);
|
||||
impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3, E:4);
|
||||
|
||||
/// A MMR proof data for a group of leaves.
|
||||
#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq)]
|
||||
pub struct BatchProof<Hash> {
|
||||
/// The indices of the leaves the proof is for.
|
||||
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).
|
||||
pub items: Vec<Hash>,
|
||||
}
|
||||
|
||||
impl<Hash> BatchProof<Hash> {
|
||||
/// Converts batch proof to single leaf proof
|
||||
pub fn into_single_leaf_proof(proof: BatchProof<Hash>) -> Result<Proof<Hash>, Error> {
|
||||
Ok(Proof {
|
||||
leaf_index: *proof.leaf_indices.get(0).ok_or(Error::InvalidLeafIndex)?,
|
||||
leaf_count: proof.leaf_count,
|
||||
items: proof.items,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Hash> Proof<Hash> {
|
||||
/// Converts a single leaf proof into a batch proof
|
||||
pub fn into_batch_proof(proof: Proof<Hash>) -> BatchProof<Hash> {
|
||||
BatchProof {
|
||||
leaf_indices: vec![proof.leaf_index],
|
||||
leaf_count: proof.leaf_count,
|
||||
items: proof.items,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Merkle Mountain Range operation error.
|
||||
#[derive(RuntimeDebug, codec::Encode, codec::Decode, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
@@ -366,6 +398,10 @@ pub enum Error {
|
||||
Verify,
|
||||
/// Leaf not found in the storage.
|
||||
LeafNotFound,
|
||||
/// Mmr Pallet not included in runtime
|
||||
PalletNotIncluded,
|
||||
/// Cannot find the requested leaf index
|
||||
InvalidLeafIndex,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
@@ -417,6 +453,27 @@ sp_api::decl_runtime_apis! {
|
||||
|
||||
/// Return the on-chain MMR root hash.
|
||||
fn mmr_root() -> Result<Hash, Error>;
|
||||
|
||||
/// Generate MMR proof for a series of leaves under given indices.
|
||||
fn generate_batch_proof(leaf_indices: Vec<LeafIndex>) -> Result<(Vec<EncodableOpaqueLeaf>, BatchProof<Hash>), Error>;
|
||||
|
||||
/// Verify MMR proof against on-chain MMR for a batch of leaves.
|
||||
///
|
||||
/// Note this function will use on-chain MMR root hash and check if the proof
|
||||
/// matches the hash.
|
||||
/// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the
|
||||
/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [BatchProof]
|
||||
fn verify_batch_proof(leaves: Vec<EncodableOpaqueLeaf>, proof: BatchProof<Hash>) -> Result<(), Error>;
|
||||
|
||||
/// Verify MMR proof against given root hash or a batch of leaves.
|
||||
///
|
||||
/// Note this function does not require any on-chain storage - the
|
||||
/// proof is verified against given MMR root hash.
|
||||
///
|
||||
/// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the
|
||||
/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [BatchProof]
|
||||
fn verify_batch_proof_stateless(root: Hash, leaves: Vec<EncodableOpaqueLeaf>, proof: BatchProof<Hash>)
|
||||
-> Result<(), Error>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user