Merkle Mountain Range pallet improvements (#7891)

* Add stateless verification helper function.

* Split MMR primitives.

* Add RuntimeAPI

* RuntimeAPI with OpaqueLeaves

* Bump spec_version,.

* Move primitives back to frame.

* Fix OpaqueLeaf encoding.

* Add block number to frame_system implementation of LeafDataProvider.

* Relax leaf codec requirements and fix OpaqueLeaf

* Add root to debug line.

* Apply suggestions from code review

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* Typo.

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
This commit is contained in:
Tomasz Drwięga
2021-01-28 11:58:52 +01:00
committed by GitHub
parent fa23de2c03
commit 6c2dd28dfb
12 changed files with 373 additions and 93 deletions
+16
View File
@@ -4741,6 +4741,7 @@ dependencies = [
"frame-support",
"frame-system",
"hex-literal",
"pallet-mmr-primitives",
"parity-scale-codec",
"serde",
"sp-core",
@@ -4749,6 +4750,21 @@ dependencies = [
"sp-std",
]
[[package]]
name = "pallet-mmr-primitives"
version = "2.0.0"
dependencies = [
"frame-support",
"frame-system",
"hex-literal",
"parity-scale-codec",
"serde",
"sp-api",
"sp-core",
"sp-runtime",
"sp-std",
]
[[package]]
name = "pallet-multisig"
version = "2.0.1"
+1
View File
@@ -85,6 +85,7 @@ members = [
"frame/lottery",
"frame/membership",
"frame/merkle-mountain-range",
"frame/merkle-mountain-range/primitives",
"frame/metadata",
"frame/multisig",
"frame/nicks",
+38 -1
View File
@@ -112,7 +112,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to 0. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 261,
spec_version: 262,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 2,
@@ -1079,6 +1079,20 @@ pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Call, SignedExt
/// Executive: handles dispatch to the various modules.
pub type Executive = frame_executive::Executive<Runtime, Block, frame_system::ChainContext<Runtime>, Runtime, AllModules>;
/// MMR helper types.
mod mmr {
use super::Runtime;
pub use pallet_mmr::primitives::*;
pub type Leaf = <
<Runtime as pallet_mmr::Config>::LeafData
as
LeafDataProvider
>::LeafData;
pub type Hash = <Runtime as pallet_mmr::Config>::Hash;
pub type Hashing = <Runtime as pallet_mmr::Config>::Hashing;
}
impl_runtime_apis! {
impl sp_api::Core<Block> for Runtime {
fn version() -> RuntimeVersion {
@@ -1273,6 +1287,29 @@ impl_runtime_apis! {
}
}
impl pallet_mmr::primitives::MmrApi<
Block,
mmr::Leaf,
mmr::Hash,
> for Runtime {
fn generate_proof(leaf_index: u64) -> Result<(mmr::Leaf, mmr::Proof<mmr::Hash>), mmr::Error> {
Mmr::generate_proof(leaf_index)
}
fn verify_proof(leaf: mmr::Leaf, proof: mmr::Proof<mmr::Hash>) -> Result<(), mmr::Error> {
Mmr::verify_leaf(leaf, proof)
}
fn verify_proof_stateless(
root: mmr::Hash,
leaf: Vec<u8>,
proof: mmr::Proof<mmr::Hash>
) -> Result<(), mmr::Error> {
let node = mmr::DataOrHash::Data(mmr::OpaqueLeaf(leaf));
pallet_mmr::verify_leaf_proof::<mmr::Hashing, _>(root, node, proof)
}
}
impl sp_session::SessionKeys<Block> for Runtime {
fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
SessionKeys::generate(seed)
@@ -17,6 +17,7 @@ frame-benchmarking = { version = "2.0.0", default-features = false, path = "../b
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" }
pallet-mmr-primitives = { version = "2.0.0", default-features = false, path = "./primitives" }
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" }
@@ -35,6 +36,7 @@ std = [
"frame-support/std",
"frame-system/std",
"mmr-lib/std",
"pallet-mmr-primitives/std",
"serde",
"sp-core/std",
"sp-io/std",
@@ -0,0 +1,38 @@
[package]
name = "pallet-mmr-primitives"
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 primitives."
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "1.3.6", default-features = false }
frame-support = { version = "2.0.0", default-features = false, path = "../../support" }
frame-system = { version = "2.0.0", default-features = false, path = "../../system" }
serde = { version = "1.0.101", optional = true, features = ["derive"] }
sp-api = { version = "2.0.0", default-features = false, path = "../../../primitives/api" }
sp-core = { version = "2.0.0", default-features = false, path = "../../../primitives/core" }
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]
hex-literal = "0.3"
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"serde",
"sp-api/std",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -17,8 +17,11 @@
//! Merkle Mountain Range primitive types.
use frame_support::RuntimeDebug;
use sp_runtime::traits;
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
use frame_support::{RuntimeDebug, debug};
use sp_runtime::traits::{self, Saturating, One};
use sp_std::fmt;
#[cfg(not(feature = "std"))]
use sp_std::prelude::Vec;
@@ -26,7 +29,7 @@ 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;
type LeafData: FullLeaf + codec::Decode;
/// The method to return leaf data that should be placed
/// in the leaf node appended MMR at this block.
@@ -47,14 +50,21 @@ impl LeafDataProvider for () {
/// 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
///
/// 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.
/// we use the `parent_hash` here along with parent block number.
impl<T: frame_system::Config> LeafDataProvider for frame_system::Module<T> {
type LeafData = <T as frame_system::Config>::Hash;
type LeafData = (
<T as frame_system::Config>::BlockNumber,
<T as frame_system::Config>::Hash
);
fn leaf_data() -> Self::LeafData {
Self::parent_hash()
(
Self::block_number().saturating_sub(One::one()),
Self::parent_hash()
)
}
}
@@ -70,7 +80,7 @@ impl<Hash> OnNewRoot<Hash> for () {
}
/// A full leaf content stored in the offchain-db.
pub trait FullLeaf: Clone + PartialEq + fmt::Debug + codec::Decode {
pub trait FullLeaf: Clone + PartialEq + fmt::Debug {
/// Encode the leaf either in it's full or compact form.
///
/// NOTE the encoding returned here MUST be `Decode`able into `FullLeaf`.
@@ -127,7 +137,7 @@ mod encoding {
}
}
impl<H: traits::Hash, L: FullLeaf> codec::Decode for DataOrHash<H, L> {
impl<H: traits::Hash, L: FullLeaf + codec::Decode> 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 {
@@ -164,6 +174,7 @@ impl<H: traits::Hash, L: FullLeaf> DataOrHash<H, L> {
/// you don't care about with their hashes.
#[derive(RuntimeDebug, Clone, PartialEq)]
pub struct Compact<H, T> {
/// Internal tuple representation.
pub tuple: T,
_hash: sp_std::marker::PhantomData<H>,
}
@@ -177,6 +188,7 @@ impl<H, T> sp_std::ops::Deref for Compact<H, T> {
}
impl<H, T> Compact<H, T> {
/// Create a new [Compact] wrapper for a tuple.
pub fn new(tuple: T) -> Self {
Self { tuple, _hash: Default::default() }
}
@@ -274,15 +286,114 @@ pub struct Proof<Hash> {
pub items: Vec<Hash>,
}
/// Merkle Mountain Range operation error.
#[derive(RuntimeDebug, codec::Encode, codec::Decode, 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 {
#![allow(unused_variables)]
/// Consume given error `e` with `self` and generate a native log entry with error details.
pub 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 fn log_debug(self, e: impl fmt::Debug) -> Self {
debug::native::debug!("[{:?}] MMR error: {:?}", self, e);
self
}
}
/// A helper type to allow using arbitrary SCALE-encoded leaf data in the RuntimeApi.
///
/// The point is to be able to verify MMR proofs from external MMRs, where we don't
/// know the exact leaf type, but it's enough for us to have it SCALE-encoded.
///
/// Note the leaf type should be encoded in its compact form when passed through this type.
/// See [FullLeaf] documentation for details.
///
/// This type does not implement SCALE encoding/decoding on purpose to avoid confusion,
/// it would have to be SCALE-compatible with the concrete leaf type, but due to SCALE limitations
/// it's not possible to know how many bytes the encoding of concrete leaf type uses.
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[derive(RuntimeDebug, Clone, PartialEq)]
pub struct OpaqueLeaf(
/// Raw bytes of the leaf type encoded in its compact form.
///
/// NOTE it DOES NOT include length prefix (like `Vec<u8>` encoding would).
#[cfg_attr(feature = "std", serde(with = "sp_core::bytes"))]
pub Vec<u8>
);
impl OpaqueLeaf {
/// Convert a concrete MMR leaf into an opaque type.
pub fn from_leaf<T: FullLeaf>(leaf: &T) -> Self {
let encoded_leaf = leaf.using_encoded(|d| d.to_vec(), true);
OpaqueLeaf::from_encoded_leaf(encoded_leaf)
}
/// Create a `OpaqueLeaf` given raw bytes of compact-encoded leaf.
pub fn from_encoded_leaf(encoded_leaf: Vec<u8>) -> Self {
OpaqueLeaf(encoded_leaf)
}
}
impl FullLeaf for OpaqueLeaf {
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F, _compact: bool) -> R {
f(&self.0)
}
}
sp_api::decl_runtime_apis! {
/// API to interact with MMR pallet.
pub trait MmrApi<Leaf: codec::Codec, Hash: codec::Codec> {
/// Generate MMR proof for a leaf under given index.
fn generate_proof(leaf_index: u64) -> Result<(Leaf, Proof<Hash>), Error>;
/// Verify MMR proof against on-chain MMR.
///
/// Note this function will use on-chain MMR root hash and check if the proof
/// matches the hash.
/// See [Self::verify_proof_stateless] for a stateless verifier.
fn verify_proof(leaf: Leaf, proof: Proof<Hash>) -> Result<(), Error>;
/// Verify MMR proof against given root hash.
///
/// Note this function does not require any on-chain storage - the
/// proof is verified against given MMR root hash.
///
/// The leaf data is expected to be encoded in it's compact form.
fn verify_proof_stateless(root: Hash, leaf: Vec<u8>, proof: Proof<Hash>)
-> Result<(), Error>;
}
}
#[cfg(test)]
mod tests {
use super::*;
use codec::Decode;
use crate::tests::hex;
use sp_core::H256;
use sp_runtime::traits::Keccak256;
pub(crate) fn hex(s: &str) -> H256 {
s.parse().unwrap()
}
type Test = DataOrHash<Keccak256, String>;
type TestCompact = Compact<Keccak256, (Test, Test)>;
type TestProof = Proof<<Keccak256 as traits::Hash>::Output>;
@@ -412,4 +523,35 @@ mod tests {
assert_eq!(decoded_compact, vec![Ok(d.clone()), Ok(d.clone())]);
}
#[test]
fn opaque_leaves_should_be_scale_compatible_with_concrete_ones() {
// 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()];
let encoded_compact = cases
.iter()
.map(|c| c.using_encoded(|x| x.to_vec(), true))
.map(OpaqueLeaf::from_encoded_leaf)
.collect::<Vec<_>>();
let opaque = cases
.iter()
.map(OpaqueLeaf::from_leaf)
.collect::<Vec<_>>();
// then
assert_eq!(
encoded_compact,
opaque,
);
}
}
@@ -73,7 +73,7 @@ mod mock;
#[cfg(test)]
mod tests;
pub mod primitives;
pub use pallet_mmr_primitives as primitives;
pub trait WeightInfo {
fn on_initialize(peaks: u64) -> Weight;
@@ -118,6 +118,9 @@ pub trait Config<I = DefaultInstance>: frame_system::Config {
/// [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.
///
/// Note that the leaf at each block MUST be unique. You may want to include a block hash or block
/// number as an easiest way to ensure that.
type LeafData: primitives::LeafDataProvider;
/// A hook to act on the new MMR root.
@@ -182,6 +185,28 @@ type LeafOf<T, I> = <<T as Config<I>>::LeafData as primitives::LeafDataProvider>
/// Hashing used for the pallet.
pub(crate) type HashingOf<T, I> = <T as Config<I>>::Hashing;
/// Stateless MMR proof verification.
///
/// This function can be used to verify received MMR proof (`proof`)
/// for given leaf data (`leaf`) against a known MMR root hash (`root`).
///
/// The verification does not require any storage access.
pub fn verify_leaf_proof<H, L>(
root: H::Output,
leaf: mmr::Node<H, L>,
proof: primitives::Proof<H::Output>,
) -> Result<(), primitives::Error> where
H: traits::Hash,
L: primitives::FullLeaf,
{
let is_valid = mmr::verify_leaf_proof::<H, L>(root, leaf, proof)?;
if is_valid {
Ok(())
} else {
Err(primitives::Error::Verify.log_debug(("The proof is incorrect.", root)))
}
}
impl<T: Config<I>, I: Instance> Module<T, I> {
fn offchain_key(pos: u64) -> sp_std::prelude::Vec<u8> {
(T::INDEXING_PREFIX, pos).encode()
@@ -195,7 +220,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> {
/// 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,
primitives::Error,
> {
let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(Self::mmr_leaves());
mmr.generate_proof(leaf_index)
@@ -210,12 +235,12 @@ impl<T: Config<I>, I: Instance> Module<T, I> {
pub fn verify_leaf(
leaf: LeafOf<T, I>,
proof: primitives::Proof<<T as Config<I>>::Hash>,
) -> Result<(), mmr::Error> {
) -> Result<(), primitives::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(
return Err(primitives::Error::Verify.log_debug(
"The proof has incorrect number of leaves or proof items."
));
}
@@ -225,7 +250,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> {
if is_valid {
Ok(())
} else {
Err(mmr::Error::Verify.log_debug("The proof is incorrect."))
Err(primitives::Error::Verify.log_debug("The proof is incorrect."))
}
}
}
@@ -22,12 +22,35 @@ use crate::{
storage::{Storage, OffchainStorage, RuntimeStorage},
utils::NodesUtils,
},
primitives,
primitives::{self, Error},
};
use frame_support::{debug, RuntimeDebug};
use sp_std::fmt;
#[cfg(not(feature = "std"))]
use sp_std::{vec, prelude::Vec};
use sp_std::vec;
/// Stateless verification of the leaf proof.
pub fn verify_leaf_proof<H, L>(
root: H::Output,
leaf: Node<H, L>,
proof: primitives::Proof<H::Output>,
) -> Result<bool, Error> where
H: sp_runtime::traits::Hash,
L: primitives::FullLeaf,
{
let size = NodesUtils::new(proof.leaf_count).size();
let leaf_position = mmr_lib::leaf_index_to_pos(proof.leaf_index);
let p = mmr_lib::MerkleProof::<
Node<H, L>,
Hasher<H, L>,
>::new(
size,
proof.items.into_iter().map(Node::Hash).collect(),
);
p.verify(
Node::Hash(root),
vec![(leaf_position, leaf)],
).map_err(|e| Error::Verify.log_debug(e))
}
/// A wrapper around a MMR library to expose limited functionality.
///
@@ -123,7 +146,7 @@ impl<T, I, L> Mmr<RuntimeStorage, T, I, L> where
impl<T, I, L> Mmr<OffchainStorage, T, I, L> where
T: Config<I>,
I: Instance,
L: primitives::FullLeaf,
L: primitives::FullLeaf + codec::Decode,
{
/// Generate a proof for given leaf index.
///
@@ -151,36 +174,3 @@ impl<T, I, L> Mmr<OffchainStorage, T, I, L> where
}
}
/// 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
}
}
@@ -22,7 +22,7 @@ mod mmr;
use crate::primitives::FullLeaf;
use sp_runtime::traits;
pub use self::mmr::{Mmr, Error};
pub use self::mmr::{Mmr, verify_leaf_proof};
/// Node type for runtime `T`.
pub type NodeOf<T, I, L> = Node<<T as crate::Config<I>>::Hashing, L>;
@@ -57,7 +57,7 @@ impl<StorageType, T, I, L> Default for Storage<StorageType, T, I, L> {
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,
L: primitives::FullLeaf + codec::Decode,
{
fn get_elem(&self, pos: u64) -> mmr_lib::Result<Option<NodeOf<T, I, L>>> {
let key = Module::<T, I>::offchain_key(pos);
@@ -16,12 +16,12 @@
// limitations under the License.
use crate::*;
use crate::primitives::{LeafDataProvider, Compact};
use codec::{Encode, Decode};
use frame_support::{
impl_outer_origin, parameter_types,
};
use pallet_mmr_primitives::{LeafDataProvider, Compact};
use sp_core::H256;
use sp_runtime::{
testing::Header,
@@ -17,7 +17,6 @@
use crate::*;
use crate::mock::*;
use crate::primitives::{Proof, Compact};
use frame_support::traits::OnInitialize;
use sp_core::{
@@ -27,6 +26,7 @@ use sp_core::{
OffchainExt,
},
};
use pallet_mmr_primitives::{Proof, Compact};
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
@@ -55,12 +55,14 @@ pub(crate) fn hex(s: &str) -> H256 {
s.parse().unwrap()
}
type BlockNumber = <Test as frame_system::Config>::BlockNumber;
fn decode_node(v: Vec<u8>) -> mmr::Node<
<Test as Config>::Hashing,
(H256, LeafData),
((BlockNumber, H256), LeafData),
> {
use crate::primitives::DataOrHash;
type A = DataOrHash::<<Test as Config>::Hashing, H256>;
type A = DataOrHash::<<Test as Config>::Hashing, (BlockNumber, 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();
@@ -97,10 +99,10 @@ fn should_start_empty() {
// then
assert_eq!(crate::NumberOfLeaves::<DefaultInstance>::get(), 1);
assert_eq!(crate::Nodes::<Test>::get(0),
Some(hex("da5e6d0616e05c6a6348605a37ca33493fc1a15ad1e6a405ee05c17843fdafed")));
Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")));
assert_eq!(
crate::RootHash::<Test>::get(),
hex("da5e6d0616e05c6a6348605a37ca33493fc1a15ad1e6a405ee05c17843fdafed")
hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")
);
assert!(weight != 0);
});
@@ -117,32 +119,34 @@ fn should_append_to_mmr_when_on_initialize_is_called() {
// 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!(
assert_eq!((
crate::Nodes::<Test>::get(0),
crate::Nodes::<Test>::get(1),
crate::Nodes::<Test>::get(2),
crate::Nodes::<Test>::get(3),
crate::RootHash::<Test>::get(),
hex("bc54778fab79f586f007bd408dca2c4aa07959b27d1f2c8f4f2549d1fcfac8f8")
);
), (
Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")),
Some(hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705")),
Some(hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854")),
None,
hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"),
));
});
// 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),
(0, 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),
(1, 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")
hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854")
)));
assert_eq!(offchain_db.get(&MMR::offchain_key(3)), None);
}
@@ -156,14 +160,15 @@ fn should_construct_larger_mmr_correctly() {
// 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!(
assert_eq!((
crate::Nodes::<Test>::get(0),
crate::Nodes::<Test>::get(10),
crate::RootHash::<Test>::get(),
hex("fc4f9042bd2f73feb26f3fc42db834c5f1943fa20070ddf106c486a478a0d561")
);
), (
Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")),
Some(hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c")),
hex("e45e25259f7930626431347fa4dd9aae7ac83b4966126d425ca70ab343709d2c"),
));
});
}
@@ -187,38 +192,38 @@ fn should_generate_proofs_correctly() {
// then
assert_eq!(proofs[0], (Compact::new((
H256::repeat_byte(1).into(),
(0, H256::repeat_byte(1)).into(),
LeafData::new(1).into(),
)), Proof {
leaf_index: 0,
leaf_count: 7,
items: vec![
hex("ff5d891b28463a3440e1b650984685efdf260e482cb3807d53c49090841e755f"),
hex("00b0046bd2d63fcb760cf50a262448bb2bbf9a264b0b0950d8744044edf00dc3"),
hex("16de0900b57bf359a0733674ebfbba0f494e95a8391b4bfeae850019399f3ec0"),
hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"),
hex("cb24f4614ad5b2a5430344c99545b421d9af83c46fd632d70a332200884b4d46"),
hex("dca421199bdcc55bb773c6b6967e8d16675de69062b52285ca63685241fdf626"),
],
}));
assert_eq!(proofs[4], (Compact::new((
H256::repeat_byte(5).into(),
(4, H256::repeat_byte(5)).into(),
LeafData::new(5).into(),
)), Proof {
leaf_index: 4,
leaf_count: 7,
items: vec![
hex("e53ee36ba6c068b1a6cfef7862fed5005df55615e1c9fa6eeefe08329ac4b94b"),
hex("c09d4a008a0f1ef37860bef33ec3088ccd94268c0bfba7ff1b3c2a1075b0eb92"),
hex("af3327deed0515c8d1902c9b5cd375942d42f388f3bfe3d1cd6e1b86f9cc456c"),
hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"),
hex("8ed25570209d8f753d02df07c1884ddb36a3d9d4770e4608b188322151c657fe"),
hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c"),
],
}));
assert_eq!(proofs[6], (Compact::new((
H256::repeat_byte(7).into(),
(6, H256::repeat_byte(7)).into(),
LeafData::new(7).into(),
)), Proof {
leaf_index: 6,
leaf_count: 7,
items: vec![
hex("e53ee36ba6c068b1a6cfef7862fed5005df55615e1c9fa6eeefe08329ac4b94b"),
hex("dad09f50b41822fc5ecadc25b08c3a61531d4d60e962a5aa0b6998fad5c37c5e"),
hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"),
hex("7e4316ae2ebf7c3b6821cb3a46ca8b7a4f9351a9b40fcf014bb0a4fd8e8f29da"),
],
}));
});
@@ -253,6 +258,30 @@ fn should_verify() {
});
}
#[test]
fn verification_should_be_stateless() {
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()
});
let root = ext.execute_with(|| crate::Module::<Test>::mmr_root_hash());
// Verify proof without relying on any on-chain data.
let leaf = crate::primitives::DataOrHash::Data(leaf);
assert_eq!(crate::verify_leaf_proof::<<Test as Config>::Hashing, _>(root, leaf, proof5), Ok(()));
}
#[test]
fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() {
let _ = env_logger::try_init();