Consolidate and deduplicate MMR API methods (#12530)

* histor. batch proof: make best block arg optional

* correct testing range

* make generate_batch_proof stub for historical

* merge generate_{historical_}batch_proof functions

* merge generate_{batch_}proof functions

* merge verify_{batch_}proof functions

* merge verify_{batch_}proof_stateless functions

* remove {Leaf}Proof

Not utilized by API anymore, so superfluous.
Removal consistent with prior changes to just use "batch" proof API.

* rename BatchProof->Proof

no need to qualify if only one universal proof type.

* cleanup

* expose verify_proof rpc api

* document verify_proof

* expose verify_proof_stateless rpc api

* add optional BlockHash to mmr_root rpc api

* fixup! expose verify_proof rpc api

* fix documentation phrasing

Co-authored-by: Adrian Catangiu <adrian@parity.io>

* documentation grammar

Co-authored-by: Adrian Catangiu <adrian@parity.io>

* define mmr error msgs together with error enum

Co-authored-by: Serban Iorga <serban@parity.io>

* fixup! define mmr error msgs together with error enum

* map decoding errors to CallError::InvalidParams

Co-authored-by: Serban Iorga <serban@parity.io>

* fixup! map decoding errors to CallError::InvalidParams

Co-authored-by: Adrian Catangiu <adrian@parity.io>
Co-authored-by: parity-processbot <>
Co-authored-by: Serban Iorga <serban@parity.io>
This commit is contained in:
Robert Hambrock
2022-11-09 15:41:32 +01:00
committed by GitHub
parent ad6630ed71
commit 53f9735058
13 changed files with 289 additions and 427 deletions
+2
View File
@@ -5667,6 +5667,7 @@ dependencies = [
name = "pallet-mmr-rpc"
version = "3.0.0"
dependencies = [
"anyhow",
"jsonrpsee",
"parity-scale-codec",
"serde",
@@ -9707,6 +9708,7 @@ dependencies = [
"sp-debug-derive",
"sp-runtime",
"sp-std",
"thiserror",
]
[[package]]
+7 -51
View File
@@ -2055,59 +2055,15 @@ impl_runtime_apis! {
mmr::Hash,
BlockNumber,
> for Runtime {
fn generate_proof(block_number: BlockNumber)
-> Result<(mmr::EncodableOpaqueLeaf, mmr::Proof<mmr::Hash>), mmr::Error>
{
Mmr::generate_batch_proof(vec![block_number]).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>)
-> Result<(), mmr::Error>
{
let leaf: mmr::Leaf = leaf
.into_opaque_leaf()
.try_decode()
.ok_or(mmr::Error::Verify)?;
Mmr::verify_leaves(vec![leaf], mmr::Proof::into_batch_proof(proof))
}
fn verify_proof_stateless(
root: mmr::Hash,
leaf: mmr::EncodableOpaqueLeaf,
proof: mmr::Proof<mmr::Hash>
) -> Result<(), mmr::Error> {
let node = mmr::DataOrHash::Data(leaf.into_opaque_leaf());
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(
fn generate_proof(
block_numbers: Vec<BlockNumber>,
) -> Result<(Vec<mmr::EncodableOpaqueLeaf>, mmr::BatchProof<mmr::Hash>), mmr::Error> {
Mmr::generate_batch_proof(block_numbers).map(|(leaves, proof)| {
(
leaves
.into_iter()
.map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf))
.collect(),
proof,
)
})
}
fn generate_historical_batch_proof(
block_numbers: Vec<BlockNumber>,
best_known_block_number: BlockNumber,
) -> Result<(Vec<mmr::EncodableOpaqueLeaf>, mmr::BatchProof<mmr::Hash>), mmr::Error> {
Mmr::generate_historical_batch_proof(block_numbers, best_known_block_number).map(
best_known_block_number: Option<BlockNumber>,
) -> Result<(Vec<mmr::EncodableOpaqueLeaf>, mmr::Proof<mmr::Hash>), mmr::Error> {
Mmr::generate_proof(block_numbers, best_known_block_number).map(
|(leaves, proof)| {
(
leaves
@@ -2120,7 +2076,7 @@ impl_runtime_apis! {
)
}
fn verify_batch_proof(leaves: Vec<mmr::EncodableOpaqueLeaf>, proof: mmr::BatchProof<mmr::Hash>)
fn verify_proof(leaves: Vec<mmr::EncodableOpaqueLeaf>, proof: mmr::Proof<mmr::Hash>)
-> Result<(), mmr::Error>
{
let leaves = leaves.into_iter().map(|leaf|
@@ -2130,10 +2086,10 @@ impl_runtime_apis! {
Mmr::verify_leaves(leaves, proof)
}
fn verify_batch_proof_stateless(
fn verify_proof_stateless(
root: mmr::Hash,
leaves: Vec<mmr::EncodableOpaqueLeaf>,
proof: mmr::BatchProof<mmr::Hash>
proof: mmr::Proof<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)
+11 -33
View File
@@ -43,7 +43,7 @@ use beefy_primitives::{
KEY_TYPE as BeefyKeyType,
};
use sc_network::{config::RequestResponseConfig, ProtocolName};
use sp_mmr_primitives::{BatchProof, EncodableOpaqueLeaf, Error as MmrError, MmrApi, Proof};
use sp_mmr_primitives::{EncodableOpaqueLeaf, Error as MmrError, MmrApi, Proof};
use sp_api::{ApiRef, ProvideRuntimeApi};
use sp_consensus::BlockOrigin;
@@ -246,47 +246,25 @@ macro_rules! create_test_api {
}
impl MmrApi<Block, MmrRootHash, NumberFor<Block>> for RuntimeApi {
fn generate_proof(_block_number: u64)
-> Result<(EncodableOpaqueLeaf, Proof<MmrRootHash>), MmrError> {
fn mmr_root() -> Result<MmrRootHash, MmrError> {
Ok($mmr_root)
}
fn generate_proof(
_block_numbers: Vec<u64>,
_best_known_block_number: Option<u64>
) -> Result<(Vec<EncodableOpaqueLeaf>, Proof<MmrRootHash>), MmrError> {
unimplemented!()
}
fn verify_proof(_leaf: EncodableOpaqueLeaf, _proof: Proof<MmrRootHash>)
-> Result<(), MmrError> {
fn verify_proof(_leaves: Vec<EncodableOpaqueLeaf>, _proof: Proof<MmrRootHash>) -> Result<(), MmrError> {
unimplemented!()
}
fn verify_proof_stateless(
_root: MmrRootHash,
_leaf: EncodableOpaqueLeaf,
_proof: Proof<MmrRootHash>
) -> Result<(), MmrError> {
unimplemented!()
}
fn mmr_root() -> Result<MmrRootHash, MmrError> {
Ok($mmr_root)
}
fn generate_batch_proof(_block_numbers: Vec<u64>) -> Result<(Vec<EncodableOpaqueLeaf>, BatchProof<MmrRootHash>), MmrError> {
unimplemented!()
}
fn generate_historical_batch_proof(
_block_numbers: Vec<u64>,
_best_known_block_number: u64
) -> 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>
_proof: Proof<MmrRootHash>
) -> Result<(), MmrError> {
unimplemented!()
}
@@ -29,7 +29,7 @@
//! Inner nodes are created by concatenating child hashes and hashing again. The implementation
//! does not perform any sorting of the input data (leaves) nor when inner nodes are created.
//!
//! If the number of leaves is not even, last leave (hash of) is promoted to the upper layer.
//! If the number of leaves is not even, last leaf (hash of) is promoted to the upper layer.
pub use sp_runtime::traits::Keccak256;
use sp_runtime::{app_crypto::sp_core, sp_std, traits::Hash as HashT};
@@ -21,6 +21,7 @@ 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", path = "../../../primitives/merkle-mountain-range" }
sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" }
anyhow = "1"
[dev-dependencies]
serde_json = "1.0.85"
@@ -22,7 +22,7 @@
use std::{marker::PhantomData, sync::Arc};
use codec::{Codec, Encode};
use codec::{Codec, Decode, Encode};
use jsonrpsee::{
core::{async_trait, RpcResult},
proc_macros::rpc,
@@ -33,58 +33,33 @@ use serde::{Deserialize, Serialize};
use sp_api::{NumberFor, ProvideRuntimeApi};
use sp_blockchain::HeaderBackend;
use sp_core::Bytes;
use sp_mmr_primitives::{BatchProof, Error as MmrError, Proof};
use sp_mmr_primitives::{Error as MmrError, Proof};
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
pub use sp_mmr_primitives::MmrApi as MmrRuntimeApi;
const RUNTIME_ERROR: i32 = 8000;
const MMR_ERROR: i32 = 8010;
const LEAF_NOT_FOUND_ERROR: i32 = MMR_ERROR + 1;
const GENERATE_PROOF_ERROR: i32 = MMR_ERROR + 2;
/// Retrieved MMR leaf and its proof.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct LeafProof<BlockHash> {
/// Block hash the proof was generated for.
pub block_hash: BlockHash,
/// SCALE-encoded leaf data.
pub leaf: Bytes,
/// SCALE-encoded proof data. See [sp_mmr_primitives::Proof].
pub proof: Bytes,
}
impl<BlockHash> LeafProof<BlockHash> {
/// Create new `LeafProof` from given concrete `leaf` and `proof`.
pub fn new<Leaf, MmrHash>(block_hash: BlockHash, leaf: Leaf, proof: Proof<MmrHash>) -> Self
where
Leaf: Encode,
MmrHash: Encode,
{
Self { block_hash, leaf: Bytes(leaf.encode()), proof: Bytes(proof.encode()) }
}
}
/// Retrieved MMR leaves and their proof.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct LeafBatchProof<BlockHash> {
pub struct LeavesProof<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].
/// SCALE-encoded proof data. See [sp_mmr_primitives::Proof].
pub proof: Bytes,
}
impl<BlockHash> LeafBatchProof<BlockHash> {
/// Create new `LeafBatchProof` from a given vector of `Leaf` and a
/// [sp_mmr_primitives::BatchProof].
impl<BlockHash> LeavesProof<BlockHash> {
/// Create new `LeavesProof` from a given vector of `Leaf` and a
/// [sp_mmr_primitives::Proof].
pub fn new<Leaf, MmrHash>(
block_hash: BlockHash,
leaves: Vec<Leaf>,
proof: BatchProof<MmrHash>,
proof: Proof<MmrHash>,
) -> Self
where
Leaf: Encode,
@@ -96,63 +71,59 @@ impl<BlockHash> LeafBatchProof<BlockHash> {
/// MMR RPC methods.
#[rpc(client, server)]
pub trait MmrApi<BlockHash, BlockNumber> {
/// Generate MMR proof for given block number.
///
/// This method calls into a runtime with MMR pallet included and attempts to generate
/// MMR proof for a block with a specified `block_number`.
/// Optionally, a block hash at which the runtime should be queried can be specified.
///
/// Returns the (full) leaf itself and a proof for this leaf (compact encoding, i.e. hash of
/// the leaf). Both parameters are SCALE-encoded.
#[method(name = "mmr_generateProof")]
fn generate_proof(
&self,
block_number: BlockNumber,
at: Option<BlockHash>,
) -> RpcResult<LeafProof<BlockHash>>;
pub trait MmrApi<BlockHash, BlockNumber, MmrHash> {
/// Get the MMR root hash for the current best block.
#[method(name = "mmr_root")]
fn mmr_root(&self, at: Option<BlockHash>) -> RpcResult<MmrHash>;
/// Generate MMR proof for the given block numbers.
/// Generate an MMR proof for the given `block_numbers`.
///
/// This method calls into a runtime with MMR pallet included and attempts to generate
/// MMR proof for a set of blocks with the specific `block_numbers`.
/// 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 `block_numbers` supplied
#[method(name = "mmr_generateBatchProof")]
fn generate_batch_proof(
&self,
block_numbers: Vec<BlockNumber>,
at: Option<BlockHash>,
) -> RpcResult<LeafBatchProof<BlockHash>>;
/// Generate a MMR proof for the given `block_numbers` given the `best_known_block_number`.
///
/// This method calls into a runtime with MMR pallet included and attempts to generate
/// a MMR proof for the set of blocks that have the given `block_numbers` with MMR given the
/// an MMR proof for the set of blocks that have the given `block_numbers` with the MMR root at
/// `best_known_block_number`. `best_known_block_number` must be larger than all the
/// `block_numbers` for the function to succeed.
///
/// Optionally, a block hash at which the runtime should be queried can be specified.
/// Note that specifying the block hash isn't super-useful here, unless you're generating
/// proof using non-finalized blocks where there are several competing forks. That's because
/// MMR state will be fixed to the state with `best_known_block_number`, which already points to
/// Optionally via `at`, a block hash at which the runtime should be queried can be specified.
/// Optionally via `best_known_block_number`, the proof can be generated using the MMR's state
/// at a specific best block. Note that if `best_known_block_number` is provided, then also
/// specifying the block hash via `at` isn't super-useful here, unless you're generating proof
/// using non-finalized blocks where there are several competing forks. That's because MMR state
/// will be fixed to the state with `best_known_block_number`, which already points to
/// some historical block.
///
/// Returns the leaves and a proof for these leaves (compact encoding, i.e. hash of
/// Returns the (full) 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 `block_numbers` supplied
#[method(name = "mmr_generateHistoricalBatchProof")]
fn generate_historical_batch_proof(
#[method(name = "mmr_generateProof")]
fn generate_proof(
&self,
block_numbers: Vec<BlockNumber>,
best_known_block_number: BlockNumber,
best_known_block_number: Option<BlockNumber>,
at: Option<BlockHash>,
) -> RpcResult<LeafBatchProof<BlockHash>>;
) -> RpcResult<LeavesProof<BlockHash>>;
/// Verify an MMR `proof`.
///
/// This method calls into a runtime with MMR pallet included and attempts to verify
/// an MMR proof.
///
/// Returns `true` if the proof is valid, else returns the verification error.
#[method(name = "mmr_verifyProof")]
fn verify_proof(&self, proof: LeavesProof<BlockHash>) -> RpcResult<bool>;
/// Verify an MMR `proof` statelessly given an `mmr_root`.
///
/// This method calls into a runtime with MMR pallet included and attempts to verify
/// an MMR proof against a provided MMR root.
///
/// Returns `true` if the proof is valid, else returns the verification error.
#[method(name = "mmr_verifyProofStateless")]
fn verify_proof_stateless(
&self,
mmr_root: MmrHash,
proof: LeavesProof<BlockHash>,
) -> RpcResult<bool>;
}
/// MMR RPC methods.
@@ -169,7 +140,7 @@ impl<C, B> Mmr<C, B> {
}
#[async_trait]
impl<Client, Block, MmrHash> MmrApiServer<<Block as BlockT>::Hash, NumberFor<Block>>
impl<Client, Block, MmrHash> MmrApiServer<<Block as BlockT>::Hash, NumberFor<Block>, MmrHash>
for Mmr<Client, (Block, MmrHash)>
where
Block: BlockT,
@@ -177,61 +148,31 @@ where
Client::Api: MmrRuntimeApi<Block, MmrHash, NumberFor<Block>>,
MmrHash: Codec + Send + Sync + 'static,
{
fn mmr_root(&self, at: Option<<Block as BlockT>::Hash>) -> RpcResult<MmrHash> {
let block_hash = at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash);
let api = self.client.runtime_api();
let mmr_root = api
.mmr_root(&BlockId::Hash(block_hash))
.map_err(runtime_error_into_rpc_error)?
.map_err(mmr_error_into_rpc_error)?;
Ok(mmr_root)
}
fn generate_proof(
&self,
block_number: NumberFor<Block>,
block_numbers: Vec<NumberFor<Block>>,
best_known_block_number: Option<NumberFor<Block>>,
at: Option<<Block as BlockT>::Hash>,
) -> RpcResult<LeafProof<Block::Hash>> {
) -> RpcResult<LeavesProof<<Block as BlockT>::Hash>> {
let api = self.client.runtime_api();
let block_hash = at.unwrap_or_else(|| self.client.info().best_hash);
let block_hash = at.unwrap_or_else(||
// If the block hash is not supplied assume the best block.
self.client.info().best_hash);
let (leaf, proof) = api
let (leaves, proof) = api
.generate_proof_with_context(
&BlockId::hash(block_hash),
sp_core::ExecutionContext::OffchainCall(None),
block_number,
)
.map_err(runtime_error_into_rpc_error)?
.map_err(mmr_error_into_rpc_error)?;
Ok(LeafProof::new(block_hash, leaf, proof))
}
fn generate_batch_proof(
&self,
block_numbers: Vec<NumberFor<Block>>,
at: Option<<Block as BlockT>::Hash>,
) -> RpcResult<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),
block_numbers,
)
.map_err(runtime_error_into_rpc_error)?
.map_err(mmr_error_into_rpc_error)?;
Ok(LeafBatchProof::new(block_hash, leaves, proof))
}
fn generate_historical_batch_proof(
&self,
block_numbers: Vec<NumberFor<Block>>,
best_known_block_number: NumberFor<Block>,
at: Option<<Block as BlockT>::Hash>,
) -> RpcResult<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_historical_batch_proof_with_context(
&BlockId::hash(block_hash),
sp_core::ExecutionContext::OffchainCall(None),
block_numbers,
@@ -240,26 +181,69 @@ where
.map_err(runtime_error_into_rpc_error)?
.map_err(mmr_error_into_rpc_error)?;
Ok(LeafBatchProof::new(block_hash, leaves, proof))
Ok(LeavesProof::new(block_hash, leaves, proof))
}
fn verify_proof(&self, proof: LeavesProof<<Block as BlockT>::Hash>) -> RpcResult<bool> {
let api = self.client.runtime_api();
let leaves = Decode::decode(&mut &proof.leaves.0[..])
.map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?;
let decoded_proof = Decode::decode(&mut &proof.proof.0[..])
.map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?;
api.verify_proof_with_context(
&BlockId::hash(proof.block_hash),
sp_core::ExecutionContext::OffchainCall(None),
leaves,
decoded_proof,
)
.map_err(runtime_error_into_rpc_error)?
.map_err(mmr_error_into_rpc_error)?;
Ok(true)
}
fn verify_proof_stateless(
&self,
mmr_root: MmrHash,
proof: LeavesProof<<Block as BlockT>::Hash>,
) -> RpcResult<bool> {
let api = self.client.runtime_api();
let leaves = Decode::decode(&mut &proof.leaves.0[..])
.map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?;
let decoded_proof = Decode::decode(&mut &proof.proof.0[..])
.map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?;
api.verify_proof_stateless(
&BlockId::hash(proof.block_hash),
mmr_root,
leaves,
decoded_proof,
)
.map_err(runtime_error_into_rpc_error)?
.map_err(mmr_error_into_rpc_error)?;
Ok(true)
}
}
/// Converts a mmr-specific error into a [`CallError`].
/// Converts an mmr-specific error into a [`CallError`].
fn mmr_error_into_rpc_error(err: MmrError) -> CallError {
let data = format!("{:?}", err);
match err {
MmrError::LeafNotFound => CallError::Custom(ErrorObject::owned(
LEAF_NOT_FOUND_ERROR,
"Leaf was not found",
Some(data),
)),
MmrError::GenerateProof => CallError::Custom(ErrorObject::owned(
GENERATE_PROOF_ERROR,
"Error while generating the proof",
Some(data),
)),
_ => CallError::Custom(ErrorObject::owned(MMR_ERROR, "Unexpected MMR error", Some(data))),
}
let error_code = MMR_ERROR +
match err {
MmrError::LeafNotFound => 1,
MmrError::GenerateProof => 2,
MmrError::Verify => 3,
MmrError::BlockNumToLeafIndex => 4,
MmrError::InvalidBestKnownBlock => 5,
_ => 0,
};
CallError::Custom(ErrorObject::owned(error_code, err.to_string(), Some(format!("{:?}", err))))
}
/// Converts a runtime trap into a [`CallError`].
@@ -281,34 +265,12 @@ mod tests {
// given
let leaf = vec![1_u8, 2, 3, 4];
let proof = Proof {
leaf_index: 1,
leaf_count: 9,
items: vec![H256::repeat_byte(1), H256::repeat_byte(2)],
};
let leaf_proof = LeafProof::new(H256::repeat_byte(0), leaf, proof);
// when
let actual = serde_json::to_string(&leaf_proof).unwrap();
// then
assert_eq!(
actual,
r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaf":"0x1001020304","proof":"0x010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"#
);
}
#[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);
let leaf_proof = LeavesProof::new(H256::repeat_byte(0), vec![leaf], proof);
// when
let actual = serde_json::to_string(&leaf_proof).unwrap();
@@ -321,40 +283,36 @@ mod tests {
}
#[test]
fn should_deserialize_leaf_proof() {
fn should_serialize_leaves_proof() {
// given
let expected = LeafProof {
block_hash: H256::repeat_byte(0),
leaf: Bytes(vec![1_u8, 2, 3, 4].encode()),
proof: Bytes(
Proof {
leaf_index: 1,
leaf_count: 9,
items: vec![H256::repeat_byte(1), H256::repeat_byte(2)],
}
.encode(),
),
let leaf_a = vec![1_u8, 2, 3, 4];
let leaf_b = vec![2_u8, 2, 3, 4];
let proof = Proof {
leaf_indices: vec![1, 2],
leaf_count: 9,
items: vec![H256::repeat_byte(1), H256::repeat_byte(2)],
};
let leaf_proof = LeavesProof::new(H256::repeat_byte(0), vec![leaf_a, leaf_b], proof);
// when
let actual: LeafProof<H256> = serde_json::from_str(r#"{
"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"leaf":"0x1001020304",
"proof":"0x010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"
}"#).unwrap();
let actual = serde_json::to_string(&leaf_proof).unwrap();
// then
assert_eq!(actual, expected);
assert_eq!(
actual,
r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaves":"0x0810010203041002020304","proof":"0x080100000000000000020000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"#
);
}
#[test]
fn should_deserialize_leaf_batch_proof() {
fn should_deserialize_leaf_proof() {
// given
let expected = LeafBatchProof {
let expected = LeavesProof {
block_hash: H256::repeat_byte(0),
leaves: Bytes(vec![vec![1_u8, 2, 3, 4]].encode()),
proof: Bytes(
BatchProof {
Proof {
leaf_indices: vec![1],
leaf_count: 9,
items: vec![H256::repeat_byte(1), H256::repeat_byte(2)],
@@ -364,7 +322,7 @@ mod tests {
};
// when
let actual: LeafBatchProof<H256> = serde_json::from_str(r#"{
let actual: LeavesProof<H256> = serde_json::from_str(r#"{
"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"leaves":"0x041001020304",
"proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"
@@ -373,4 +331,31 @@ mod tests {
// then
assert_eq!(actual, expected);
}
#[test]
fn should_deserialize_leaves_proof() {
// given
let expected = LeavesProof {
block_hash: H256::repeat_byte(0),
leaves: Bytes(vec![vec![1_u8, 2, 3, 4], vec![2_u8, 2, 3, 4]].encode()),
proof: Bytes(
Proof {
leaf_indices: vec![1, 2],
leaf_count: 9,
items: vec![H256::repeat_byte(1), H256::repeat_byte(2)],
}
.encode(),
),
};
// when
let actual: LeavesProof<H256> = serde_json::from_str(r#"{
"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"leaves":"0x0810010203041002020304",
"proof":"0x080100000000000000020000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"
}"#).unwrap();
// then
assert_eq!(actual, expected);
}
}
@@ -22,7 +22,7 @@
//! Details on Merkle Mountain Ranges (MMRs) can be found here:
//! <https://github.com/mimblewimble/grin/blob/master/doc/mmr.md>
//!
//! The MMR pallet constructs a MMR from leaf data obtained on every block from
//! The MMR pallet constructs an MMR from leaf data obtained on every block from
//! `LeafDataProvider`. MMR nodes are stored both in:
//! - on-chain storage - hashes only; not full leaf content)
//! - off-chain storage - via Indexing API we push full leaf content (and all internal nodes as
@@ -50,7 +50,7 @@
//!
//! Secondary use case is to archive historical data, but still be able to retrieve them on-demand
//! if needed. For instance if parent block hashes are stored in the MMR it's possible at any point
//! in time to provide a MMR proof about some past block hash, while this data can be safely pruned
//! in time to provide an MMR proof about some past block hash, while this data can be safely pruned
//! from on-chain storage.
//!
//! NOTE This pallet is experimental and not proven to work in production.
@@ -103,7 +103,7 @@ pub trait WeightInfo {
fn on_initialize(peaks: NodeIndex) -> Weight;
}
/// A MMR specific to the pallet.
/// An MMR specific to the pallet.
type ModuleMmr<StorageType, T, I> = mmr::Mmr<StorageType, T, I, LeafOf<T, I>>;
/// Leaf data.
@@ -287,15 +287,15 @@ pub mod pallet {
/// Stateless MMR proof verification for batch of leaves.
///
/// This function can be used to verify received MMR [primitives::BatchProof] (`proof`)
/// This function can be used to verify received MMR [primitives::Proof] (`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].
/// [primitives::Proof].
pub fn verify_leaves_proof<H, L>(
root: H::Output,
leaves: Vec<mmr::Node<H, L>>,
proof: primitives::BatchProof<H::Output>,
proof: primitives::Proof<H::Output>,
) -> Result<(), primitives::Error>
where
H: traits::Hash,
@@ -381,37 +381,23 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Ok(leaf_idx)
}
/// Generate a MMR proof for the given `block_numbers`.
/// Generate an MMR proof for the given `block_numbers`.
/// If `best_known_block_number = Some(n)`, this generates a historical proof for
/// the chain with head at height `n`.
/// Else it generates a proof for the MMR at the current block height.
///
/// 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_batch_proof(
pub fn generate_proof(
block_numbers: Vec<T::BlockNumber>,
) -> Result<
(Vec<LeafOf<T, I>>, primitives::BatchProof<<T as Config<I>>::Hash>),
primitives::Error,
> {
Self::generate_historical_batch_proof(
block_numbers,
<frame_system::Pallet<T>>::block_number(),
)
}
best_known_block_number: Option<T::BlockNumber>,
) -> Result<(Vec<LeafOf<T, I>>, primitives::Proof<<T as Config<I>>::Hash>), primitives::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());
/// Generate a MMR proof for the given `block_numbers` given the `best_known_block_number`.
///
/// 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_historical_batch_proof(
block_numbers: Vec<T::BlockNumber>,
best_known_block_number: T::BlockNumber,
) -> Result<
(Vec<LeafOf<T, I>>, primitives::BatchProof<<T as Config<I>>::Hash>),
primitives::Error,
> {
let leaves_count =
Self::block_num_to_leaf_index(best_known_block_number)?.saturating_add(1);
@@ -424,7 +410,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
.collect::<Result<Vec<LeafIndex>, _>>()?;
let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(leaves_count);
mmr.generate_batch_proof(leaf_indices)
mmr.generate_proof(leaf_indices)
}
/// Return the on-chain MMR root hash.
@@ -440,7 +426,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// or the proof is invalid.
pub fn verify_leaves(
leaves: Vec<LeafOf<T, I>>,
proof: primitives::BatchProof<<T as Config<I>>::Hash>,
proof: primitives::Proof<<T as Config<I>>::Hash>,
) -> Result<(), primitives::Error> {
if proof.leaf_count > Self::mmr_leaves() ||
proof.leaf_count == 0 ||
@@ -29,11 +29,11 @@ use sp_std::prelude::*;
/// 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]
/// [primitives::Proof]
pub fn verify_leaves_proof<H, L>(
root: H::Output,
leaves: Vec<Node<H, L>>,
proof: primitives::BatchProof<H::Output>,
proof: primitives::Proof<H::Output>,
) -> Result<bool, Error>
where
H: sp_runtime::traits::Hash,
@@ -60,7 +60,7 @@ where
.map_err(|e| Error::Verify.log_debug(e))
}
/// A wrapper around a MMR library to expose limited functionality.
/// A wrapper around an MMR library to expose limited functionality.
///
/// Available functions depend on the storage kind ([Runtime](crate::mmr::storage::RuntimeStorage)
/// vs [Off-chain](crate::mmr::storage::OffchainStorage)).
@@ -91,11 +91,11 @@ where
/// 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]
/// [primitives::Proof]
pub fn verify_leaves_proof(
&self,
leaves: Vec<L>,
proof: primitives::BatchProof<<T as Config<I>>::Hash>,
proof: primitives::Proof<<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(),
@@ -163,10 +163,10 @@ where
///
/// 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_batch_proof(
pub fn generate_proof(
&self,
leaf_indices: Vec<NodeIndex>,
) -> Result<(Vec<L>, primitives::BatchProof<<T as Config<I>>::Hash>), Error> {
) -> Result<(Vec<L>, primitives::Proof<<T as Config<I>>::Hash>), Error> {
let positions = leaf_indices
.iter()
.map(|index| mmr_lib::leaf_index_to_pos(*index))
@@ -184,7 +184,7 @@ where
self.mmr
.gen_proof(positions)
.map_err(|e| Error::GenerateProof.log_error(e))
.map(|p| primitives::BatchProof {
.map(|p| primitives::Proof {
leaf_indices,
leaf_count,
items: p.proof_items().iter().map(|x| x.hash()).collect(),
@@ -15,7 +15,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! A MMR storage implementations.
//! An MMR storage implementation.
use codec::Encode;
use frame_support::log::{debug, error, trace};
@@ -27,7 +27,7 @@ use sp_core::{
offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt},
H256,
};
use sp_mmr_primitives::{BatchProof, Compact};
use sp_mmr_primitives::{Compact, Proof};
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
@@ -236,18 +236,18 @@ fn should_generate_proofs_correctly() {
// when generate proofs for all leaves.
let proofs = (1_u64..=best_block_number)
.into_iter()
.map(|block_num| crate::Pallet::<Test>::generate_batch_proof(vec![block_num]).unwrap())
.map(|block_num| crate::Pallet::<Test>::generate_proof(vec![block_num], None).unwrap())
.collect::<Vec<_>>();
// when generate historical proofs for all leaves
let historical_proofs = (1_u64..best_block_number)
.into_iter()
.map(|block_num| {
let mut proofs = vec![];
for leaves_count in block_num..=num_blocks {
for historical_best_block in block_num..=num_blocks {
proofs.push(
crate::Pallet::<Test>::generate_historical_batch_proof(
crate::Pallet::<Test>::generate_proof(
vec![block_num],
leaves_count,
Some(historical_best_block),
)
.unwrap(),
)
@@ -261,7 +261,7 @@ fn should_generate_proofs_correctly() {
proofs[0],
(
vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))],
BatchProof {
Proof {
leaf_indices: vec![0],
leaf_count: 7,
items: vec![
@@ -276,7 +276,7 @@ fn should_generate_proofs_correctly() {
historical_proofs[0][0],
(
vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))],
BatchProof { leaf_indices: vec![0], leaf_count: 1, items: vec![] }
Proof { leaf_indices: vec![0], leaf_count: 1, items: vec![] }
)
);
@@ -292,7 +292,7 @@ fn should_generate_proofs_correctly() {
proofs[2],
(
vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))],
BatchProof {
Proof {
leaf_indices: vec![2],
leaf_count: 7,
items: vec![
@@ -312,7 +312,7 @@ fn should_generate_proofs_correctly() {
historical_proofs[2][0],
(
vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))],
BatchProof {
Proof {
leaf_indices: vec![2],
leaf_count: 3,
items: vec![hex(
@@ -332,7 +332,7 @@ fn should_generate_proofs_correctly() {
historical_proofs[2][2],
(
vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))],
BatchProof {
Proof {
leaf_indices: vec![2],
leaf_count: 5,
items: vec![
@@ -350,7 +350,7 @@ fn should_generate_proofs_correctly() {
(
// NOTE: the leaf index is equivalent to the block number(in this case 5) - 1
vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))],
BatchProof {
Proof {
leaf_indices: vec![4],
leaf_count: 7,
items: vec![
@@ -365,7 +365,7 @@ fn should_generate_proofs_correctly() {
historical_proofs[4][0],
(
vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))],
BatchProof {
Proof {
leaf_indices: vec![4],
leaf_count: 5,
items: vec![hex(
@@ -380,7 +380,7 @@ fn should_generate_proofs_correctly() {
proofs[6],
(
vec![Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),))],
BatchProof {
Proof {
leaf_indices: vec![6],
leaf_count: 7,
items: vec![
@@ -407,11 +407,11 @@ fn should_generate_batch_proof_correctly() {
register_offchain_ext(&mut ext);
ext.execute_with(|| {
// when generate proofs for a batch of leaves
let (.., proof) = crate::Pallet::<Test>::generate_batch_proof(vec![1, 5, 6]).unwrap();
let (.., proof) = crate::Pallet::<Test>::generate_proof(vec![1, 5, 6], None).unwrap();
// then
assert_eq!(
proof,
BatchProof {
Proof {
// the leaf indices are equivalent to the above specified block numbers - 1.
leaf_indices: vec![0, 4, 5],
leaf_count: 7,
@@ -425,11 +425,11 @@ fn should_generate_batch_proof_correctly() {
// when generate historical proofs for a batch of leaves
let (.., historical_proof) =
crate::Pallet::<Test>::generate_historical_batch_proof(vec![1, 5, 6], 6).unwrap();
crate::Pallet::<Test>::generate_proof(vec![1, 5, 6], Some(6)).unwrap();
// then
assert_eq!(
historical_proof,
BatchProof {
Proof {
leaf_indices: vec![0, 4, 5],
leaf_count: 6,
items: vec![
@@ -441,7 +441,7 @@ fn should_generate_batch_proof_correctly() {
// when generate historical proofs for a batch of leaves
let (.., historical_proof) =
crate::Pallet::<Test>::generate_historical_batch_proof(vec![1, 5, 6], 7).unwrap();
crate::Pallet::<Test>::generate_proof(vec![1, 5, 6], None).unwrap();
// then
assert_eq!(historical_proof, proof);
});
@@ -462,15 +462,15 @@ fn should_verify() {
register_offchain_ext(&mut ext);
let (leaves, proof5) = ext.execute_with(|| {
// when
crate::Pallet::<Test>::generate_batch_proof(vec![5]).unwrap()
crate::Pallet::<Test>::generate_proof(vec![5], None).unwrap()
});
let (simple_historical_leaves, simple_historical_proof5) = ext.execute_with(|| {
// when
crate::Pallet::<Test>::generate_historical_batch_proof(vec![5], 6).unwrap()
crate::Pallet::<Test>::generate_proof(vec![5], Some(6)).unwrap()
});
let (advanced_historical_leaves, advanced_historical_proof5) = ext.execute_with(|| {
// when
crate::Pallet::<Test>::generate_historical_batch_proof(vec![5], 7).unwrap()
crate::Pallet::<Test>::generate_proof(vec![5], Some(7)).unwrap()
});
ext.execute_with(|| {
@@ -502,22 +502,18 @@ fn should_verify_batch_proofs() {
blocks_to_add: usize,
) {
let (leaves, proof) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_batch_proof(block_numbers.to_vec()).unwrap()
crate::Pallet::<Test>::generate_proof(block_numbers.to_vec(), None).unwrap()
});
let mmr_size = ext.execute_with(|| crate::Pallet::<Test>::mmr_leaves());
let min_mmr_size = block_numbers.iter().max().unwrap() + 1;
let max_block_number = ext.execute_with(|| frame_system::Pallet::<Test>::block_number());
let min_block_number = block_numbers.iter().max().unwrap();
// generate historical proofs for all possible mmr sizes,
// lower bound being index of highest leaf to be proven
let historical_proofs = (min_mmr_size..=mmr_size)
.map(|mmr_size| {
// 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_historical_batch_proof(
block_numbers.to_vec(),
mmr_size,
)
.unwrap()
crate::Pallet::<Test>::generate_proof(block_numbers.to_vec(), Some(best_block))
.unwrap()
})
})
.collect::<Vec<_>>();
@@ -602,11 +598,11 @@ fn verification_should_be_stateless() {
register_offchain_ext(&mut ext);
let (leaves, proof5) = ext.execute_with(|| {
// when
crate::Pallet::<Test>::generate_batch_proof(vec![5]).unwrap()
crate::Pallet::<Test>::generate_proof(vec![5], None).unwrap()
});
let (_, historical_proof5) = ext.execute_with(|| {
// when
crate::Pallet::<Test>::generate_historical_batch_proof(vec![5], 6).unwrap()
crate::Pallet::<Test>::generate_proof(vec![5], Some(6)).unwrap()
});
// Verify proof without relying on any on-chain data.
@@ -650,11 +646,11 @@ fn should_verify_batch_proof_statelessly() {
register_offchain_ext(&mut ext);
let (leaves, proof) = ext.execute_with(|| {
// when
crate::Pallet::<Test>::generate_batch_proof(vec![1, 4, 5]).unwrap()
crate::Pallet::<Test>::generate_proof(vec![1, 4, 5], None).unwrap()
});
let (historical_leaves, historical_proof) = ext.execute_with(|| {
// when
crate::Pallet::<Test>::generate_historical_batch_proof(vec![1, 4, 5], 6).unwrap()
crate::Pallet::<Test>::generate_proof(vec![1, 4, 5], Some(6)).unwrap()
});
// Verify proof without relying on any on-chain data.
@@ -694,7 +690,7 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() {
ext.execute_with(|| {
// when
let (leaves, proof5) = crate::Pallet::<Test>::generate_batch_proof(vec![5]).unwrap();
let (leaves, proof5) = crate::Pallet::<Test>::generate_proof(vec![5], None).unwrap();
new_block();
// then
@@ -928,7 +924,7 @@ fn should_verify_canonicalized() {
// Generate proofs for some blocks.
let (leaves, proofs) =
ext.execute_with(|| crate::Pallet::<Test>::generate_batch_proof(vec![1, 4, 5, 7]).unwrap());
ext.execute_with(|| crate::Pallet::<Test>::generate_proof(vec![1, 4, 5, 7], None).unwrap());
// Verify all previously generated proofs.
ext.execute_with(|| {
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proofs), Ok(()));
@@ -936,7 +932,7 @@ fn should_verify_canonicalized() {
// Generate proofs for some new blocks.
let (leaves, proofs) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_batch_proof(vec![block_hash_size + 7]).unwrap()
crate::Pallet::<Test>::generate_proof(vec![block_hash_size + 7], None).unwrap()
});
// Add some more blocks then verify all previously generated proofs.
ext.execute_with(|| {
@@ -960,19 +956,19 @@ fn does_not_panic_when_generating_historical_proofs() {
ext.execute_with(|| {
// when leaf index is invalid
assert_eq!(
crate::Pallet::<Test>::generate_historical_batch_proof(vec![10], 7),
crate::Pallet::<Test>::generate_proof(vec![10], None),
Err(Error::BlockNumToLeafIndex),
);
// when leaves count is invalid
assert_eq!(
crate::Pallet::<Test>::generate_historical_batch_proof(vec![3], 100),
crate::Pallet::<Test>::generate_proof(vec![3], Some(100)),
Err(Error::BlockNumToLeafIndex),
);
// when both leaf index and leaves count are invalid
assert_eq!(
crate::Pallet::<Test>::generate_historical_batch_proof(vec![10], 100),
crate::Pallet::<Test>::generate_proof(vec![10], Some(100)),
Err(Error::BlockNumToLeafIndex),
);
});
+1 -1
View File
@@ -64,7 +64,7 @@ pub struct MmrLeaf<BlockNumber, Hash, MerkleRoot, ExtraData> {
pub leaf_extra: ExtraData,
}
/// A MMR leaf versioning scheme.
/// An MMR leaf versioning scheme.
///
/// Version is a single byte that constist of two components:
/// - `major` - 3 bits
@@ -21,6 +21,7 @@ sp-core = { version = "6.0.0", default-features = false, path = "../core" }
sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" }
sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" }
sp-std = { version = "4.0.0", default-features = false, path = "../std" }
thiserror = "1.0"
[dev-dependencies]
array-bytes = "4.1"
@@ -23,9 +23,9 @@
use scale_info::TypeInfo;
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;
@@ -69,17 +69,6 @@ impl<Hash> OnNewRoot<Hash> for () {
fn on_new_root(_root: &Hash) {}
}
/// A MMR proof data for one of the leaves.
#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)]
pub struct Proof<Hash> {
/// The index of the leaf the proof is for.
pub leaf_index: 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>,
}
/// A full leaf content stored in the offchain-db.
pub trait FullLeaf: Clone + PartialEq + fmt::Debug {
/// Encode the leaf either in its full or compact form.
@@ -352,9 +341,9 @@ 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.
/// An MMR proof data for a group of leaves.
#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)]
pub struct BatchProof<Hash> {
pub struct Proof<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.
@@ -363,49 +352,39 @@ pub struct BatchProof<Hash> {
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.
#[cfg_attr(feature = "std", derive(thiserror::Error))]
#[derive(RuntimeDebug, codec::Encode, codec::Decode, PartialEq, Eq)]
pub enum Error {
/// Error during translation of a block number into a leaf index.
#[cfg_attr(feature = "std", error("Error translation block number into leaf index"))]
BlockNumToLeafIndex,
/// Error while pushing new node.
#[cfg_attr(feature = "std", error("Error pushing new node"))]
Push,
/// Error getting the new root.
#[cfg_attr(feature = "std", error("Error getting new root"))]
GetRoot,
/// Error commiting changes.
/// Error committing changes.
#[cfg_attr(feature = "std", error("Error committing changes"))]
Commit,
/// Error during proof generation.
#[cfg_attr(feature = "std", error("Error generating proof"))]
GenerateProof,
/// Proof verification error.
#[cfg_attr(feature = "std", error("Invalid proof"))]
Verify,
/// Leaf not found in the storage.
#[cfg_attr(feature = "std", error("Leaf was not found"))]
LeafNotFound,
/// Mmr Pallet not included in runtime
#[cfg_attr(feature = "std", error("MMR pallet not included in runtime"))]
PalletNotIncluded,
/// Cannot find the requested leaf index
#[cfg_attr(feature = "std", error("Requested leaf index invalid"))]
InvalidLeafIndex,
/// The provided best know block number is invalid.
#[cfg_attr(feature = "std", error("Provided best known block number invalid"))]
InvalidBestKnownBlock,
}
@@ -437,53 +416,31 @@ impl Error {
sp_api::decl_runtime_apis! {
/// API to interact with MMR pallet.
pub trait MmrApi<Hash: codec::Codec, BlockNumber: codec::Codec> {
/// Generate MMR proof for a block with a specified `block_number`.
fn generate_proof(block_number: BlockNumber) -> Result<(EncodableOpaqueLeaf, Proof<Hash>), Error>;
/// Verify MMR proof against on-chain MMR.
///
/// Note this function will use on-chain MMR root hash and check if the proof
/// matches the hash.
/// See [Self::verify_proof_stateless] for a stateless verifier.
fn verify_proof(leaf: EncodableOpaqueLeaf, proof: Proof<Hash>) -> Result<(), Error>;
/// Verify MMR proof against given root hash.
///
/// Note this function does not require any on-chain storage - the
/// proof is verified against given MMR root hash.
///
/// The leaf data is expected to be encoded in its compact form.
fn verify_proof_stateless(root: Hash, leaf: EncodableOpaqueLeaf, proof: Proof<Hash>)
-> Result<(), Error>;
/// Return the on-chain MMR root hash.
fn mmr_root() -> Result<Hash, Error>;
/// Generate MMR proof for a series of blocks with the specified block numbers.
fn generate_batch_proof(block_numbers: Vec<BlockNumber>) -> Result<(Vec<EncodableOpaqueLeaf>, BatchProof<Hash>), Error>;
/// Generate MMR proof for a series of `block_numbers`, given the `best_known_block_number`.
fn generate_historical_batch_proof(
/// Generate MMR proof for a series of block numbers. If `best_known_block_number = Some(n)`,
/// use historical MMR state at given block height `n`. Else, use current MMR state.
fn generate_proof(
block_numbers: Vec<BlockNumber>,
best_known_block_number: BlockNumber
) -> Result<(Vec<EncodableOpaqueLeaf>, BatchProof<Hash>), Error>;
best_known_block_number: Option<BlockNumber>
) -> Result<(Vec<EncodableOpaqueLeaf>, Proof<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 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>;
/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [Proof]
fn verify_proof(leaves: Vec<EncodableOpaqueLeaf>, proof: Proof<Hash>) -> Result<(), Error>;
/// Verify MMR proof against given root hash or a batch of leaves.
/// Verify MMR proof against given root hash for 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>)
/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [Proof]
fn verify_proof_stateless(root: Hash, leaves: Vec<EncodableOpaqueLeaf>, proof: Proof<Hash>)
-> Result<(), Error>;
}
}
@@ -508,7 +465,7 @@ mod tests {
fn should_encode_decode_proof() {
// given
let proof: TestProof = Proof {
leaf_index: 5,
leaf_indices: vec![5],
leaf_count: 10,
items: vec![
hex("c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd"),