referrerpolicy=no-referrer-when-downgrade

pallet_bridge_beefy/
utils.rs

1use crate::{
2	BridgedBeefyAuthorityId, BridgedBeefyAuthoritySet, BridgedBeefyAuthoritySetInfo,
3	BridgedBeefyMmrLeaf, BridgedBeefySignedCommitment, BridgedChain, BridgedMmrHash,
4	BridgedMmrHashing, BridgedMmrProof, Config, Error, LOG_TARGET,
5};
6use bp_beefy::{merkle_root, verify_mmr_leaves_proof, BeefyAuthorityId, MmrDataOrHash};
7use codec::Encode;
8use frame_support::ensure;
9use sp_runtime::traits::{Convert, Hash};
10use sp_std::{vec, vec::Vec};
11
12type BridgedMmrDataOrHash<T, I> = MmrDataOrHash<BridgedMmrHashing<T, I>, BridgedBeefyMmrLeaf<T, I>>;
13/// A way to encode validator id to the BEEFY merkle tree leaf.
14type BridgedBeefyAuthorityIdToMerkleLeaf<T, I> =
15	bp_beefy::BeefyAuthorityIdToMerkleLeafOf<BridgedChain<T, I>>;
16
17/// Get the MMR root for a collection of validators.
18pub(crate) fn get_authorities_mmr_root<
19	'a,
20	T: Config<I>,
21	I: 'static,
22	V: Iterator<Item = &'a BridgedBeefyAuthorityId<T, I>>,
23>(
24	authorities: V,
25) -> BridgedMmrHash<T, I> {
26	let merkle_leafs = authorities
27		.cloned()
28		.map(BridgedBeefyAuthorityIdToMerkleLeaf::<T, I>::convert)
29		.collect::<Vec<_>>();
30	merkle_root::<BridgedMmrHashing<T, I>, _>(merkle_leafs)
31}
32
33fn verify_authority_set<T: Config<I>, I: 'static>(
34	authority_set_info: &BridgedBeefyAuthoritySetInfo<T, I>,
35	authority_set: &BridgedBeefyAuthoritySet<T, I>,
36) -> Result<(), Error<T, I>> {
37	ensure!(authority_set.id() == authority_set_info.id, Error::<T, I>::InvalidValidatorSetId);
38	ensure!(
39		authority_set.len() == authority_set_info.len as usize,
40		Error::<T, I>::InvalidValidatorSetLen
41	);
42
43	// Ensure that the authority set that signed the commitment is the expected one.
44	let root = get_authorities_mmr_root::<T, I, _>(authority_set.validators().iter());
45	ensure!(root == authority_set_info.keyset_commitment, Error::<T, I>::InvalidValidatorSetRoot);
46
47	Ok(())
48}
49
50/// Number of correct signatures, required from given validators set to accept signed
51/// commitment.
52///
53/// We're using 'conservative' approach here, where signatures of `2/3+1` validators are
54/// required..
55pub(crate) fn signatures_required(validators_len: usize) -> usize {
56	validators_len - validators_len.saturating_sub(1) / 3
57}
58
59fn verify_signatures<T: Config<I>, I: 'static>(
60	commitment: &BridgedBeefySignedCommitment<T, I>,
61	authority_set: &BridgedBeefyAuthoritySet<T, I>,
62) -> Result<(), Error<T, I>> {
63	ensure!(
64		commitment.signatures.len() == authority_set.len(),
65		Error::<T, I>::InvalidCommitmentSignaturesLen
66	);
67
68	// Ensure that the commitment was signed by enough authorities.
69	let msg = commitment.commitment.encode();
70	let mut missing_signatures = signatures_required(authority_set.len());
71	for (idx, (authority, maybe_sig)) in
72		authority_set.validators().iter().zip(commitment.signatures.iter()).enumerate()
73	{
74		if let Some(sig) = maybe_sig {
75			if authority.verify(sig, &msg) {
76				missing_signatures = missing_signatures.saturating_sub(1);
77				if missing_signatures == 0 {
78					break
79				}
80			} else {
81				tracing::debug!(
82					target: LOG_TARGET,
83					%idx,
84					?authority,
85					?sig,
86					"Signed commitment contains incorrect signature of validator"
87				);
88			}
89		}
90	}
91	ensure!(missing_signatures == 0, Error::<T, I>::NotEnoughCorrectSignatures);
92
93	Ok(())
94}
95
96/// Extract MMR root from commitment payload.
97fn extract_mmr_root<T: Config<I>, I: 'static>(
98	commitment: &BridgedBeefySignedCommitment<T, I>,
99) -> Result<BridgedMmrHash<T, I>, Error<T, I>> {
100	commitment
101		.commitment
102		.payload
103		.get_decoded(&bp_beefy::MMR_ROOT_PAYLOAD_ID)
104		.ok_or(Error::MmrRootMissingFromCommitment)
105}
106
107pub(crate) fn verify_commitment<T: Config<I>, I: 'static>(
108	commitment: &BridgedBeefySignedCommitment<T, I>,
109	authority_set_info: &BridgedBeefyAuthoritySetInfo<T, I>,
110	authority_set: &BridgedBeefyAuthoritySet<T, I>,
111) -> Result<BridgedMmrHash<T, I>, Error<T, I>> {
112	// Ensure that the commitment is signed by the best known BEEFY validator set.
113	ensure!(
114		commitment.commitment.validator_set_id == authority_set_info.id,
115		Error::<T, I>::InvalidCommitmentValidatorSetId
116	);
117	ensure!(
118		commitment.signatures.len() == authority_set_info.len as usize,
119		Error::<T, I>::InvalidCommitmentSignaturesLen
120	);
121
122	verify_authority_set(authority_set_info, authority_set)?;
123	verify_signatures(commitment, authority_set)?;
124
125	extract_mmr_root(commitment)
126}
127
128/// Verify MMR proof of given leaf.
129pub(crate) fn verify_beefy_mmr_leaf<T: Config<I>, I: 'static>(
130	mmr_leaf: &BridgedBeefyMmrLeaf<T, I>,
131	mmr_proof: BridgedMmrProof<T, I>,
132	mmr_root: BridgedMmrHash<T, I>,
133) -> Result<(), Error<T, I>> {
134	let mmr_proof_leaf_count = mmr_proof.leaf_count;
135	let mmr_proof_length = mmr_proof.items.len();
136
137	// Verify the mmr proof for the provided leaf.
138	let mmr_leaf_hash = BridgedMmrHashing::<T, I>::hash(&mmr_leaf.encode());
139	verify_mmr_leaves_proof(
140		mmr_root,
141		vec![BridgedMmrDataOrHash::<T, I>::Hash(mmr_leaf_hash)],
142		mmr_proof,
143	)
144	.map_err(|e| {
145		tracing::error!(
146			target: LOG_TARGET,
147			error=?e,
148			?mmr_leaf_hash,
149			root=?mmr_root,
150			leaf_count=%mmr_proof_leaf_count,
151			len=%mmr_proof_length,
152			"MMR proof of leaf verification has failed"
153		);
154
155		Error::<T, I>::MmrProofVerificationFailed
156	})
157}
158
159#[cfg(test)]
160mod tests {
161	use super::*;
162	use crate::{mock::*, mock_chain::*, *};
163	use bp_beefy::{BeefyPayload, MMR_ROOT_PAYLOAD_ID};
164	use frame_support::{assert_noop, assert_ok};
165	use sp_consensus_beefy::ValidatorSet;
166
167	#[test]
168	fn submit_commitment_checks_metadata() {
169		run_test_with_initialize(8, || {
170			// Fails if `commitment.commitment.validator_set_id` differs.
171			let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
172			header.customize_commitment(
173				|commitment| {
174					commitment.validator_set_id += 1;
175				},
176				&validator_pairs(0, 8),
177				6,
178			);
179			assert_noop!(
180				import_commitment(header),
181				Error::<TestRuntime, ()>::InvalidCommitmentValidatorSetId,
182			);
183
184			// Fails if `commitment.signatures.len()` differs.
185			let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
186			header.customize_signatures(|signatures| {
187				signatures.pop();
188			});
189			assert_noop!(
190				import_commitment(header),
191				Error::<TestRuntime, ()>::InvalidCommitmentSignaturesLen,
192			);
193		});
194	}
195
196	#[test]
197	fn submit_commitment_checks_validator_set() {
198		run_test_with_initialize(8, || {
199			// Fails if `ValidatorSet::id` differs.
200			let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
201			header.validator_set = ValidatorSet::new(validator_ids(0, 8), 1).unwrap();
202			assert_noop!(
203				import_commitment(header),
204				Error::<TestRuntime, ()>::InvalidValidatorSetId,
205			);
206
207			// Fails if `ValidatorSet::len()` differs.
208			let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
209			header.validator_set = ValidatorSet::new(validator_ids(0, 5), 0).unwrap();
210			assert_noop!(
211				import_commitment(header),
212				Error::<TestRuntime, ()>::InvalidValidatorSetLen,
213			);
214
215			// Fails if the validators differ.
216			let mut header = ChainBuilder::new(8).append_finalized_header().to_header();
217			header.validator_set = ValidatorSet::new(validator_ids(3, 8), 0).unwrap();
218			assert_noop!(
219				import_commitment(header),
220				Error::<TestRuntime, ()>::InvalidValidatorSetRoot,
221			);
222		});
223	}
224
225	#[test]
226	fn submit_commitment_checks_signatures() {
227		run_test_with_initialize(20, || {
228			// Fails when there aren't enough signatures.
229			let mut header = ChainBuilder::new(20).append_finalized_header().to_header();
230			header.customize_signatures(|signatures| {
231				let first_signature_idx = signatures.iter().position(Option::is_some).unwrap();
232				signatures[first_signature_idx] = None;
233			});
234			assert_noop!(
235				import_commitment(header),
236				Error::<TestRuntime, ()>::NotEnoughCorrectSignatures,
237			);
238
239			// Fails when there aren't enough correct signatures.
240			let mut header = ChainBuilder::new(20).append_finalized_header().to_header();
241			header.customize_signatures(|signatures| {
242				let first_signature_idx = signatures.iter().position(Option::is_some).unwrap();
243				let last_signature_idx = signatures.len() -
244					signatures.iter().rev().position(Option::is_some).unwrap() -
245					1;
246				signatures[first_signature_idx] = signatures[last_signature_idx].clone();
247			});
248			assert_noop!(
249				import_commitment(header),
250				Error::<TestRuntime, ()>::NotEnoughCorrectSignatures,
251			);
252
253			// Returns Ok(()) when there are enough signatures, even if some are incorrect.
254			let mut header = ChainBuilder::new(20).append_finalized_header().to_header();
255			header.customize_signatures(|signatures| {
256				let first_signature_idx = signatures.iter().position(Option::is_some).unwrap();
257				let first_missing_signature_idx =
258					signatures.iter().position(Option::is_none).unwrap();
259				signatures[first_missing_signature_idx] = signatures[first_signature_idx].clone();
260			});
261			assert_ok!(import_commitment(header));
262		});
263	}
264
265	#[test]
266	fn submit_commitment_checks_mmr_proof() {
267		run_test_with_initialize(1, || {
268			let validators = validator_pairs(0, 1);
269
270			// Fails if leaf is not for parent.
271			let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
272			header.leaf.parent_number_and_hash.0 += 1;
273			assert_noop!(
274				import_commitment(header),
275				Error::<TestRuntime, ()>::MmrProofVerificationFailed,
276			);
277
278			// Fails if mmr proof is incorrect.
279			let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
280			header.leaf_proof.leaf_indices[0] += 1;
281			assert_noop!(
282				import_commitment(header),
283				Error::<TestRuntime, ()>::MmrProofVerificationFailed,
284			);
285
286			// Fails if mmr root is incorrect.
287			let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
288			// Replace MMR root with zeroes.
289			header.customize_commitment(
290				|commitment| {
291					commitment.payload =
292						BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, [0u8; 32].encode());
293				},
294				&validators,
295				1,
296			);
297			assert_noop!(
298				import_commitment(header),
299				Error::<TestRuntime, ()>::MmrProofVerificationFailed,
300			);
301		});
302	}
303
304	#[test]
305	fn submit_commitment_extracts_mmr_root() {
306		run_test_with_initialize(1, || {
307			let validators = validator_pairs(0, 1);
308
309			// Fails if there is no mmr root in the payload.
310			let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
311			// Remove MMR root from the payload.
312			header.customize_commitment(
313				|commitment| {
314					commitment.payload = BeefyPayload::from_single_entry(*b"xy", vec![]);
315				},
316				&validators,
317				1,
318			);
319			assert_noop!(
320				import_commitment(header),
321				Error::<TestRuntime, ()>::MmrRootMissingFromCommitment,
322			);
323
324			// Fails if mmr root can't be decoded.
325			let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
326			// MMR root is a 32-byte array and we have replaced it with single byte
327			header.customize_commitment(
328				|commitment| {
329					commitment.payload =
330						BeefyPayload::from_single_entry(MMR_ROOT_PAYLOAD_ID, vec![42]);
331				},
332				&validators,
333				1,
334			);
335			assert_noop!(
336				import_commitment(header),
337				Error::<TestRuntime, ()>::MmrRootMissingFromCommitment,
338			);
339		});
340	}
341
342	#[test]
343	fn submit_commitment_stores_valid_data() {
344		run_test_with_initialize(20, || {
345			let header = ChainBuilder::new(20).append_handoff_header(30).to_header();
346			assert_ok!(import_commitment(header.clone()));
347
348			assert_eq!(ImportedCommitmentsInfo::<TestRuntime>::get().unwrap().best_block_number, 1);
349			assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().id, 1);
350			assert_eq!(CurrentAuthoritySetInfo::<TestRuntime>::get().len, 30);
351			assert_eq!(
352				ImportedCommitments::<TestRuntime>::get(1).unwrap(),
353				bp_beefy::ImportedCommitment {
354					parent_number_and_hash: (0, [0; 32].into()),
355					mmr_root: header.mmr_root,
356				},
357			);
358		});
359	}
360}