diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 19bb14e0df..d09e873ce6 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -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" diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index fc22f440ca..38b3a2bdcf 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -85,6 +85,7 @@ members = [ "frame/lottery", "frame/membership", "frame/merkle-mountain-range", + "frame/merkle-mountain-range/primitives", "frame/metadata", "frame/multisig", "frame/nicks", diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index e74c61a9c0..beaef37eeb 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -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, Runtime, AllModules>; +/// MMR helper types. +mod mmr { + use super::Runtime; + pub use pallet_mmr::primitives::*; + + pub type Leaf = < + ::LeafData + as + LeafDataProvider + >::LeafData; + pub type Hash = ::Hash; + pub type Hashing = ::Hashing; +} + impl_runtime_apis! { impl sp_api::Core 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::Error> { + Mmr::generate_proof(leaf_index) + } + + fn verify_proof(leaf: mmr::Leaf, proof: mmr::Proof) -> Result<(), mmr::Error> { + Mmr::verify_leaf(leaf, proof) + } + + fn verify_proof_stateless( + root: mmr::Hash, + leaf: Vec, + proof: mmr::Proof + ) -> Result<(), mmr::Error> { + let node = mmr::DataOrHash::Data(mmr::OpaqueLeaf(leaf)); + pallet_mmr::verify_leaf_proof::(root, node, proof) + } + } + impl sp_session::SessionKeys for Runtime { fn generate_session_keys(seed: Option>) -> Vec { SessionKeys::generate(seed) diff --git a/substrate/frame/merkle-mountain-range/Cargo.toml b/substrate/frame/merkle-mountain-range/Cargo.toml index 096333680c..f6441b103f 100644 --- a/substrate/frame/merkle-mountain-range/Cargo.toml +++ b/substrate/frame/merkle-mountain-range/Cargo.toml @@ -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", diff --git a/substrate/frame/merkle-mountain-range/primitives/Cargo.toml b/substrate/frame/merkle-mountain-range/primitives/Cargo.toml new file mode 100644 index 0000000000..1db7bf2fd3 --- /dev/null +++ b/substrate/frame/merkle-mountain-range/primitives/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "pallet-mmr-primitives" +version = "2.0.0" +authors = ["Parity Technologies "] +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", +] diff --git a/substrate/frame/merkle-mountain-range/src/primitives.rs b/substrate/frame/merkle-mountain-range/primitives/src/lib.rs similarity index 71% rename from substrate/frame/merkle-mountain-range/src/primitives.rs rename to substrate/frame/merkle-mountain-range/primitives/src/lib.rs index 4d13a32c89..b8258d9b73 100644 --- a/substrate/frame/merkle-mountain-range/src/primitives.rs +++ b/substrate/frame/merkle-mountain-range/primitives/src/lib.rs @@ -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 LeafDataProvider for frame_system::Module { - type LeafData = ::Hash; + type LeafData = ( + ::BlockNumber, + ::Hash + ); fn leaf_data() -> Self::LeafData { - Self::parent_hash() + ( + Self::block_number().saturating_sub(One::one()), + Self::parent_hash() + ) } } @@ -70,7 +80,7 @@ impl OnNewRoot 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 codec::Decode for DataOrHash { + impl codec::Decode for DataOrHash { fn decode(value: &mut I) -> Result { let decoded: Either, H::Output> = Either::decode(value)?; Ok(match decoded { @@ -164,6 +174,7 @@ impl DataOrHash { /// you don't care about with their hashes. #[derive(RuntimeDebug, Clone, PartialEq)] pub struct Compact { + /// Internal tuple representation. pub tuple: T, _hash: sp_std::marker::PhantomData, } @@ -177,6 +188,7 @@ impl sp_std::ops::Deref for Compact { } impl Compact { + /// 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 { pub items: Vec, } +/// 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` encoding would). + #[cfg_attr(feature = "std", serde(with = "sp_core::bytes"))] + pub Vec +); + +impl OpaqueLeaf { + /// Convert a concrete MMR leaf into an opaque type. + pub fn from_leaf(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) -> Self { + OpaqueLeaf(encoded_leaf) + } +} + +impl FullLeaf for OpaqueLeaf { + fn using_encoded R>(&self, f: F, _compact: bool) -> R { + f(&self.0) + } +} + +sp_api::decl_runtime_apis! { + /// API to interact with MMR pallet. + pub trait MmrApi { + /// Generate MMR proof for a leaf under given index. + fn generate_proof(leaf_index: u64) -> Result<(Leaf, Proof), 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) -> 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, proof: Proof) + -> 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; type TestCompact = Compact; type TestProof = Proof<::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::>(); + + let opaque = cases + .iter() + .map(OpaqueLeaf::from_leaf) + .collect::>(); + + // then + assert_eq!( + encoded_compact, + opaque, + ); + } } diff --git a/substrate/frame/merkle-mountain-range/src/lib.rs b/substrate/frame/merkle-mountain-range/src/lib.rs index 85e448fd3a..b137be7b53 100644 --- a/substrate/frame/merkle-mountain-range/src/lib.rs +++ b/substrate/frame/merkle-mountain-range/src/lib.rs @@ -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: 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 = <>::LeafData as primitives::LeafDataProvider> /// Hashing used for the pallet. pub(crate) type HashingOf = >::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( + root: H::Output, + leaf: mmr::Node, + proof: primitives::Proof, +) -> Result<(), primitives::Error> where + H: traits::Hash, + L: primitives::FullLeaf, +{ + let is_valid = mmr::verify_leaf_proof::(root, leaf, proof)?; + if is_valid { + Ok(()) + } else { + Err(primitives::Error::Verify.log_debug(("The proof is incorrect.", root))) + } +} + impl, I: Instance> Module { fn offchain_key(pos: u64) -> sp_std::prelude::Vec { (T::INDEXING_PREFIX, pos).encode() @@ -195,7 +220,7 @@ impl, I: Instance> Module { /// It may return an error or panic if used incorrectly. pub fn generate_proof(leaf_index: u64) -> Result< (LeafOf, primitives::Proof<>::Hash>), - mmr::Error, + primitives::Error, > { let mmr: ModuleMmr = mmr::Mmr::new(Self::mmr_leaves()); mmr.generate_proof(leaf_index) @@ -210,12 +235,12 @@ impl, I: Instance> Module { pub fn verify_leaf( leaf: LeafOf, proof: primitives::Proof<>::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, I: Instance> Module { 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.")) } } } diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs index 10762d98d7..a3d373bfd2 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -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( + root: H::Output, + leaf: Node, + proof: primitives::Proof, +) -> Result 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, + Hasher, + >::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 Mmr where impl Mmr where T: Config, I: Instance, - L: primitives::FullLeaf, + L: primitives::FullLeaf + codec::Decode, { /// Generate a proof for given leaf index. /// @@ -151,36 +174,3 @@ impl Mmr 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 - } - -} - diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs index 38833af6f2..e705b24706 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs @@ -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 = Node<>::Hashing, L>; diff --git a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs index c8390e2704..0bff53f2fb 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs @@ -57,7 +57,7 @@ impl Default for Storage { impl mmr_lib::MMRStore> for Storage where T: Config, I: Instance, - L: primitives::FullLeaf, + L: primitives::FullLeaf + codec::Decode, { fn get_elem(&self, pos: u64) -> mmr_lib::Result>> { let key = Module::::offchain_key(pos); diff --git a/substrate/frame/merkle-mountain-range/src/mock.rs b/substrate/frame/merkle-mountain-range/src/mock.rs index 153aecdbd3..2cb4e7c4dc 100644 --- a/substrate/frame/merkle-mountain-range/src/mock.rs +++ b/substrate/frame/merkle-mountain-range/src/mock.rs @@ -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, diff --git a/substrate/frame/merkle-mountain-range/src/tests.rs b/substrate/frame/merkle-mountain-range/src/tests.rs index c279e42a8c..63e4ec2257 100644 --- a/substrate/frame/merkle-mountain-range/src/tests.rs +++ b/substrate/frame/merkle-mountain-range/src/tests.rs @@ -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::().unwrap().into() @@ -55,12 +55,14 @@ pub(crate) fn hex(s: &str) -> H256 { s.parse().unwrap() } +type BlockNumber = ::BlockNumber; + fn decode_node(v: Vec) -> mmr::Node< ::Hashing, - (H256, LeafData), + ((BlockNumber, H256), LeafData), > { use crate::primitives::DataOrHash; - type A = DataOrHash::<::Hashing, H256>; + type A = DataOrHash::<::Hashing, (BlockNumber, H256)>; type B = DataOrHash::<::Hashing, LeafData>; type Node = mmr::Node<::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::::get(), 1); assert_eq!(crate::Nodes::::get(0), - Some(hex("da5e6d0616e05c6a6348605a37ca33493fc1a15ad1e6a405ee05c17843fdafed"))); + Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0"))); assert_eq!( crate::RootHash::::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::::get(), 2); - assert_eq!(crate::Nodes::::get(0), - Some(hex("da5e6d0616e05c6a6348605a37ca33493fc1a15ad1e6a405ee05c17843fdafed"))); - assert_eq!(crate::Nodes::::get(1), - Some(hex("ff5d891b28463a3440e1b650984685efdf260e482cb3807d53c49090841e755f"))); - assert_eq!(crate::Nodes::::get(2), - Some(hex("bc54778fab79f586f007bd408dca2c4aa07959b27d1f2c8f4f2549d1fcfac8f8"))); - assert_eq!(crate::Nodes::::get(3), None); - assert_eq!( + assert_eq!(( + crate::Nodes::::get(0), + crate::Nodes::::get(1), + crate::Nodes::::get(2), + crate::Nodes::::get(3), crate::RootHash::::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::::get(), 7); - assert_eq!(crate::Nodes::::get(0), - Some(hex("da5e6d0616e05c6a6348605a37ca33493fc1a15ad1e6a405ee05c17843fdafed"))); - assert_eq!(crate::Nodes::::get(10), - Some(hex("af3327deed0515c8d1902c9b5cd375942d42f388f3bfe3d1cd6e1b86f9cc456c"))); - assert_eq!( + assert_eq!(( + crate::Nodes::::get(0), + crate::Nodes::::get(10), crate::RootHash::::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::::generate_proof(5).unwrap() + }); + let root = ext.execute_with(|| crate::Module::::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::<::Hashing, _>(root, leaf, proof5), Ok(())); +} + #[test] fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { let _ = env_logger::try_init();