pallet-mmr: move offchain logic to client-side gadget (#12753)

* Move MMR utils methods from pallet to primitives

Signed-off-by: Serban Iorga <serban@parity.io>

* Add method to MmrApi

* Move forks expanding logic from babe to primitives

* Implement MMR gadget

* Remove prunning logic from the MMR pallet

* Code review changes: 1st iteration

* Replace MaybeCanonEngine with CanonEngineBuilder

* fix mmr_leaves_count() for kitchen sink demo

* Update client/merkle-mountain-range/src/canon_engine.rs

Co-authored-by: Adrian Catangiu <adrian@parity.io>

* Code review changes: 2nd iteration

* fix INDEXING_PREFIX

* impl review comments

* add documentation and minor rename

Signed-off-by: Serban Iorga <serban@parity.io>
Co-authored-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
Serban Iorga
2022-11-29 16:39:52 +02:00
committed by GitHub
parent d56214c21f
commit ff439ee335
21 changed files with 1161 additions and 561 deletions
@@ -15,19 +15,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{
mmr::{storage::PruningMap, utils},
mock::*,
*,
};
use crate::{mock::*, *};
use frame_support::traits::{Get, OnInitialize};
use mmr_lib::helper;
use sp_core::{
offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt},
H256,
};
use sp_mmr_primitives::{Compact, Proof};
use sp_mmr_primitives::{mmr_lib::helper, utils, Compact, Proof};
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
@@ -171,21 +166,26 @@ fn should_append_to_mmr_when_on_initialize_is_called() {
let offchain_db = ext.offchain_db();
let expected = Some(mmr::Node::Data(((0, H256::repeat_byte(1)), LeafData::new(1))));
assert_eq!(offchain_db.get(&MMR::node_offchain_key(0, parent_b1)).map(decode_node), expected);
assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(0)).map(decode_node), expected);
assert_eq!(
offchain_db.get(&MMR::node_temp_offchain_key(0, parent_b1)).map(decode_node),
expected
);
let expected = Some(mmr::Node::Data(((1, H256::repeat_byte(2)), LeafData::new(2))));
assert_eq!(offchain_db.get(&MMR::node_offchain_key(1, parent_b2)).map(decode_node), expected);
assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(1)).map(decode_node), expected);
assert_eq!(
offchain_db.get(&MMR::node_temp_offchain_key(1, parent_b2)).map(decode_node),
expected
);
let expected = Some(mmr::Node::Hash(hex(
"672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854",
)));
assert_eq!(offchain_db.get(&MMR::node_offchain_key(2, parent_b2)).map(decode_node), expected);
assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(2)).map(decode_node), expected);
assert_eq!(
offchain_db.get(&MMR::node_temp_offchain_key(2, parent_b2)).map(decode_node),
expected
);
assert_eq!(offchain_db.get(&MMR::node_offchain_key(3, parent_b2)), None);
assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(3)), None);
assert_eq!(offchain_db.get(&MMR::node_temp_offchain_key(3, parent_b2)), None);
}
#[test]
@@ -219,6 +219,27 @@ fn should_construct_larger_mmr_correctly() {
});
}
#[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];
// size cross-check
let mut actual_sizes = vec![];
for s in &leaves[1..] {
new_test_ext().execute_with(|| {
let mut mmr = mmr::Mmr::<mmr::storage::RuntimeStorage, crate::mock::Test, _, _>::new(0);
for i in 0..*s {
mmr.push(i);
}
actual_sizes.push(mmr.size());
})
}
assert_eq!(sizes[1..], actual_sizes[..]);
}
#[test]
fn should_generate_proofs_correctly() {
let _ = env_logger::try_init();
@@ -698,208 +719,6 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() {
});
}
#[test]
fn should_verify_pruning_map() {
use sp_core::offchain::StorageKind;
use sp_io::offchain;
let _ = env_logger::try_init();
let mut ext = new_test_ext();
register_offchain_ext(&mut ext);
ext.execute_with(|| {
type TestPruningMap = PruningMap<Test, ()>;
fn offchain_decoded(key: Vec<u8>) -> Option<Vec<H256>> {
offchain::local_storage_get(StorageKind::PERSISTENT, &key)
.and_then(|v| codec::Decode::decode(&mut &*v).ok())
}
// test append
{
TestPruningMap::append(1, H256::repeat_byte(1));
TestPruningMap::append(2, H256::repeat_byte(21));
TestPruningMap::append(2, H256::repeat_byte(22));
TestPruningMap::append(3, H256::repeat_byte(31));
TestPruningMap::append(3, H256::repeat_byte(32));
TestPruningMap::append(3, H256::repeat_byte(33));
// `0` not present
let map_key = TestPruningMap::pruning_map_offchain_key(0);
assert_eq!(offchain::local_storage_get(StorageKind::PERSISTENT, &map_key), None);
// verify `1` entries
let map_key = TestPruningMap::pruning_map_offchain_key(1);
let expected = vec![H256::repeat_byte(1)];
assert_eq!(offchain_decoded(map_key), Some(expected));
// verify `2` entries
let map_key = TestPruningMap::pruning_map_offchain_key(2);
let expected = vec![H256::repeat_byte(21), H256::repeat_byte(22)];
assert_eq!(offchain_decoded(map_key), Some(expected));
// verify `3` entries
let map_key = TestPruningMap::pruning_map_offchain_key(3);
let expected =
vec![H256::repeat_byte(31), H256::repeat_byte(32), H256::repeat_byte(33)];
assert_eq!(offchain_decoded(map_key), Some(expected));
// `4` not present
let map_key = TestPruningMap::pruning_map_offchain_key(4);
assert_eq!(offchain::local_storage_get(StorageKind::PERSISTENT, &map_key), None);
}
// test remove
{
// `0` doesn't return anything
assert_eq!(TestPruningMap::remove(0), None);
// remove and verify `1` entries
let expected = vec![H256::repeat_byte(1)];
assert_eq!(TestPruningMap::remove(1), Some(expected));
// remove and verify `2` entries
let expected = vec![H256::repeat_byte(21), H256::repeat_byte(22)];
assert_eq!(TestPruningMap::remove(2), Some(expected));
// remove and verify `3` entries
let expected =
vec![H256::repeat_byte(31), H256::repeat_byte(32), H256::repeat_byte(33)];
assert_eq!(TestPruningMap::remove(3), Some(expected));
// `4` doesn't return anything
assert_eq!(TestPruningMap::remove(4), None);
// no entries left in offchain map
for block in 0..5 {
let map_key = TestPruningMap::pruning_map_offchain_key(block);
assert_eq!(offchain::local_storage_get(StorageKind::PERSISTENT, &map_key), None);
}
}
})
}
#[test]
fn should_canonicalize_offchain() {
use frame_support::traits::Hooks;
let _ = env_logger::try_init();
let mut ext = new_test_ext();
register_offchain_ext(&mut ext);
// adding 13 blocks that we'll later check have been canonicalized,
// (test assumes `13 < frame_system::BlockHashCount`).
let to_canon_count = 13u32;
// add 13 blocks and verify leaves and nodes for them have been added to
// offchain MMR using fork-proof keys.
for blocknum in 0..to_canon_count {
ext.execute_with(|| {
new_block();
<Pallet<Test> as Hooks<BlockNumber>>::offchain_worker(blocknum.into());
});
ext.persist_offchain_overlay();
}
let offchain_db = ext.offchain_db();
ext.execute_with(|| {
// verify leaves added by blocks 1..=13
for block_num in 1..=to_canon_count {
let parent_num: BlockNumber = (block_num - 1).into();
let leaf_index = u64::from(block_num - 1);
let pos = helper::leaf_index_to_pos(leaf_index.into());
let parent_hash = <frame_system::Pallet<Test>>::block_hash(parent_num);
// Available in offchain db under both fork-proof key and canon key.
// We'll later check it is pruned from fork-proof key.
let expected = Some(mmr::Node::Data((
(leaf_index, H256::repeat_byte(u8::try_from(block_num).unwrap())),
LeafData::new(block_num.into()),
)));
assert_eq!(
offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node),
expected
);
assert_eq!(
offchain_db.get(&MMR::node_offchain_key(pos, parent_hash)).map(decode_node),
expected
);
}
// verify a couple of nodes and peaks:
// `pos` is node to verify,
// `leaf_index` is leaf that added node `pos`,
// `expected` is expected value of node at `pos`.
let verify = |pos: NodeIndex, leaf_index: LeafIndex, expected: H256| {
let parent_num: BlockNumber = leaf_index.try_into().unwrap();
let parent_hash = <frame_system::Pallet<Test>>::block_hash(parent_num);
// Available in offchain db under both fork-proof key and canon key.
// We'll later check it is pruned from fork-proof key.
let expected = Some(mmr::Node::Hash(expected));
assert_eq!(
offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node),
expected
);
assert_eq!(
offchain_db.get(&MMR::node_offchain_key(pos, parent_hash)).map(decode_node),
expected
);
};
verify(2, 1, hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"));
verify(13, 7, hex("441bf63abc7cf9b9e82eb57b8111c883d50ae468d9fd7f301e12269fc0fa1e75"));
verify(21, 11, hex("f323ac1a7f56de5f40ed8df3e97af74eec0ee9d72883679e49122ffad2ffd03b"));
});
// add another `frame_system::BlockHashCount` blocks and verify all nodes and leaves
// added by our original `to_canon_count` blocks have now been canonicalized in offchain db.
let block_hash_size: u64 = <Test as frame_system::Config>::BlockHashCount::get();
let base = to_canon_count;
for blocknum in base..(base + u32::try_from(block_hash_size).unwrap()) {
ext.execute_with(|| {
new_block();
<Pallet<Test> as Hooks<BlockNumber>>::offchain_worker(blocknum.into());
});
ext.persist_offchain_overlay();
}
ext.execute_with(|| {
// verify leaves added by blocks 1..=13, should be in offchain under canon key.
for block_num in 1..=to_canon_count {
let leaf_index = u64::from(block_num - 1);
let pos = helper::leaf_index_to_pos(leaf_index.into());
let parent_num: BlockNumber = (block_num - 1).into();
let parent_hash = <frame_system::Pallet<Test>>::block_hash(parent_num);
// no longer available in fork-proof storage (was pruned),
assert_eq!(offchain_db.get(&MMR::node_offchain_key(pos, parent_hash)), None);
// but available using canon key.
assert_eq!(
offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node),
Some(mmr::Node::Data((
(leaf_index, H256::repeat_byte(u8::try_from(block_num).unwrap())),
LeafData::new(block_num.into()),
)))
);
}
// also check some nodes and peaks:
// `pos` is node to verify,
// `leaf_index` is leaf that added node `pos`,
// `expected` is expected value of node at `pos`.
let verify = |pos: NodeIndex, leaf_index: LeafIndex, expected: H256| {
let parent_num: BlockNumber = leaf_index.try_into().unwrap();
let parent_hash = <frame_system::Pallet<Test>>::block_hash(parent_num);
// no longer available in fork-proof storage (was pruned),
assert_eq!(offchain_db.get(&MMR::node_offchain_key(pos, parent_hash)), None);
// but available using canon key.
assert_eq!(
offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node),
Some(mmr::Node::Hash(expected))
);
};
verify(2, 1, hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"));
verify(13, 7, hex("441bf63abc7cf9b9e82eb57b8111c883d50ae468d9fd7f301e12269fc0fa1e75"));
verify(21, 11, hex("f323ac1a7f56de5f40ed8df3e97af74eec0ee9d72883679e49122ffad2ffd03b"));
});
}
#[test]
fn should_verify_canonicalized() {
use frame_support::traits::Hooks;
@@ -955,21 +774,18 @@ fn does_not_panic_when_generating_historical_proofs() {
register_offchain_ext(&mut ext);
ext.execute_with(|| {
// when leaf index is invalid
assert_eq!(
crate::Pallet::<Test>::generate_proof(vec![10], None),
Err(Error::BlockNumToLeafIndex),
);
assert_eq!(crate::Pallet::<Test>::generate_proof(vec![10], None), Err(Error::LeafNotFound),);
// when leaves count is invalid
assert_eq!(
crate::Pallet::<Test>::generate_proof(vec![3], Some(100)),
Err(Error::BlockNumToLeafIndex),
Err(Error::GenerateProof),
);
// when both leaf index and leaves count are invalid
assert_eq!(
crate::Pallet::<Test>::generate_proof(vec![10], Some(100)),
Err(Error::BlockNumToLeafIndex),
Err(Error::LeafNotFound),
);
});
}