1use 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#[derive(Debug, Clone, Encode, Decode)]
20pub enum PreDigest<RewardAddress> {
21 #[codec(index = 0)]
23 V0 {
24 slot: Slot,
26 solution: Solution<RewardAddress>,
28 pot_info: PreDigestPotInfo,
30 },
31}
32
33impl<RewardAddress> PreDigest<RewardAddress> {
34 #[inline]
36 pub fn slot(&self) -> Slot {
37 let Self::V0 { slot, .. } = self;
38 *slot
39 }
40
41 #[inline]
43 pub fn solution(&self) -> &Solution<RewardAddress> {
44 let Self::V0 { solution, .. } = self;
45 solution
46 }
47
48 #[inline]
50 pub fn pot_info(&self) -> &PreDigestPotInfo {
51 let Self::V0 { pot_info, .. } = self;
52 pot_info
53 }
54}
55
56#[derive(Debug, Clone, Encode, Decode)]
58pub enum PreDigestPotInfo {
59 #[codec(index = 0)]
61 V0 {
62 proof_of_time: PotOutput,
64 future_proof_of_time: PotOutput,
66 },
67}
68
69impl PreDigestPotInfo {
70 #[inline]
72 pub fn proof_of_time(&self) -> PotOutput {
73 let Self::V0 { proof_of_time, .. } = self;
74 *proof_of_time
75 }
76
77 #[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
88pub trait CompatibleDigestItem: Sized {
90 fn subspace_pre_digest<AccountId: Encode>(pre_digest: &PreDigest<AccountId>) -> Self;
92
93 fn as_subspace_pre_digest<AccountId: Decode>(&self) -> Option<PreDigest<AccountId>>;
95
96 fn subspace_seal(signature: RewardSignature) -> Self;
98
99 fn as_subspace_seal(&self) -> Option<RewardSignature>;
101
102 fn pot_slot_iterations(pot_slot_iterations: NonZeroU32) -> Self;
105
106 fn as_pot_slot_iterations(&self) -> Option<NonZeroU32>;
108
109 fn solution_range(solution_range: SolutionRange) -> Self;
111
112 fn as_solution_range(&self) -> Option<SolutionRange>;
114
115 fn pot_parameters_change(pot_parameters_change: PotParametersChange) -> Self;
117
118 fn as_pot_parameters_change(&self) -> Option<PotParametersChange>;
120
121 fn next_solution_range(solution_range: SolutionRange) -> Self;
123
124 fn as_next_solution_range(&self) -> Option<SolutionRange>;
126
127 fn segment_commitment(
129 segment_index: SegmentIndex,
130 segment_commitment: SegmentCommitment,
131 ) -> Self;
132
133 fn as_segment_commitment(&self) -> Option<(SegmentIndex, SegmentCommitment)>;
135
136 fn enable_solution_range_adjustment_and_override(
139 override_solution_range: Option<SolutionRange>,
140 ) -> Self;
141
142 fn as_enable_solution_range_adjustment_and_override(&self) -> Option<Option<SolutionRange>>;
145
146 fn root_plot_public_key_update(root_plot_public_key: Option<PublicKey>) -> Self;
148
149 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
301pub enum ErrorDigestType {
302 PreDigest,
304 Seal,
306 PotSlotIterations,
308 SolutionRange,
310 PotParametersChange,
312 NextSolutionRange,
314 SegmentCommitment,
316 Consensus,
318 EnableSolutionRangeAdjustmentAndOverride,
320 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#[derive(Debug, PartialEq, Eq, thiserror::Error)]
363pub enum Error {
364 #[error("Subspace {0} digest not found")]
366 Missing(ErrorDigestType),
367 #[error("Failed to decode Subspace {0} digest: {1}")]
369 FailedToDecode(ErrorDigestType, parity_scale_codec::Error),
370 #[error("Duplicate Subspace {0} digests, rejecting!")]
372 Duplicate(ErrorDigestType),
373
374 #[error("Failed to derive next {0} digest, rejecting!")]
376 NextDigestDerivationError(ErrorDigestType),
377
378 #[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#[derive(Debug)]
393pub struct SubspaceDigestItems<RewardAddress> {
394 pub pre_digest: PreDigest<RewardAddress>,
396 pub signature: Option<RewardSignature>,
398 pub pot_slot_iterations: NonZeroU32,
401 pub solution_range: SolutionRange,
403 pub pot_parameters_change: Option<PotParametersChange>,
405 pub next_solution_range: Option<SolutionRange>,
407 pub segment_commitments: BTreeMap<SegmentIndex, SegmentCommitment>,
409 pub enable_solution_range_adjustment_and_override: Option<Option<SolutionRange>>,
411 pub root_plot_public_key_update: Option<Option<PublicKey>>,
413}
414
415pub 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 }
554 DigestItem::RuntimeEnvironmentUpdated => {
555 }
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
575pub fn extract_pre_digest<Header>(header: &Header) -> Result<PreDigest<PublicKey>, Error>
578where
579 Header: HeaderT,
580{
581 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
611pub struct DeriveNextSolutionRangeParams<Header: HeaderT> {
613 pub number: NumberOf<Header>,
615 pub era_duration: NumberOf<Header>,
617 pub slot_probability: (u64, u64),
619 pub current_slot: Slot,
621 pub current_solution_range: SolutionRange,
623 pub era_start_slot: Slot,
625 pub should_adjust_solution_range: bool,
627 pub maybe_next_solution_range_override: Option<SolutionRange>,
629}
630
631pub 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 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 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
671pub struct NextDigestsVerificationParams<'a, Header: HeaderT> {
673 pub number: NumberOf<Header>,
675 pub header_digests: &'a SubspaceDigestItems<PublicKey>,
677 pub era_duration: NumberOf<Header>,
679 pub slot_probability: (u64, u64),
681 pub era_start_slot: Slot,
683 pub should_adjust_solution_range: &'a mut bool,
686 pub maybe_next_solution_range_override: &'a mut Option<SolutionRange>,
689 pub maybe_root_plot_public_key: &'a mut Option<PublicKey>,
692}
693
694pub 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 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 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 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}