sp_consensus_subspace/
digests.rs

1//! Private implementation details of Subspace consensus digests.
2
3use crate::{ConsensusLog, PotParametersChange, SUBSPACE_ENGINE_ID};
4use log::trace;
5use parity_scale_codec::{Decode, Encode};
6use sp_consensus_slots::Slot;
7use sp_runtime::traits::{Header as HeaderT, One, Zero};
8use sp_runtime::DigestItem;
9use sp_std::collections::btree_map::{BTreeMap, Entry};
10use sp_std::fmt;
11use sp_std::num::NonZeroU32;
12use subspace_core_primitives::pot::PotOutput;
13use subspace_core_primitives::segments::{SegmentCommitment, SegmentIndex};
14use subspace_core_primitives::solutions::{RewardSignature, Solution, SolutionRange};
15use subspace_core_primitives::PublicKey;
16
17/// A Subspace pre-runtime digest. This contains all data required to validate a block and for the
18/// Subspace runtime module.
19#[derive(Debug, Clone, Encode, Decode)]
20pub enum PreDigest<RewardAddress> {
21    /// Initial version of the pre-digest
22    #[codec(index = 0)]
23    V0 {
24        /// Slot
25        slot: Slot,
26        /// Solution (includes PoR)
27        solution: Solution<RewardAddress>,
28        /// Proof of time information
29        pot_info: PreDigestPotInfo,
30    },
31}
32
33impl<RewardAddress> PreDigest<RewardAddress> {
34    /// Slot
35    #[inline]
36    pub fn slot(&self) -> Slot {
37        let Self::V0 { slot, .. } = self;
38        *slot
39    }
40
41    /// Solution (includes PoR)
42    #[inline]
43    pub fn solution(&self) -> &Solution<RewardAddress> {
44        let Self::V0 { solution, .. } = self;
45        solution
46    }
47
48    /// Proof of time information
49    #[inline]
50    pub fn pot_info(&self) -> &PreDigestPotInfo {
51        let Self::V0 { pot_info, .. } = self;
52        pot_info
53    }
54}
55
56/// Proof of time information in pre-digest
57#[derive(Debug, Clone, Encode, Decode)]
58pub enum PreDigestPotInfo {
59    /// Initial version of proof of time information
60    #[codec(index = 0)]
61    V0 {
62        /// Proof of time for this slot
63        proof_of_time: PotOutput,
64        /// Future proof of time
65        future_proof_of_time: PotOutput,
66    },
67}
68
69impl PreDigestPotInfo {
70    /// Proof of time for this slot
71    #[inline]
72    pub fn proof_of_time(&self) -> PotOutput {
73        let Self::V0 { proof_of_time, .. } = self;
74        *proof_of_time
75    }
76
77    /// Future proof of time
78    #[inline]
79    pub fn future_proof_of_time(&self) -> PotOutput {
80        let Self::V0 {
81            future_proof_of_time,
82            ..
83        } = self;
84        *future_proof_of_time
85    }
86}
87
88/// A digest item which is usable with Subspace consensus.
89pub trait CompatibleDigestItem: Sized {
90    /// Construct a digest item which contains a Subspace pre-digest.
91    fn subspace_pre_digest<AccountId: Encode>(pre_digest: &PreDigest<AccountId>) -> Self;
92
93    /// If this item is an Subspace pre-digest, return it.
94    fn as_subspace_pre_digest<AccountId: Decode>(&self) -> Option<PreDigest<AccountId>>;
95
96    /// Construct a digest item which contains a Subspace seal.
97    fn subspace_seal(signature: RewardSignature) -> Self;
98
99    /// If this item is a Subspace signature, return the signature.
100    fn as_subspace_seal(&self) -> Option<RewardSignature>;
101
102    /// Number of iterations for proof of time per slot, corresponds to slot that directly follows
103    /// parent block's slot and can change before slot for which block is produced
104    fn pot_slot_iterations(pot_slot_iterations: NonZeroU32) -> Self;
105
106    /// If this item is a Subspace proof of time slot iterations, return it.
107    fn as_pot_slot_iterations(&self) -> Option<NonZeroU32>;
108
109    /// Construct a digest item which contains a solution range.
110    fn solution_range(solution_range: SolutionRange) -> Self;
111
112    /// If this item is a Subspace solution range, return it.
113    fn as_solution_range(&self) -> Option<SolutionRange>;
114
115    /// Change of parameters to apply to PoT chain
116    fn pot_parameters_change(pot_parameters_change: PotParametersChange) -> Self;
117
118    /// If this item is a Subspace proof of time change of parameters, return it.
119    fn as_pot_parameters_change(&self) -> Option<PotParametersChange>;
120
121    /// Construct a digest item which contains next solution range.
122    fn next_solution_range(solution_range: SolutionRange) -> Self;
123
124    /// If this item is a Subspace next solution range, return it.
125    fn as_next_solution_range(&self) -> Option<SolutionRange>;
126
127    /// Construct a digest item which contains segment commitment.
128    fn segment_commitment(
129        segment_index: SegmentIndex,
130        segment_commitment: SegmentCommitment,
131    ) -> Self;
132
133    /// If this item is a Subspace segment commitment, return it.
134    fn as_segment_commitment(&self) -> Option<(SegmentIndex, SegmentCommitment)>;
135
136    /// Construct digest item than indicates enabling of solution range adjustment and override next
137    /// solution range.
138    fn enable_solution_range_adjustment_and_override(
139        override_solution_range: Option<SolutionRange>,
140    ) -> Self;
141
142    /// If this item is a Subspace Enable solution range adjustment and override next solution
143    /// range, return it.
144    fn as_enable_solution_range_adjustment_and_override(&self) -> Option<Option<SolutionRange>>;
145
146    /// Construct digest item that indicates update of root plot public key.
147    fn root_plot_public_key_update(root_plot_public_key: Option<PublicKey>) -> Self;
148
149    /// If this item is a Subspace update of root plot public key, return it.
150    fn as_root_plot_public_key_update(&self) -> Option<Option<PublicKey>>;
151}
152
153impl CompatibleDigestItem for DigestItem {
154    fn subspace_pre_digest<RewardAddress: Encode>(pre_digest: &PreDigest<RewardAddress>) -> Self {
155        Self::PreRuntime(SUBSPACE_ENGINE_ID, pre_digest.encode())
156    }
157
158    fn as_subspace_pre_digest<RewardAddress: Decode>(&self) -> Option<PreDigest<RewardAddress>> {
159        self.pre_runtime_try_to(&SUBSPACE_ENGINE_ID)
160    }
161
162    fn subspace_seal(signature: RewardSignature) -> Self {
163        Self::Seal(SUBSPACE_ENGINE_ID, signature.encode())
164    }
165
166    fn as_subspace_seal(&self) -> Option<RewardSignature> {
167        self.seal_try_to(&SUBSPACE_ENGINE_ID)
168    }
169
170    fn pot_slot_iterations(pot_slot_iterations: NonZeroU32) -> Self {
171        Self::Consensus(
172            SUBSPACE_ENGINE_ID,
173            ConsensusLog::PotSlotIterations(pot_slot_iterations).encode(),
174        )
175    }
176
177    fn as_pot_slot_iterations(&self) -> Option<NonZeroU32> {
178        self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
179            if let ConsensusLog::PotSlotIterations(pot_slot_iterations) = c {
180                Some(pot_slot_iterations)
181            } else {
182                None
183            }
184        })
185    }
186
187    fn solution_range(solution_range: SolutionRange) -> Self {
188        Self::Consensus(
189            SUBSPACE_ENGINE_ID,
190            ConsensusLog::SolutionRange(solution_range).encode(),
191        )
192    }
193
194    fn as_solution_range(&self) -> Option<SolutionRange> {
195        self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
196            if let ConsensusLog::SolutionRange(solution_range) = c {
197                Some(solution_range)
198            } else {
199                None
200            }
201        })
202    }
203
204    fn pot_parameters_change(pot_parameters_change: PotParametersChange) -> Self {
205        Self::Consensus(
206            SUBSPACE_ENGINE_ID,
207            ConsensusLog::PotParametersChange(pot_parameters_change).encode(),
208        )
209    }
210
211    fn as_pot_parameters_change(&self) -> Option<PotParametersChange> {
212        self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
213            if let ConsensusLog::PotParametersChange(pot_parameters_change) = c {
214                Some(pot_parameters_change)
215            } else {
216                None
217            }
218        })
219    }
220
221    fn next_solution_range(solution_range: SolutionRange) -> Self {
222        Self::Consensus(
223            SUBSPACE_ENGINE_ID,
224            ConsensusLog::NextSolutionRange(solution_range).encode(),
225        )
226    }
227
228    fn as_next_solution_range(&self) -> Option<SolutionRange> {
229        self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
230            if let ConsensusLog::NextSolutionRange(solution_range) = c {
231                Some(solution_range)
232            } else {
233                None
234            }
235        })
236    }
237
238    fn segment_commitment(
239        segment_index: SegmentIndex,
240        segment_commitment: SegmentCommitment,
241    ) -> Self {
242        Self::Consensus(
243            SUBSPACE_ENGINE_ID,
244            ConsensusLog::SegmentCommitment((segment_index, segment_commitment)).encode(),
245        )
246    }
247
248    fn as_segment_commitment(&self) -> Option<(SegmentIndex, SegmentCommitment)> {
249        self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
250            if let ConsensusLog::SegmentCommitment(segment_commitment) = c {
251                Some(segment_commitment)
252            } else {
253                None
254            }
255        })
256    }
257
258    fn enable_solution_range_adjustment_and_override(
259        maybe_override_solution_range: Option<SolutionRange>,
260    ) -> Self {
261        Self::Consensus(
262            SUBSPACE_ENGINE_ID,
263            ConsensusLog::EnableSolutionRangeAdjustmentAndOverride(maybe_override_solution_range)
264                .encode(),
265        )
266    }
267
268    fn as_enable_solution_range_adjustment_and_override(&self) -> Option<Option<SolutionRange>> {
269        self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
270            if let ConsensusLog::EnableSolutionRangeAdjustmentAndOverride(
271                maybe_override_solution_range,
272            ) = c
273            {
274                Some(maybe_override_solution_range)
275            } else {
276                None
277            }
278        })
279    }
280
281    fn root_plot_public_key_update(root_plot_public_key: Option<PublicKey>) -> Self {
282        Self::Consensus(
283            SUBSPACE_ENGINE_ID,
284            ConsensusLog::RootPlotPublicKeyUpdate(root_plot_public_key).encode(),
285        )
286    }
287
288    fn as_root_plot_public_key_update(&self) -> Option<Option<PublicKey>> {
289        self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
290            if let ConsensusLog::RootPlotPublicKeyUpdate(root_plot_public_key) = c {
291                Some(root_plot_public_key)
292            } else {
293                None
294            }
295        })
296    }
297}
298
299/// Various kinds of digest types used in errors
300#[derive(Debug, Copy, Clone, PartialEq, Eq)]
301pub enum ErrorDigestType {
302    /// Pre-digest
303    PreDigest,
304    /// Seal (signature)
305    Seal,
306    /// Number of iterations for proof of time per slot
307    PotSlotIterations,
308    /// Solution range
309    SolutionRange,
310    /// Change of parameters to apply to PoT chain
311    PotParametersChange,
312    /// Next solution range
313    NextSolutionRange,
314    /// Segment commitment
315    SegmentCommitment,
316    /// Generic consensus
317    Consensus,
318    /// Enable solution range adjustment and override solution range
319    EnableSolutionRangeAdjustmentAndOverride,
320    /// Root plot public key was updated
321    RootPlotPublicKeyUpdate,
322}
323
324impl fmt::Display for ErrorDigestType {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        match self {
327            ErrorDigestType::PreDigest => {
328                write!(f, "PreDigest")
329            }
330            ErrorDigestType::Seal => {
331                write!(f, "Seal")
332            }
333            ErrorDigestType::PotSlotIterations => {
334                write!(f, "PotSlotIterations")
335            }
336            ErrorDigestType::SolutionRange => {
337                write!(f, "SolutionRange")
338            }
339            ErrorDigestType::PotParametersChange => {
340                write!(f, "PotParametersChange")
341            }
342            ErrorDigestType::NextSolutionRange => {
343                write!(f, "NextSolutionRange")
344            }
345            ErrorDigestType::SegmentCommitment => {
346                write!(f, "SegmentCommitment")
347            }
348            ErrorDigestType::Consensus => {
349                write!(f, "Consensus")
350            }
351            ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride => {
352                write!(f, "EnableSolutionRangeAdjustmentAndOverride")
353            }
354            ErrorDigestType::RootPlotPublicKeyUpdate => {
355                write!(f, "RootPlotPublicKeyUpdate")
356            }
357        }
358    }
359}
360
361/// Digest error
362#[derive(Debug, PartialEq, Eq, thiserror::Error)]
363pub enum Error {
364    /// Subspace digest missing
365    #[error("Subspace {0} digest not found")]
366    Missing(ErrorDigestType),
367    /// Failed to decode Subspace digest
368    #[error("Failed to decode Subspace {0} digest: {1}")]
369    FailedToDecode(ErrorDigestType, parity_scale_codec::Error),
370    /// Duplicate Subspace digests
371    #[error("Duplicate Subspace {0} digests, rejecting!")]
372    Duplicate(ErrorDigestType),
373
374    /// Error when deriving next digests
375    #[error("Failed to derive next {0} digest, rejecting!")]
376    NextDigestDerivationError(ErrorDigestType),
377
378    /// Error when verifying next digests
379    #[error("Failed to verify next {0} digest, rejecting!")]
380    NextDigestVerificationError(ErrorDigestType),
381}
382
383#[cfg(feature = "std")]
384impl From<Error> for String {
385    #[inline]
386    fn from(error: Error) -> String {
387        error.to_string()
388    }
389}
390
391/// Digest items extracted from a header into convenient form
392#[derive(Debug)]
393pub struct SubspaceDigestItems<RewardAddress> {
394    /// Pre-runtime digest
395    pub pre_digest: PreDigest<RewardAddress>,
396    /// Signature (seal) if present
397    pub signature: Option<RewardSignature>,
398    /// Number of iterations for proof of time per slot, corresponds to slot that directly follows
399    /// parent block's slot and can change before slot for which block is produced
400    pub pot_slot_iterations: NonZeroU32,
401    /// Solution range
402    pub solution_range: SolutionRange,
403    /// Change of parameters to apply to PoT chain
404    pub pot_parameters_change: Option<PotParametersChange>,
405    /// Next solution range
406    pub next_solution_range: Option<SolutionRange>,
407    /// Segment commitments
408    pub segment_commitments: BTreeMap<SegmentIndex, SegmentCommitment>,
409    /// Enable solution range adjustment and Override solution range
410    pub enable_solution_range_adjustment_and_override: Option<Option<SolutionRange>>,
411    /// Root plot public key was updated
412    pub root_plot_public_key_update: Option<Option<PublicKey>>,
413}
414
415/// Extract the Subspace global randomness from the given header.
416pub fn extract_subspace_digest_items<Header, RewardAddress>(
417    header: &Header,
418) -> Result<SubspaceDigestItems<RewardAddress>, Error>
419where
420    Header: HeaderT,
421    RewardAddress: Decode,
422{
423    let mut maybe_pre_digest = None;
424    let mut maybe_seal = None;
425    let mut maybe_pot_slot_iterations = None;
426    let mut maybe_solution_range = None;
427    let mut maybe_pot_parameters_change = None;
428    let mut maybe_next_solution_range = None;
429    let mut segment_commitments = BTreeMap::new();
430    let mut maybe_enable_and_override_solution_range = None;
431    let mut maybe_root_plot_public_key_update = None;
432
433    for log in header.digest().logs() {
434        match log {
435            DigestItem::PreRuntime(id, data) => {
436                if id != &SUBSPACE_ENGINE_ID {
437                    continue;
438                }
439
440                let pre_digest = PreDigest::<RewardAddress>::decode(&mut data.as_slice())
441                    .map_err(|error| Error::FailedToDecode(ErrorDigestType::PreDigest, error))?;
442
443                match maybe_pre_digest {
444                    Some(_) => {
445                        return Err(Error::Duplicate(ErrorDigestType::PreDigest));
446                    }
447                    None => {
448                        maybe_pre_digest.replace(pre_digest);
449                    }
450                }
451            }
452            DigestItem::Consensus(id, data) => {
453                if id != &SUBSPACE_ENGINE_ID {
454                    continue;
455                }
456
457                let consensus = ConsensusLog::decode(&mut data.as_slice())
458                    .map_err(|error| Error::FailedToDecode(ErrorDigestType::Consensus, error))?;
459
460                match consensus {
461                    ConsensusLog::PotSlotIterations(pot_slot_iterations) => {
462                        match maybe_pot_slot_iterations {
463                            Some(_) => {
464                                return Err(Error::Duplicate(ErrorDigestType::PotSlotIterations));
465                            }
466                            None => {
467                                maybe_pot_slot_iterations.replace(pot_slot_iterations);
468                            }
469                        }
470                    }
471                    ConsensusLog::SolutionRange(solution_range) => match maybe_solution_range {
472                        Some(_) => {
473                            return Err(Error::Duplicate(ErrorDigestType::SolutionRange));
474                        }
475                        None => {
476                            maybe_solution_range.replace(solution_range);
477                        }
478                    },
479                    ConsensusLog::PotParametersChange(pot_parameters_change) => {
480                        match maybe_pot_parameters_change {
481                            Some(_) => {
482                                return Err(Error::Duplicate(ErrorDigestType::PotParametersChange));
483                            }
484                            None => {
485                                maybe_pot_parameters_change.replace(pot_parameters_change);
486                            }
487                        }
488                    }
489                    ConsensusLog::NextSolutionRange(solution_range) => {
490                        match maybe_next_solution_range {
491                            Some(_) => {
492                                return Err(Error::Duplicate(ErrorDigestType::NextSolutionRange));
493                            }
494                            None => {
495                                maybe_next_solution_range.replace(solution_range);
496                            }
497                        }
498                    }
499                    ConsensusLog::SegmentCommitment((segment_index, segment_commitment)) => {
500                        if let Entry::Vacant(entry) = segment_commitments.entry(segment_index) {
501                            entry.insert(segment_commitment);
502                        } else {
503                            return Err(Error::Duplicate(ErrorDigestType::SegmentCommitment));
504                        }
505                    }
506                    ConsensusLog::EnableSolutionRangeAdjustmentAndOverride(
507                        override_solution_range,
508                    ) => match maybe_enable_and_override_solution_range {
509                        Some(_) => {
510                            return Err(Error::Duplicate(
511                                ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride,
512                            ));
513                        }
514                        None => {
515                            maybe_enable_and_override_solution_range
516                                .replace(override_solution_range);
517                        }
518                    },
519                    ConsensusLog::RootPlotPublicKeyUpdate(root_plot_public_key_update) => {
520                        match maybe_root_plot_public_key_update {
521                            Some(_) => {
522                                return Err(Error::Duplicate(
523                                    ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride,
524                                ));
525                            }
526                            None => {
527                                maybe_root_plot_public_key_update
528                                    .replace(root_plot_public_key_update);
529                            }
530                        }
531                    }
532                }
533            }
534            DigestItem::Seal(id, data) => {
535                if id != &SUBSPACE_ENGINE_ID {
536                    continue;
537                }
538
539                let seal = RewardSignature::decode(&mut data.as_slice())
540                    .map_err(|error| Error::FailedToDecode(ErrorDigestType::Seal, error))?;
541
542                match maybe_seal {
543                    Some(_) => {
544                        return Err(Error::Duplicate(ErrorDigestType::Seal));
545                    }
546                    None => {
547                        maybe_seal.replace(seal);
548                    }
549                }
550            }
551            DigestItem::Other(_data) => {
552                // Ignore
553            }
554            DigestItem::RuntimeEnvironmentUpdated => {
555                // Ignore
556            }
557        }
558    }
559
560    Ok(SubspaceDigestItems {
561        pre_digest: maybe_pre_digest.ok_or(Error::Missing(ErrorDigestType::PreDigest))?,
562        signature: maybe_seal,
563        pot_slot_iterations: maybe_pot_slot_iterations
564            .ok_or(Error::Missing(ErrorDigestType::PotSlotIterations))?,
565        solution_range: maybe_solution_range
566            .ok_or(Error::Missing(ErrorDigestType::SolutionRange))?,
567        pot_parameters_change: maybe_pot_parameters_change,
568        next_solution_range: maybe_next_solution_range,
569        segment_commitments,
570        enable_solution_range_adjustment_and_override: maybe_enable_and_override_solution_range,
571        root_plot_public_key_update: maybe_root_plot_public_key_update,
572    })
573}
574
575/// Extract the Subspace pre digest from the given header. Pre-runtime digests are mandatory, the
576/// function will return `Err` if none is found.
577pub fn extract_pre_digest<Header>(header: &Header) -> Result<PreDigest<PublicKey>, Error>
578where
579    Header: HeaderT,
580{
581    // genesis block doesn't contain a pre digest so let's generate a
582    // dummy one to not break any invariants in the rest of the code
583    if header.number().is_zero() {
584        return Ok(PreDigest::V0 {
585            slot: Slot::from(0),
586            solution: Solution::genesis_solution(
587                PublicKey::from([0u8; 32]),
588                PublicKey::from([0u8; 32]),
589            ),
590            pot_info: PreDigestPotInfo::V0 {
591                proof_of_time: Default::default(),
592                future_proof_of_time: Default::default(),
593            },
594        });
595    }
596
597    let mut pre_digest = None;
598    for log in header.digest().logs() {
599        trace!(target: "subspace", "Checking log {:?}, looking for pre runtime digest", log);
600        match (log.as_subspace_pre_digest(), pre_digest.is_some()) {
601            (Some(_), true) => return Err(Error::Duplicate(ErrorDigestType::PreDigest)),
602            (None, _) => trace!(target: "subspace", "Ignoring digest not meant for us"),
603            (s, false) => pre_digest = s,
604        }
605    }
606    pre_digest.ok_or(Error::Missing(ErrorDigestType::PreDigest))
607}
608
609type NumberOf<T> = <T as HeaderT>::Number;
610
611/// Params used to derive the next solution range.
612pub struct DeriveNextSolutionRangeParams<Header: HeaderT> {
613    /// Current number of the block.
614    pub number: NumberOf<Header>,
615    /// Era duration of the chain.
616    pub era_duration: NumberOf<Header>,
617    /// Slot probability at which a block is produced.
618    pub slot_probability: (u64, u64),
619    /// Current slot of the block.
620    pub current_slot: Slot,
621    /// Current solution range of the block.
622    pub current_solution_range: SolutionRange,
623    /// Slot at which era has begun.
624    pub era_start_slot: Slot,
625    /// Flag to check if the next solution range should be adjusted.
626    pub should_adjust_solution_range: bool,
627    /// Solution range override that should be used instead of deriving from current.
628    pub maybe_next_solution_range_override: Option<SolutionRange>,
629}
630
631/// Derives next solution range if era duration interval has met.
632pub fn derive_next_solution_range<Header: HeaderT>(
633    params: DeriveNextSolutionRangeParams<Header>,
634) -> Result<Option<SolutionRange>, Error> {
635    let DeriveNextSolutionRangeParams {
636        number,
637        era_duration,
638        slot_probability,
639        current_slot,
640        current_solution_range,
641        era_start_slot,
642        should_adjust_solution_range,
643        maybe_next_solution_range_override,
644    } = params;
645
646    if number.is_zero() || number % era_duration != Zero::zero() {
647        return Ok(None);
648    }
649
650    // if the solution range should not be adjusted, return the current solution range
651    let next_solution_range = if !should_adjust_solution_range {
652        current_solution_range
653    } else if let Some(solution_range_override) = maybe_next_solution_range_override {
654        // era has change so take this override and reset it
655        solution_range_override
656    } else {
657        subspace_verification::derive_next_solution_range(
658            u64::from(era_start_slot),
659            u64::from(current_slot),
660            slot_probability,
661            current_solution_range,
662            era_duration
663                .try_into()
664                .unwrap_or_else(|_| panic!("Era duration is always within u64; qed")),
665        )
666    };
667
668    Ok(Some(next_solution_range))
669}
670
671/// Type that holds the parameters to derive and verify next digest items.
672pub struct NextDigestsVerificationParams<'a, Header: HeaderT> {
673    /// Header number for which we are verifying the digests.
674    pub number: NumberOf<Header>,
675    /// Digests present in the header that corresponds to number above.
676    pub header_digests: &'a SubspaceDigestItems<PublicKey>,
677    /// Era duration at which solution range is updated.
678    pub era_duration: NumberOf<Header>,
679    /// Slot probability.
680    pub slot_probability: (u64, u64),
681    /// Current Era start slot.
682    pub era_start_slot: Slot,
683    /// Should the solution range be adjusted on era change.
684    /// If the digest logs indicate that solution range adjustment has been enabled, value is updated.
685    pub should_adjust_solution_range: &'a mut bool,
686    /// Next Solution range override.
687    /// If the digest logs indicate that solution range override is provided, value is updated.
688    pub maybe_next_solution_range_override: &'a mut Option<SolutionRange>,
689    /// Root plot public key.
690    /// Value is updated when digest items contain an update.
691    pub maybe_root_plot_public_key: &'a mut Option<PublicKey>,
692}
693
694/// Derives and verifies next digest items based on their respective intervals.
695pub fn verify_next_digests<Header: HeaderT>(
696    params: NextDigestsVerificationParams<Header>,
697) -> Result<(), Error> {
698    let NextDigestsVerificationParams {
699        number,
700        header_digests,
701        era_duration,
702        slot_probability,
703        era_start_slot,
704        should_adjust_solution_range,
705        maybe_next_solution_range_override,
706        maybe_root_plot_public_key: root_plot_public_key,
707    } = params;
708
709    // verify solution range adjustment and override
710    // if the adjustment is already enabled, then error out
711    if *should_adjust_solution_range
712        && header_digests
713            .enable_solution_range_adjustment_and_override
714            .is_some()
715    {
716        return Err(Error::NextDigestVerificationError(
717            ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride,
718        ));
719    }
720
721    if let Some(solution_range_override) =
722        header_digests.enable_solution_range_adjustment_and_override
723    {
724        *should_adjust_solution_range = true;
725        *maybe_next_solution_range_override = solution_range_override;
726    }
727
728    // verify if the solution range should be derived at this block header
729    let expected_next_solution_range =
730        derive_next_solution_range::<Header>(DeriveNextSolutionRangeParams {
731            number,
732            era_duration,
733            slot_probability,
734            current_slot: header_digests.pre_digest.slot(),
735            current_solution_range: header_digests.solution_range,
736            era_start_slot,
737            should_adjust_solution_range: *should_adjust_solution_range,
738            maybe_next_solution_range_override: *maybe_next_solution_range_override,
739        })?;
740
741    if expected_next_solution_range.is_some() {
742        // Whatever override we had, it is no longer necessary
743        maybe_next_solution_range_override.take();
744    }
745    if expected_next_solution_range != header_digests.next_solution_range {
746        return Err(Error::NextDigestVerificationError(
747            ErrorDigestType::NextSolutionRange,
748        ));
749    }
750
751    if let Some(updated_root_plot_public_key) = header_digests.root_plot_public_key_update {
752        match updated_root_plot_public_key {
753            Some(updated_root_plot_public_key) => {
754                if number.is_one()
755                    && root_plot_public_key.is_none()
756                    && header_digests.pre_digest.solution().public_key
757                        == updated_root_plot_public_key
758                {
759                    root_plot_public_key.replace(updated_root_plot_public_key);
760                } else {
761                    return Err(Error::NextDigestVerificationError(
762                        ErrorDigestType::RootPlotPublicKeyUpdate,
763                    ));
764                }
765            }
766            None => {
767                root_plot_public_key.take();
768            }
769        }
770    }
771
772    Ok(())
773}