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:
Tomasz Drwięga
2020-12-09 16:35:13 +01:00
committed by GitHub
parent 2ed2832046
commit 02f66e8823
19 changed files with 1751 additions and 50 deletions
@@ -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[..],
);
}
}