pallet_block_participation/
lib.rs

1//! Pallet providing configuration and supporting runtime logic for the block participation data feature of Partner Chains SDK.
2//!
3//! ## Purpose of this pallet
4//!
5//! This pallet provides the runtime-side logic supporting the block participation data feature of PC SDK.
6//! Unlike most pallets, this one is not meant to be interacted with either by the chain's users or other
7//! runtime components in the system. Instead, it only serves two purposes:
8//! - it provides all configuration required by the feature's inherent data provider defined in the primitives crate
9//! - it provides an inhrenent extrinsic that removes from the runtime storage data that has been already
10//!   processed by the inherent data provider
11//! The reason for that is that the feature's purpose is to produce inherent data containing block participation
12//! data for consumption by custom-written pallet provided by each Partner Chain itself.
13//!
14//! The pallet is expected to be used together with the `pallet_block_production_log` when deployed in the
15//! context of Partner Chains SDK.
16//!
17//! ## Usage
18//!
19//! ### Adding into the runtime
20//!
21//! Consult documentation of [pallet::Config] for details on each configuration field.
22//!
23//! Assuming that the runtime also contains the `pallet_block_production_log`, an example configuration of
24//! the pallet might look like the following:
25//! ```rust,ignore
26//! const RELEASE_PERIOD: u64 = 128;
27//!
28//! impl pallet_block_participation::Config for Runtime {
29//!     type WeightInfo = pallet_block_participation::weights::SubstrateWeight<Runtime>;
30//!     type BlockAuthor = BlockAuthor;
31//!     type DelegatorId = DelegatorKey;
32//!
33//!     type BlockParticipationProvider = BlockProductionLog;
34//!
35//!     const TARGET_INHERENT_ID: InherentIdentifier = *b"_example";
36//! }
37//! ```
38#![cfg_attr(not(feature = "std"), no_std)]
39#![deny(missing_docs)]
40
41#[cfg(feature = "runtime-benchmarks")]
42mod benchmarking;
43#[cfg(test)]
44mod mock;
45#[cfg(test)]
46mod tests;
47pub mod weights;
48
49use frame_support::pallet_prelude::*;
50pub use pallet::*;
51use sp_block_participation::*;
52
53/// Source of block participation data
54pub trait BlockParticipationProvider<Moment, BlockProducer> {
55	/// Returns the block data for processing
56	fn blocks_to_process(moment: &Moment) -> impl Iterator<Item = (Moment, BlockProducer)>;
57
58	/// Discards processed data
59	fn discard_processed_blocks(moment: &Moment);
60}
61
62#[frame_support::pallet]
63pub mod pallet {
64	use super::*;
65	use frame_system::pallet_prelude::*;
66	use sp_std::vec::Vec;
67
68	#[pallet::pallet]
69	pub struct Pallet<T>(_);
70
71	#[pallet::config]
72	pub trait Config: frame_system::Config {
73		/// Weight info for this pallet's extrinsics
74		type WeightInfo: crate::weights::WeightInfo;
75
76		/// Moment in time at which the participation data should be processed
77		///
78		/// This type should be convertible to a timestamp value. If it represents a time range,
79		/// a representative timestamp, such as the start of the range should be computable from it.
80		type Moment: Parameter + Default + MaxEncodedLen + PartialOrd;
81
82		/// Source of block participation data
83		///
84		/// The default implementation provided by the Partner Chains toolit is the block production
85		/// log pallet implemented by the `pallet_block_production_log` crate.
86		type BlockParticipationProvider: BlockParticipationProvider<Self::Moment, Self::BlockAuthor>;
87
88		/// Type identifying the producer of a block on the Partner Chain
89		type BlockAuthor: Member + Parameter + MaxEncodedLen;
90
91		/// Type identifying indirect block production participants on the Partner Chain
92		/// This can be native stakers on Partner Chain, stakers on the main chain or other.
93		type DelegatorId: Member + Parameter + MaxEncodedLen;
94
95		/// Inherent ID under which block participation data should be provided.
96		/// It should be set to the ID used by the pallet that will process participation data for
97		/// paying out block rewards or other purposes.
98		const TARGET_INHERENT_ID: InherentIdentifier;
99	}
100
101	#[pallet::storage]
102	pub type ProcessedUpTo<T: Config> = StorageValue<_, T::Moment, ValueQuery>;
103
104	#[pallet::error]
105	pub enum Error<T> {
106		///sss
107		MomentNotIncreasing,
108	}
109
110	#[pallet::inherent]
111	impl<T: Config> ProvideInherent for Pallet<T> {
112		type Call = Call<T>;
113		type Error = sp_block_participation::InherentError;
114		const INHERENT_IDENTIFIER: InherentIdentifier = sp_block_participation::INHERENT_IDENTIFIER;
115
116		fn create_inherent(data: &InherentData) -> Option<Self::Call> {
117			let up_to_moment = Self::decode_inherent_data(data).unwrap()?;
118			Some(Call::note_processing { up_to_moment })
119		}
120
121		fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
122			let Some(expected_moment) = Self::decode_inherent_data(data)? else {
123				return Err(Self::Error::UnexpectedInherent);
124			};
125
126			let Self::Call::note_processing { up_to_moment } = call else {
127				unreachable!("There should be no other extrinsic in the pallet")
128			};
129
130			ensure!(expected_moment == *up_to_moment, Self::Error::InvalidInherentData);
131
132			Ok(())
133		}
134
135		fn is_inherent(call: &Self::Call) -> bool {
136			matches!(call, Call::note_processing { .. })
137		}
138
139		fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
140			if Self::decode_inherent_data(data)?.is_some() {
141				Ok(Some(Self::Error::InherentRequired))
142			} else {
143				Ok(None)
144			}
145		}
146	}
147
148	impl<T: Config> Pallet<T> {
149		fn decode_inherent_data(data: &InherentData) -> Result<Option<T::Moment>, InherentError> {
150			data.get_data(&Self::INHERENT_IDENTIFIER)
151				.map_err(|_| InherentError::InvalidInherentData)
152		}
153	}
154
155	#[pallet::call]
156	impl<T: Config> Pallet<T> {
157		/// Registers the fact that block participation data has been released for processing
158		/// and removes the handled data from block production log.
159		///
160		/// This inherent does not by itself process any data and only serves an operational function
161		/// by cleaning up data that has been already processed by other components.
162		#[pallet::call_index(0)]
163		#[pallet::weight((0, DispatchClass::Mandatory))]
164		pub fn note_processing(origin: OriginFor<T>, up_to_moment: T::Moment) -> DispatchResult {
165			ensure_none(origin)?;
166			ensure!(ProcessedUpTo::<T>::get() < up_to_moment, Error::<T>::MomentNotIncreasing);
167			log::info!("🧾 Processing block participation data");
168			T::BlockParticipationProvider::discard_processed_blocks(&up_to_moment);
169			ProcessedUpTo::<T>::set(up_to_moment);
170			Ok(())
171		}
172	}
173
174	impl<T: Config> Pallet<T> {
175		/// Fetches all blocks to be processed
176		pub fn blocks_to_process(moment: &T::Moment) -> Vec<(T::Moment, T::BlockAuthor)> {
177			<T as Config>::BlockParticipationProvider::blocks_to_process(moment).collect()
178		}
179
180		/// Discards processed data
181		pub fn discard_processed_blocks(moment: &T::Moment) {
182			T::BlockParticipationProvider::discard_processed_blocks(moment);
183		}
184
185		/// Returns the inherent ID at which the participation feature should provide participation data
186		pub fn target_inherent_id() -> InherentIdentifier {
187			<T as Config>::TARGET_INHERENT_ID
188		}
189	}
190}