// This file is part of Substrate. // Copyright (C) 2020-2022 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::{mmr::utils, mock::*, *}; use frame_support::traits::OnInitialize; use mmr_lib::helper; use pallet_mmr_primitives::{Compact, Proof}; use sp_core::{ offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, H256, }; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::default().build_storage::().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(OffchainDbExt::new(offchain.clone())); ext.register_extension(OffchainWorkerExt::new(offchain)); } fn new_block() -> u64 { let number = frame_system::Pallet::::block_number() + 1; let hash = H256::repeat_byte(number as u8); LEAF_DATA.with(|r| r.borrow_mut().a = number); frame_system::Pallet::::reset_events(); frame_system::Pallet::::initialize(&number, &hash, &Default::default()); MMR::on_initialize(number) } fn peaks_from_leaves_count(leaves_count: NodeIndex) -> Vec { let size = utils::NodesUtils::new(leaves_count).size(); helper::get_peaks(size) } pub(crate) fn hex(s: &str) -> H256 { s.parse().unwrap() } type BlockNumber = ::BlockNumber; fn decode_node( v: Vec, ) -> mmr::Node<::Hashing, ((BlockNumber, H256), LeafData)> { use crate::primitives::DataOrHash; 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(); 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::::get(), "0000000000000000000000000000000000000000000000000000000000000000" .parse() .unwrap() ); assert_eq!(crate::NumberOfLeaves::::get(), 0); assert_eq!(crate::Nodes::::get(0), None); // when let weight = new_block(); // then assert_eq!(crate::NumberOfLeaves::::get(), 1); assert_eq!( crate::Nodes::::get(0), Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")) ); assert_eq!( crate::RootHash::::get(), hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0") ); 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(); // then assert_eq!(crate::NumberOfLeaves::::get(), 1); assert_eq!( ( crate::Nodes::::get(0), crate::Nodes::::get(1), crate::RootHash::::get(), ), ( Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")), None, hex("0x4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0"), ) ); // when new_block(); // then assert_eq!(crate::NumberOfLeaves::::get(), 2); let peaks = peaks_from_leaves_count(2); assert_eq!(peaks, vec![2]); assert_eq!( ( crate::Nodes::::get(0), crate::Nodes::::get(1), crate::Nodes::::get(2), crate::Nodes::::get(3), crate::RootHash::::get(), ), ( None, None, 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(((0, H256::repeat_byte(1)), LeafData::new(1),))) ); assert_eq!( offchain_db.get(&MMR::offchain_key(1)).map(decode_node), Some(mmr::Node::Data(((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( "672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854" ))) ); 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::::get(), 7); let peaks = peaks_from_leaves_count(7); assert_eq!(peaks, vec![6, 9, 10]); for i in (0..=10).filter(|p| !peaks.contains(p)) { assert!(crate::Nodes::::get(i).is_none()); } assert_eq!( ( crate::Nodes::::get(6), crate::Nodes::::get(9), crate::Nodes::::get(10), crate::RootHash::::get(), ), ( Some(hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252")), Some(hex("7e4316ae2ebf7c3b6821cb3a46ca8b7a4f9351a9b40fcf014bb0a4fd8e8f29da")), Some(hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c")), hex("e45e25259f7930626431347fa4dd9aae7ac83b4966126d425ca70ab343709d2c"), ) ); }); } #[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::::get()) .into_iter() .map(|leaf_index| crate::Pallet::::generate_proof(leaf_index).unwrap()) .collect::>(); // then assert_eq!( proofs[0], ( Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),)), Proof { leaf_index: 0, leaf_count: 7, items: vec![ hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"), hex("cb24f4614ad5b2a5430344c99545b421d9af83c46fd632d70a332200884b4d46"), hex("dca421199bdcc55bb773c6b6967e8d16675de69062b52285ca63685241fdf626"), ], } ) ); assert_eq!( proofs[4], ( Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),)), Proof { leaf_index: 4, leaf_count: 7, items: vec![ hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"), hex("8ed25570209d8f753d02df07c1884ddb36a3d9d4770e4608b188322151c657fe"), hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c"), ], } ) ); assert_eq!( proofs[6], ( Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),)), Proof { leaf_index: 6, leaf_count: 7, items: vec![ hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"), hex("7e4316ae2ebf7c3b6821cb3a46ca8b7a4f9351a9b40fcf014bb0a4fd8e8f29da"), ], } ) ); }); } #[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::Pallet::::generate_proof(5).unwrap() }); ext.execute_with(|| { init_chain(7); // then assert_eq!(crate::Pallet::::verify_leaf(leaf, proof5), Ok(())); }); } #[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::Pallet::::generate_proof(5).unwrap() }); let root = ext.execute_with(|| crate::Pallet::::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(); 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::Pallet::::generate_proof(5).unwrap(); new_block(); // then assert_eq!(crate::Pallet::::verify_leaf(leaf, proof5), Ok(())); }); }