use crate::{
ConsensusLog, FarmerPublicKey, FarmerSignature, PotParametersChange, SUBSPACE_ENGINE_ID,
};
use codec::{Decode, Encode};
use log::trace;
use sp_consensus_slots::Slot;
use sp_core::crypto::UncheckedFrom;
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::{
PotOutput, SegmentCommitment, SegmentIndex, Solution, SolutionRange,
};
#[derive(Debug, Clone, Encode, Decode)]
pub enum PreDigest<PublicKey, RewardAddress> {
#[codec(index = 0)]
V0 {
slot: Slot,
solution: Solution<PublicKey, RewardAddress>,
pot_info: PreDigestPotInfo,
},
}
impl<PublicKey, RewardAddress> PreDigest<PublicKey, RewardAddress> {
#[inline]
pub fn slot(&self) -> Slot {
let Self::V0 { slot, .. } = self;
*slot
}
#[inline]
pub fn solution(&self) -> &Solution<PublicKey, 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<FarmerPublicKey, AccountId>,
) -> Self;
fn as_subspace_pre_digest<AccountId: Decode>(
&self,
) -> Option<PreDigest<FarmerPublicKey, AccountId>>;
fn subspace_seal(signature: FarmerSignature) -> Self;
fn as_subspace_seal(&self) -> Option<FarmerSignature>;
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<FarmerPublicKey>) -> Self;
fn as_root_plot_public_key_update(&self) -> Option<Option<FarmerPublicKey>>;
}
impl CompatibleDigestItem for DigestItem {
fn subspace_pre_digest<RewardAddress: Encode>(
pre_digest: &PreDigest<FarmerPublicKey, RewardAddress>,
) -> Self {
Self::PreRuntime(SUBSPACE_ENGINE_ID, pre_digest.encode())
}
fn as_subspace_pre_digest<RewardAddress: Decode>(
&self,
) -> Option<PreDigest<FarmerPublicKey, RewardAddress>> {
self.pre_runtime_try_to(&SUBSPACE_ENGINE_ID)
}
fn subspace_seal(signature: FarmerSignature) -> Self {
Self::Seal(SUBSPACE_ENGINE_ID, signature.encode())
}
fn as_subspace_seal(&self) -> Option<FarmerSignature> {
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<FarmerPublicKey>) -> Self {
Self::Consensus(
SUBSPACE_ENGINE_ID,
ConsensusLog::RootPlotPublicKeyUpdate(root_plot_public_key).encode(),
)
}
fn as_root_plot_public_key_update(&self) -> Option<Option<FarmerPublicKey>> {
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)]
#[cfg_attr(feature = "thiserror", derive(thiserror::Error))]
pub enum Error {
#[cfg_attr(feature = "thiserror", error("Subspace {0} digest not found"))]
Missing(ErrorDigestType),
#[cfg_attr(
feature = "thiserror",
error("Failed to decode Subspace {0} digest: {1}")
)]
FailedToDecode(ErrorDigestType, codec::Error),
#[cfg_attr(
feature = "thiserror",
error("Duplicate Subspace {0} digests, rejecting!")
)]
Duplicate(ErrorDigestType),
#[cfg_attr(
feature = "thiserror",
error("Failed to derive next {0} digest, rejecting!")
)]
NextDigestDerivationError(ErrorDigestType),
#[cfg_attr(
feature = "thiserror",
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<PublicKey, RewardAddress, Signature> {
pub pre_digest: PreDigest<PublicKey, RewardAddress>,
pub signature: Option<Signature>,
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<FarmerPublicKey>>,
}
pub fn extract_subspace_digest_items<Header, PublicKey, RewardAddress, Signature>(
header: &Header,
) -> Result<SubspaceDigestItems<PublicKey, RewardAddress, Signature>, Error>
where
Header: HeaderT,
PublicKey: Decode,
RewardAddress: Decode,
Signature: 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::<PublicKey, 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 = Signature::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<FarmerPublicKey, FarmerPublicKey>, Error>
where
Header: HeaderT,
{
if header.number().is_zero() {
return Ok(PreDigest::V0 {
slot: Slot::from(0),
solution: Solution::genesis_solution(
FarmerPublicKey::unchecked_from([0u8; 32]),
FarmerPublicKey::unchecked_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<FarmerPublicKey, FarmerPublicKey, FarmerSignature>,
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<FarmerPublicKey>,
}
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.clone());
} else {
return Err(Error::NextDigestVerificationError(
ErrorDigestType::RootPlotPublicKeyUpdate,
));
}
}
None => {
root_plot_public_key.take();
}
}
}
Ok(())
}