use crate::{ConsensusLog, PotParametersChange, SUBSPACE_ENGINE_ID};
use codec::{Decode, Encode};
use log::trace;
use sp_consensus_slots::Slot;
use sp_runtime::traits::{Header as HeaderT, One, Zero};
use sp_runtime::DigestItem;
use sp_std::collections::btree_map::{BTreeMap, Entry};
use sp_std::fmt;
use sp_std::num::NonZeroU32;
use subspace_core_primitives::pot::PotOutput;
use subspace_core_primitives::segments::{SegmentCommitment, SegmentIndex};
use subspace_core_primitives::solutions::{RewardSignature, Solution, SolutionRange};
use subspace_core_primitives::PublicKey;
#[derive(Debug, Clone, Encode, Decode)]
pub enum PreDigest<RewardAddress> {
#[codec(index = 0)]
V0 {
slot: Slot,
solution: Solution<RewardAddress>,
pot_info: PreDigestPotInfo,
},
}
impl<RewardAddress> PreDigest<RewardAddress> {
#[inline]
pub fn slot(&self) -> Slot {
let Self::V0 { slot, .. } = self;
*slot
}
#[inline]
pub fn solution(&self) -> &Solution<RewardAddress> {
let Self::V0 { solution, .. } = self;
solution
}
#[inline]
pub fn pot_info(&self) -> &PreDigestPotInfo {
let Self::V0 { pot_info, .. } = self;
pot_info
}
}
#[derive(Debug, Clone, Encode, Decode)]
pub enum PreDigestPotInfo {
#[codec(index = 0)]
V0 {
proof_of_time: PotOutput,
future_proof_of_time: PotOutput,
},
}
impl PreDigestPotInfo {
#[inline]
pub fn proof_of_time(&self) -> PotOutput {
let Self::V0 { proof_of_time, .. } = self;
*proof_of_time
}
#[inline]
pub fn future_proof_of_time(&self) -> PotOutput {
let Self::V0 {
future_proof_of_time,
..
} = self;
*future_proof_of_time
}
}
pub trait CompatibleDigestItem: Sized {
fn subspace_pre_digest<AccountId: Encode>(pre_digest: &PreDigest<AccountId>) -> Self;
fn as_subspace_pre_digest<AccountId: Decode>(&self) -> Option<PreDigest<AccountId>>;
fn subspace_seal(signature: RewardSignature) -> Self;
fn as_subspace_seal(&self) -> Option<RewardSignature>;
fn pot_slot_iterations(pot_slot_iterations: NonZeroU32) -> Self;
fn as_pot_slot_iterations(&self) -> Option<NonZeroU32>;
fn solution_range(solution_range: SolutionRange) -> Self;
fn as_solution_range(&self) -> Option<SolutionRange>;
fn pot_parameters_change(pot_parameters_change: PotParametersChange) -> Self;
fn as_pot_parameters_change(&self) -> Option<PotParametersChange>;
fn next_solution_range(solution_range: SolutionRange) -> Self;
fn as_next_solution_range(&self) -> Option<SolutionRange>;
fn segment_commitment(
segment_index: SegmentIndex,
segment_commitment: SegmentCommitment,
) -> Self;
fn as_segment_commitment(&self) -> Option<(SegmentIndex, SegmentCommitment)>;
fn enable_solution_range_adjustment_and_override(
override_solution_range: Option<SolutionRange>,
) -> Self;
fn as_enable_solution_range_adjustment_and_override(&self) -> Option<Option<SolutionRange>>;
fn root_plot_public_key_update(root_plot_public_key: Option<PublicKey>) -> Self;
fn as_root_plot_public_key_update(&self) -> Option<Option<PublicKey>>;
}
impl CompatibleDigestItem for DigestItem {
fn subspace_pre_digest<RewardAddress: Encode>(pre_digest: &PreDigest<RewardAddress>) -> Self {
Self::PreRuntime(SUBSPACE_ENGINE_ID, pre_digest.encode())
}
fn as_subspace_pre_digest<RewardAddress: Decode>(&self) -> Option<PreDigest<RewardAddress>> {
self.pre_runtime_try_to(&SUBSPACE_ENGINE_ID)
}
fn subspace_seal(signature: RewardSignature) -> Self {
Self::Seal(SUBSPACE_ENGINE_ID, signature.encode())
}
fn as_subspace_seal(&self) -> Option<RewardSignature> {
self.seal_try_to(&SUBSPACE_ENGINE_ID)
}
fn pot_slot_iterations(pot_slot_iterations: NonZeroU32) -> Self {
Self::Consensus(
SUBSPACE_ENGINE_ID,
ConsensusLog::PotSlotIterations(pot_slot_iterations).encode(),
)
}
fn as_pot_slot_iterations(&self) -> Option<NonZeroU32> {
self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
if let ConsensusLog::PotSlotIterations(pot_slot_iterations) = c {
Some(pot_slot_iterations)
} else {
None
}
})
}
fn solution_range(solution_range: SolutionRange) -> Self {
Self::Consensus(
SUBSPACE_ENGINE_ID,
ConsensusLog::SolutionRange(solution_range).encode(),
)
}
fn as_solution_range(&self) -> Option<SolutionRange> {
self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
if let ConsensusLog::SolutionRange(solution_range) = c {
Some(solution_range)
} else {
None
}
})
}
fn pot_parameters_change(pot_parameters_change: PotParametersChange) -> Self {
Self::Consensus(
SUBSPACE_ENGINE_ID,
ConsensusLog::PotParametersChange(pot_parameters_change).encode(),
)
}
fn as_pot_parameters_change(&self) -> Option<PotParametersChange> {
self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
if let ConsensusLog::PotParametersChange(pot_parameters_change) = c {
Some(pot_parameters_change)
} else {
None
}
})
}
fn next_solution_range(solution_range: SolutionRange) -> Self {
Self::Consensus(
SUBSPACE_ENGINE_ID,
ConsensusLog::NextSolutionRange(solution_range).encode(),
)
}
fn as_next_solution_range(&self) -> Option<SolutionRange> {
self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
if let ConsensusLog::NextSolutionRange(solution_range) = c {
Some(solution_range)
} else {
None
}
})
}
fn segment_commitment(
segment_index: SegmentIndex,
segment_commitment: SegmentCommitment,
) -> Self {
Self::Consensus(
SUBSPACE_ENGINE_ID,
ConsensusLog::SegmentCommitment((segment_index, segment_commitment)).encode(),
)
}
fn as_segment_commitment(&self) -> Option<(SegmentIndex, SegmentCommitment)> {
self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
if let ConsensusLog::SegmentCommitment(segment_commitment) = c {
Some(segment_commitment)
} else {
None
}
})
}
fn enable_solution_range_adjustment_and_override(
maybe_override_solution_range: Option<SolutionRange>,
) -> Self {
Self::Consensus(
SUBSPACE_ENGINE_ID,
ConsensusLog::EnableSolutionRangeAdjustmentAndOverride(maybe_override_solution_range)
.encode(),
)
}
fn as_enable_solution_range_adjustment_and_override(&self) -> Option<Option<SolutionRange>> {
self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
if let ConsensusLog::EnableSolutionRangeAdjustmentAndOverride(
maybe_override_solution_range,
) = c
{
Some(maybe_override_solution_range)
} else {
None
}
})
}
fn root_plot_public_key_update(root_plot_public_key: Option<PublicKey>) -> Self {
Self::Consensus(
SUBSPACE_ENGINE_ID,
ConsensusLog::RootPlotPublicKeyUpdate(root_plot_public_key).encode(),
)
}
fn as_root_plot_public_key_update(&self) -> Option<Option<PublicKey>> {
self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| {
if let ConsensusLog::RootPlotPublicKeyUpdate(root_plot_public_key) = c {
Some(root_plot_public_key)
} else {
None
}
})
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ErrorDigestType {
PreDigest,
Seal,
PotSlotIterations,
SolutionRange,
PotParametersChange,
NextSolutionRange,
SegmentCommitment,
Consensus,
EnableSolutionRangeAdjustmentAndOverride,
RootPlotPublicKeyUpdate,
}
impl fmt::Display for ErrorDigestType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ErrorDigestType::PreDigest => {
write!(f, "PreDigest")
}
ErrorDigestType::Seal => {
write!(f, "Seal")
}
ErrorDigestType::PotSlotIterations => {
write!(f, "PotSlotIterations")
}
ErrorDigestType::SolutionRange => {
write!(f, "SolutionRange")
}
ErrorDigestType::PotParametersChange => {
write!(f, "PotParametersChange")
}
ErrorDigestType::NextSolutionRange => {
write!(f, "NextSolutionRange")
}
ErrorDigestType::SegmentCommitment => {
write!(f, "SegmentCommitment")
}
ErrorDigestType::Consensus => {
write!(f, "Consensus")
}
ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride => {
write!(f, "EnableSolutionRangeAdjustmentAndOverride")
}
ErrorDigestType::RootPlotPublicKeyUpdate => {
write!(f, "RootPlotPublicKeyUpdate")
}
}
}
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum Error {
#[error("Subspace {0} digest not found")]
Missing(ErrorDigestType),
#[error("Failed to decode Subspace {0} digest: {1}")]
FailedToDecode(ErrorDigestType, codec::Error),
#[error("Duplicate Subspace {0} digests, rejecting!")]
Duplicate(ErrorDigestType),
#[error("Failed to derive next {0} digest, rejecting!")]
NextDigestDerivationError(ErrorDigestType),
#[error("Failed to verify next {0} digest, rejecting!")]
NextDigestVerificationError(ErrorDigestType),
}
#[cfg(feature = "std")]
impl From<Error> for String {
#[inline]
fn from(error: Error) -> String {
error.to_string()
}
}
#[derive(Debug)]
pub struct SubspaceDigestItems<RewardAddress> {
pub pre_digest: PreDigest<RewardAddress>,
pub signature: Option<RewardSignature>,
pub pot_slot_iterations: NonZeroU32,
pub solution_range: SolutionRange,
pub pot_parameters_change: Option<PotParametersChange>,
pub next_solution_range: Option<SolutionRange>,
pub segment_commitments: BTreeMap<SegmentIndex, SegmentCommitment>,
pub enable_solution_range_adjustment_and_override: Option<Option<SolutionRange>>,
pub root_plot_public_key_update: Option<Option<PublicKey>>,
}
pub fn extract_subspace_digest_items<Header, RewardAddress>(
header: &Header,
) -> Result<SubspaceDigestItems<RewardAddress>, Error>
where
Header: HeaderT,
RewardAddress: Decode,
{
let mut maybe_pre_digest = None;
let mut maybe_seal = None;
let mut maybe_pot_slot_iterations = None;
let mut maybe_solution_range = None;
let mut maybe_pot_parameters_change = None;
let mut maybe_next_solution_range = None;
let mut segment_commitments = BTreeMap::new();
let mut maybe_enable_and_override_solution_range = None;
let mut maybe_root_plot_public_key_update = None;
for log in header.digest().logs() {
match log {
DigestItem::PreRuntime(id, data) => {
if id != &SUBSPACE_ENGINE_ID {
continue;
}
let pre_digest = PreDigest::<RewardAddress>::decode(&mut data.as_slice())
.map_err(|error| Error::FailedToDecode(ErrorDigestType::PreDigest, error))?;
match maybe_pre_digest {
Some(_) => {
return Err(Error::Duplicate(ErrorDigestType::PreDigest));
}
None => {
maybe_pre_digest.replace(pre_digest);
}
}
}
DigestItem::Consensus(id, data) => {
if id != &SUBSPACE_ENGINE_ID {
continue;
}
let consensus = ConsensusLog::decode(&mut data.as_slice())
.map_err(|error| Error::FailedToDecode(ErrorDigestType::Consensus, error))?;
match consensus {
ConsensusLog::PotSlotIterations(pot_slot_iterations) => {
match maybe_pot_slot_iterations {
Some(_) => {
return Err(Error::Duplicate(ErrorDigestType::PotSlotIterations));
}
None => {
maybe_pot_slot_iterations.replace(pot_slot_iterations);
}
}
}
ConsensusLog::SolutionRange(solution_range) => match maybe_solution_range {
Some(_) => {
return Err(Error::Duplicate(ErrorDigestType::SolutionRange));
}
None => {
maybe_solution_range.replace(solution_range);
}
},
ConsensusLog::PotParametersChange(pot_parameters_change) => {
match maybe_pot_parameters_change {
Some(_) => {
return Err(Error::Duplicate(ErrorDigestType::PotParametersChange));
}
None => {
maybe_pot_parameters_change.replace(pot_parameters_change);
}
}
}
ConsensusLog::NextSolutionRange(solution_range) => {
match maybe_next_solution_range {
Some(_) => {
return Err(Error::Duplicate(ErrorDigestType::NextSolutionRange));
}
None => {
maybe_next_solution_range.replace(solution_range);
}
}
}
ConsensusLog::SegmentCommitment((segment_index, segment_commitment)) => {
if let Entry::Vacant(entry) = segment_commitments.entry(segment_index) {
entry.insert(segment_commitment);
} else {
return Err(Error::Duplicate(ErrorDigestType::SegmentCommitment));
}
}
ConsensusLog::EnableSolutionRangeAdjustmentAndOverride(
override_solution_range,
) => match maybe_enable_and_override_solution_range {
Some(_) => {
return Err(Error::Duplicate(
ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride,
));
}
None => {
maybe_enable_and_override_solution_range
.replace(override_solution_range);
}
},
ConsensusLog::RootPlotPublicKeyUpdate(root_plot_public_key_update) => {
match maybe_root_plot_public_key_update {
Some(_) => {
return Err(Error::Duplicate(
ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride,
));
}
None => {
maybe_root_plot_public_key_update
.replace(root_plot_public_key_update);
}
}
}
}
}
DigestItem::Seal(id, data) => {
if id != &SUBSPACE_ENGINE_ID {
continue;
}
let seal = RewardSignature::decode(&mut data.as_slice())
.map_err(|error| Error::FailedToDecode(ErrorDigestType::Seal, error))?;
match maybe_seal {
Some(_) => {
return Err(Error::Duplicate(ErrorDigestType::Seal));
}
None => {
maybe_seal.replace(seal);
}
}
}
DigestItem::Other(_data) => {
}
DigestItem::RuntimeEnvironmentUpdated => {
}
}
}
Ok(SubspaceDigestItems {
pre_digest: maybe_pre_digest.ok_or(Error::Missing(ErrorDigestType::PreDigest))?,
signature: maybe_seal,
pot_slot_iterations: maybe_pot_slot_iterations
.ok_or(Error::Missing(ErrorDigestType::PotSlotIterations))?,
solution_range: maybe_solution_range
.ok_or(Error::Missing(ErrorDigestType::SolutionRange))?,
pot_parameters_change: maybe_pot_parameters_change,
next_solution_range: maybe_next_solution_range,
segment_commitments,
enable_solution_range_adjustment_and_override: maybe_enable_and_override_solution_range,
root_plot_public_key_update: maybe_root_plot_public_key_update,
})
}
pub fn extract_pre_digest<Header>(header: &Header) -> Result<PreDigest<PublicKey>, Error>
where
Header: HeaderT,
{
if header.number().is_zero() {
return Ok(PreDigest::V0 {
slot: Slot::from(0),
solution: Solution::genesis_solution(
PublicKey::from([0u8; 32]),
PublicKey::from([0u8; 32]),
),
pot_info: PreDigestPotInfo::V0 {
proof_of_time: Default::default(),
future_proof_of_time: Default::default(),
},
});
}
let mut pre_digest = None;
for log in header.digest().logs() {
trace!(target: "subspace", "Checking log {:?}, looking for pre runtime digest", log);
match (log.as_subspace_pre_digest(), pre_digest.is_some()) {
(Some(_), true) => return Err(Error::Duplicate(ErrorDigestType::PreDigest)),
(None, _) => trace!(target: "subspace", "Ignoring digest not meant for us"),
(s, false) => pre_digest = s,
}
}
pre_digest.ok_or(Error::Missing(ErrorDigestType::PreDigest))
}
type NumberOf<T> = <T as HeaderT>::Number;
pub struct DeriveNextSolutionRangeParams<Header: HeaderT> {
pub number: NumberOf<Header>,
pub era_duration: NumberOf<Header>,
pub slot_probability: (u64, u64),
pub current_slot: Slot,
pub current_solution_range: SolutionRange,
pub era_start_slot: Slot,
pub should_adjust_solution_range: bool,
pub maybe_next_solution_range_override: Option<SolutionRange>,
}
pub fn derive_next_solution_range<Header: HeaderT>(
params: DeriveNextSolutionRangeParams<Header>,
) -> Result<Option<SolutionRange>, Error> {
let DeriveNextSolutionRangeParams {
number,
era_duration,
slot_probability,
current_slot,
current_solution_range,
era_start_slot,
should_adjust_solution_range,
maybe_next_solution_range_override,
} = params;
if number.is_zero() || number % era_duration != Zero::zero() {
return Ok(None);
}
let next_solution_range = if !should_adjust_solution_range {
current_solution_range
} else if let Some(solution_range_override) = maybe_next_solution_range_override {
solution_range_override
} else {
subspace_verification::derive_next_solution_range(
u64::from(era_start_slot),
u64::from(current_slot),
slot_probability,
current_solution_range,
era_duration
.try_into()
.unwrap_or_else(|_| panic!("Era duration is always within u64; qed")),
)
};
Ok(Some(next_solution_range))
}
pub struct NextDigestsVerificationParams<'a, Header: HeaderT> {
pub number: NumberOf<Header>,
pub header_digests: &'a SubspaceDigestItems<PublicKey>,
pub era_duration: NumberOf<Header>,
pub slot_probability: (u64, u64),
pub era_start_slot: Slot,
pub should_adjust_solution_range: &'a mut bool,
pub maybe_next_solution_range_override: &'a mut Option<SolutionRange>,
pub maybe_root_plot_public_key: &'a mut Option<PublicKey>,
}
pub fn verify_next_digests<Header: HeaderT>(
params: NextDigestsVerificationParams<Header>,
) -> Result<(), Error> {
let NextDigestsVerificationParams {
number,
header_digests,
era_duration,
slot_probability,
era_start_slot,
should_adjust_solution_range,
maybe_next_solution_range_override,
maybe_root_plot_public_key: root_plot_public_key,
} = params;
if *should_adjust_solution_range
&& header_digests
.enable_solution_range_adjustment_and_override
.is_some()
{
return Err(Error::NextDigestVerificationError(
ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride,
));
}
if let Some(solution_range_override) =
header_digests.enable_solution_range_adjustment_and_override
{
*should_adjust_solution_range = true;
*maybe_next_solution_range_override = solution_range_override;
}
let expected_next_solution_range =
derive_next_solution_range::<Header>(DeriveNextSolutionRangeParams {
number,
era_duration,
slot_probability,
current_slot: header_digests.pre_digest.slot(),
current_solution_range: header_digests.solution_range,
era_start_slot,
should_adjust_solution_range: *should_adjust_solution_range,
maybe_next_solution_range_override: *maybe_next_solution_range_override,
})?;
if expected_next_solution_range.is_some() {
maybe_next_solution_range_override.take();
}
if expected_next_solution_range != header_digests.next_solution_range {
return Err(Error::NextDigestVerificationError(
ErrorDigestType::NextSolutionRange,
));
}
if let Some(updated_root_plot_public_key) = header_digests.root_plot_public_key_update {
match updated_root_plot_public_key {
Some(updated_root_plot_public_key) => {
if number.is_one()
&& root_plot_public_key.is_none()
&& header_digests.pre_digest.solution().public_key
== updated_root_plot_public_key
{
root_plot_public_key.replace(updated_root_plot_public_key);
} else {
return Err(Error::NextDigestVerificationError(
ErrorDigestType::RootPlotPublicKeyUpdate,
));
}
}
None => {
root_plot_public_key.take();
}
}
}
Ok(())
}