referrerpolicy=no-referrer-when-downgrade

pallet_beefy_mmr/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19#![warn(missing_docs)]
20
21//! A BEEFY+MMR pallet combo.
22//!
23//! While both BEEFY and Merkle Mountain Range (MMR) can be used separately,
24//! these tools were designed to work together in unison.
25//!
26//! The pallet provides a standardized MMR Leaf format that can be used
27//! to bridge BEEFY+MMR-based networks (both standalone and Polkadot-like).
28//!
29//! The MMR leaf contains:
30//! 1. Block number and parent block hash.
31//! 2. Merkle Tree Root Hash of next BEEFY validator set.
32//! 3. Arbitrary extra leaf data to be used by downstream pallets to include custom data.
33//!
34//! and thanks to versioning can be easily updated in the future.
35
36extern 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
67/// A BEEFY consensus digest item with MMR root hash.
68pub 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
86/// Convert BEEFY secp256k1 public keys into Ethereum addresses
87pub 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	/// BEEFY-MMR pallet.
110	#[pallet::pallet]
111	pub struct Pallet<T>(_);
112
113	/// The module's configuration trait.
114	#[pallet::config]
115	#[pallet::disable_frame_system_supertrait_check]
116	pub trait Config: pallet_mmr::Config + pallet_beefy::Config {
117		/// Current leaf version.
118		///
119		/// Specifies the version number added to every leaf that get's appended to the MMR.
120		/// Read more in [`MmrLeafVersion`] docs about versioning leaves.
121		type LeafVersion: Get<MmrLeafVersion>;
122
123		/// Convert BEEFY AuthorityId to a form that would end up in the Merkle Tree.
124		///
125		/// For instance for ECDSA (secp256k1) we want to store uncompressed public keys (65 bytes)
126		/// and later to Ethereum Addresses (160 bits) to simplify using them on Ethereum chain,
127		/// but the rest of the Substrate codebase is storing them compressed (33 bytes) for
128		/// efficiency reasons.
129		type BeefyAuthorityToMerkleLeaf: Convert<<Self as pallet_beefy::Config>::BeefyId, Vec<u8>>;
130
131		/// The type expected for the leaf extra data
132		type LeafExtra: Member + codec::FullCodec;
133
134		/// Retrieve arbitrary data that should be added to the mmr leaf
135		type BeefyDataProvider: BeefyDataProvider<Self::LeafExtra>;
136
137		type WeightInfo: WeightInfo;
138	}
139
140	/// Details of current BEEFY authority set.
141	#[pallet::storage]
142	pub type BeefyAuthorities<T: Config> =
143		StorageValue<_, BeefyAuthoritySet<MerkleRootOf<T>>, ValueQuery>;
144
145	/// Details of next BEEFY authority set.
146	///
147	/// This storage entry is used as cache for calls to `update_beefy_next_authority_set`.
148	#[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	/// Compute and cache BEEFY authority sets based on updated BEEFY validator sets.
176	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		// cache the result
183		BeefyAuthorities::<T>::put(&current);
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		// We don't check the proof size when running benchmarks, since we use mock proofs
217		// which would cause the test to fail.
218		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		// Check if the provided header is canonical.
227		let expected_hash = frame_system::Pallet::<T>::block_hash(header.number());
228		if expected_hash != header.hash() {
229			return None;
230		}
231
232		// Extract the MMR root from the header digest
233		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					// We can't prove that the commitment is non-canonical if the
252					// `commitment.block_number` is invalid.
253					return false
254				},
255			};
256		if commitment_leaf_count != proof.prev_leaf_count {
257			// Can't prove that the commitment is non-canonical if the `commitment.block_number`
258			// doesn't match the ancestry proof.
259			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					// Can't prove that the commitment is non-canonical if the proof
268					// is invalid.
269					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						// If the commitment contains an MMR root, that is not equal to
283						// `canonical_prev_root`, the commitment is invalid
284						return true;
285					}
286				},
287				None => {
288					// If the commitment contains an MMR root, that can't be decoded, it is invalid.
289					return true;
290				},
291			}
292		}
293		if !found_commitment_root {
294			// If the commitment doesn't contain any MMR root, while the proof is valid,
295			// the commitment is invalid
296			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		// The approximated cost of verifying an ancestry proof with `n` nodes.
320		// We add the previous peaks to the total number of nodes,
321		// since they have to be processed as well.
322		<T as Config>::WeightInfo::n_items_proof_is_non_canonical(
323			proof.items.len().saturating_add(proof.prev_peaks.len()).saturated_into(),
324		)
325		// `n_items_proof_is_non_canonical()` uses inflated proofs that contain all the leafs,
326		// where no peak needs to be read. So we need to also add the cost of reading the peaks.
327		.saturating_add(<T as Config>::WeightInfo::read_peak().saturating_mul(num_peaks))
328	}
329}
330
331impl<T: Config> Pallet<T> {
332	/// Return the currently active BEEFY authority set proof.
333	pub fn authority_set_proof() -> BeefyAuthoritySet<MerkleRootOf<T>> {
334		BeefyAuthorities::<T>::get()
335	}
336
337	/// Return the next/queued BEEFY authority set proof.
338	pub fn next_authority_set_proof() -> BeefyNextAuthoritySet<MerkleRootOf<T>> {
339		BeefyNextAuthorities::<T>::get()
340	}
341
342	/// Returns details of a BEEFY authority set.
343	///
344	/// Details contain authority set id, authority set length and a merkle root,
345	/// constructed from uncompressed secp256k1 public keys converted to Ethereum addresses
346	/// of the next BEEFY authority set.
347	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	/// API useful for BEEFY light clients.
382	pub trait BeefyMmrApi<H>
383	where
384		BeefyAuthoritySet<H>: Decode,
385	{
386		/// Return the currently active BEEFY authority set proof.
387		fn authority_set_proof() -> BeefyAuthoritySet<H>;
388
389		/// Return the next/queued BEEFY authority set proof.
390		fn next_authority_set_proof() -> BeefyNextAuthoritySet<H>;
391	}
392}