1#![cfg_attr(not(feature = "std"), no_std)]
19#![warn(missing_docs)]
20
21extern crate alloc;
37
38use sp_runtime::{
39 generic::OpaqueDigestItemId,
40 traits::{Convert, Header, Member},
41 SaturatedConversion,
42};
43
44use alloc::vec::Vec;
45use codec::Decode;
46use pallet_mmr::{primitives::AncestryProof, LeafDataProvider, NodesUtils, ParentNumberAndHash};
47use sp_consensus_beefy::{
48 known_payloads,
49 mmr::{BeefyAuthoritySet, BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion},
50 AncestryHelper, AncestryHelperWeightInfo, Commitment, ConsensusLog,
51 ValidatorSet as BeefyValidatorSet,
52};
53
54#[cfg(any(feature = "try-runtime", test))]
55use frame_support::ensure;
56use frame_support::{crypto::ecdsa::ECDSAExt, pallet_prelude::Weight, traits::Get};
57use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor};
58
59pub use pallet::*;
60pub use weights::WeightInfo;
61
62mod benchmarking;
63#[cfg(test)]
64mod mock;
65#[cfg(test)]
66mod tests;
67mod weights;
68
69pub struct DepositBeefyDigest<T>(core::marker::PhantomData<T>);
71
72impl<T> pallet_mmr::primitives::OnNewRoot<sp_consensus_beefy::MmrRootHash> for DepositBeefyDigest<T>
73where
74 T: pallet_mmr::Config<Hashing = sp_consensus_beefy::MmrHashing>,
75 T: pallet_beefy::Config,
76{
77 fn on_new_root(root: &sp_consensus_beefy::MmrRootHash) {
78 let digest = sp_runtime::generic::DigestItem::Consensus(
79 sp_consensus_beefy::BEEFY_ENGINE_ID,
80 codec::Encode::encode(&sp_consensus_beefy::ConsensusLog::<
81 <T as pallet_beefy::Config>::BeefyId,
82 >::MmrRoot(*root)),
83 );
84 frame_system::Pallet::<T>::deposit_log(digest);
85 }
86}
87
88pub const FAILED_BEEFY_TO_ETH_ADDRESS: [u8; 20] = [0u8; 20];
94
95pub struct BeefyEcdsaToEthereum;
97impl Convert<sp_consensus_beefy::ecdsa_crypto::AuthorityId, Vec<u8>> for BeefyEcdsaToEthereum {
98 fn convert(beefy_id: sp_consensus_beefy::ecdsa_crypto::AuthorityId) -> Vec<u8> {
99 sp_core::ecdsa::Public::from(beefy_id)
100 .to_eth_address()
101 .map(|v| v.to_vec())
102 .unwrap_or_else(|_| {
103 log::debug!(target: "runtime::beefy", "Failed to convert BEEFY PublicKey to ETH address!");
104 FAILED_BEEFY_TO_ETH_ADDRESS.to_vec()
105 })
106 }
107}
108
109type MerkleRootOf<T> = <<T as pallet_mmr::Config>::Hashing as sp_runtime::traits::Hash>::Output;
110
111#[frame_support::pallet]
112pub mod pallet {
113 #![allow(missing_docs)]
114
115 use super::*;
116 use frame_support::pallet_prelude::*;
117
118 #[pallet::pallet]
120 pub struct Pallet<T>(_);
121
122 #[pallet::config]
124 #[pallet::disable_frame_system_supertrait_check]
125 pub trait Config: pallet_mmr::Config + pallet_beefy::Config {
126 type LeafVersion: Get<MmrLeafVersion>;
131
132 type BeefyAuthorityToMerkleLeaf: Convert<<Self as pallet_beefy::Config>::BeefyId, Vec<u8>>;
139
140 type LeafExtra: Member + codec::FullCodec;
142
143 type BeefyDataProvider: BeefyDataProvider<Self::LeafExtra>;
145
146 type WeightInfo: WeightInfo;
147 }
148
149 #[pallet::storage]
151 pub type BeefyAuthorities<T: Config> =
152 StorageValue<_, BeefyAuthoritySet<MerkleRootOf<T>>, ValueQuery>;
153
154 #[pallet::storage]
158 pub type BeefyNextAuthorities<T: Config> =
159 StorageValue<_, BeefyNextAuthoritySet<MerkleRootOf<T>>, ValueQuery>;
160
161 #[pallet::hooks]
162 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
163 #[cfg(feature = "try-runtime")]
164 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
165 Self::do_try_state()
166 }
167 }
168}
169
170impl<T: Config> LeafDataProvider for Pallet<T> {
171 type LeafData = MmrLeaf<
172 BlockNumberFor<T>,
173 <T as frame_system::Config>::Hash,
174 MerkleRootOf<T>,
175 T::LeafExtra,
176 >;
177
178 fn leaf_data() -> Self::LeafData {
179 MmrLeaf {
180 version: T::LeafVersion::get(),
181 parent_number_and_hash: ParentNumberAndHash::<T>::leaf_data(),
182 leaf_extra: T::BeefyDataProvider::extra_data(),
183 beefy_next_authority_set: BeefyNextAuthorities::<T>::get(),
184 }
185 }
186}
187
188impl<T> sp_consensus_beefy::OnNewValidatorSet<<T as pallet_beefy::Config>::BeefyId> for Pallet<T>
189where
190 T: pallet::Config,
191{
192 fn on_new_validator_set(
194 current_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
195 next_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
196 ) {
197 let current = Pallet::<T>::compute_authority_set(current_set);
198 let next = Pallet::<T>::compute_authority_set(next_set);
199 BeefyAuthorities::<T>::put(¤t);
201 BeefyNextAuthorities::<T>::put(&next);
202 }
203}
204
205impl<T: Config> AncestryHelper<HeaderFor<T>> for Pallet<T>
206where
207 T: pallet_mmr::Config<Hashing = sp_consensus_beefy::MmrHashing>,
208{
209 type Proof = AncestryProof<MerkleRootOf<T>>;
210 type ValidationContext = MerkleRootOf<T>;
211
212 fn is_proof_optimal(proof: &Self::Proof) -> bool {
213 let is_proof_optimal = pallet_mmr::Pallet::<T>::is_ancestry_proof_optimal(proof);
214
215 if cfg!(feature = "runtime-benchmarks") {
218 return true;
219 }
220
221 is_proof_optimal
222 }
223
224 fn extract_validation_context(header: HeaderFor<T>) -> Option<Self::ValidationContext> {
225 let expected_hash = frame_system::Pallet::<T>::block_hash(header.number());
227 if expected_hash != header.hash() {
228 return None;
229 }
230
231 header.digest().convert_first(|l| {
233 l.try_to(OpaqueDigestItemId::Consensus(&sp_consensus_beefy::BEEFY_ENGINE_ID))
234 .and_then(|log: ConsensusLog<<T as pallet_beefy::Config>::BeefyId>| match log {
235 ConsensusLog::MmrRoot(mmr_root) => Some(mmr_root),
236 _ => None,
237 })
238 })
239 }
240
241 fn is_non_canonical(
242 commitment: &Commitment<BlockNumberFor<T>>,
243 proof: Self::Proof,
244 context: Self::ValidationContext,
245 ) -> bool {
246 let commitment_leaf_count =
247 match pallet_mmr::Pallet::<T>::block_num_to_leaf_count(commitment.block_number) {
248 Ok(commitment_leaf_count) => commitment_leaf_count,
249 Err(_) => {
250 return false;
253 },
254 };
255 if commitment_leaf_count != proof.prev_leaf_count {
256 return false;
259 }
260
261 let canonical_mmr_root = context;
262 let canonical_prev_root =
263 match pallet_mmr::Pallet::<T>::verify_ancestry_proof(canonical_mmr_root, proof) {
264 Ok(canonical_prev_root) => canonical_prev_root,
265 Err(_) => {
266 return false;
269 },
270 };
271
272 let mut found_commitment_root = false;
273 let commitment_roots = commitment
274 .payload
275 .get_all_decoded::<MerkleRootOf<T>>(&known_payloads::MMR_ROOT_ID);
276 for maybe_commitment_root in commitment_roots {
277 match maybe_commitment_root {
278 Some(commitment_root) => {
279 found_commitment_root = true;
280 if canonical_prev_root != commitment_root {
281 return true;
284 }
285 },
286 None => {
287 return true;
289 },
290 }
291 }
292 if !found_commitment_root {
293 return true;
296 }
297
298 false
299 }
300}
301
302impl<T: Config> AncestryHelperWeightInfo<HeaderFor<T>> for Pallet<T>
303where
304 T: pallet_mmr::Config<Hashing = sp_consensus_beefy::MmrHashing>,
305{
306 fn is_proof_optimal(proof: &<Self as AncestryHelper<HeaderFor<T>>>::Proof) -> Weight {
307 <T as Config>::WeightInfo::n_leafs_proof_is_optimal(proof.leaf_count.saturated_into())
308 }
309
310 fn extract_validation_context() -> Weight {
311 <T as Config>::WeightInfo::extract_validation_context()
312 }
313
314 fn is_non_canonical(proof: &<Self as AncestryHelper<HeaderFor<T>>>::Proof) -> Weight {
315 let mmr_utils = NodesUtils::new(proof.leaf_count);
316 let num_peaks = mmr_utils.number_of_peaks();
317
318 <T as Config>::WeightInfo::n_items_proof_is_non_canonical(
322 proof.items.len().saturating_add(proof.prev_peaks.len()).saturated_into(),
323 )
324 .saturating_add(<T as Config>::WeightInfo::read_peak().saturating_mul(num_peaks))
327 }
328}
329
330impl<T: Config> Pallet<T> {
331 pub fn authority_set_proof() -> BeefyAuthoritySet<MerkleRootOf<T>> {
333 BeefyAuthorities::<T>::get()
334 }
335
336 pub fn next_authority_set_proof() -> BeefyNextAuthoritySet<MerkleRootOf<T>> {
338 BeefyNextAuthorities::<T>::get()
339 }
340
341 fn compute_authority_set(
347 validator_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
348 ) -> BeefyAuthoritySet<MerkleRootOf<T>> {
349 let id = validator_set.id();
350 let beefy_addresses = validator_set
351 .validators()
352 .into_iter()
353 .cloned()
354 .map(T::BeefyAuthorityToMerkleLeaf::convert)
355 .collect::<Vec<_>>();
356 let len = beefy_addresses.len() as u32;
357 let uninitialized_addresses = beefy_addresses
358 .iter()
359 .filter(|&addr| addr.as_slice().eq(&FAILED_BEEFY_TO_ETH_ADDRESS))
360 .count();
361 if uninitialized_addresses > 0 {
362 log::error!(
363 target: "runtime::beefy",
364 "Failed to convert {} out of {} BEEFY PublicKeys to ETH addresses!",
365 uninitialized_addresses,
366 len,
367 );
368 }
369 let keyset_commitment = binary_merkle_tree::merkle_root::<
370 <T as pallet_mmr::Config>::Hashing,
371 _,
372 >(beefy_addresses)
373 .into();
374 BeefyAuthoritySet { id, len, keyset_commitment }
375 }
376
377 #[cfg(any(feature = "try-runtime", test))]
379 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
380 let current = BeefyAuthorities::<T>::get();
381 let next = BeefyNextAuthorities::<T>::get();
382
383 ensure!(next.id == current.id + 1, "Next authority set id must be one ahead of current");
384
385 ensure!(current.len > 0, "Current authority set must not be empty");
386 ensure!(next.len > 0, "Next authority set must not be empty");
387
388 Ok(())
389 }
390}
391
392sp_api::decl_runtime_apis! {
393 pub trait BeefyMmrApi<H>
395 where
396 BeefyAuthoritySet<H>: Decode,
397 {
398 fn authority_set_proof() -> BeefyAuthoritySet<H>;
400
401 fn next_authority_set_proof() -> BeefyNextAuthoritySet<H>;
403 }
404}