pallet_block_production_log/
lib.rs

1//! A Substrate pallet maintaining a consumable log of block production information.
2//!
3//! ## Purpose of this pallet
4//!
5//! This pallet keeps a log containing block producer IDs along with times of blocks produced by them.
6//! This log is updated every block and is meant to consumed by other features.
7//! The intended use of this pallet within the Partner Chains SDK is to expose block production data for consumption
8//! by the Block Participation feature implemented by the `sp_block_participation` and `pallet_block_participation`
9//! crates.
10//!
11//! ## Usage - PC Builder
12//!
13//! ### Adding to the runtime
14//!
15//! The feature requires two types to be defind by the chain builders in their code:
16//! - `BlockProducerId`: the type representing the block author
17//! - `Moment`: a moment in time when the block was produced, which carries enough information to
18//!             calculate the block's author. Typcally, this type can be a timestamp, or a slot,
19//!             depending on the consensus mechanism used, but can be a richer type if needed.
20//!
21//! In addition, implementations of [GetAuthor] and [GetMoment] must be provided that can be used to
22//! retrieve the current block's author and moment when it was produced.
23//!
24//! An example configuration for a runtime using Aura consensus and Partner Chain toolkit's session management
25//! pallet might look like this:
26//!
27//! ```rust,ignore
28//! impl pallet_block_production_log::Config for Runtime {
29//!     type BlockProducerId = BlockAuthor;
30//!
31//!     type Moment = Slot;
32//!
33//!     type GetMoment = FromStorage<pallet_aura::CurrentSlot<Runtime>>;
34//!     type GetAuthor = FromFindAuthorIndex<Runtime, Aura, u32>;
35//! }
36//! ```
37//!
38//! #### Defining block producer ID
39//!
40//! The pallet expects the Partner Chain to provide a type representing its block producers.
41//! This type can be as simple as an Aura public key but can also be a more complex type if block producers
42//! are not a homogenous group. For example, in the context of a Partner Chain using Ariadne committee selection,
43//! it's typical to have two kinds of block producers: permissioned producers provided by the governance authority
44//! and registered candidates recruited from among Cardano stake pool operators. In this instance an example
45//! author type could be:
46//! ```rust
47//! use sidechain_domain::*;
48//!
49//! pub enum BlockAuthor {
50//!     Incentivized(CrossChainPublicKey, StakePoolPublicKey),
51//!     ProBono(CrossChainPublicKey),
52//! }
53//! ```
54//!
55//! Keep in mind that other Partner Chains SDK components put their own constraints on the block author type
56//! that need to be adhered to for a Partner Chain to integrated them.
57//!
58//! #### Defining moment type
59//!
60//! The pallet abstracts away the notion of time when a block was produced and allows the chain builders to
61//! configure it according to their chain's needs by providing a `Moment` type. This type can be a timestamp,
62//! a slot or round number, depending on the consensus mechanism used.
63//!
64//! #### Support for adding to a running chain
65//!
66//! The pallet is written in a way that allows for adding it to an already live chain.
67//!
68//! ### Consuming the log
69//!
70//! **Important**: Consuming the log is a destructive operation. Multiple features should not consume the log data
71//!                unless they are coordinated to read and clear the same log prefix.
72//!
73//! The pallet exposes three functions that allow other pallets to consume its data: `take_prefix`, `peek_prefix`
74//! and `drop_prefix`. Any feature using the log should be able to identify the time up to which it should
75//! process the log data and either:
76//! - call `take_prefix` from some pallet's logic and process the returned data within the same block
77//! - call `peek_prefix` inside an inherent data provider and use `drop_prefix` from the corresponding pallet
78//!   to clear the previously peeked data within the same block
79//!
80//! It is critically important to drop exactly the prefix processed to avoid either skipping or double-counting some blocks.
81//!
82//! ## Usage - PC user
83//!
84//! This pallet does not expose any user-facing functionalities.
85//!
86
87#![cfg_attr(not(feature = "std"), no_std)]
88#![deny(missing_docs)]
89
90pub mod weights;
91
92#[cfg(test)]
93mod mock;
94
95#[cfg(test)]
96mod test;
97
98use core::marker::PhantomData;
99
100pub use pallet::*;
101pub use weights::WeightInfo;
102
103/// Source of the current block's author
104pub trait GetAuthor<BlockProducerId> {
105	/// Returns the current block's author
106	fn get_author() -> Option<BlockProducerId>;
107}
108
109/// [GetAuthor] implementation that uses a [FindAuthor] instance to get the current block's author index
110/// of type `I` and uses it to read the author from `pallet_session_validator_management`.
111pub struct FromFindAuthorIndex<T, FA, I>(PhantomData<(T, FA, I)>);
112
113/// Source of the current block's moment
114pub trait GetMoment<Moment> {
115	/// Returns the current block's moment
116	fn get_moment() -> Option<Moment>;
117}
118
119/// [GetMoment] implementation that fetches current block's `Moment` from storage `S`
120pub struct FromStorage<S>(PhantomData<S>);
121
122#[frame_support::pallet]
123pub mod pallet {
124	use super::*;
125	use frame_support::pallet_prelude::*;
126	use frame_system::pallet_prelude::*;
127	use sp_runtime::traits::Member;
128	use sp_std::vec::Vec;
129
130	#[pallet::pallet]
131	pub struct Pallet<T>(_);
132
133	#[pallet::config]
134	pub trait Config: frame_system::Config {
135		/// ID type that can represent any block producer in the network.
136		/// This type should be defined by the Partner Chain Depending on its consensus mechanism and possible block producer types.
137		type BlockProducerId: Member + Parameter + MaxEncodedLen;
138
139		/// Type used to identify the moment in time when the block was produced, eg. a timestamp or slot number.
140		type Moment: Member + Parameter + MaxEncodedLen + PartialOrd + Ord + PartialEq + Eq;
141
142		/// Source of current block's author
143		type GetAuthor: GetAuthor<Self::BlockProducerId>;
144
145		/// Source of current block's moment
146		type GetMoment: GetMoment<Self::Moment>;
147	}
148
149	#[pallet::storage]
150	#[pallet::unbounded]
151	pub type Log<T: Config> = StorageValue<_, Vec<(T::Moment, T::BlockProducerId)>, ValueQuery>;
152
153	#[pallet::hooks]
154	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
155		/// Block initialization hook that adds current block's author to the log
156		fn on_initialize(block: BlockNumberFor<T>) -> Weight {
157			let Some(author) = T::GetAuthor::get_author() else {
158				log::warn!(
159					"👷 Block production log update skipped - could not determine block {block:?} producer"
160				);
161				return T::DbWeight::get().reads(1);
162			};
163			let Some(moment) = T::GetMoment::get_moment() else {
164				log::warn!(
165					"👷 Block production log update skipped - could not determine block {block:?} time"
166				);
167				return T::DbWeight::get().reads(1);
168			};
169
170			log::info!("👷 Block {block:?} producer is {author:?}");
171			Log::<T>::append((moment, author));
172
173			T::DbWeight::get().reads_writes(2, 1)
174		}
175	}
176
177	impl<T: Config> Pallet<T> {
178		/// Returns all entries up to `moment` (inclusive) and removes them from the log
179		pub fn take_prefix(moment: &T::Moment) -> Vec<(T::Moment, T::BlockProducerId)> {
180			let removed_prefix = Log::<T>::mutate(|log| {
181				let pos = log.partition_point(|(s, _)| s <= moment);
182				log.drain(..pos).collect()
183			});
184			removed_prefix
185		}
186
187		/// Returns all entries up to `moment` (inclusive) from the log
188		pub fn peek_prefix(
189			moment: &T::Moment,
190		) -> impl Iterator<Item = (T::Moment, T::BlockProducerId)> {
191			Log::<T>::get().into_iter().take_while(move |(s, _)| s <= moment)
192		}
193
194		/// Removes all entries up to `moment` (inclusive) from the log
195		pub fn drop_prefix(moment: &T::Moment) {
196			Log::<T>::mutate(|log| {
197				let position = log.partition_point(|(s, _)| s <= moment);
198				log.drain(..position);
199			});
200		}
201	}
202}
203
204#[cfg(feature = "block-participation")]
205mod block_participation {
206	use pallet_block_participation::BlockParticipationProvider;
207
208	impl<T: crate::Config> BlockParticipationProvider<T::Moment, T::BlockProducerId>
209		for crate::Pallet<T>
210	{
211		fn blocks_to_process(
212			moment: &T::Moment,
213		) -> impl Iterator<Item = (T::Moment, T::BlockProducerId)> {
214			Self::peek_prefix(moment)
215		}
216
217		fn discard_processed_blocks(moment: &T::Moment) {
218			Self::drop_prefix(moment)
219		}
220	}
221}
222
223mod source_impls {
224	use super::*;
225	use frame_support::{
226		pallet_prelude::StorageValue,
227		storage::types::QueryKindTrait,
228		traits::{FindAuthor, StorageInstance},
229	};
230	use pallet_session_validator_management as psvm;
231	use parity_scale_codec::FullCodec;
232	use sp_runtime::traits::Get;
233
234	impl<BlockProducerId, I, FA, T> GetAuthor<BlockProducerId> for FromFindAuthorIndex<T, FA, I>
235	where
236		FA: FindAuthor<I>,
237		I: TryInto<usize>,
238		T: psvm::Config,
239		psvm::CommitteeMemberOf<T>: Into<BlockProducerId>,
240	{
241		fn get_author() -> Option<BlockProducerId> {
242			Some(psvm::Pallet::<T>::find_current_authority::<I, FA>()?.into())
243		}
244	}
245
246	impl<Prefix, Value, Moment, QueryKind, OnEmpty> GetMoment<Moment>
247		for FromStorage<StorageValue<Prefix, Value, QueryKind, OnEmpty>>
248	where
249		Prefix: StorageInstance,
250		Value: FullCodec,
251		QueryKind: QueryKindTrait<Value, OnEmpty>,
252		OnEmpty: Get<QueryKind::Query> + 'static,
253		Option<Moment>: From<<QueryKind as QueryKindTrait<Value, OnEmpty>>::Query>,
254	{
255		fn get_moment() -> Option<Moment> {
256			StorageValue::<Prefix, Value, QueryKind, OnEmpty>::get().into()
257		}
258	}
259}