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
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
69/// A BEEFY consensus digest item with MMR root hash.
70pub 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
88/// Sentinel returned by [`BeefyEcdsaToEthereum`] when an ECDSA public key cannot be
89/// converted to an Ethereum address. Both producer and consumer must reference this
90/// constant so the two ends of the conversion can never drift to different sentinels
91/// (see `Pallet::compute_authority_set`, which counts failed conversions by matching
92/// against this value).
93pub const FAILED_BEEFY_TO_ETH_ADDRESS: [u8; 20] = [0u8; 20];
94
95/// Convert BEEFY secp256k1 public keys into Ethereum addresses
96pub 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	/// BEEFY-MMR pallet.
119	#[pallet::pallet]
120	pub struct Pallet<T>(_);
121
122	/// The module's configuration trait.
123	#[pallet::config]
124	#[pallet::disable_frame_system_supertrait_check]
125	pub trait Config: pallet_mmr::Config + pallet_beefy::Config {
126		/// Current leaf version.
127		///
128		/// Specifies the version number added to every leaf that get's appended to the MMR.
129		/// Read more in [`MmrLeafVersion`] docs about versioning leaves.
130		type LeafVersion: Get<MmrLeafVersion>;
131
132		/// Convert BEEFY AuthorityId to a form that would end up in the Merkle Tree.
133		///
134		/// For instance for ECDSA (secp256k1) we want to store uncompressed public keys (65 bytes)
135		/// and later to Ethereum Addresses (160 bits) to simplify using them on Ethereum chain,
136		/// but the rest of the Substrate codebase is storing them compressed (33 bytes) for
137		/// efficiency reasons.
138		type BeefyAuthorityToMerkleLeaf: Convert<<Self as pallet_beefy::Config>::BeefyId, Vec<u8>>;
139
140		/// The type expected for the leaf extra data
141		type LeafExtra: Member + codec::FullCodec;
142
143		/// Retrieve arbitrary data that should be added to the mmr leaf
144		type BeefyDataProvider: BeefyDataProvider<Self::LeafExtra>;
145
146		type WeightInfo: WeightInfo;
147	}
148
149	/// Details of current BEEFY authority set.
150	#[pallet::storage]
151	pub type BeefyAuthorities<T: Config> =
152		StorageValue<_, BeefyAuthoritySet<MerkleRootOf<T>>, ValueQuery>;
153
154	/// Details of next BEEFY authority set.
155	///
156	/// This storage entry is used as cache for calls to `update_beefy_next_authority_set`.
157	#[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	/// Compute and cache BEEFY authority sets based on updated BEEFY validator sets.
193	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		// cache the result
200		BeefyAuthorities::<T>::put(&current);
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		// We don't check the proof size when running benchmarks, since we use mock proofs
216		// which would cause the test to fail.
217		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		// Check if the provided header is canonical.
226		let expected_hash = frame_system::Pallet::<T>::block_hash(header.number());
227		if expected_hash != header.hash() {
228			return None;
229		}
230
231		// Extract the MMR root from the header digest
232		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					// We can't prove that the commitment is non-canonical if the
251					// `commitment.block_number` is invalid.
252					return false;
253				},
254			};
255		if commitment_leaf_count != proof.prev_leaf_count {
256			// Can't prove that the commitment is non-canonical if the `commitment.block_number`
257			// doesn't match the ancestry proof.
258			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					// Can't prove that the commitment is non-canonical if the proof
267					// is invalid.
268					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						// If the commitment contains an MMR root, that is not equal to
282						// `canonical_prev_root`, the commitment is invalid
283						return true;
284					}
285				},
286				None => {
287					// If the commitment contains an MMR root, that can't be decoded, it is invalid.
288					return true;
289				},
290			}
291		}
292		if !found_commitment_root {
293			// If the commitment doesn't contain any MMR root, while the proof is valid,
294			// the commitment is invalid
295			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		// The approximated cost of verifying an ancestry proof with `n` nodes.
319		// We add the previous peaks to the total number of nodes,
320		// since they have to be processed as well.
321		<T as Config>::WeightInfo::n_items_proof_is_non_canonical(
322			proof.items.len().saturating_add(proof.prev_peaks.len()).saturated_into(),
323		)
324		// `n_items_proof_is_non_canonical()` uses inflated proofs that contain all the leafs,
325		// where no peak needs to be read. So we need to also add the cost of reading the peaks.
326		.saturating_add(<T as Config>::WeightInfo::read_peak().saturating_mul(num_peaks))
327	}
328}
329
330impl<T: Config> Pallet<T> {
331	/// Return the currently active BEEFY authority set proof.
332	pub fn authority_set_proof() -> BeefyAuthoritySet<MerkleRootOf<T>> {
333		BeefyAuthorities::<T>::get()
334	}
335
336	/// Return the next/queued BEEFY authority set proof.
337	pub fn next_authority_set_proof() -> BeefyNextAuthoritySet<MerkleRootOf<T>> {
338		BeefyNextAuthorities::<T>::get()
339	}
340
341	/// Returns details of a BEEFY authority set.
342	///
343	/// Details contain authority set id, authority set length and a merkle root,
344	/// constructed from uncompressed secp256k1 public keys converted to Ethereum addresses
345	/// of the next BEEFY authority set.
346	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	/// Validates the invariants of this pallet's storage.
378	#[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	/// API useful for BEEFY light clients.
394	pub trait BeefyMmrApi<H>
395	where
396		BeefyAuthoritySet<H>: Decode,
397	{
398		/// Return the currently active BEEFY authority set proof.
399		fn authority_set_proof() -> BeefyAuthoritySet<H>;
400
401		/// Return the next/queued BEEFY authority set proof.
402		fn next_authority_set_proof() -> BeefyNextAuthoritySet<H>;
403	}
404}