mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-25 03:35:43 +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,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[..],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user