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}