sp_block_production_log/
lib.rs

1//! Primitives and support crate for `pallet_block_production_log`.
2//!
3//! This crate defines the primitive types and the inherent data provider for the block production log feature.
4//!
5//! ## Usage
6//!
7//! This crate supports operation of `pallet_block_production_log`.
8//! Consult the pallet's documentation on how to include it in the runtime.
9//!
10//! ### Adding to the node
11//!
12//! #### Defining the types
13//!
14//! The feature requires two types to be defind by the chain builders in their code:
15//! - `Author`: the type representing the block author, as it's configured in the feature's pallet
16//! - `Moment`: a moment in time when the block was produced, which carries enough information to
17//!             calculate the block's author. Typcally, this type can be a timestamp, or a slot,
18//!             depending on the consensus mechanism used, but can be a richer type if needed.
19//!
20//! #### Implementing the runtime API
21//!
22//! The block production log feature requires [BlockProductionLogApi] to be implemented by the Partner Chain runtime
23//! so the current block author can be identified. The concrete implementation must use or match the mechanism of
24//! block author selection used by the particular Partner Chain's consensus mechanism.
25//!
26//! An example for a Partner Chain using Aura consensus looks like this:
27//! ```rust, ignore
28//! impl_runtime_apis! {
29//!     impl BlockProductionLogApi<Block, CommitteeMember, Slot> for Runtime {
30//!         fn get_author(slot: Slot) -> Option<CommitteeMember> {
31//!             SessionCommitteeManagement::get_current_authority_round_robin(u64::from(*slot) as usize)
32//!         }
33//!     }
34//! }
35//! ```
36//! using the `pallet_session_committee_management::Pallet::get_current_authority_round_robin` function
37//! which performs the same round-robin author selection that Aura does internally.
38//!
39//! #### Adding the inherent data provider
40//!
41//! The inherent data provider should be added to the node's `CreateInherentDataProviders` implementation for
42//! both proposal and validation of blocks.  eg.:
43//!
44//! ```rust,ignore
45//! // Create the inherent data provider. `slot` must be the slot number of the block currently being produced/verified
46//! let block_author_idp = BlockAuthorInherentProvider::new(client.as_ref(), parent_hash, slot)?;
47//! ...
48//! // Return the inherent data provider together with other IDPs
49//! Ok((timestamp_idp, slot_idp, ..., block_author_idp, ...))
50//! ```
51//!
52//! Notice that the IDP must be passed the current `Moment`, which in this instance is `Slot`.
53//!
54//! The inherent data provider created using `BlockAuthorInherentProvider::new` will check whether `BlockProductionLogApi`
55//! is available in the runtime and will only provide inherent data if the API is present.
56//!
57
58#![cfg_attr(not(feature = "std"), no_std)]
59#![deny(missing_docs)]
60
61extern crate alloc;
62
63#[cfg(test)]
64mod test;
65
66use parity_scale_codec::{Decode, Encode};
67use sp_inherents::{InherentIdentifier, IsFatalError};
68#[cfg(feature = "std")]
69use {
70	sp_api::{ApiExt, ProvideRuntimeApi},
71	sp_inherents::InherentData,
72	sp_runtime::traits::Block as BlockT,
73};
74
75/// Inherent identifier used by the Block Production Log pallet
76pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"blprdlog";
77
78/// Error type used for failing calls of the block production log feature's inherent.
79#[derive(Encode, PartialEq)]
80#[cfg_attr(not(feature = "std"), derive(Debug))]
81#[cfg_attr(feature = "std", derive(Decode, thiserror::Error, sp_runtime::RuntimeDebug))]
82pub enum InherentError {
83	/// Inherent was not produced when expected
84	#[cfg_attr(
85		feature = "std",
86		error("Block Author inherent must be provided every block after initialization")
87	)]
88	InherentRequired,
89	/// Block Author inherent data is not correctly encoded
90	#[cfg_attr(feature = "std", error("Block Author inherent data is not correctly encoded"))]
91	InvalidInherentData,
92}
93impl IsFatalError for InherentError {
94	fn is_fatal_error(&self) -> bool {
95		true
96	}
97}
98
99/// Current block producer data used as inherent data
100#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)]
101pub struct BlockProductionInherentDataV1<Moment, BlockProducerId> {
102	/// Moment of the block
103	pub moment: Moment,
104	/// Block producer ID
105	pub block_producer_id: BlockProducerId,
106}
107
108/// Inherent data provider providing the block author of the current block
109/// Type parameters:
110/// - `Author`: Type representing a block author.
111#[cfg(feature = "std")]
112#[derive(Debug)]
113pub struct BlockAuthorInherentProvider<Moment, Author> {
114	/// Optional value of the current block author. Inherent data is not provided if [None].
115	pub data: Option<BlockProductionInherentDataV1<Moment, Author>>,
116}
117
118#[cfg(feature = "std")]
119impl<Moment, Author> BlockAuthorInherentProvider<Moment, Author> {
120	/// Creates a new [BlockAuthorInherentProvider] using runtime API [BlockProductionLogApi].
121	///
122	/// The inherent data provider returned will be inert if [BlockProductionLogApi] is not detected in the runtime.
123	pub fn new<C, Block>(
124		client: &C,
125		parent_hash: Block::Hash,
126		moment: Moment,
127	) -> Result<Self, Box<dyn core::error::Error + Send + Sync>>
128	where
129		Moment: Encode,
130		Author: Decode,
131		Block: BlockT,
132		C: ProvideRuntimeApi<Block>,
133		C::Api: BlockProductionLogApi<Block, Author, Moment>,
134	{
135		let api = client.runtime_api();
136		if !api.has_api::<dyn BlockProductionLogApi<Block, Author, Moment>>(parent_hash)? {
137			return Ok(Self { data: None });
138		}
139		let data = client
140			.runtime_api()
141			.get_author(parent_hash, &moment)?
142			.map(|author| BlockProductionInherentDataV1 { moment, block_producer_id: author });
143
144		Ok(BlockAuthorInherentProvider { data })
145	}
146}
147
148#[cfg(feature = "std")]
149#[async_trait::async_trait]
150impl<Moment, T> sp_inherents::InherentDataProvider for BlockAuthorInherentProvider<Moment, T>
151where
152	T: Send + Sync + Encode + Decode,
153	Moment: Send + Sync + Encode + Decode,
154{
155	async fn provide_inherent_data(
156		&self,
157		inherent_data: &mut InherentData,
158	) -> Result<(), sp_inherents::Error> {
159		if let Some(data) = &self.data {
160			inherent_data.put_data(INHERENT_IDENTIFIER, data)?;
161		}
162		Ok(())
163	}
164
165	async fn try_handle_error(
166		&self,
167		identifier: &InherentIdentifier,
168		mut error: &[u8],
169	) -> Option<Result<(), sp_inherents::Error>> {
170		if identifier == &INHERENT_IDENTIFIER {
171			let error = InherentError::decode(&mut error).ok()?;
172			Some(Err(sp_inherents::Error::Application(Box::from(error))))
173		} else {
174			None
175		}
176	}
177}
178
179sp_api::decl_runtime_apis! {
180	/// Runtime API exposing data required for the [BlockAuthorInherentProvider] to operate.
181	/// Type parameters:
182	/// - `Author`: type representing a committee member eligible to be a block author. This type should correspond
183	///             to what is configured as the block author type used by the pallet.
184	pub trait BlockProductionLogApi<Author, Moment>
185	where
186		Author: Decode,
187		Moment: Encode
188	{
189		/// Function returning the current block's author.
190		///
191		/// Its implementation must either use data exposed by the consensus mechanism used by the Partner Chain,
192		/// independently calculate it, or obtain it from another source.
193		///
194		/// Parameters:
195		/// - `moment`: value representing the current block's moment in time. It will typically be a timestamp or
196		///             slot but can be any other value that is needed to decide the current block's author and can
197		///             be passed from the node.
198		fn get_author(moment: &Moment) -> Option<Author>;
199	}
200}