pezbp_test_utils/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Bridges Common is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Utilities for testing runtime code.
18
19#![warn(missing_docs)]
20#![cfg_attr(not(feature = "std"), no_std)]
21
22use pezbp_header_pez_chain::justification::{required_justification_precommits, GrandpaJustification};
23use pezbp_pezkuwi_core::teyrchains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
24use pezbp_teyrchains::teyrchain_head_storage_key_at_source;
25use codec::Encode;
26use pezbp_runtime::record_all_trie_keys;
27use pezsp_consensus_grandpa::{AuthorityId, AuthoritySignature, AuthorityWeight, SetId};
28use pezsp_runtime::traits::{Header as HeaderT, One, Zero};
29use pezsp_std::prelude::*;
30use pezsp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut};
31
32// Re-export all our test account utilities
33pub use keyring::*;
34
35mod keyring;
36
37/// GRANDPA round number used across tests.
38pub const TEST_GRANDPA_ROUND: u64 = 1;
39/// GRANDPA validators set id used across tests.
40pub const TEST_GRANDPA_SET_ID: SetId = 1;
41/// Name of the `Paras` pezpallet used across tests.
42pub const PARAS_PALLET_NAME: &str = "Paras";
43
44/// Configuration parameters when generating test GRANDPA justifications.
45#[derive(Clone)]
46pub struct JustificationGeneratorParams<H> {
47	/// The header which we want to finalize.
48	pub header: H,
49	/// The GRANDPA round number for the current authority set.
50	pub round: u64,
51	/// The current authority set ID.
52	pub set_id: SetId,
53	/// The current GRANDPA authority set.
54	///
55	/// The size of the set will determine the number of pre-commits in our justification.
56	pub authorities: Vec<(Account, AuthorityWeight)>,
57	/// The total number of precommit ancestors in the `votes_ancestries` field our justification.
58	///
59	/// These may be distributed among many forks.
60	pub ancestors: u32,
61	/// The number of forks.
62	///
63	/// Useful for creating a "worst-case" scenario in which each authority is on its own fork.
64	pub forks: u32,
65}
66
67impl<H: HeaderT> Default for JustificationGeneratorParams<H> {
68	fn default() -> Self {
69		let required_signatures = required_justification_precommits(test_keyring().len() as _);
70		Self {
71			header: test_header(One::one()),
72			round: TEST_GRANDPA_ROUND,
73			set_id: TEST_GRANDPA_SET_ID,
74			authorities: test_keyring().into_iter().take(required_signatures as _).collect(),
75			ancestors: 2,
76			forks: 1,
77		}
78	}
79}
80
81/// Make a valid GRANDPA justification with sensible defaults
82pub fn make_default_justification<H: HeaderT>(header: &H) -> GrandpaJustification<H> {
83	let params = JustificationGeneratorParams::<H> { header: header.clone(), ..Default::default() };
84
85	make_justification_for_header(params)
86}
87
88/// Generate justifications in a way where we are able to tune the number of pre-commits
89/// and vote ancestries which are included in the justification.
90///
91/// This is useful for benchmarks where we want to generate valid justifications with
92/// a specific number of pre-commits (tuned with the number of "authorities") and/or a specific
93/// number of vote ancestries (tuned with the "votes" parameter).
94///
95/// Note: This needs at least three authorities or else the verifier will complain about
96/// being given an invalid commit.
97pub fn make_justification_for_header<H: HeaderT>(
98	params: JustificationGeneratorParams<H>,
99) -> GrandpaJustification<H> {
100	let JustificationGeneratorParams { header, round, set_id, authorities, mut ancestors, forks } =
101		params;
102	let (target_hash, target_number) = (header.hash(), *header.number());
103	let mut votes_ancestries = vec![];
104	let mut precommits = vec![];
105
106	assert!(forks != 0, "Need at least one fork to have a chain..");
107	assert!(
108		forks as usize <= authorities.len(),
109		"If we have more forks than authorities we can't create valid pre-commits for all the forks."
110	);
111
112	// Roughly, how many vote ancestries do we want per fork
113	let target_depth = ancestors.div_ceil(forks);
114
115	let mut unsigned_precommits = vec![];
116	for i in 0..forks {
117		let depth = if ancestors >= target_depth {
118			ancestors -= target_depth;
119			target_depth
120		} else {
121			ancestors
122		};
123
124		// Note: Adding 1 to account for the target header
125		let chain = generate_chain(i, depth + 1, &header);
126
127		// We don't include our finality target header in the vote ancestries
128		for child in &chain[1..] {
129			votes_ancestries.push(child.clone());
130		}
131
132		// The header we need to use when pre-committing is the one at the highest height
133		// on our chain.
134		let precommit_candidate = chain.last().map(|h| (h.hash(), *h.number())).unwrap();
135		unsigned_precommits.push(precommit_candidate);
136	}
137
138	for (i, (id, _weight)) in authorities.iter().enumerate() {
139		// Assign authorities to sign pre-commits in a round-robin fashion
140		let target = unsigned_precommits[i % forks as usize];
141		let precommit = signed_precommit::<H>(id, target, round, set_id);
142
143		precommits.push(precommit);
144	}
145
146	GrandpaJustification {
147		round,
148		commit: finality_grandpa::Commit { target_hash, target_number, precommits },
149		votes_ancestries,
150	}
151}
152
153fn generate_chain<H: HeaderT>(fork_id: u32, depth: u32, ancestor: &H) -> Vec<H> {
154	let mut headers = vec![ancestor.clone()];
155
156	for i in 1..depth {
157		let parent = &headers[(i - 1) as usize];
158		let (hash, num) = (parent.hash(), *parent.number());
159
160		let mut header = test_header::<H>(num + One::one());
161		header.set_parent_hash(hash);
162
163		// Modifying the digest so headers at the same height but in different forks have different
164		// hashes
165		header
166			.digest_mut()
167			.logs
168			.push(pezsp_runtime::DigestItem::Other(fork_id.encode()));
169
170		headers.push(header);
171	}
172
173	headers
174}
175
176/// Make valid proof for teyrchain `heads`
177pub fn prepare_teyrchain_heads_proof<H: HeaderT>(
178	heads: Vec<(u32, ParaHead)>,
179) -> (H::Hash, ParaHeadsProof, Vec<(ParaId, ParaHash)>) {
180	let mut teyrchains = Vec::with_capacity(heads.len());
181	let mut root = Default::default();
182	let mut mdb = MemoryDB::default();
183	let mut storage_keys = vec![];
184	{
185		let mut trie = TrieDBMutBuilderV1::<H::Hashing>::new(&mut mdb, &mut root).build();
186		for (teyrchain, head) in heads {
187			let storage_key =
188				teyrchain_head_storage_key_at_source(PARAS_PALLET_NAME, ParaId(teyrchain));
189			trie.insert(&storage_key.0, &head.encode())
190				.map_err(|_| "TrieMut::insert has failed")
191				.expect("TrieMut::insert should not fail in tests");
192			storage_keys.push(storage_key.0);
193			teyrchains.push((ParaId(teyrchain), head.hash()));
194		}
195	}
196
197	// generate storage proof to be delivered to this chain
198	let storage_proof = record_all_trie_keys::<LayoutV1<H::Hashing>, _>(&mdb, &root)
199		.map_err(|_| "record_all_trie_keys has failed")
200		.expect("record_all_trie_keys should not fail in benchmarks");
201
202	(root, ParaHeadsProof { storage_proof }, teyrchains)
203}
204
205/// Create signed precommit with given target.
206pub fn signed_precommit<H: HeaderT>(
207	signer: &Account,
208	target: (H::Hash, H::Number),
209	round: u64,
210	set_id: SetId,
211) -> finality_grandpa::SignedPrecommit<H::Hash, H::Number, AuthoritySignature, AuthorityId> {
212	let precommit = finality_grandpa::Precommit { target_hash: target.0, target_number: target.1 };
213
214	let encoded = pezsp_consensus_grandpa::localized_payload(
215		round,
216		set_id,
217		&finality_grandpa::Message::Precommit(precommit.clone()),
218	);
219
220	let signature = signer.sign(&encoded);
221	let raw_signature: Vec<u8> = signature.to_bytes().into();
222
223	// Need to wrap our signature and id types that they match what our `SignedPrecommit` is
224	// expecting
225	let signature = AuthoritySignature::try_from(raw_signature).expect(
226		"We know our Keypair is good,
227		so our signature must also be good.",
228	);
229	let id = (*signer).into();
230
231	finality_grandpa::SignedPrecommit { precommit, signature, id }
232}
233
234/// Get a header for testing.
235///
236/// The correct parent hash will be used if given a non-zero header.
237pub fn test_header<H: HeaderT>(number: H::Number) -> H {
238	let default = |num| {
239		H::new(num, Default::default(), Default::default(), Default::default(), Default::default())
240	};
241
242	let mut header = default(number);
243	if number != Zero::zero() {
244		let parent_hash = default(number - One::one()).hash();
245		header.set_parent_hash(parent_hash);
246	}
247
248	header
249}
250
251/// Get a header for testing with given `state_root`.
252///
253/// The correct parent hash will be used if given a non-zero header.
254pub fn test_header_with_root<H: HeaderT>(number: H::Number, state_root: H::Hash) -> H {
255	let mut header: H = test_header(number);
256	header.set_state_root(state_root);
257	header
258}
259
260/// Convenience function for generating a Header ID at a given block number.
261pub fn header_id<H: HeaderT>(index: u8) -> (H::Hash, H::Number) {
262	(test_header::<H>(index.into()).hash(), index.into())
263}
264
265#[macro_export]
266/// Adds methods for testing the `set_owner()` and `set_operating_mode()` for a pezpallet.
267/// Some values are hardcoded like:
268/// - `run_test()`
269/// - `Pezpallet::<TestRuntime>`
270/// - `PalletOwner::<TestRuntime>`
271/// - `PalletOperatingMode::<TestRuntime>`
272/// While this is not ideal, all the pallets use the same names, so it works for the moment.
273/// We can revisit this in the future if anything changes.
274macro_rules! generate_owned_bridge_module_tests {
275	($normal_operating_mode: expr, $halted_operating_mode: expr) => {
276		#[test]
277		fn test_set_owner() {
278			run_test(|| {
279				PalletOwner::<TestRuntime>::put(1);
280
281				// The root should be able to change the owner.
282				assert_ok!(Pezpallet::<TestRuntime>::set_owner(RuntimeOrigin::root(), Some(2)));
283				assert_eq!(PalletOwner::<TestRuntime>::get(), Some(2));
284
285				// The owner should be able to change the owner.
286				assert_ok!(Pezpallet::<TestRuntime>::set_owner(RuntimeOrigin::signed(2), Some(3)));
287				assert_eq!(PalletOwner::<TestRuntime>::get(), Some(3));
288
289				// Other users shouldn't be able to change the owner.
290				assert_noop!(
291					Pezpallet::<TestRuntime>::set_owner(RuntimeOrigin::signed(1), Some(4)),
292					DispatchError::BadOrigin
293				);
294				assert_eq!(PalletOwner::<TestRuntime>::get(), Some(3));
295			});
296		}
297
298		#[test]
299		fn test_set_operating_mode() {
300			run_test(|| {
301				PalletOwner::<TestRuntime>::put(1);
302				PalletOperatingMode::<TestRuntime>::put($normal_operating_mode);
303
304				// The root should be able to halt the pezpallet.
305				assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
306					RuntimeOrigin::root(),
307					$halted_operating_mode
308				));
309				assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $halted_operating_mode);
310				// The root should be able to resume the pezpallet.
311				assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
312					RuntimeOrigin::root(),
313					$normal_operating_mode
314				));
315				assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $normal_operating_mode);
316
317				// The owner should be able to halt the pezpallet.
318				assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
319					RuntimeOrigin::signed(1),
320					$halted_operating_mode
321				));
322				assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $halted_operating_mode);
323				// The owner should be able to resume the pezpallet.
324				assert_ok!(Pezpallet::<TestRuntime>::set_operating_mode(
325					RuntimeOrigin::signed(1),
326					$normal_operating_mode
327				));
328				assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $normal_operating_mode);
329
330				// Other users shouldn't be able to halt the pezpallet.
331				assert_noop!(
332					Pezpallet::<TestRuntime>::set_operating_mode(
333						RuntimeOrigin::signed(2),
334						$halted_operating_mode
335					),
336					DispatchError::BadOrigin
337				);
338				assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $normal_operating_mode);
339				// Other users shouldn't be able to resume the pezpallet.
340				PalletOperatingMode::<TestRuntime>::put($halted_operating_mode);
341				assert_noop!(
342					Pezpallet::<TestRuntime>::set_operating_mode(
343						RuntimeOrigin::signed(2),
344						$normal_operating_mode
345					),
346					DispatchError::BadOrigin
347				);
348				assert_eq!(PalletOperatingMode::<TestRuntime>::get(), $halted_operating_mode);
349			});
350		}
351	};
352}