mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-15 19:41:05 +00:00
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:
Generated
+2
@@ -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]]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user