1#![cfg_attr(not(feature = "std"), no_std)]
65#![deny(missing_docs)]
66
67extern crate alloc;
68
69use alloc::vec::Vec;
70use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
71use scale_info::TypeInfo;
72use sidechain_domain::{DelegatorKey, MainchainKeyHash, McEpochNumber};
73use sp_inherents::{InherentIdentifier, IsFatalError};
74
75#[cfg(test)]
76mod tests;
77
78pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"blokpart";
83
84#[derive(
88 Clone, Debug, PartialEq, Eq, Decode, DecodeWithMemTracking, Encode, TypeInfo, PartialOrd, Ord,
89)]
90pub struct DelegatorBlockParticipationData<DelegatorId> {
91 pub id: DelegatorId,
93 pub share: u64,
95}
96
97#[derive(
101 Clone, Debug, PartialEq, Eq, Decode, DecodeWithMemTracking, Encode, TypeInfo, PartialOrd, Ord,
102)]
103pub struct BlockProducerParticipationData<BlockProducerId, DelegatorId> {
104 pub block_producer: BlockProducerId,
106 pub block_count: u32,
108 pub delegator_total_shares: u64,
110 pub delegators: Vec<DelegatorBlockParticipationData<DelegatorId>>,
112}
113
114#[derive(Clone, Debug, PartialEq, Eq, Decode, DecodeWithMemTracking, Encode, TypeInfo)]
118pub struct BlockProductionData<BlockProducerId, DelegatorId> {
119 producer_participation: Vec<BlockProducerParticipationData<BlockProducerId, DelegatorId>>,
124}
125
126impl<BlockProducerId, DelegatorId> BlockProductionData<BlockProducerId, DelegatorId> {
127 pub fn new(
129 mut producer_participation: Vec<
130 BlockProducerParticipationData<BlockProducerId, DelegatorId>,
131 >,
132 ) -> Self
133 where
134 BlockProducerId: Ord,
135 DelegatorId: Ord,
136 {
137 for breakdown in &mut producer_participation {
138 breakdown.delegators.sort()
139 }
140 producer_participation.sort();
141 Self { producer_participation }
142 }
143
144 pub fn producer_participation(
146 &self,
147 ) -> &[BlockProducerParticipationData<BlockProducerId, DelegatorId>] {
148 &self.producer_participation
149 }
150}
151
152#[derive(Encode, PartialEq)]
154#[cfg_attr(not(feature = "std"), derive(Debug))]
155#[cfg_attr(
156 feature = "std",
157 derive(Decode, DecodeWithMemTracking, thiserror::Error, sp_runtime::RuntimeDebug)
158)]
159pub enum InherentError {
160 #[cfg_attr(feature = "std", error("Block participation inherent not produced when expected"))]
162 InherentRequired,
163 #[cfg_attr(feature = "std", error("Block participation inherent produced when not expected"))]
165 UnexpectedInherent,
166 #[cfg_attr(feature = "std", error("Inherent data provided by the node is invalid"))]
168 InvalidInherentData,
169}
170
171impl IsFatalError for InherentError {
172 fn is_fatal_error(&self) -> bool {
173 true
174 }
175}
176
177sp_api::decl_runtime_apis! {
178 pub trait BlockParticipationApi<BlockProducerId: Decode, Moment: Decode + Encode> {
182 fn blocks_to_process(moment: &Moment) -> Vec<(Moment, BlockProducerId)>;
184 fn target_inherent_id() -> InherentIdentifier;
186 fn moment_to_timestamp_millis(moment: Moment) -> u64;
188 }
189}
190
191pub trait AsCardanoSPO {
193 fn as_cardano_spo(&self) -> Option<MainchainKeyHash>;
195}
196impl AsCardanoSPO for Option<MainchainKeyHash> {
197 fn as_cardano_spo(&self) -> Option<MainchainKeyHash> {
198 *self
199 }
200}
201
202pub trait CardanoDelegator {
204 fn from_delegator_key(key: DelegatorKey) -> Self;
206}
207impl<T: From<DelegatorKey>> CardanoDelegator for T {
208 fn from_delegator_key(key: DelegatorKey) -> Self {
209 key.into()
210 }
211}
212
213#[cfg(feature = "std")]
215pub mod inherent_data {
216 use super::*;
217 use alloc::fmt::Debug;
218 use core::error::Error;
219 use core::ops::Deref;
220 use sidechain_domain::mainchain_epoch::*;
221 use sidechain_domain::*;
222 use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
223 use sp_inherents::{InherentData, InherentDataProvider};
224 use sp_runtime::traits::Block as BlockT;
225 use std::collections::HashMap;
226 use std::hash::Hash;
227
228 #[async_trait::async_trait]
230 pub trait BlockParticipationDataSource {
231 async fn get_stake_pool_delegation_distribution_for_pools(
233 &self,
234 epoch: McEpochNumber,
235 pool_hashes: &[MainchainKeyHash],
236 ) -> Result<StakeDistribution, Box<dyn std::error::Error + Send + Sync>>;
237 }
238
239 #[derive(thiserror::Error, Debug)]
241 pub enum InherentDataCreationError<BlockProducerId: Debug> {
242 #[error("Runtime API call failed: {0}")]
244 ApiError(#[from] ApiError),
245 #[error("Data source call failed: {0}")]
247 DataSourceError(Box<dyn Error + Send + Sync>),
248 #[error("Missing epoch {0} data for {1:?}")]
252 DataMissing(McEpochNumber, BlockProducerId),
253 #[error("Offset of {1} can not be applied to main chain epoch {0}")]
258 McEpochBelowOffset(McEpochNumber, u32),
259 }
260
261 #[derive(Debug, Clone, PartialEq)]
269 pub enum BlockParticipationInherentDataProvider<BlockProducerId, DelegatorId, Moment> {
270 Active {
273 moment: Moment,
275 target_inherent_id: InherentIdentifier,
277 block_production_data: BlockProductionData<BlockProducerId, DelegatorId>,
279 },
280 Inert,
282 }
283
284 impl<BlockProducer, Delegator, Moment>
285 BlockParticipationInherentDataProvider<BlockProducer, Delegator, Moment>
286 where
287 BlockProducer: AsCardanoSPO + Decode + Clone + Hash + Eq + Ord + Debug,
288 Delegator: CardanoDelegator + Ord + Debug,
289 Moment: Encode + Decode + Send + Sync,
290 {
291 pub async fn new<Block: BlockT, T>(
296 client: &T,
297 data_source: &(dyn BlockParticipationDataSource + Send + Sync),
298 parent_hash: <Block as BlockT>::Hash,
299 moment: Moment,
300 mc_epoch_config: &MainchainEpochConfig,
301 ) -> Result<Self, InherentDataCreationError<BlockProducer>>
302 where
303 Moment: Decode + Encode,
304 T: ProvideRuntimeApi<Block> + Send + Sync,
305 T::Api: BlockParticipationApi<Block, BlockProducer, Moment>,
306 {
307 let api = client.runtime_api();
308
309 if !api
310 .has_api::<dyn BlockParticipationApi<Block, BlockProducer, Moment>>(parent_hash)?
311 {
312 return Ok(Self::Inert);
313 }
314
315 let blocks_to_process = api.blocks_to_process(parent_hash, &moment)?;
316
317 if blocks_to_process.is_empty() {
318 log::debug!("💤︎ Skipping computing block participation data this block...");
319 return Ok(Self::Inert);
320 };
321
322 let target_inherent_id = api.target_inherent_id(parent_hash)?;
323
324 let block_counts_by_epoch_and_producer = Self::count_blocks_by_epoch_and_producer(
325 blocks_to_process,
326 mc_epoch_config,
327 parent_hash,
328 api.deref(),
329 )?;
330
331 let mut production_summaries = vec![];
332 for (mc_epoch, producer_blocks) in block_counts_by_epoch_and_producer {
333 let stake_distribution =
334 Self::fetch_delegations(mc_epoch, producer_blocks.keys().cloned(), data_source)
335 .await?;
336 for (producer, block_count) in producer_blocks {
337 let breakdown = Self::production_breakdown_for(
338 mc_epoch,
339 producer,
340 block_count,
341 &stake_distribution,
342 )?;
343
344 production_summaries.push(breakdown);
345 }
346 }
347
348 Ok(Self::Active {
349 moment,
350 target_inherent_id,
351 block_production_data: BlockProductionData::new(production_summaries),
352 })
353 }
354
355 fn production_breakdown_for(
356 mc_epoch: McEpochNumber,
357 block_producer: BlockProducer,
358 block_count: u32,
359 distribution: &StakeDistribution,
360 ) -> Result<
361 BlockProducerParticipationData<BlockProducer, Delegator>,
362 InherentDataCreationError<BlockProducer>,
363 > {
364 let (beneficiary_total_share, beneficiaries) = match block_producer.as_cardano_spo() {
365 None => (0, vec![]),
366 Some(cardano_producer) => {
367 let PoolDelegation { total_stake, delegators } =
368 distribution.0.get(&cardano_producer).ok_or_else(|| {
369 InherentDataCreationError::DataMissing(mc_epoch, block_producer.clone())
370 })?;
371 let beneficiaries = delegators
372 .iter()
373 .map(|(delegator_key, stake_amount)| DelegatorBlockParticipationData {
374 id: Delegator::from_delegator_key(delegator_key.clone()),
375 share: stake_amount.0.into(),
376 })
377 .collect();
378 (total_stake.0, beneficiaries)
379 },
380 };
381
382 Ok(BlockProducerParticipationData {
383 block_producer,
384 block_count,
385 delegator_total_shares: beneficiary_total_share,
386 delegators: beneficiaries,
387 })
388 }
389
390 async fn fetch_delegations(
391 mc_epoch: McEpochNumber,
392 producers: impl Iterator<Item = BlockProducer>,
393 data_source: &(dyn BlockParticipationDataSource + Send + Sync),
394 ) -> Result<StakeDistribution, InherentDataCreationError<BlockProducer>> {
395 let pools: Vec<_> = producers.flat_map(|p| p.as_cardano_spo()).collect();
396 data_source
397 .get_stake_pool_delegation_distribution_for_pools(mc_epoch, &pools)
398 .await
399 .map_err(InherentDataCreationError::DataSourceError)
400 }
401
402 fn data_mc_epoch_for_timestamp(
403 timestamp: Timestamp,
404 mc_epoch_config: &MainchainEpochConfig,
405 ) -> Result<McEpochNumber, InherentDataCreationError<BlockProducer>> {
406 let mc_epoch = mc_epoch_config
407 .timestamp_to_mainchain_epoch(timestamp)
408 .expect("Mainchain epoch for past timestamps exists");
409
410 offset_data_epoch(&mc_epoch)
411 .map_err(|offset| InherentDataCreationError::McEpochBelowOffset(mc_epoch, offset))
412 }
413
414 fn count_blocks_by_epoch_and_producer<Block: BlockT, Api>(
415 blocks: Vec<(Moment, BlockProducer)>,
416 mc_epoch_config: &MainchainEpochConfig,
417 parent_hash: Block::Hash,
418 api: &Api,
419 ) -> Result<
420 HashMap<McEpochNumber, HashMap<BlockProducer, u32>>,
421 InherentDataCreationError<BlockProducer>,
422 >
423 where
424 Api: BlockParticipationApi<Block, BlockProducer, Moment>,
425 {
426 let mut epoch_producers: HashMap<McEpochNumber, HashMap<BlockProducer, u32>> =
427 HashMap::new();
428
429 for (moment, producer) in blocks {
430 let timestamp = Timestamp::from_unix_millis(
431 api.moment_to_timestamp_millis(parent_hash, moment)?,
432 );
433 let mc_epoch = Self::data_mc_epoch_for_timestamp(timestamp, mc_epoch_config)?;
434 let producer_block_count =
435 epoch_producers.entry(mc_epoch).or_default().entry(producer).or_default();
436
437 *producer_block_count += 1;
438 }
439
440 Ok(epoch_producers)
441 }
442 }
443
444 #[async_trait::async_trait]
445 impl<BlockProducerId, DelegatorId, Moment> InherentDataProvider
446 for BlockParticipationInherentDataProvider<BlockProducerId, DelegatorId, Moment>
447 where
448 DelegatorId: Encode + Send + Sync,
449 BlockProducerId: Encode + Send + Sync,
450 Moment: Encode + Send + Sync,
451 {
452 async fn provide_inherent_data(
453 &self,
454 inherent_data: &mut InherentData,
455 ) -> Result<(), sp_inherents::Error> {
456 if let Self::Active { target_inherent_id, block_production_data, moment } = &self {
457 inherent_data.put_data(*target_inherent_id, block_production_data)?;
458 inherent_data.put_data(INHERENT_IDENTIFIER, &moment)?;
459 }
460 Ok(())
461 }
462
463 async fn try_handle_error(
464 &self,
465 identifier: &InherentIdentifier,
466 mut error: &[u8],
467 ) -> Option<Result<(), sp_inherents::Error>> {
468 if *identifier == INHERENT_IDENTIFIER {
469 let err = match InherentError::decode(&mut error) {
470 Ok(error) => Box::from(error),
471 Err(decoding_err) => Box::from(format!(
472 "Undecodable block production inherent error: {decoding_err:?}"
473 )),
474 };
475
476 Some(Err(sp_inherents::Error::Application(err)))
477 } else {
478 None
479 }
480 }
481 }
482}