use crate::{mock::*, *};
use crate::primitives::{mmr_lib::helper, utils, Compact, LeafProof};
use frame::{
deps::sp_core::{
offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt},
H256,
},
testing_prelude::*,
};
pub(crate) fn new_test_ext() -> TestState {
frame_system::GenesisConfig::<Test>::default().build_storage().unwrap().into()
}
fn register_offchain_ext(ext: &mut TestState) {
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() -> Weight {
let number = frame_system::Pallet::<Test>::block_number() + 1;
let hash = H256::repeat_byte(number as u8);
LeafDataTestValue::mutate(|r| r.a = number);
frame_system::Pallet::<Test>::reset_events();
frame_system::Pallet::<Test>::initialize(&number, &hash, &Default::default());
MMR::on_initialize(number)
}
fn peaks_from_leaves_count(leaves_count: NodeIndex) -> Vec<NodeIndex> {
let size = utils::NodesUtils::new(leaves_count).size();
helper::get_peaks(size)
}
pub(crate) fn hex(s: &str) -> H256 {
s.parse().unwrap()
}
type BlockNumber = BlockNumberFor<Test>;
fn decode_node(
v: Vec<u8>,
) -> mmr::Node<<Test as Config>::Hashing, ((BlockNumber, H256), LeafData)> {
use crate::primitives::DataOrHash;
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();
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 add_blocks(blocks: usize) {
for _ in 0..blocks {
new_block();
}
}
#[test]
fn should_start_empty() {
sp_tracing::init_for_tests();
new_test_ext().execute_with(|| {
assert_eq!(
crate::RootHash::<Test>::get(),
"0000000000000000000000000000000000000000000000000000000000000000"
.parse()
.unwrap()
);
assert_eq!(crate::NumberOfLeaves::<Test>::get(), 0);
assert_eq!(crate::Nodes::<Test>::get(0), None);
let weight = new_block();
assert_eq!(crate::NumberOfLeaves::<Test>::get(), 1);
assert_eq!(
crate::Nodes::<Test>::get(0),
Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0"))
);
assert_eq!(
crate::RootHash::<Test>::get(),
hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")
);
assert!(weight != Weight::zero());
});
}
#[test]
fn should_append_to_mmr_when_on_initialize_is_called() {
sp_tracing::init_for_tests();
let mut ext = new_test_ext();
let (parent_b1, parent_b2) = ext.execute_with(|| {
new_block();
let parent_b1 = <frame_system::Pallet<Test>>::parent_hash();
assert_eq!(crate::NumberOfLeaves::<Test>::get(), 1);
assert_eq!(
(
crate::Nodes::<Test>::get(0),
crate::Nodes::<Test>::get(1),
crate::RootHash::<Test>::get(),
),
(
Some(hex("4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0")),
None,
hex("0x4320435e8c3318562dba60116bdbcc0b82ffcecb9bb39aae3300cfda3ad0b8b0"),
)
);
new_block();
let parent_b2 = <frame_system::Pallet<Test>>::parent_hash();
assert_eq!(crate::NumberOfLeaves::<Test>::get(), 2);
let peaks = peaks_from_leaves_count(2);
assert_eq!(peaks, vec![2]);
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(),
),
(
None,
None,
Some(hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854")),
None,
hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"),
)
);
(parent_b1, parent_b2)
});
ext.persist_offchain_overlay();
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_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_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_temp_offchain_key(2, parent_b2)).map(decode_node),
expected
);
assert_eq!(offchain_db.get(&MMR::node_temp_offchain_key(3, parent_b2)), None);
}
#[test]
fn should_construct_larger_mmr_correctly() {
sp_tracing::init_for_tests();
new_test_ext().execute_with(|| {
add_blocks(7);
assert_eq!(crate::NumberOfLeaves::<Test>::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::<Test>::get(i).is_none());
}
assert_eq!(
(
crate::Nodes::<Test>::get(6),
crate::Nodes::<Test>::get(9),
crate::Nodes::<Test>::get(10),
crate::RootHash::<Test>::get(),
),
(
Some(hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252")),
Some(hex("7e4316ae2ebf7c3b6821cb3a46ca8b7a4f9351a9b40fcf014bb0a4fd8e8f29da")),
Some(hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c")),
hex("e45e25259f7930626431347fa4dd9aae7ac83b4966126d425ca70ab343709d2c"),
)
);
});
}
#[test]
fn should_calculate_the_size_correctly() {
sp_tracing::init_for_tests();
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];
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() {
sp_tracing::init_for_tests();
let mut ext = new_test_ext();
let num_blocks: u64 = 7;
ext.execute_with(|| add_blocks(num_blocks as usize));
ext.persist_offchain_overlay();
register_offchain_ext(&mut ext);
ext.execute_with(|| {
let best_block_number = frame_system::Pallet::<Test>::block_number();
let proofs = (1_u64..=best_block_number)
.into_iter()
.map(|block_num| crate::Pallet::<Test>::generate_proof(vec![block_num], None).unwrap())
.collect::<Vec<_>>();
let historical_proofs = (1_u64..best_block_number)
.into_iter()
.map(|block_num| {
let mut proofs = vec![];
for historical_best_block in block_num..=num_blocks {
proofs.push(
crate::Pallet::<Test>::generate_proof(
vec![block_num],
Some(historical_best_block),
)
.unwrap(),
)
}
proofs
})
.collect::<Vec<_>>();
assert_eq!(
proofs[0],
(
vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))],
LeafProof {
leaf_indices: vec![0],
leaf_count: 7,
items: vec![
hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"),
hex("cb24f4614ad5b2a5430344c99545b421d9af83c46fd632d70a332200884b4d46"),
hex("dca421199bdcc55bb773c6b6967e8d16675de69062b52285ca63685241fdf626"),
],
}
)
);
assert_eq!(
historical_proofs[0][0],
(
vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))],
LeafProof { leaf_indices: vec![0], leaf_count: 1, items: vec![] }
)
);
assert_eq!(
proofs[2],
(
vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))],
LeafProof {
leaf_indices: vec![2],
leaf_count: 7,
items: vec![
hex("1b14c1dc7d3e4def11acdf31be0584f4b85c3673f1ff72a3af467b69a3b0d9d0"),
hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"),
hex("dca421199bdcc55bb773c6b6967e8d16675de69062b52285ca63685241fdf626"),
],
}
)
);
assert_eq!(
historical_proofs[2][0],
(
vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))],
LeafProof {
leaf_indices: vec![2],
leaf_count: 3,
items: vec![hex(
"672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"
)],
}
)
);
assert_eq!(
historical_proofs[2][2],
(
vec![Compact::new(((2, H256::repeat_byte(3)).into(), LeafData::new(3).into(),))],
LeafProof {
leaf_indices: vec![2],
leaf_count: 5,
items: vec![
hex("1b14c1dc7d3e4def11acdf31be0584f4b85c3673f1ff72a3af467b69a3b0d9d0"),
hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"),
hex("3b031d22e24f1126c8f7d2f394b663f9b960ed7abbedb7152e17ce16112656d0")
],
}
)
);
assert_eq!(historical_proofs[2][4], proofs[2]);
assert_eq!(
proofs[4],
(
vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))],
LeafProof {
leaf_indices: vec![4],
leaf_count: 7,
items: vec![
hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"),
hex("8ed25570209d8f753d02df07c1884ddb36a3d9d4770e4608b188322151c657fe"),
hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c"),
],
}
)
);
assert_eq!(
historical_proofs[4][0],
(
vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))],
LeafProof {
leaf_indices: vec![4],
leaf_count: 5,
items: vec![hex(
"ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"
),],
}
)
);
assert_eq!(historical_proofs[4][2], proofs[4]);
assert_eq!(
proofs[6],
(
vec![Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),))],
LeafProof {
leaf_indices: vec![6],
leaf_count: 7,
items: vec![
hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"),
hex("7e4316ae2ebf7c3b6821cb3a46ca8b7a4f9351a9b40fcf014bb0a4fd8e8f29da"),
],
}
)
);
assert_eq!(historical_proofs[5][1], proofs[5]);
});
}
#[test]
fn should_generate_batch_proof_correctly() {
sp_tracing::init_for_tests();
let mut ext = new_test_ext();
ext.execute_with(|| add_blocks(7));
ext.persist_offchain_overlay();
register_offchain_ext(&mut ext);
ext.execute_with(|| {
let (.., proof) = crate::Pallet::<Test>::generate_proof(vec![1, 5, 6], None).unwrap();
assert_eq!(
proof,
LeafProof {
leaf_indices: vec![0, 4, 5],
leaf_count: 7,
items: vec![
hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"),
hex("cb24f4614ad5b2a5430344c99545b421d9af83c46fd632d70a332200884b4d46"),
hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c"),
],
}
);
let (.., historical_proof) =
crate::Pallet::<Test>::generate_proof(vec![1, 5, 6], Some(6)).unwrap();
assert_eq!(
historical_proof,
LeafProof {
leaf_indices: vec![0, 4, 5],
leaf_count: 6,
items: vec![
hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"),
hex("cb24f4614ad5b2a5430344c99545b421d9af83c46fd632d70a332200884b4d46"),
],
}
);
let (.., historical_proof) =
crate::Pallet::<Test>::generate_proof(vec![1, 5, 6], None).unwrap();
assert_eq!(historical_proof, proof);
});
}
#[test]
fn should_verify() {
sp_tracing::init_for_tests();
let mut ext = new_test_ext();
ext.execute_with(|| add_blocks(7));
ext.persist_offchain_overlay();
register_offchain_ext(&mut ext);
let (leaves, proof5) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(vec![5], None).unwrap()
});
let (simple_historical_leaves, simple_historical_proof5) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(vec![5], Some(6)).unwrap()
});
let (advanced_historical_leaves, advanced_historical_proof5) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(vec![5], Some(7)).unwrap()
});
ext.execute_with(|| {
add_blocks(7);
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proof5), Ok(()));
assert_eq!(
crate::Pallet::<Test>::verify_leaves(
simple_historical_leaves,
simple_historical_proof5
),
Ok(())
);
assert_eq!(
crate::Pallet::<Test>::verify_leaves(
advanced_historical_leaves,
advanced_historical_proof5
),
Ok(())
);
});
}
fn generate_and_verify_batch_proof(
ext: &mut TestExternalities,
block_numbers: &Vec<u64>,
blocks_to_add: usize,
) {
let (leaves, proof) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(block_numbers.to_vec(), None).unwrap()
});
let max_block_number = ext.execute_with(|| frame_system::Pallet::<Test>::block_number());
let min_block_number = block_numbers.iter().max().unwrap();
let historical_proofs = (*min_block_number..=max_block_number)
.map(|best_block| {
ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(block_numbers.to_vec(), Some(best_block))
.unwrap()
})
})
.collect::<Vec<_>>();
ext.execute_with(|| {
add_blocks(blocks_to_add);
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proof), Ok(()));
historical_proofs.iter().for_each(|(leaves, proof)| {
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves.clone(), proof.clone()), Ok(()));
});
})
}
#[test]
fn should_verify_batch_proofs() {
sp_tracing::init_for_tests();
use itertools::Itertools;
let mut ext = new_test_ext();
register_offchain_ext(&mut ext);
for n in 1..=10 {
ext.execute_with(|| new_block());
ext.persist_offchain_overlay();
let blocks_set: Vec<Vec<u64>> = (1..=n).into_iter().powerset().skip(1).collect();
blocks_set.iter().for_each(|blocks_subset| {
generate_and_verify_batch_proof(&mut ext, &blocks_subset, 0);
ext.persist_offchain_overlay();
});
}
for n in 11..=15 {
ext.execute_with(|| new_block());
ext.persist_offchain_overlay();
let blocks_set: Vec<Vec<u64>> = (1..=n).into_iter().combinations(2).collect();
blocks_set.iter().for_each(|blocks_subset| {
generate_and_verify_batch_proof(&mut ext, &blocks_subset, 0);
ext.persist_offchain_overlay();
});
}
generate_and_verify_batch_proof(&mut ext, &vec![8, 12], 20);
ext.execute_with(|| add_blocks(1000));
ext.persist_offchain_overlay();
generate_and_verify_batch_proof(&mut ext, &vec![8, 12, 100, 800], 100);
}
#[test]
fn verification_should_be_stateless() {
sp_tracing::init_for_tests();
let mut ext = new_test_ext();
let (root_6, root_7) = ext.execute_with(|| {
add_blocks(6);
let root_6 = crate::Pallet::<Test>::mmr_root();
add_blocks(1);
let root_7 = crate::Pallet::<Test>::mmr_root();
(root_6, root_7)
});
ext.persist_offchain_overlay();
register_offchain_ext(&mut ext);
let (leaves, proof5) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(vec![5], None).unwrap()
});
let (_, historical_proof5) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(vec![5], Some(6)).unwrap()
});
let leaf = crate::primitives::DataOrHash::Data(leaves[0].clone());
assert_eq!(
crate::verify_leaves_proof::<<Test as Config>::Hashing, _>(
root_7,
vec![leaf.clone()],
proof5
),
Ok(())
);
assert_eq!(
crate::verify_leaves_proof::<<Test as Config>::Hashing, _>(
root_6,
vec![leaf],
historical_proof5
),
Ok(())
);
}
#[test]
fn should_verify_batch_proof_statelessly() {
sp_tracing::init_for_tests();
let mut ext = new_test_ext();
let (root_6, root_7) = ext.execute_with(|| {
add_blocks(6);
let root_6 = crate::Pallet::<Test>::mmr_root();
add_blocks(1);
let root_7 = crate::Pallet::<Test>::mmr_root();
(root_6, root_7)
});
ext.persist_offchain_overlay();
register_offchain_ext(&mut ext);
let (leaves, proof) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(vec![1, 4, 5], None).unwrap()
});
let (historical_leaves, historical_proof) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(vec![1, 4, 5], Some(6)).unwrap()
});
assert_eq!(
crate::verify_leaves_proof::<<Test as Config>::Hashing, _>(
root_7,
leaves
.into_iter()
.map(|leaf| crate::primitives::DataOrHash::Data(leaf))
.collect(),
proof
),
Ok(())
);
assert_eq!(
crate::verify_leaves_proof::<<Test as Config>::Hashing, _>(
root_6,
historical_leaves
.into_iter()
.map(|leaf| crate::primitives::DataOrHash::Data(leaf))
.collect(),
historical_proof
),
Ok(())
);
}
#[test]
fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() {
sp_tracing::init_for_tests();
let mut ext = new_test_ext();
ext.execute_with(|| add_blocks(7));
ext.persist_offchain_overlay();
register_offchain_ext(&mut ext);
ext.execute_with(|| {
let (leaves, proof5) = crate::Pallet::<Test>::generate_proof(vec![5], None).unwrap();
new_block();
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proof5), Ok(()));
});
}
#[test]
fn should_verify_canonicalized() {
sp_tracing::init_for_tests();
let block_hash_size: u64 = <Test as frame_system::Config>::BlockHashCount::get();
let mut ext = new_test_ext();
register_offchain_ext(&mut ext);
for blocknum in 0u32..(2 * block_hash_size).try_into().unwrap() {
ext.execute_with(|| {
new_block();
<Pallet<Test> as Hooks<BlockNumber>>::offchain_worker(blocknum.into());
});
ext.persist_offchain_overlay();
}
let (leaves, proofs) =
ext.execute_with(|| crate::Pallet::<Test>::generate_proof(vec![1, 4, 5, 7], None).unwrap());
ext.execute_with(|| {
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proofs), Ok(()));
});
let (leaves, proofs) = ext.execute_with(|| {
crate::Pallet::<Test>::generate_proof(vec![block_hash_size + 7], None).unwrap()
});
ext.execute_with(|| {
add_blocks(7);
assert_eq!(crate::Pallet::<Test>::verify_leaves(leaves, proofs), Ok(()));
});
}
#[test]
fn does_not_panic_when_generating_historical_proofs() {
sp_tracing::init_for_tests();
let mut ext = new_test_ext();
ext.execute_with(|| add_blocks(7));
ext.persist_offchain_overlay();
register_offchain_ext(&mut ext);
ext.execute_with(|| {
assert_eq!(crate::Pallet::<Test>::generate_proof(vec![10], None), Err(Error::LeafNotFound),);
assert_eq!(
crate::Pallet::<Test>::generate_proof(vec![3], Some(100)),
Err(Error::GenerateProof),
);
assert_eq!(
crate::Pallet::<Test>::generate_proof(vec![10], Some(100)),
Err(Error::LeafNotFound),
);
});
}
#[test]
fn generating_and_verifying_ancestry_proofs_works_correctly() {
sp_tracing::init_for_tests();
let mut ext = new_test_ext();
let mut prev_roots = vec![];
ext.execute_with(|| {
for _ in 1..=500 {
add_blocks(1);
prev_roots.push(Pallet::<Test>::mmr_root())
}
});
ext.persist_offchain_overlay();
register_offchain_ext(&mut ext);
ext.execute_with(|| {
let root = Pallet::<Test>::mmr_root();
for prev_block_number in 1usize..=500 {
let proof =
Pallet::<Test>::generate_ancestry_proof(prev_block_number as u64, None).unwrap();
assert!(Pallet::<Test>::is_ancestry_proof_optimal(&proof));
assert_eq!(
Pallet::<Test>::verify_ancestry_proof(root, proof),
Ok(prev_roots[prev_block_number - 1])
);
}
assert_eq!(Pallet::<Test>::generate_ancestry_proof(501, None), Err(Error::GenerateProof));
});
}