mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 01:11:04 +00:00
Merkle Mountain Range pallet (#7312)
* Add MMR pallet. * WiP * Working on testing. * WiP - test * Tests passing. * Add proof generation. * Generate and verify proofs. * Allow verification of older proofs. * Move stuff to a module. * Split MMR stuff to it's own module. * Add docs. * Make parent hash optional. * LeafData failed approach. * Finally implement Compact stuff. * Compact encoding WiP * Implement remaining pieces. * Fix tests * Add docs to compact. * Implement for tuples. * Fix documentation. * Fix warnings and address review suggestion. * Update frame/merkle-mountain-range/src/primitives.rs Co-authored-by: cheme <emericchevalier.pro@gmail.com> * Address review grumbles. * Removing missing crate. * Fix test. * Add some docs and test. * Add multiple instances. * Cargo.toml sync. * Fix no_std compilation. * More no_std stuff. * Rename MMR struct. * Addressing other grumbles. * Fix test. * Remove format for no_std compat. * Add test for MMR pallet. * Fix std feature. * Update versions. * Add to node/runtime. * Add hook to insert digest. * Make primitives public. * Update lib.rs tech spec/typos etc * Use WeightInfo and benchmarks. * Fix test. * Fix benchmarks. * Trait -> Config. * Fix typo. * Fix tests. Co-authored-by: cheme <emericchevalier.pro@gmail.com> Co-authored-by: Addie Wagenknecht <addie@nortd.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
[package]
|
||||
name = "pallet-mmr"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.dev"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME Merkle Mountain Range pallet."
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false }
|
||||
frame-benchmarking = { version = "2.0.0", default-features = false, path = "../benchmarking", optional = true }
|
||||
frame-support = { version = "2.0.0", default-features = false, path = "../support" }
|
||||
frame-system = { version = "2.0.0", default-features = false, path = "../system" }
|
||||
mmr-lib = { package = "ckb-merkle-mountain-range", default-features = false, version = "0.3.1" }
|
||||
serde = { version = "1.0.101", optional = true }
|
||||
sp-core = { version = "2.0.0", default-features = false, path = "../../primitives/core" }
|
||||
sp-io = { version = "2.0.0", default-features = false, path = "../../primitives/io" }
|
||||
sp-runtime = { version = "2.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.5"
|
||||
hex-literal = "0.3"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"mmr-lib/std",
|
||||
"serde",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
runtime-benchmarks = ["frame-benchmarking"]
|
||||
@@ -0,0 +1,56 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the MMR pallet.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use crate::*;
|
||||
use frame_support::traits::OnInitialize;
|
||||
use frame_benchmarking::benchmarks;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
benchmarks! {
|
||||
_ { }
|
||||
|
||||
on_initialize {
|
||||
let x in 1 .. 1_000;
|
||||
|
||||
let leaves = x as u64;
|
||||
}: {
|
||||
for b in 0..leaves {
|
||||
Module::<T>::on_initialize((b as u32).into());
|
||||
}
|
||||
} verify {
|
||||
assert_eq!(crate::NumberOfLeaves::<DefaultInstance>::get(), leaves);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::*;
|
||||
use crate::tests::new_test_ext;
|
||||
use frame_support::assert_ok;
|
||||
|
||||
#[test]
|
||||
fn test_benchmarks() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(test_benchmark_on_initialize::<Test>());
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Default weights for the MMR Pallet
|
||||
//! This file was not auto-generated.
|
||||
|
||||
use frame_support::weights::{
|
||||
Weight, constants::{WEIGHT_PER_NANOS, RocksDbWeight as DbWeight},
|
||||
};
|
||||
|
||||
impl crate::WeightInfo for () {
|
||||
fn on_initialize(peaks: u64) -> Weight {
|
||||
// Reading the parent hash.
|
||||
let leaf_weight = DbWeight::get().reads(1);
|
||||
// Blake2 hash cost.
|
||||
let hash_weight = 2 * WEIGHT_PER_NANOS;
|
||||
// No-op hook.
|
||||
let hook_weight = 0;
|
||||
|
||||
leaf_weight
|
||||
.saturating_add(hash_weight)
|
||||
.saturating_add(hook_weight)
|
||||
.saturating_add(DbWeight::get().reads_writes(
|
||||
2 + peaks,
|
||||
2 + peaks,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Merkle Mountain Range
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! 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
|
||||
//! `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
|
||||
//! well) to the Off-chain DB, so that the data is available for Off-chain workers.
|
||||
//! Hashing used for MMR is configurable independently from the rest of the runtime (i.e. not using
|
||||
//! `frame_system::Hashing`) so something compatible with external chains can be used (like
|
||||
//! Keccak256 for Ethereum compatibility).
|
||||
//!
|
||||
//! Depending on the usage context (off-chain vs on-chain) the pallet is able to:
|
||||
//! - verify MMR leaf proofs (on-chain)
|
||||
//! - generate leaf proofs (off-chain)
|
||||
//!
|
||||
//! See [primitives::Compact] documentation for how you can optimize proof size for leafs that are
|
||||
//! composed from multiple elements.
|
||||
//!
|
||||
//! ## What for?
|
||||
//!
|
||||
//! Primary use case for this pallet is to generate MMR root hashes, that can latter on be used by
|
||||
//! BEEFY protocol (see https://github.com/paritytech/grandpa-bridge-gadget).
|
||||
//! MMR root hashes along with BEEFY will make it possible to build Super Light Clients (SLC) of
|
||||
//! Substrate-based chains. The SLC will be able to follow finality and can be shown proofs of more
|
||||
//! details that happened on the source chain.
|
||||
//! In that case the chain which contains the pallet generates the Root Hashes and Proofs, which
|
||||
//! are then presented to another chain acting as a light client which can verify them.
|
||||
//!
|
||||
//! 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
|
||||
//! from on-chain storage.
|
||||
//!
|
||||
//! NOTE This pallet is experimental and not proven to work in production.
|
||||
//!
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::Encode;
|
||||
use frame_support::{
|
||||
decl_module, decl_storage,
|
||||
weights::Weight,
|
||||
};
|
||||
use sp_runtime::traits;
|
||||
|
||||
mod default_weights;
|
||||
mod mmr;
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
mod benchmarking;
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod primitives;
|
||||
|
||||
pub trait WeightInfo {
|
||||
fn on_initialize(peaks: u64) -> Weight;
|
||||
}
|
||||
|
||||
/// This pallet's configuration trait
|
||||
pub trait Config<I = DefaultInstance>: frame_system::Config {
|
||||
/// Prefix for elements stored in the Off-chain DB via Indexing API.
|
||||
///
|
||||
/// Each node of the MMR is inserted both on-chain and off-chain via Indexing API.
|
||||
/// The former does not store full leaf content, just it's compact version (hash),
|
||||
/// and some of the inner mmr nodes might be pruned from on-chain storage.
|
||||
/// The later will contain all the entries in their full form.
|
||||
///
|
||||
/// Each node is stored in the Off-chain DB under key derived from the [INDEXING_PREFIX] and
|
||||
/// it's in-tree index (MMR position).
|
||||
const INDEXING_PREFIX: &'static [u8];
|
||||
|
||||
/// A hasher type for MMR.
|
||||
///
|
||||
/// To construct trie nodes that result in merging (bagging) two peaks, depending on the node
|
||||
/// kind we take either:
|
||||
/// - The node (hash) itself if it's an inner node.
|
||||
/// - The hash of SCALE-encoding of the leaf data if it's a leaf node.
|
||||
///
|
||||
/// Then we create a tuple of these two hashes, SCALE-encode it (concatenate) and
|
||||
/// hash, to obtain a new MMR inner node - the new peak.
|
||||
type Hashing: traits::Hash<Output = <Self as Config<I>>::Hash>;
|
||||
|
||||
/// The hashing output type.
|
||||
///
|
||||
/// This type is actually going to be stored in the MMR.
|
||||
/// Required to be provided again, to satisfy trait bounds for storage items.
|
||||
type Hash: traits::Member + traits::MaybeSerializeDeserialize + sp_std::fmt::Debug
|
||||
+ sp_std::hash::Hash + AsRef<[u8]> + AsMut<[u8]> + Copy + Default + codec::Codec
|
||||
+ codec::EncodeLike;
|
||||
|
||||
/// Data stored in the leaf nodes.
|
||||
///
|
||||
/// The [LeafData](primitives::LeafDataProvider) is responsible for returning the entire leaf
|
||||
/// data that will be inserted to the MMR.
|
||||
/// [LeafDataProvider](primitives::LeafDataProvider)s can be composed into tuples to put
|
||||
/// multiple elements into the tree. In such a case it might be worth using [primitives::Compact]
|
||||
/// to make MMR proof for one element of the tuple leaner.
|
||||
type LeafData: primitives::LeafDataProvider;
|
||||
|
||||
/// A hook to act on the new MMR root.
|
||||
///
|
||||
/// For some applications it might be beneficial to make the MMR root available externally
|
||||
/// apart from having it in the storage. For instance you might output it in the header digest
|
||||
/// (see [frame_system::Module::deposit_log]) to make it available for Light Clients.
|
||||
/// Hook complexity should be `O(1)`.
|
||||
type OnNewRoot: primitives::OnNewRoot<<Self as Config<I>>::Hash>;
|
||||
|
||||
/// Weights for this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Config<I>, I: Instance = DefaultInstance> as MerkleMountainRange {
|
||||
/// Latest MMR Root hash.
|
||||
pub RootHash get(fn mmr_root_hash): <T as Config<I>>::Hash;
|
||||
|
||||
/// Current size of the MMR (number of leaves).
|
||||
pub NumberOfLeaves get(fn mmr_leaves): u64;
|
||||
|
||||
/// Hashes of the nodes in the MMR.
|
||||
///
|
||||
/// Note this collection only contains MMR peaks, the inner nodes (and leaves)
|
||||
/// are pruned and only stored in the Offchain DB.
|
||||
pub Nodes get(fn mmr_peak): map hasher(identity) u64 => Option<<T as Config<I>>::Hash>;
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
/// A public part of the pallet.
|
||||
pub struct Module<T: Config<I>, I: Instance = DefaultInstance> for enum Call where origin: T::Origin {
|
||||
fn on_initialize(n: T::BlockNumber) -> Weight {
|
||||
use primitives::LeafDataProvider;
|
||||
let leaves = Self::mmr_leaves();
|
||||
let peaks_before = mmr::utils::NodesUtils::new(leaves).number_of_peaks();
|
||||
let data = T::LeafData::leaf_data();
|
||||
// append new leaf to MMR
|
||||
let mut mmr: ModuleMmr<mmr::storage::RuntimeStorage, T, I> = mmr::Mmr::new(leaves);
|
||||
mmr.push(data).expect("MMR push never fails.");
|
||||
|
||||
// update the size
|
||||
let (leaves, root) = mmr.finalize().expect("MMR finalize never fails.");
|
||||
<T::OnNewRoot as primitives::OnNewRoot<_>>::on_new_root(&root);
|
||||
|
||||
<NumberOfLeaves>::put(leaves);
|
||||
<RootHash<T, I>>::put(root);
|
||||
|
||||
let peaks_after = mmr::utils::NodesUtils::new(leaves).number_of_peaks();
|
||||
T::WeightInfo::on_initialize(peaks_before.max(peaks_after))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A MMR specific to the pallet.
|
||||
type ModuleMmr<StorageType, T, I> = mmr::Mmr<StorageType, T, I, LeafOf<T, I>>;
|
||||
|
||||
/// Leaf data.
|
||||
type LeafOf<T, I> = <<T as Config<I>>::LeafData as primitives::LeafDataProvider>::LeafData;
|
||||
|
||||
/// Hashing used for the pallet.
|
||||
pub(crate) type HashingOf<T, I> = <T as Config<I>>::Hashing;
|
||||
|
||||
impl<T: Config<I>, I: Instance> Module<T, I> {
|
||||
fn offchain_key(pos: u64) -> sp_std::prelude::Vec<u8> {
|
||||
(T::INDEXING_PREFIX, pos).encode()
|
||||
}
|
||||
|
||||
/// Generate a MMR proof for the given `leaf_index`.
|
||||
///
|
||||
/// Note this method can only be used from an off-chain context
|
||||
/// (Offchain Worker or Runtime API call), since it requires
|
||||
/// all the leaves to be present.
|
||||
/// It may return an error or panic if used incorrectly.
|
||||
pub fn generate_proof(leaf_index: u64) -> Result<
|
||||
(LeafOf<T, I>, primitives::Proof<<T as Config<I>>::Hash>),
|
||||
mmr::Error,
|
||||
> {
|
||||
let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(Self::mmr_leaves());
|
||||
mmr.generate_proof(leaf_index)
|
||||
}
|
||||
|
||||
/// Verify MMR proof for given `leaf`.
|
||||
///
|
||||
/// This method is safe to use within the runtime code.
|
||||
/// It will return `Ok(())` if the proof is valid
|
||||
/// and an `Err(..)` if MMR is inconsistent (some leaves are missing)
|
||||
/// or the proof is invalid.
|
||||
pub fn verify_leaf(
|
||||
leaf: LeafOf<T, I>,
|
||||
proof: primitives::Proof<<T as Config<I>>::Hash>,
|
||||
) -> Result<(), mmr::Error> {
|
||||
if proof.leaf_count > Self::mmr_leaves()
|
||||
|| proof.leaf_count == 0
|
||||
|| proof.items.len() as u32 > mmr::utils::NodesUtils::new(proof.leaf_count).depth()
|
||||
{
|
||||
return Err(mmr::Error::Verify.log_debug(
|
||||
"The proof has incorrect number of leaves or proof items."
|
||||
));
|
||||
}
|
||||
|
||||
let mmr: ModuleMmr<mmr::storage::RuntimeStorage, T, I> = mmr::Mmr::new(proof.leaf_count);
|
||||
let is_valid = mmr.verify_leaf_proof(leaf, proof)?;
|
||||
if is_valid {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(mmr::Error::Verify.log_debug("The proof is incorrect."))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{
|
||||
Config, HashingOf, Instance,
|
||||
mmr::{
|
||||
Node, NodeOf, Hasher,
|
||||
storage::{Storage, OffchainStorage, RuntimeStorage},
|
||||
utils::NodesUtils,
|
||||
},
|
||||
primitives,
|
||||
};
|
||||
use frame_support::{debug, RuntimeDebug};
|
||||
use sp_std::fmt;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use sp_std::{vec, prelude::Vec};
|
||||
|
||||
/// A wrapper around a 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)).
|
||||
pub struct Mmr<StorageType, T, I, L> where
|
||||
T: Config<I>,
|
||||
I: Instance,
|
||||
L: primitives::FullLeaf,
|
||||
Storage<StorageType, T, I, L>: mmr_lib::MMRStore<NodeOf<T, I, L>>,
|
||||
{
|
||||
mmr: mmr_lib::MMR<
|
||||
NodeOf<T, I, L>,
|
||||
Hasher<HashingOf<T, I>, L>,
|
||||
Storage<StorageType, T, I, L>
|
||||
>,
|
||||
leaves: u64,
|
||||
}
|
||||
|
||||
impl<StorageType, T, I, L> Mmr<StorageType, T, I, L> where
|
||||
T: Config<I>,
|
||||
I: Instance,
|
||||
L: primitives::FullLeaf,
|
||||
Storage<StorageType, T, I, L>: mmr_lib::MMRStore<NodeOf<T, I, L>>,
|
||||
{
|
||||
/// Create a pointer to an existing MMR with given number of leaves.
|
||||
pub fn new(leaves: u64) -> Self {
|
||||
let size = NodesUtils::new(leaves).size();
|
||||
Self {
|
||||
mmr: mmr_lib::MMR::new(size, Default::default()),
|
||||
leaves,
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify proof of a single leaf.
|
||||
pub fn verify_leaf_proof(
|
||||
&self,
|
||||
leaf: L,
|
||||
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(),
|
||||
proof.items.into_iter().map(Node::Hash).collect(),
|
||||
);
|
||||
let position = mmr_lib::leaf_index_to_pos(proof.leaf_index);
|
||||
let root = self.mmr.get_root().map_err(|e| Error::GetRoot.log_error(e))?;
|
||||
p.verify(
|
||||
root,
|
||||
vec![(position, Node::Data(leaf))],
|
||||
).map_err(|e| Error::Verify.log_debug(e))
|
||||
}
|
||||
|
||||
/// Return the internal size of the MMR (number of nodes).
|
||||
#[cfg(test)]
|
||||
pub fn size(&self) -> u64 {
|
||||
self.mmr.mmr_size()
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime specific MMR functions.
|
||||
impl<T, I, L> Mmr<RuntimeStorage, T, I, L> where
|
||||
T: Config<I>,
|
||||
I: Instance,
|
||||
L: primitives::FullLeaf,
|
||||
{
|
||||
|
||||
/// Push another item to the MMR.
|
||||
///
|
||||
/// Returns element position (index) in the MMR.
|
||||
pub fn push(&mut self, leaf: L) -> Option<u64> {
|
||||
let position = self.mmr.push(Node::Data(leaf))
|
||||
.map_err(|e| Error::Push.log_error(e))
|
||||
.ok()?;
|
||||
|
||||
self.leaves += 1;
|
||||
|
||||
Some(position)
|
||||
}
|
||||
|
||||
/// Commit the changes to underlying storage, return current number of leaves and
|
||||
/// calculate the new MMR's root hash.
|
||||
pub fn finalize(self) -> Result<(u64, <T as Config<I>>::Hash), Error> {
|
||||
let root = self.mmr.get_root().map_err(|e| Error::GetRoot.log_error(e))?;
|
||||
self.mmr.commit().map_err(|e| Error::Commit.log_error(e))?;
|
||||
Ok((self.leaves, root.hash()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Off-chain specific MMR functions.
|
||||
impl<T, I, L> Mmr<OffchainStorage, T, I, L> where
|
||||
T: Config<I>,
|
||||
I: Instance,
|
||||
L: primitives::FullLeaf,
|
||||
{
|
||||
/// Generate a proof for given leaf index.
|
||||
///
|
||||
/// Proof generation requires all the nodes (or their hashes) to be available in the storage.
|
||||
/// (i.e. you can't run the function in the pruned storage).
|
||||
pub fn generate_proof(&self, leaf_index: u64) -> Result<
|
||||
(L, primitives::Proof<<T as Config<I>>::Hash>),
|
||||
Error
|
||||
> {
|
||||
let position = mmr_lib::leaf_index_to_pos(leaf_index);
|
||||
let store = <Storage<OffchainStorage, T, I, L>>::default();
|
||||
let leaf = match mmr_lib::MMRStore::get_elem(&store, position) {
|
||||
Ok(Some(Node::Data(leaf))) => leaf,
|
||||
e => return Err(Error::LeafNotFound.log_debug(e)),
|
||||
};
|
||||
let leaf_count = self.leaves;
|
||||
self.mmr.gen_proof(vec![position])
|
||||
.map_err(|e| Error::GenerateProof.log_error(e))
|
||||
.map(|p| primitives::Proof {
|
||||
leaf_index,
|
||||
leaf_count,
|
||||
items: p.proof_items().iter().map(|x| x.hash()).collect(),
|
||||
})
|
||||
.map(|p| (leaf, p))
|
||||
}
|
||||
}
|
||||
|
||||
/// Merkle Mountain Range operation error.
|
||||
#[derive(RuntimeDebug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub enum Error {
|
||||
/// Error while pushing new node.
|
||||
Push,
|
||||
/// Error getting the new root.
|
||||
GetRoot,
|
||||
/// Error commiting changes.
|
||||
Commit,
|
||||
/// Error during proof generation.
|
||||
GenerateProof,
|
||||
/// Proof verification error.
|
||||
Verify,
|
||||
/// Leaf not found in the storage.
|
||||
LeafNotFound,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Consume given error `e` with `self` and generate a native log entry with error details.
|
||||
pub(crate) fn log_error(self, e: impl fmt::Debug) -> Self {
|
||||
debug::native::error!("[{:?}] MMR error: {:?}", self, e);
|
||||
self
|
||||
}
|
||||
|
||||
/// Consume given error `e` with `self` and generate a native log entry with error details.
|
||||
pub(crate) fn log_debug(self, e: impl fmt::Debug) -> Self {
|
||||
debug::native::debug!("[{:?}] MMR error: {:?}", self, e);
|
||||
self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod storage;
|
||||
pub mod utils;
|
||||
mod mmr;
|
||||
|
||||
use crate::primitives::FullLeaf;
|
||||
use sp_runtime::traits;
|
||||
|
||||
pub use self::mmr::{Mmr, Error};
|
||||
|
||||
/// Node type for runtime `T`.
|
||||
pub type NodeOf<T, I, L> = Node<<T as crate::Config<I>>::Hashing, L>;
|
||||
|
||||
/// A node stored in the MMR.
|
||||
pub type Node<H, L> = crate::primitives::DataOrHash<H, L>;
|
||||
|
||||
/// Default Merging & Hashing behavior for MMR.
|
||||
pub struct Hasher<H, L>(sp_std::marker::PhantomData<(H, L)>);
|
||||
|
||||
impl<H: traits::Hash, L: FullLeaf> mmr_lib::Merge for Hasher<H, L> {
|
||||
type Item = Node<H, L>;
|
||||
|
||||
fn merge(left: &Self::Item, right: &Self::Item) -> Self::Item {
|
||||
let mut concat = left.hash().as_ref().to_vec();
|
||||
concat.extend_from_slice(right.hash().as_ref());
|
||||
|
||||
Node::Hash(<H as traits::Hash>::hash(&concat))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A MMR storage implementations.
|
||||
|
||||
use codec::Encode;
|
||||
use crate::mmr::{NodeOf, Node};
|
||||
use crate::{NumberOfLeaves, Nodes, Module, Config, Instance, primitives};
|
||||
use frame_support::{StorageMap, StorageValue};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use sp_std::prelude::Vec;
|
||||
|
||||
/// A marker type for runtime-specific storage implementation.
|
||||
///
|
||||
/// Allows appending new items to the MMR and proof verification.
|
||||
/// MMR nodes are appended to two different storages:
|
||||
/// 1. We add nodes (leaves) hashes to the on-chain storge (see [crate::Nodes]).
|
||||
/// 2. We add full leaves (and all inner nodes as well) into the `IndexingAPI` during block
|
||||
/// processing, so the values end up in the Offchain DB if indexing is enabled.
|
||||
pub struct RuntimeStorage;
|
||||
|
||||
/// A marker type for offchain-specific storage implementation.
|
||||
///
|
||||
/// Allows proof generation and verification, but does not support appending new items.
|
||||
/// MMR nodes are assumed to be stored in the Off-Chain DB. Note this storage type
|
||||
/// DOES NOT support adding new items to the MMR.
|
||||
pub struct OffchainStorage;
|
||||
|
||||
/// A storage layer for MMR.
|
||||
///
|
||||
/// There are two different implementations depending on the use case.
|
||||
/// See docs for [RuntimeStorage] and [OffchainStorage].
|
||||
pub struct Storage<StorageType, T, I, L>(
|
||||
sp_std::marker::PhantomData<(StorageType, T, I, L)>
|
||||
);
|
||||
|
||||
impl<StorageType, T, I, L> Default for Storage<StorageType, T, I, L> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I, L> mmr_lib::MMRStore<NodeOf<T, I, L>> for Storage<OffchainStorage, T, I, L> where
|
||||
T: Config<I>,
|
||||
I: Instance,
|
||||
L: primitives::FullLeaf,
|
||||
{
|
||||
fn get_elem(&self, pos: u64) -> mmr_lib::Result<Option<NodeOf<T, I, L>>> {
|
||||
let key = Module::<T, I>::offchain_key(pos);
|
||||
// Retrieve the element from Off-chain DB.
|
||||
Ok(
|
||||
sp_io::offchain ::local_storage_get(sp_core::offchain::StorageKind::PERSISTENT, &key)
|
||||
.and_then(|v| codec::Decode::decode(&mut &*v).ok())
|
||||
)
|
||||
}
|
||||
|
||||
fn append(&mut self, _: u64, _: Vec<NodeOf<T, I, L>>) -> mmr_lib::Result<()> {
|
||||
panic!("MMR must not be altered in the off-chain context.")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I, L> mmr_lib::MMRStore<NodeOf<T, I, L>> for Storage<RuntimeStorage, T, I, L> where
|
||||
T: Config<I>,
|
||||
I: Instance,
|
||||
L: primitives::FullLeaf,
|
||||
{
|
||||
fn get_elem(&self, pos: u64) -> mmr_lib::Result<Option<NodeOf<T, I, L>>> {
|
||||
Ok(<Nodes<T, I>>::get(pos)
|
||||
.map(Node::Hash)
|
||||
)
|
||||
}
|
||||
|
||||
fn append(&mut self, pos: u64, elems: Vec<NodeOf<T, I, L>>) -> mmr_lib::Result<()> {
|
||||
let mut leaves = crate::NumberOfLeaves::<I>::get();
|
||||
let mut size = crate::mmr::utils::NodesUtils::new(leaves).size();
|
||||
if pos != size {
|
||||
return Err(mmr_lib::Error::InconsistentStore);
|
||||
}
|
||||
|
||||
for elem in elems {
|
||||
// on-chain we only store the hash (even if it's a leaf)
|
||||
<Nodes<T, I>>::insert(size, elem.hash());
|
||||
// Indexing API is used to store the full leaf content.
|
||||
elem.using_encoded(|elem| {
|
||||
sp_io::offchain_index::set(&Module::<T, I>::offchain_key(size), elem)
|
||||
});
|
||||
size += 1;
|
||||
|
||||
if let Node::Data(..) = elem {
|
||||
leaves += 1;
|
||||
}
|
||||
}
|
||||
|
||||
NumberOfLeaves::<I>::put(leaves);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Merkle Mountain Range utilities.
|
||||
|
||||
/// MMR nodes & size -related utilities.
|
||||
pub struct NodesUtils {
|
||||
no_of_leaves: u64,
|
||||
}
|
||||
|
||||
impl NodesUtils {
|
||||
/// Create new instance of MMR nodes utilities for given number of leaves.
|
||||
pub fn new(no_of_leaves: u64) -> Self {
|
||||
Self { no_of_leaves }
|
||||
}
|
||||
|
||||
/// Calculate number of peaks in the MMR.
|
||||
pub fn number_of_peaks(&self) -> u64 {
|
||||
self.number_of_leaves().count_ones() as u64
|
||||
}
|
||||
|
||||
/// Return the number of leaves in the MMR.
|
||||
pub fn number_of_leaves(&self) -> u64 {
|
||||
self.no_of_leaves
|
||||
}
|
||||
|
||||
/// Calculate the total size of MMR (number of nodes).
|
||||
pub fn size(&self) -> u64 {
|
||||
2 * self.no_of_leaves - self.number_of_peaks()
|
||||
}
|
||||
|
||||
/// Calculate maximal depth of the MMR.
|
||||
pub fn depth(&self) -> u32 {
|
||||
if self.no_of_leaves == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
64 - self.no_of_leaves
|
||||
.next_power_of_two()
|
||||
.leading_zeros()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_calculate_number_of_leaves_correctly() {
|
||||
assert_eq!(
|
||||
vec![0, 1, 2, 3, 4, 9, 15, 21]
|
||||
.into_iter()
|
||||
.map(|n| NodesUtils::new(n).depth())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![0, 1, 2, 3, 3, 5, 5, 6]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_calculate_depth_correclty() {
|
||||
assert_eq!(
|
||||
vec![0, 1, 2, 3, 4, 9, 15, 21]
|
||||
.into_iter()
|
||||
.map(|n| NodesUtils::new(n).number_of_leaves())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![0, 1, 2, 3, 4, 9, 15, 21]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_calculate_number_of_peaks_correctly() {
|
||||
assert_eq!(
|
||||
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21]
|
||||
.into_iter()
|
||||
.map(|n| NodesUtils::new(n).number_of_peaks())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 3]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_calculate_the_size_correctly() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let leaves = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 21];
|
||||
let sizes = vec![0, 1, 3, 4, 7, 8, 10, 11, 15, 16, 18, 19, 22, 23, 25, 26, 39];
|
||||
assert_eq!(
|
||||
leaves
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|n| NodesUtils::new(n).size())
|
||||
.collect::<Vec<_>>(),
|
||||
sizes.clone()
|
||||
);
|
||||
|
||||
// size cross-check
|
||||
let mut actual_sizes = vec![];
|
||||
for s in &leaves[1..] {
|
||||
crate::tests::new_test_ext().execute_with(|| {
|
||||
let mut mmr = crate::mmr::Mmr::<
|
||||
crate::mmr::storage::RuntimeStorage,
|
||||
crate::mock::Test,
|
||||
crate::DefaultInstance,
|
||||
_,
|
||||
>::new(0);
|
||||
for i in 0..*s {
|
||||
mmr.push(i);
|
||||
}
|
||||
actual_sizes.push(mmr.size());
|
||||
})
|
||||
}
|
||||
assert_eq!(
|
||||
sizes[1..],
|
||||
actual_sizes[..],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::*;
|
||||
use crate::primitives::{LeafDataProvider, Compact};
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use frame_support::{
|
||||
impl_outer_origin, parameter_types,
|
||||
};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
testing::Header,
|
||||
traits::{
|
||||
BlakeTwo256, Keccak256, IdentityLookup,
|
||||
},
|
||||
};
|
||||
use sp_std::cell::RefCell;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode)]
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = ();
|
||||
type Origin = Origin;
|
||||
type Call = ();
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = sp_core::sr25519::Public;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type DbWeight = ();
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type Version = ();
|
||||
type PalletInfo = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
const INDEXING_PREFIX: &'static [u8] = b"mmr-";
|
||||
|
||||
type Hashing = Keccak256;
|
||||
type Hash = H256;
|
||||
type LeafData = Compact<Keccak256, (frame_system::Module<Test>, LeafData)>;
|
||||
type OnNewRoot = ();
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, Default, Eq, PartialEq, Debug)]
|
||||
pub struct LeafData {
|
||||
pub a: u64,
|
||||
pub b: Vec<u8>,
|
||||
}
|
||||
|
||||
impl LeafData {
|
||||
pub fn new(a: u64) -> Self {
|
||||
Self {
|
||||
a,
|
||||
b: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static LEAF_DATA: RefCell<LeafData> = RefCell::new(Default::default());
|
||||
}
|
||||
|
||||
impl LeafDataProvider for LeafData {
|
||||
type LeafData = Self;
|
||||
|
||||
fn leaf_data() -> Self::LeafData {
|
||||
LEAF_DATA.with(|r| r.borrow().clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type MMR = Module<Test>;
|
||||
@@ -0,0 +1,415 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Merkle Mountain Range primitive types.
|
||||
|
||||
use frame_support::RuntimeDebug;
|
||||
use sp_runtime::traits;
|
||||
use sp_std::fmt;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use sp_std::prelude::Vec;
|
||||
|
||||
/// A provider of the MMR's leaf data.
|
||||
pub trait LeafDataProvider {
|
||||
/// A type that should end up in the leaf of MMR.
|
||||
type LeafData: FullLeaf;
|
||||
|
||||
/// The method to return leaf data that should be placed
|
||||
/// in the leaf node appended MMR at this block.
|
||||
///
|
||||
/// This is being called by the `on_initialize` method of
|
||||
/// this pallet at the very beginning of each block.
|
||||
fn leaf_data() -> Self::LeafData;
|
||||
}
|
||||
|
||||
impl LeafDataProvider for () {
|
||||
type LeafData = ();
|
||||
|
||||
fn leaf_data() -> Self::LeafData {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
/// The most common use case for MMRs is to store historical block hashes,
|
||||
/// so that any point in time in the future we can receive a proof about some past
|
||||
/// blocks without using excessive on-chain storage.
|
||||
/// Hence we implement the [LeafDataProvider] for [frame_system::Module], since the
|
||||
/// current block hash is not available (since the block is not finished yet),
|
||||
/// we use the `parent_hash` here.
|
||||
impl<T: frame_system::Config> LeafDataProvider for frame_system::Module<T> {
|
||||
type LeafData = <T as frame_system::Config>::Hash;
|
||||
|
||||
fn leaf_data() -> Self::LeafData {
|
||||
Self::parent_hash()
|
||||
}
|
||||
}
|
||||
|
||||
/// New MMR root notification hook.
|
||||
pub trait OnNewRoot<Hash> {
|
||||
/// Function called by the pallet in case new MMR root has been computed.
|
||||
fn on_new_root(root: &Hash);
|
||||
}
|
||||
|
||||
/// No-op implementation of [OnNewRoot].
|
||||
impl<Hash> OnNewRoot<Hash> for () {
|
||||
fn on_new_root(_root: &Hash) {}
|
||||
}
|
||||
|
||||
/// A full leaf content stored in the offchain-db.
|
||||
pub trait FullLeaf: Clone + PartialEq + fmt::Debug + codec::Decode {
|
||||
/// Encode the leaf either in it's full or compact form.
|
||||
///
|
||||
/// NOTE the encoding returned here MUST be `Decode`able into `FullLeaf`.
|
||||
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F, compact: bool) -> R;
|
||||
}
|
||||
|
||||
impl<T: codec::Encode + codec::Decode + Clone + PartialEq + fmt::Debug> FullLeaf for T {
|
||||
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F, _compact: bool) -> R {
|
||||
codec::Encode::using_encoded(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// An element representing either full data or it's hash.
|
||||
///
|
||||
/// See [Compact] to see how it may be used in practice to reduce the size
|
||||
/// of proofs in case multiple [LeafDataProvider]s are composed together.
|
||||
/// This is also used internally by the MMR to differentiate leaf nodes (data)
|
||||
/// and inner nodes (hashes).
|
||||
///
|
||||
/// [DataOrHash::hash] method calculates the hash of this element in it's compact form,
|
||||
/// so should be used instead of hashing the encoded form (which will always be non-compact).
|
||||
#[derive(RuntimeDebug, Clone, PartialEq)]
|
||||
pub enum DataOrHash<H: traits::Hash, L> {
|
||||
/// Arbitrary data in it's full form.
|
||||
Data(L),
|
||||
/// A hash of some data.
|
||||
Hash(H::Output),
|
||||
}
|
||||
|
||||
impl<H: traits::Hash, L> From<L> for DataOrHash<H, L> {
|
||||
fn from(l: L) -> Self {
|
||||
Self::Data(l)
|
||||
}
|
||||
}
|
||||
|
||||
mod encoding {
|
||||
use super::*;
|
||||
|
||||
/// A helper type to implement [codec::Codec] for [DataOrHash].
|
||||
#[derive(codec::Encode, codec::Decode)]
|
||||
enum Either<A, B> {
|
||||
Left(A),
|
||||
Right(B),
|
||||
}
|
||||
|
||||
impl<H: traits::Hash, L: FullLeaf> codec::Encode for DataOrHash<H, L> {
|
||||
fn encode_to<T: codec::Output>(&self, dest: &mut T) {
|
||||
match self {
|
||||
Self::Data(l) => l.using_encoded(
|
||||
|data| Either::<&[u8], &H::Output>::Left(data).encode_to(dest), false
|
||||
),
|
||||
Self::Hash(h) => Either::<&[u8], &H::Output>::Right(h).encode_to(dest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: traits::Hash, L: FullLeaf> codec::Decode for DataOrHash<H, L> {
|
||||
fn decode<I: codec::Input>(value: &mut I) -> Result<Self, codec::Error> {
|
||||
let decoded: Either<Vec<u8>, H::Output> = Either::decode(value)?;
|
||||
Ok(match decoded {
|
||||
Either::Left(l) => DataOrHash::Data(L::decode(&mut &*l)?),
|
||||
Either::Right(r) => DataOrHash::Hash(r),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: traits::Hash, L: FullLeaf> DataOrHash<H, L> {
|
||||
/// Retrieve a hash of this item.
|
||||
///
|
||||
/// Depending on the node type it's going to either be a contained value for [DataOrHash::Hash]
|
||||
/// node, or a hash of SCALE-encoded [DataOrHash::Data] data.
|
||||
pub fn hash(&self) -> H::Output {
|
||||
match *self {
|
||||
Self::Data(ref leaf) => leaf.using_encoded(<H as traits::Hash>::hash, true),
|
||||
Self::Hash(ref hash) => hash.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A composition of multiple leaf elements with compact form representation.
|
||||
///
|
||||
/// When composing together multiple [LeafDataProvider]s you will end up with
|
||||
/// a tuple of `LeafData` that each element provides.
|
||||
///
|
||||
/// However this will cause the leaves to have significant size, while for some
|
||||
/// use cases it will be enough to prove only one element of the tuple.
|
||||
/// That's the rationale for [Compact] struct. We wrap each element of the tuple
|
||||
/// into [DataOrHash] and each tuple element is hashed first before constructing
|
||||
/// the final hash of the entire tuple. This allows you to replace tuple elements
|
||||
/// you don't care about with their hashes.
|
||||
#[derive(RuntimeDebug, Clone, PartialEq)]
|
||||
pub struct Compact<H, T> {
|
||||
pub tuple: T,
|
||||
_hash: sp_std::marker::PhantomData<H>,
|
||||
}
|
||||
|
||||
impl<H, T> sp_std::ops::Deref for Compact<H, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.tuple
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T> Compact<H, T> {
|
||||
pub fn new(tuple: T) -> Self {
|
||||
Self { tuple, _hash: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T: codec::Decode> codec::Decode for Compact<H, T> {
|
||||
fn decode<I: codec::Input>(value: &mut I) -> Result<Self, codec::Error> {
|
||||
T::decode(value).map(Compact::new)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_leaf_data_for_tuple {
|
||||
( $( $name:ident : $id:tt ),+ ) => {
|
||||
/// [FullLeaf] implementation for `Compact<H, (DataOrHash<H, Tuple>, ...)>`
|
||||
impl<H, $( $name ),+> FullLeaf for Compact<H, ( $( DataOrHash<H, $name>, )+ )> where
|
||||
H: traits::Hash,
|
||||
$( $name: FullLeaf ),+
|
||||
{
|
||||
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F, compact: bool) -> R {
|
||||
if compact {
|
||||
codec::Encode::using_encoded(&(
|
||||
$( DataOrHash::<H, $name>::Hash(self.tuple.$id.hash()), )+
|
||||
), f)
|
||||
} else {
|
||||
codec::Encode::using_encoded(&self.tuple, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [LeafDataProvider] implementation for `Compact<H, (DataOrHash<H, Tuple>, ...)>`
|
||||
///
|
||||
/// This provides a compact-form encoding for tuples wrapped in [Compact].
|
||||
impl<H, $( $name ),+> LeafDataProvider for Compact<H, ( $( $name, )+ )> where
|
||||
H: traits::Hash,
|
||||
$( $name: LeafDataProvider ),+
|
||||
{
|
||||
type LeafData = Compact<
|
||||
H,
|
||||
( $( DataOrHash<H, $name::LeafData>, )+ ),
|
||||
>;
|
||||
|
||||
fn leaf_data() -> Self::LeafData {
|
||||
let tuple = (
|
||||
$( DataOrHash::Data($name::leaf_data()), )+
|
||||
);
|
||||
Compact::new(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
/// [LeafDataProvider] implementation for `(Tuple, ...)`
|
||||
///
|
||||
/// This provides regular (non-compactable) composition of [LeafDataProvider]s.
|
||||
impl<$( $name ),+> LeafDataProvider for ( $( $name, )+ ) where
|
||||
( $( $name::LeafData, )+ ): FullLeaf,
|
||||
$( $name: LeafDataProvider ),+
|
||||
{
|
||||
type LeafData = ( $( $name::LeafData, )+ );
|
||||
|
||||
fn leaf_data() -> Self::LeafData {
|
||||
(
|
||||
$( $name::leaf_data(), )+
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test functions implementation for `Compact<H, (DataOrHash<H, Tuple>, ...)>`
|
||||
#[cfg(test)]
|
||||
impl<H, A, B> Compact<H, (DataOrHash<H, A>, DataOrHash<H, B>)> where
|
||||
H: traits::Hash,
|
||||
A: FullLeaf,
|
||||
B: FullLeaf,
|
||||
{
|
||||
/// Retrieve a hash of this item in it's compact form.
|
||||
pub fn hash(&self) -> H::Output {
|
||||
self.using_encoded(<H as traits::Hash>::hash, true)
|
||||
}
|
||||
}
|
||||
|
||||
impl_leaf_data_for_tuple!(A:0);
|
||||
impl_leaf_data_for_tuple!(A:0, B:1);
|
||||
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 one of the leaves.
|
||||
#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq)]
|
||||
pub struct Proof<Hash> {
|
||||
/// The index of the leaf the proof is for.
|
||||
pub leaf_index: u64,
|
||||
/// Number of leaves in MMR, when the proof was generated.
|
||||
pub leaf_count: u64,
|
||||
/// Proof elements (hashes of siblings of inner nodes on the path to the leaf).
|
||||
pub items: Vec<Hash>,
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use codec::Decode;
|
||||
use crate::tests::hex;
|
||||
use sp_runtime::traits::Keccak256;
|
||||
|
||||
type Test = DataOrHash<Keccak256, String>;
|
||||
type TestCompact = Compact<Keccak256, (Test, Test)>;
|
||||
type TestProof = Proof<<Keccak256 as traits::Hash>::Output>;
|
||||
|
||||
#[test]
|
||||
fn should_encode_decode_proof() {
|
||||
// given
|
||||
let proof: TestProof = Proof {
|
||||
leaf_index: 5,
|
||||
leaf_count: 10,
|
||||
items: vec![
|
||||
hex("c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd"),
|
||||
hex("d3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd"),
|
||||
hex("e3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd"),
|
||||
],
|
||||
};
|
||||
|
||||
// when
|
||||
let encoded = codec::Encode::encode(&proof);
|
||||
let decoded = TestProof::decode(&mut &*encoded);
|
||||
|
||||
// then
|
||||
assert_eq!(decoded, Ok(proof));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_decode_correctly_if_no_compact() {
|
||||
// given
|
||||
let cases = vec![
|
||||
Test::Data("Hello World!".into()),
|
||||
Test::Hash(hex("c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd")),
|
||||
Test::Data("".into()),
|
||||
Test::Data("3e48d6bcd417fb22e044747242451e2c0f3e602d1bcad2767c34808621956417".into()),
|
||||
];
|
||||
|
||||
// when
|
||||
let encoded = cases
|
||||
.iter()
|
||||
.map(codec::Encode::encode)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let decoded = encoded
|
||||
.iter()
|
||||
.map(|x| Test::decode(&mut &**x))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// then
|
||||
assert_eq!(decoded, cases.into_iter().map(Result::<_, codec::Error>::Ok).collect::<Vec<_>>());
|
||||
// check encoding correctness
|
||||
assert_eq!(&encoded[0], &hex_literal::hex!("00343048656c6c6f20576f726c6421"));
|
||||
assert_eq!(
|
||||
encoded[1].as_slice(),
|
||||
hex_literal::hex!(
|
||||
"01c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd"
|
||||
).as_ref()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_the_hash_correctly() {
|
||||
// given
|
||||
let a = Test::Data("Hello World!".into());
|
||||
let b = Test::Hash(hex("c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd"));
|
||||
|
||||
// when
|
||||
let a = a.hash();
|
||||
let b = b.hash();
|
||||
|
||||
// then
|
||||
assert_eq!(a, hex("a9c321be8c24ba4dc2bd73f5300bde67dc57228ab8b68b607bb4c39c5374fac9"));
|
||||
assert_eq!(b, hex("c3e7ba6b511162fead58f2c8b5764ce869ed1118011ac37392522ed16720bbcd"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_should_work() {
|
||||
// given
|
||||
let a = Test::Data("Hello World!".into());
|
||||
let b = Test::Data("".into());
|
||||
|
||||
// when
|
||||
let c: TestCompact = Compact::new((a.clone(), b.clone()));
|
||||
let d: TestCompact = Compact::new((
|
||||
Test::Hash(a.hash()),
|
||||
Test::Hash(b.hash()),
|
||||
));
|
||||
|
||||
// then
|
||||
assert_eq!(c.hash(), d.hash());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_should_encode_decode_correctly() {
|
||||
// given
|
||||
let a = Test::Data("Hello World!".into());
|
||||
let b = Test::Data("".into());
|
||||
|
||||
let c: TestCompact = Compact::new((a.clone(), b.clone()));
|
||||
let d: TestCompact = Compact::new((
|
||||
Test::Hash(a.hash()),
|
||||
Test::Hash(b.hash()),
|
||||
));
|
||||
let cases = vec![c, d.clone()];
|
||||
|
||||
// when
|
||||
let encoded_compact = cases
|
||||
.iter()
|
||||
.map(|c| c.using_encoded(|x| x.to_vec(), true))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let encoded = cases
|
||||
.iter()
|
||||
.map(|c| c.using_encoded(|x| x.to_vec(), false))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let decoded_compact = encoded_compact
|
||||
.iter()
|
||||
.map(|x| TestCompact::decode(&mut &**x))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let decoded = encoded
|
||||
.iter()
|
||||
.map(|x| TestCompact::decode(&mut &**x))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// then
|
||||
assert_eq!(decoded, cases.into_iter().map(Result::<_, codec::Error>::Ok).collect::<Vec<_>>());
|
||||
|
||||
assert_eq!(decoded_compact, vec![Ok(d.clone()), Ok(d.clone())]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::*;
|
||||
use crate::mock::*;
|
||||
use crate::primitives::{Proof, Compact};
|
||||
|
||||
use frame_support::traits::OnInitialize;
|
||||
use sp_core::{
|
||||
H256,
|
||||
offchain::{
|
||||
testing::TestOffchainExt,
|
||||
OffchainExt,
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
|
||||
frame_system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
|
||||
}
|
||||
|
||||
fn register_offchain_ext(ext: &mut sp_io::TestExternalities) {
|
||||
let (offchain, _offchain_state) = TestOffchainExt::with_offchain_db(ext.offchain_db());
|
||||
ext.register_extension(OffchainExt::new(offchain));
|
||||
}
|
||||
|
||||
fn new_block() -> u64 {
|
||||
let number = frame_system::Module::<Test>::block_number() + 1;
|
||||
let hash = H256::repeat_byte(number as u8);
|
||||
LEAF_DATA.with(|r| r.borrow_mut().a = number);
|
||||
|
||||
frame_system::Module::<Test>::initialize(
|
||||
&number,
|
||||
&hash,
|
||||
&Default::default(),
|
||||
&Default::default(),
|
||||
frame_system::InitKind::Full,
|
||||
);
|
||||
MMR::on_initialize(number)
|
||||
}
|
||||
|
||||
pub(crate) fn hex(s: &str) -> H256 {
|
||||
s.parse().unwrap()
|
||||
}
|
||||
|
||||
fn decode_node(v: Vec<u8>) -> mmr::Node<
|
||||
<Test as Config>::Hashing,
|
||||
(H256, LeafData),
|
||||
> {
|
||||
use crate::primitives::DataOrHash;
|
||||
type A = DataOrHash::<<Test as Config>::Hashing, H256>;
|
||||
type B = DataOrHash::<<Test as Config>::Hashing, LeafData>;
|
||||
type Node = mmr::Node<<Test as Config>::Hashing, (A, B)>;
|
||||
let tuple: Node = codec::Decode::decode(&mut &v[..]).unwrap();
|
||||
|
||||
match tuple {
|
||||
mmr::Node::Data((DataOrHash::Data(a), DataOrHash::Data(b))) => mmr::Node::Data((a, b)),
|
||||
mmr::Node::Hash(hash) => mmr::Node::Hash(hash),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn init_chain(blocks: usize) {
|
||||
// given
|
||||
for _ in 0..blocks {
|
||||
new_block();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_start_empty() {
|
||||
let _ = env_logger::try_init();
|
||||
new_test_ext().execute_with(|| {
|
||||
// given
|
||||
assert_eq!(
|
||||
crate::RootHash::<Test>::get(),
|
||||
"0000000000000000000000000000000000000000000000000000000000000000".parse().unwrap()
|
||||
);
|
||||
assert_eq!(crate::NumberOfLeaves::<DefaultInstance>::get(), 0);
|
||||
assert_eq!(crate::Nodes::<Test>::get(0), None);
|
||||
|
||||
// when
|
||||
let weight = new_block();
|
||||
|
||||
// then
|
||||
assert_eq!(crate::NumberOfLeaves::<DefaultInstance>::get(), 1);
|
||||
assert_eq!(crate::Nodes::<Test>::get(0),
|
||||
Some(hex("da5e6d0616e05c6a6348605a37ca33493fc1a15ad1e6a405ee05c17843fdafed")));
|
||||
assert_eq!(
|
||||
crate::RootHash::<Test>::get(),
|
||||
hex("da5e6d0616e05c6a6348605a37ca33493fc1a15ad1e6a405ee05c17843fdafed")
|
||||
);
|
||||
assert!(weight != 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_append_to_mmr_when_on_initialize_is_called() {
|
||||
let _ = env_logger::try_init();
|
||||
let mut ext = new_test_ext();
|
||||
ext.execute_with(|| {
|
||||
// when
|
||||
new_block();
|
||||
new_block();
|
||||
|
||||
// then
|
||||
assert_eq!(crate::NumberOfLeaves::<DefaultInstance>::get(), 2);
|
||||
assert_eq!(crate::Nodes::<Test>::get(0),
|
||||
Some(hex("da5e6d0616e05c6a6348605a37ca33493fc1a15ad1e6a405ee05c17843fdafed")));
|
||||
assert_eq!(crate::Nodes::<Test>::get(1),
|
||||
Some(hex("ff5d891b28463a3440e1b650984685efdf260e482cb3807d53c49090841e755f")));
|
||||
assert_eq!(crate::Nodes::<Test>::get(2),
|
||||
Some(hex("bc54778fab79f586f007bd408dca2c4aa07959b27d1f2c8f4f2549d1fcfac8f8")));
|
||||
assert_eq!(crate::Nodes::<Test>::get(3), None);
|
||||
assert_eq!(
|
||||
crate::RootHash::<Test>::get(),
|
||||
hex("bc54778fab79f586f007bd408dca2c4aa07959b27d1f2c8f4f2549d1fcfac8f8")
|
||||
);
|
||||
});
|
||||
|
||||
// make sure the leaves end up in the offchain DB
|
||||
ext.persist_offchain_overlay();
|
||||
let offchain_db = ext.offchain_db();
|
||||
assert_eq!(offchain_db.get(&MMR::offchain_key(0)).map(decode_node), Some(mmr::Node::Data((
|
||||
H256::repeat_byte(1),
|
||||
LeafData::new(1),
|
||||
))));
|
||||
assert_eq!(offchain_db.get(&MMR::offchain_key(1)).map(decode_node), Some(mmr::Node::Data((
|
||||
H256::repeat_byte(2),
|
||||
LeafData::new(2),
|
||||
))));
|
||||
assert_eq!(offchain_db.get(&MMR::offchain_key(2)).map(decode_node), Some(mmr::Node::Hash(
|
||||
hex("bc54778fab79f586f007bd408dca2c4aa07959b27d1f2c8f4f2549d1fcfac8f8")
|
||||
)));
|
||||
assert_eq!(offchain_db.get(&MMR::offchain_key(3)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_construct_larger_mmr_correctly() {
|
||||
let _ = env_logger::try_init();
|
||||
new_test_ext().execute_with(|| {
|
||||
// when
|
||||
init_chain(7);
|
||||
|
||||
// then
|
||||
assert_eq!(crate::NumberOfLeaves::<DefaultInstance>::get(), 7);
|
||||
assert_eq!(crate::Nodes::<Test>::get(0),
|
||||
Some(hex("da5e6d0616e05c6a6348605a37ca33493fc1a15ad1e6a405ee05c17843fdafed")));
|
||||
assert_eq!(crate::Nodes::<Test>::get(10),
|
||||
Some(hex("af3327deed0515c8d1902c9b5cd375942d42f388f3bfe3d1cd6e1b86f9cc456c")));
|
||||
assert_eq!(
|
||||
crate::RootHash::<Test>::get(),
|
||||
hex("fc4f9042bd2f73feb26f3fc42db834c5f1943fa20070ddf106c486a478a0d561")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_generate_proofs_correctly() {
|
||||
let _ = env_logger::try_init();
|
||||
let mut ext = new_test_ext();
|
||||
// given
|
||||
ext.execute_with(|| init_chain(7));
|
||||
ext.persist_offchain_overlay();
|
||||
|
||||
// Try to generate proofs now. This requires the offchain extensions to be present
|
||||
// to retrieve full leaf data.
|
||||
register_offchain_ext(&mut ext);
|
||||
ext.execute_with(|| {
|
||||
// when generate proofs for all leaves
|
||||
let proofs = (0_u64..crate::NumberOfLeaves::<DefaultInstance>::get())
|
||||
.into_iter()
|
||||
.map(|leaf_index| crate::Module::<Test>::generate_proof(leaf_index).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// then
|
||||
assert_eq!(proofs[0], (Compact::new((
|
||||
H256::repeat_byte(1).into(),
|
||||
LeafData::new(1).into(),
|
||||
)), Proof {
|
||||
leaf_index: 0,
|
||||
leaf_count: 7,
|
||||
items: vec![
|
||||
hex("ff5d891b28463a3440e1b650984685efdf260e482cb3807d53c49090841e755f"),
|
||||
hex("00b0046bd2d63fcb760cf50a262448bb2bbf9a264b0b0950d8744044edf00dc3"),
|
||||
hex("16de0900b57bf359a0733674ebfbba0f494e95a8391b4bfeae850019399f3ec0"),
|
||||
],
|
||||
}));
|
||||
assert_eq!(proofs[4], (Compact::new((
|
||||
H256::repeat_byte(5).into(),
|
||||
LeafData::new(5).into(),
|
||||
)), Proof {
|
||||
leaf_index: 4,
|
||||
leaf_count: 7,
|
||||
items: vec![
|
||||
hex("e53ee36ba6c068b1a6cfef7862fed5005df55615e1c9fa6eeefe08329ac4b94b"),
|
||||
hex("c09d4a008a0f1ef37860bef33ec3088ccd94268c0bfba7ff1b3c2a1075b0eb92"),
|
||||
hex("af3327deed0515c8d1902c9b5cd375942d42f388f3bfe3d1cd6e1b86f9cc456c"),
|
||||
],
|
||||
}));
|
||||
assert_eq!(proofs[6], (Compact::new((
|
||||
H256::repeat_byte(7).into(),
|
||||
LeafData::new(7).into(),
|
||||
)), Proof {
|
||||
leaf_index: 6,
|
||||
leaf_count: 7,
|
||||
items: vec![
|
||||
hex("e53ee36ba6c068b1a6cfef7862fed5005df55615e1c9fa6eeefe08329ac4b94b"),
|
||||
hex("dad09f50b41822fc5ecadc25b08c3a61531d4d60e962a5aa0b6998fad5c37c5e"),
|
||||
],
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_verify() {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
// Start off with chain initialisation and storing indexing data off-chain
|
||||
// (MMR Leafs)
|
||||
let mut ext = new_test_ext();
|
||||
ext.execute_with(|| init_chain(7));
|
||||
ext.persist_offchain_overlay();
|
||||
|
||||
// Try to generate proof now. This requires the offchain extensions to be present
|
||||
// to retrieve full leaf data.
|
||||
register_offchain_ext(&mut ext);
|
||||
let (leaf, proof5) = ext.execute_with(|| {
|
||||
// when
|
||||
crate::Module::<Test>::generate_proof(5).unwrap()
|
||||
});
|
||||
|
||||
// Now to verify the proof, we really shouldn't require offchain storage or extension.
|
||||
// Hence we initialize the storage once again, using different externalities and then
|
||||
// verify.
|
||||
let mut ext2 = new_test_ext();
|
||||
ext2.execute_with(|| {
|
||||
init_chain(7);
|
||||
// then
|
||||
assert_eq!(crate::Module::<Test>::verify_leaf(leaf, proof5), Ok(()));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() {
|
||||
let _ = env_logger::try_init();
|
||||
let mut ext = new_test_ext();
|
||||
// given
|
||||
ext.execute_with(|| init_chain(7));
|
||||
|
||||
ext.persist_offchain_overlay();
|
||||
register_offchain_ext(&mut ext);
|
||||
|
||||
ext.execute_with(|| {
|
||||
// when
|
||||
let (leaf, proof5) = crate::Module::<Test>::generate_proof(5).unwrap();
|
||||
new_block();
|
||||
|
||||
// then
|
||||
assert_eq!(crate::Module::<Test>::verify_leaf(leaf, proof5), Ok(()));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user