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>>;
13type BridgedBeefyAuthorityIdToMerkleLeaf<T, I> =
15 bp_beefy::BeefyAuthorityIdToMerkleLeafOf<BridgedChain<T, I>>;
16
17pub(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 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
50pub(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 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
96fn 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!(
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
128pub(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 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 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 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 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 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 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 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 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 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 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 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 let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
288 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 let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
311 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 let mut header = ChainBuilder::new(1).append_finalized_header().to_header();
326 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}