1#![cfg_attr(not(feature = "std"), no_std)]
61#![deny(missing_docs)]
62
63extern crate alloc;
64
65use alloc::vec::Vec;
66use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
67use scale_info::TypeInfo;
68use sidechain_domain::{DelegatorKey, MainchainKeyHash, McEpochNumber};
69pub use sp_consensus_slots::{Slot, SlotDuration};
70use sp_inherents::{InherentIdentifier, IsFatalError};
71
72#[cfg(test)]
73mod tests;
74
75pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"blokpart";
80
81#[derive(
85 Clone, Debug, PartialEq, Eq, Decode, DecodeWithMemTracking, Encode, TypeInfo, PartialOrd, Ord,
86)]
87pub struct DelegatorBlockParticipationData<DelegatorId> {
88 pub id: DelegatorId,
90 pub share: u64,
92}
93
94#[derive(
98 Clone, Debug, PartialEq, Eq, Decode, DecodeWithMemTracking, Encode, TypeInfo, PartialOrd, Ord,
99)]
100pub struct BlockProducerParticipationData<BlockProducerId, DelegatorId> {
101 pub block_producer: BlockProducerId,
103 pub block_count: u32,
105 pub delegator_total_shares: u64,
107 pub delegators: Vec<DelegatorBlockParticipationData<DelegatorId>>,
109}
110
111#[derive(Clone, Debug, PartialEq, Eq, Decode, DecodeWithMemTracking, Encode, TypeInfo)]
115pub struct BlockProductionData<BlockProducerId, DelegatorId> {
116 up_to_slot: Slot,
118 producer_participation: Vec<BlockProducerParticipationData<BlockProducerId, DelegatorId>>,
123}
124
125impl<BlockProducerId, DelegatorId> BlockProductionData<BlockProducerId, DelegatorId> {
126 pub fn new(
128 up_to_slot: Slot,
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 { up_to_slot, producer_participation }
142 }
143
144 pub fn up_to_slot(&self) -> Slot {
146 self.up_to_slot
147 }
148
149 pub fn producer_participation(
151 &self,
152 ) -> &[BlockProducerParticipationData<BlockProducerId, DelegatorId>] {
153 &self.producer_participation
154 }
155}
156
157#[derive(Encode, PartialEq)]
159#[cfg_attr(not(feature = "std"), derive(Debug))]
160#[cfg_attr(
161 feature = "std",
162 derive(Decode, DecodeWithMemTracking, thiserror::Error, sp_runtime::RuntimeDebug)
163)]
164pub enum InherentError {
165 #[cfg_attr(feature = "std", error("Block participation inherent not produced when expected"))]
167 InherentRequired,
168 #[cfg_attr(feature = "std", error("Block participation inherent produced when not expected"))]
170 UnexpectedInherent,
171 #[cfg_attr(feature = "std", error("Block participation up_to_slot incorrect"))]
173 IncorrectSlotBoundary,
174 #[cfg_attr(feature = "std", error("Inherent data provided by the node is invalid"))]
176 InvalidInherentData,
177}
178
179impl IsFatalError for InherentError {
180 fn is_fatal_error(&self) -> bool {
181 true
182 }
183}
184
185sp_api::decl_runtime_apis! {
186 pub trait BlockParticipationApi<BlockProducerId: Decode> {
190 fn should_release_data(slot: Slot) -> Option<Slot>;
192 fn blocks_produced_up_to_slot(slot: Slot) -> Vec<(Slot, BlockProducerId)>;
194 fn target_inherent_id() -> InherentIdentifier;
196 }
197}
198
199pub trait AsCardanoSPO {
201 fn as_cardano_spo(&self) -> Option<MainchainKeyHash>;
203}
204impl AsCardanoSPO for Option<MainchainKeyHash> {
205 fn as_cardano_spo(&self) -> Option<MainchainKeyHash> {
206 *self
207 }
208}
209
210pub trait CardanoDelegator {
212 fn from_delegator_key(key: DelegatorKey) -> Self;
214}
215impl<T: From<DelegatorKey>> CardanoDelegator for T {
216 fn from_delegator_key(key: DelegatorKey) -> Self {
217 key.into()
218 }
219}
220
221#[cfg(feature = "std")]
223pub mod inherent_data {
224 use super::*;
225 use alloc::fmt::Debug;
226 use core::error::Error;
227 use sidechain_domain::mainchain_epoch::*;
228 use sidechain_domain::*;
229 use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
230 use sp_inherents::{InherentData, InherentDataProvider};
231 use sp_runtime::traits::Block as BlockT;
232 use std::collections::HashMap;
233 use std::hash::Hash;
234
235 #[async_trait::async_trait]
237 pub trait BlockParticipationDataSource {
238 async fn get_stake_pool_delegation_distribution_for_pools(
240 &self,
241 epoch: McEpochNumber,
242 pool_hashes: &[MainchainKeyHash],
243 ) -> Result<StakeDistribution, Box<dyn std::error::Error + Send + Sync>>;
244 }
245
246 #[derive(thiserror::Error, Debug)]
248 pub enum InherentDataCreationError<BlockProducerId: Debug> {
249 #[error("Runtime API call failed: {0}")]
251 ApiError(#[from] ApiError),
252 #[error("Data source call failed: {0}")]
254 DataSourceError(Box<dyn Error + Send + Sync>),
255 #[error("Missing epoch {0} data for {1:?}")]
259 DataMissing(McEpochNumber, BlockProducerId),
260 #[error("Offset of {1} can not be applied to main chain epoch {0}")]
265 McEpochBelowOffset(McEpochNumber, u32),
266 }
267
268 #[derive(Debug, Clone, PartialEq)]
277 pub enum BlockParticipationInherentDataProvider<BlockProducerId, DelegatorId> {
278 Active {
281 target_inherent_id: InherentIdentifier,
283 block_production_data: BlockProductionData<BlockProducerId, DelegatorId>,
285 },
286 Inert,
288 }
289
290 impl<BlockProducer, Delegator> BlockParticipationInherentDataProvider<BlockProducer, Delegator>
291 where
292 BlockProducer: AsCardanoSPO + Decode + Clone + Hash + Eq + Ord + Debug,
293 Delegator: CardanoDelegator + Ord + Debug,
294 {
295 pub async fn new<Block: BlockT, T>(
300 client: &T,
301 data_source: &(dyn BlockParticipationDataSource + Send + Sync),
302 parent_hash: <Block as BlockT>::Hash,
303 current_slot: Slot,
304 mc_epoch_config: &MainchainEpochConfig,
305 slot_duration: SlotDuration,
306 ) -> Result<Self, InherentDataCreationError<BlockProducer>>
307 where
308 T: ProvideRuntimeApi<Block> + Send + Sync,
309 T::Api: BlockParticipationApi<Block, BlockProducer>,
310 {
311 let api = client.runtime_api();
312
313 if !api.has_api::<dyn BlockParticipationApi<Block, BlockProducer>>(parent_hash)? {
314 return Ok(Self::Inert);
315 }
316
317 let Some(up_to_slot) = api.should_release_data(parent_hash, current_slot)? else {
318 log::debug!("💤︎ Skipping computing block participation data this block...");
319 return Ok(Self::Inert);
320 };
321 let blocks_produced_up_to_slot =
322 api.blocks_produced_up_to_slot(parent_hash, up_to_slot)?;
323 let target_inherent_id = api.target_inherent_id(parent_hash)?;
324
325 let block_counts_by_epoch_and_producer = Self::count_blocks_by_epoch_and_producer(
326 blocks_produced_up_to_slot,
327 mc_epoch_config,
328 slot_duration,
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 target_inherent_id,
350 block_production_data: BlockProductionData::new(up_to_slot, production_summaries),
351 })
352 }
353
354 fn production_breakdown_for(
355 mc_epoch: McEpochNumber,
356 block_producer: BlockProducer,
357 block_count: u32,
358 distribution: &StakeDistribution,
359 ) -> Result<
360 BlockProducerParticipationData<BlockProducer, Delegator>,
361 InherentDataCreationError<BlockProducer>,
362 > {
363 let (beneficiary_total_share, beneficiaries) = match block_producer.as_cardano_spo() {
364 None => (0, vec![]),
365 Some(cardano_producer) => {
366 let PoolDelegation { total_stake, delegators } =
367 distribution.0.get(&cardano_producer).ok_or_else(|| {
368 InherentDataCreationError::DataMissing(mc_epoch, block_producer.clone())
369 })?;
370 let beneficiaries = delegators
371 .iter()
372 .map(|(delegator_key, stake_amount)| DelegatorBlockParticipationData {
373 id: Delegator::from_delegator_key(delegator_key.clone()),
374 share: stake_amount.0.into(),
375 })
376 .collect();
377 (total_stake.0, beneficiaries)
378 },
379 };
380
381 Ok(BlockProducerParticipationData {
382 block_producer,
383 block_count,
384 delegator_total_shares: beneficiary_total_share,
385 delegators: beneficiaries,
386 })
387 }
388
389 async fn fetch_delegations(
390 mc_epoch: McEpochNumber,
391 producers: impl Iterator<Item = BlockProducer>,
392 data_source: &(dyn BlockParticipationDataSource + Send + Sync),
393 ) -> Result<StakeDistribution, InherentDataCreationError<BlockProducer>> {
394 let pools: Vec<_> = producers.flat_map(|p| p.as_cardano_spo()).collect();
395 data_source
396 .get_stake_pool_delegation_distribution_for_pools(mc_epoch, &pools)
397 .await
398 .map_err(InherentDataCreationError::DataSourceError)
399 }
400
401 fn data_mc_epoch_for_slot(
402 slot: Slot,
403 slot_duration: SlotDuration,
404 mc_epoch_config: &MainchainEpochConfig,
405 ) -> Result<McEpochNumber, InherentDataCreationError<BlockProducer>> {
406 let timestamp = Timestamp::from_unix_millis(
407 slot.timestamp(slot_duration)
408 .expect("Timestamp for past slots can not overflow")
409 .as_millis(),
410 );
411 let mc_epoch = mc_epoch_config
412 .timestamp_to_mainchain_epoch(timestamp)
413 .expect("Mainchain epoch for past slots exists");
414
415 offset_data_epoch(&mc_epoch)
416 .map_err(|offset| InherentDataCreationError::McEpochBelowOffset(mc_epoch, offset))
417 }
418
419 fn count_blocks_by_epoch_and_producer(
420 slot_producers: Vec<(Slot, BlockProducer)>,
421 mc_epoch_config: &MainchainEpochConfig,
422 slot_duration: SlotDuration,
423 ) -> Result<
424 HashMap<McEpochNumber, HashMap<BlockProducer, u32>>,
425 InherentDataCreationError<BlockProducer>,
426 > {
427 let mut epoch_producers: HashMap<McEpochNumber, HashMap<BlockProducer, u32>> =
428 HashMap::new();
429
430 for (slot, producer) in slot_producers {
431 let mc_epoch = Self::data_mc_epoch_for_slot(slot, slot_duration, mc_epoch_config)?;
432 let producer_block_count =
433 epoch_producers.entry(mc_epoch).or_default().entry(producer).or_default();
434
435 *producer_block_count += 1;
436 }
437
438 Ok(epoch_producers)
439 }
440 }
441
442 #[async_trait::async_trait]
443 impl<BlockProducerId, DelegatorId> InherentDataProvider
444 for BlockParticipationInherentDataProvider<BlockProducerId, DelegatorId>
445 where
446 DelegatorId: Encode + Send + Sync,
447 BlockProducerId: Encode + Send + Sync,
448 {
449 async fn provide_inherent_data(
450 &self,
451 inherent_data: &mut InherentData,
452 ) -> Result<(), sp_inherents::Error> {
453 if let Self::Active { target_inherent_id, block_production_data } = &self {
454 inherent_data.put_data(*target_inherent_id, block_production_data)?;
455 inherent_data.put_data(INHERENT_IDENTIFIER, &block_production_data.up_to_slot)?;
456 }
457 Ok(())
458 }
459
460 async fn try_handle_error(
461 &self,
462 identifier: &InherentIdentifier,
463 mut error: &[u8],
464 ) -> Option<Result<(), sp_inherents::Error>> {
465 if *identifier == INHERENT_IDENTIFIER {
466 let err = match InherentError::decode(&mut error) {
467 Ok(error) => Box::from(error),
468 Err(decoding_err) => Box::from(format!(
469 "Undecodable block production inherent error: {decoding_err:?}"
470 )),
471 };
472
473 Some(Err(sp_inherents::Error::Application(err)))
474 } else {
475 None
476 }
477 }
478 }
479}