mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-20 23:21:02 +00:00
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:
@@ -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),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user