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
54use frame_support::{crypto::ecdsa::ECDSAExt, pallet_prelude::Weight, traits::Get};
55use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor};
56
57pub use pallet::*;
58pub use weights::WeightInfo;
59
60mod benchmarking;
61#[cfg(test)]
62mod mock;
63#[cfg(test)]
64mod tests;
65mod weights;
66
67pub struct DepositBeefyDigest<T>(core::marker::PhantomData<T>);
69
70impl<T> pallet_mmr::primitives::OnNewRoot<sp_consensus_beefy::MmrRootHash> for DepositBeefyDigest<T>
71where
72	T: pallet_mmr::Config<Hashing = sp_consensus_beefy::MmrHashing>,
73	T: pallet_beefy::Config,
74{
75	fn on_new_root(root: &sp_consensus_beefy::MmrRootHash) {
76		let digest = sp_runtime::generic::DigestItem::Consensus(
77			sp_consensus_beefy::BEEFY_ENGINE_ID,
78			codec::Encode::encode(&sp_consensus_beefy::ConsensusLog::<
79				<T as pallet_beefy::Config>::BeefyId,
80			>::MmrRoot(*root)),
81		);
82		frame_system::Pallet::<T>::deposit_log(digest);
83	}
84}
85
86pub struct BeefyEcdsaToEthereum;
88impl Convert<sp_consensus_beefy::ecdsa_crypto::AuthorityId, Vec<u8>> for BeefyEcdsaToEthereum {
89	fn convert(beefy_id: sp_consensus_beefy::ecdsa_crypto::AuthorityId) -> Vec<u8> {
90		sp_core::ecdsa::Public::from(beefy_id)
91			.to_eth_address()
92			.map(|v| v.to_vec())
93			.map_err(|_| {
94				log::debug!(target: "runtime::beefy", "Failed to convert BEEFY PublicKey to ETH address!");
95			})
96			.unwrap_or_default()
97	}
98}
99
100type MerkleRootOf<T> = <<T as pallet_mmr::Config>::Hashing as sp_runtime::traits::Hash>::Output;
101
102#[frame_support::pallet]
103pub mod pallet {
104	#![allow(missing_docs)]
105
106	use super::*;
107	use frame_support::pallet_prelude::*;
108
109	#[pallet::pallet]
111	pub struct Pallet<T>(_);
112
113	#[pallet::config]
115	#[pallet::disable_frame_system_supertrait_check]
116	pub trait Config: pallet_mmr::Config + pallet_beefy::Config {
117		type LeafVersion: Get<MmrLeafVersion>;
122
123		type BeefyAuthorityToMerkleLeaf: Convert<<Self as pallet_beefy::Config>::BeefyId, Vec<u8>>;
130
131		type LeafExtra: Member + codec::FullCodec;
133
134		type BeefyDataProvider: BeefyDataProvider<Self::LeafExtra>;
136
137		type WeightInfo: WeightInfo;
138	}
139
140	#[pallet::storage]
142	pub type BeefyAuthorities<T: Config> =
143		StorageValue<_, BeefyAuthoritySet<MerkleRootOf<T>>, ValueQuery>;
144
145	#[pallet::storage]
149	pub type BeefyNextAuthorities<T: Config> =
150		StorageValue<_, BeefyNextAuthoritySet<MerkleRootOf<T>>, ValueQuery>;
151}
152
153impl<T: Config> LeafDataProvider for Pallet<T> {
154	type LeafData = MmrLeaf<
155		BlockNumberFor<T>,
156		<T as frame_system::Config>::Hash,
157		MerkleRootOf<T>,
158		T::LeafExtra,
159	>;
160
161	fn leaf_data() -> Self::LeafData {
162		MmrLeaf {
163			version: T::LeafVersion::get(),
164			parent_number_and_hash: ParentNumberAndHash::<T>::leaf_data(),
165			leaf_extra: T::BeefyDataProvider::extra_data(),
166			beefy_next_authority_set: BeefyNextAuthorities::<T>::get(),
167		}
168	}
169}
170
171impl<T> sp_consensus_beefy::OnNewValidatorSet<<T as pallet_beefy::Config>::BeefyId> for Pallet<T>
172where
173	T: pallet::Config,
174{
175	fn on_new_validator_set(
177		current_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
178		next_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
179	) {
180		let current = Pallet::<T>::compute_authority_set(current_set);
181		let next = Pallet::<T>::compute_authority_set(next_set);
182		BeefyAuthorities::<T>::put(¤t);
184		BeefyNextAuthorities::<T>::put(&next);
185	}
186}
187
188impl<T: Config> AncestryHelper<HeaderFor<T>> for Pallet<T>
189where
190	T: pallet_mmr::Config<Hashing = sp_consensus_beefy::MmrHashing>,
191{
192	type Proof = AncestryProof<MerkleRootOf<T>>;
193	type ValidationContext = MerkleRootOf<T>;
194
195	fn generate_proof(
196		prev_block_number: BlockNumberFor<T>,
197		best_known_block_number: Option<BlockNumberFor<T>>,
198	) -> Option<Self::Proof> {
199		pallet_mmr::Pallet::<T>::generate_ancestry_proof(prev_block_number, best_known_block_number)
200			.map_err(|e| {
201				log::error!(
202					target: "runtime::beefy",
203					"Failed to generate ancestry proof for block {:?} at {:?}: {:?}",
204					prev_block_number,
205					best_known_block_number,
206					e
207				);
208				e
209			})
210			.ok()
211	}
212
213	fn is_proof_optimal(proof: &Self::Proof) -> bool {
214		let is_proof_optimal = pallet_mmr::Pallet::<T>::is_ancestry_proof_optimal(proof);
215
216		if cfg!(feature = "runtime-benchmarks") {
219			return true
220		}
221
222		is_proof_optimal
223	}
224
225	fn extract_validation_context(header: HeaderFor<T>) -> Option<Self::ValidationContext> {
226		let expected_hash = frame_system::Pallet::<T>::block_hash(header.number());
228		if expected_hash != header.hash() {
229			return None;
230		}
231
232		header.digest().convert_first(|l| {
234			l.try_to(OpaqueDigestItemId::Consensus(&sp_consensus_beefy::BEEFY_ENGINE_ID))
235				.and_then(|log: ConsensusLog<<T as pallet_beefy::Config>::BeefyId>| match log {
236					ConsensusLog::MmrRoot(mmr_root) => Some(mmr_root),
237					_ => None,
238				})
239		})
240	}
241
242	fn is_non_canonical(
243		commitment: &Commitment<BlockNumberFor<T>>,
244		proof: Self::Proof,
245		context: Self::ValidationContext,
246	) -> bool {
247		let commitment_leaf_count =
248			match pallet_mmr::Pallet::<T>::block_num_to_leaf_count(commitment.block_number) {
249				Ok(commitment_leaf_count) => commitment_leaf_count,
250				Err(_) => {
251					return false
254				},
255			};
256		if commitment_leaf_count != proof.prev_leaf_count {
257			return false;
260		}
261
262		let canonical_mmr_root = context;
263		let canonical_prev_root =
264			match pallet_mmr::Pallet::<T>::verify_ancestry_proof(canonical_mmr_root, proof) {
265				Ok(canonical_prev_root) => canonical_prev_root,
266				Err(_) => {
267					return false
270				},
271			};
272
273		let mut found_commitment_root = false;
274		let commitment_roots = commitment
275			.payload
276			.get_all_decoded::<MerkleRootOf<T>>(&known_payloads::MMR_ROOT_ID);
277		for maybe_commitment_root in commitment_roots {
278			match maybe_commitment_root {
279				Some(commitment_root) => {
280					found_commitment_root = true;
281					if canonical_prev_root != commitment_root {
282						return true;
285					}
286				},
287				None => {
288					return true;
290				},
291			}
292		}
293		if !found_commitment_root {
294			return true;
297		}
298
299		false
300	}
301}
302
303impl<T: Config> AncestryHelperWeightInfo<HeaderFor<T>> for Pallet<T>
304where
305	T: pallet_mmr::Config<Hashing = sp_consensus_beefy::MmrHashing>,
306{
307	fn is_proof_optimal(proof: &<Self as AncestryHelper<HeaderFor<T>>>::Proof) -> Weight {
308		<T as Config>::WeightInfo::n_leafs_proof_is_optimal(proof.leaf_count.saturated_into())
309	}
310
311	fn extract_validation_context() -> Weight {
312		<T as Config>::WeightInfo::extract_validation_context()
313	}
314
315	fn is_non_canonical(proof: &<Self as AncestryHelper<HeaderFor<T>>>::Proof) -> Weight {
316		let mmr_utils = NodesUtils::new(proof.leaf_count);
317		let num_peaks = mmr_utils.number_of_peaks();
318
319		<T as Config>::WeightInfo::n_items_proof_is_non_canonical(
323			proof.items.len().saturating_add(proof.prev_peaks.len()).saturated_into(),
324		)
325		.saturating_add(<T as Config>::WeightInfo::read_peak().saturating_mul(num_peaks))
328	}
329}
330
331impl<T: Config> Pallet<T> {
332	pub fn authority_set_proof() -> BeefyAuthoritySet<MerkleRootOf<T>> {
334		BeefyAuthorities::<T>::get()
335	}
336
337	pub fn next_authority_set_proof() -> BeefyNextAuthoritySet<MerkleRootOf<T>> {
339		BeefyNextAuthorities::<T>::get()
340	}
341
342	fn compute_authority_set(
348		validator_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
349	) -> BeefyAuthoritySet<MerkleRootOf<T>> {
350		let id = validator_set.id();
351		let beefy_addresses = validator_set
352			.validators()
353			.into_iter()
354			.cloned()
355			.map(T::BeefyAuthorityToMerkleLeaf::convert)
356			.collect::<Vec<_>>();
357		let default_eth_addr = [0u8; 20];
358		let len = beefy_addresses.len() as u32;
359		let uninitialized_addresses = beefy_addresses
360			.iter()
361			.filter(|&addr| addr.as_slice().eq(&default_eth_addr))
362			.count();
363		if uninitialized_addresses > 0 {
364			log::error!(
365				target: "runtime::beefy",
366				"Failed to convert {} out of {} BEEFY PublicKeys to ETH addresses!",
367				uninitialized_addresses,
368				len,
369			);
370		}
371		let keyset_commitment = binary_merkle_tree::merkle_root::<
372			<T as pallet_mmr::Config>::Hashing,
373			_,
374		>(beefy_addresses)
375		.into();
376		BeefyAuthoritySet { id, len, keyset_commitment }
377	}
378}
379
380sp_api::decl_runtime_apis! {
381	pub trait BeefyMmrApi<H>
383	where
384		BeefyAuthoritySet<H>: Decode,
385	{
386		fn authority_set_proof() -> BeefyAuthoritySet<H>;
388
389		fn next_authority_set_proof() -> BeefyNextAuthoritySet<H>;
391	}
392}