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 is_proof_optimal(proof: &Self::Proof) -> bool {
196		let is_proof_optimal = pallet_mmr::Pallet::<T>::is_ancestry_proof_optimal(proof);
197
198		// We don't check the proof size when running benchmarks, since we use mock proofs
199		// which would cause the test to fail.
200		if cfg!(feature = "runtime-benchmarks") {
201			return true
202		}
203
204		is_proof_optimal
205	}
206
207	fn extract_validation_context(header: HeaderFor<T>) -> Option<Self::ValidationContext> {
208		// Check if the provided header is canonical.
209		let expected_hash = frame_system::Pallet::<T>::block_hash(header.number());
210		if expected_hash != header.hash() {
211			return None;
212		}
213
214		// Extract the MMR root from the header digest
215		header.digest().convert_first(|l| {
216			l.try_to(OpaqueDigestItemId::Consensus(&sp_consensus_beefy::BEEFY_ENGINE_ID))
217				.and_then(|log: ConsensusLog<<T as pallet_beefy::Config>::BeefyId>| match log {
218					ConsensusLog::MmrRoot(mmr_root) => Some(mmr_root),
219					_ => None,
220				})
221		})
222	}
223
224	fn is_non_canonical(
225		commitment: &Commitment<BlockNumberFor<T>>,
226		proof: Self::Proof,
227		context: Self::ValidationContext,
228	) -> bool {
229		let commitment_leaf_count =
230			match pallet_mmr::Pallet::<T>::block_num_to_leaf_count(commitment.block_number) {
231				Ok(commitment_leaf_count) => commitment_leaf_count,
232				Err(_) => {
233					// We can't prove that the commitment is non-canonical if the
234					// `commitment.block_number` is invalid.
235					return false
236				},
237			};
238		if commitment_leaf_count != proof.prev_leaf_count {
239			// Can't prove that the commitment is non-canonical if the `commitment.block_number`
240			// doesn't match the ancestry proof.
241			return false;
242		}
243
244		let canonical_mmr_root = context;
245		let canonical_prev_root =
246			match pallet_mmr::Pallet::<T>::verify_ancestry_proof(canonical_mmr_root, proof) {
247				Ok(canonical_prev_root) => canonical_prev_root,
248				Err(_) => {
249					// Can't prove that the commitment is non-canonical if the proof
250					// is invalid.
251					return false
252				},
253			};
254
255		let mut found_commitment_root = false;
256		let commitment_roots = commitment
257			.payload
258			.get_all_decoded::<MerkleRootOf<T>>(&known_payloads::MMR_ROOT_ID);
259		for maybe_commitment_root in commitment_roots {
260			match maybe_commitment_root {
261				Some(commitment_root) => {
262					found_commitment_root = true;
263					if canonical_prev_root != commitment_root {
264						// If the commitment contains an MMR root, that is not equal to
265						// `canonical_prev_root`, the commitment is invalid
266						return true;
267					}
268				},
269				None => {
270					// If the commitment contains an MMR root, that can't be decoded, it is invalid.
271					return true;
272				},
273			}
274		}
275		if !found_commitment_root {
276			// If the commitment doesn't contain any MMR root, while the proof is valid,
277			// the commitment is invalid
278			return true;
279		}
280
281		false
282	}
283}
284
285impl<T: Config> AncestryHelperWeightInfo<HeaderFor<T>> for Pallet<T>
286where
287	T: pallet_mmr::Config<Hashing = sp_consensus_beefy::MmrHashing>,
288{
289	fn is_proof_optimal(proof: &<Self as AncestryHelper<HeaderFor<T>>>::Proof) -> Weight {
290		<T as Config>::WeightInfo::n_leafs_proof_is_optimal(proof.leaf_count.saturated_into())
291	}
292
293	fn extract_validation_context() -> Weight {
294		<T as Config>::WeightInfo::extract_validation_context()
295	}
296
297	fn is_non_canonical(proof: &<Self as AncestryHelper<HeaderFor<T>>>::Proof) -> Weight {
298		let mmr_utils = NodesUtils::new(proof.leaf_count);
299		let num_peaks = mmr_utils.number_of_peaks();
300
301		// The approximated cost of verifying an ancestry proof with `n` nodes.
302		// We add the previous peaks to the total number of nodes,
303		// since they have to be processed as well.
304		<T as Config>::WeightInfo::n_items_proof_is_non_canonical(
305			proof.items.len().saturating_add(proof.prev_peaks.len()).saturated_into(),
306		)
307		// `n_items_proof_is_non_canonical()` uses inflated proofs that contain all the leafs,
308		// where no peak needs to be read. So we need to also add the cost of reading the peaks.
309		.saturating_add(<T as Config>::WeightInfo::read_peak().saturating_mul(num_peaks))
310	}
311}
312
313impl<T: Config> Pallet<T> {
314	/// Return the currently active BEEFY authority set proof.
315	pub fn authority_set_proof() -> BeefyAuthoritySet<MerkleRootOf<T>> {
316		BeefyAuthorities::<T>::get()
317	}
318
319	/// Return the next/queued BEEFY authority set proof.
320	pub fn next_authority_set_proof() -> BeefyNextAuthoritySet<MerkleRootOf<T>> {
321		BeefyNextAuthorities::<T>::get()
322	}
323
324	/// Returns details of a BEEFY authority set.
325	///
326	/// Details contain authority set id, authority set length and a merkle root,
327	/// constructed from uncompressed secp256k1 public keys converted to Ethereum addresses
328	/// of the next BEEFY authority set.
329	fn compute_authority_set(
330		validator_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
331	) -> BeefyAuthoritySet<MerkleRootOf<T>> {
332		let id = validator_set.id();
333		let beefy_addresses = validator_set
334			.validators()
335			.into_iter()
336			.cloned()
337			.map(T::BeefyAuthorityToMerkleLeaf::convert)
338			.collect::<Vec<_>>();
339		let default_eth_addr = [0u8; 20];
340		let len = beefy_addresses.len() as u32;
341		let uninitialized_addresses = beefy_addresses
342			.iter()
343			.filter(|&addr| addr.as_slice().eq(&default_eth_addr))
344			.count();
345		if uninitialized_addresses > 0 {
346			log::error!(
347				target: "runtime::beefy",
348				"Failed to convert {} out of {} BEEFY PublicKeys to ETH addresses!",
349				uninitialized_addresses,
350				len,
351			);
352		}
353		let keyset_commitment = binary_merkle_tree::merkle_root::<
354			<T as pallet_mmr::Config>::Hashing,
355			_,
356		>(beefy_addresses)
357		.into();
358		BeefyAuthoritySet { id, len, keyset_commitment }
359	}
360}
361
362sp_api::decl_runtime_apis! {
363	/// API useful for BEEFY light clients.
364	pub trait BeefyMmrApi<H>
365	where
366		BeefyAuthoritySet<H>: Decode,
367	{
368		/// Return the currently active BEEFY authority set proof.
369		fn authority_set_proof() -> BeefyAuthoritySet<H>;
370
371		/// Return the next/queued BEEFY authority set proof.
372		fn next_authority_set_proof() -> BeefyNextAuthoritySet<H>;
373	}
374}