#![forbid(unsafe_code, missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#![feature(let_chains)]
extern crate alloc;
pub mod digests;
pub mod inherents;
use alloc::borrow::Cow;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_consensus_slots::{Slot, SlotDuration};
use sp_core::H256;
use sp_io::hashing;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use sp_runtime::{ConsensusEngineId, Justification};
use sp_runtime_interface::pass_by::PassBy;
use sp_runtime_interface::{pass_by, runtime_interface};
use sp_std::num::NonZeroU32;
use subspace_core_primitives::hashes::Blake3Hash;
use subspace_core_primitives::pot::{PotCheckpoints, PotOutput, PotSeed};
use subspace_core_primitives::segments::{
HistorySize, SegmentCommitment, SegmentHeader, SegmentIndex,
};
use subspace_core_primitives::solutions::{RewardSignature, Solution, SolutionRange};
use subspace_core_primitives::{BlockHash, BlockNumber, PublicKey, SlotNumber};
#[cfg(feature = "std")]
use subspace_kzg::Kzg;
#[cfg(feature = "std")]
use subspace_proof_of_space::chia::ChiaTable;
#[cfg(feature = "std")]
use subspace_proof_of_space::shim::ShimTable;
#[cfg(feature = "std")]
use subspace_proof_of_space::PosTableType;
#[cfg(feature = "std")]
use subspace_proof_of_space::Table;
use subspace_verification::VerifySolutionParams;
const SUBSPACE_ENGINE_ID: ConsensusEngineId = *b"SUB_";
#[derive(Debug, Clone, Encode, Decode, TypeInfo)]
pub enum SubspaceJustification {
#[codec(index = 0)]
PotCheckpoints {
seed: PotSeed,
checkpoints: Vec<PotCheckpoints>,
},
}
impl From<SubspaceJustification> for Justification {
#[inline]
fn from(justification: SubspaceJustification) -> Self {
(SUBSPACE_ENGINE_ID, justification.encode())
}
}
impl SubspaceJustification {
pub fn try_from_justification(
(consensus_engine_id, encoded_justification): &Justification,
) -> Option<Result<Self, codec::Error>> {
(*consensus_engine_id == SUBSPACE_ENGINE_ID)
.then(|| Self::decode(&mut encoded_justification.as_slice()))
}
pub fn must_be_archived(&self) -> bool {
match self {
SubspaceJustification::PotCheckpoints { .. } => true,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
pub struct PotNextSlotInput {
pub slot: Slot,
pub slot_iterations: NonZeroU32,
pub seed: PotSeed,
}
impl PotNextSlotInput {
pub fn derive(
base_slot_iterations: NonZeroU32,
parent_slot: Slot,
parent_output: PotOutput,
pot_parameters_change: &Option<PotParametersChange>,
) -> Self {
let next_slot = parent_slot + Slot::from(1);
let slot_iterations;
let seed;
if let Some(parameters_change) = pot_parameters_change
&& parameters_change.slot <= next_slot
{
slot_iterations = parameters_change.slot_iterations;
if parameters_change.slot == next_slot {
seed = parent_output.seed_with_entropy(¶meters_change.entropy);
} else {
seed = parent_output.seed();
}
} else {
slot_iterations = base_slot_iterations;
seed = parent_output.seed();
}
PotNextSlotInput {
slot: next_slot,
slot_iterations,
seed,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
pub struct PotParametersChange {
pub slot: Slot,
pub slot_iterations: NonZeroU32,
pub entropy: Blake3Hash,
}
#[derive(Debug, Decode, Encode, Clone, PartialEq, Eq)]
enum ConsensusLog {
#[codec(index = 0)]
PotSlotIterations(NonZeroU32),
#[codec(index = 1)]
SolutionRange(SolutionRange),
#[codec(index = 2)]
PotParametersChange(PotParametersChange),
#[codec(index = 3)]
NextSolutionRange(SolutionRange),
#[codec(index = 4)]
SegmentCommitment((SegmentIndex, SegmentCommitment)),
#[codec(index = 5)]
EnableSolutionRangeAdjustmentAndOverride(Option<SolutionRange>),
#[codec(index = 6)]
RootPlotPublicKeyUpdate(Option<PublicKey>),
}
#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
pub enum Vote<Number, Hash, RewardAddress> {
V0 {
height: Number,
parent_hash: Hash,
slot: Slot,
solution: Solution<RewardAddress>,
proof_of_time: PotOutput,
future_proof_of_time: PotOutput,
},
}
impl<Number, Hash, RewardAddress> Vote<Number, Hash, RewardAddress>
where
Number: Encode,
Hash: Encode,
RewardAddress: Encode,
{
pub fn solution(&self) -> &Solution<RewardAddress> {
let Self::V0 { solution, .. } = self;
solution
}
pub fn slot(&self) -> &Slot {
let Self::V0 { slot, .. } = self;
slot
}
pub fn hash(&self) -> H256 {
hashing::blake2_256(&self.encode()).into()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
pub struct SignedVote<Number, Hash, RewardAddress> {
pub vote: Vote<Number, Hash, RewardAddress>,
pub signature: RewardSignature,
}
#[derive(Decode, Encode, MaxEncodedLen, PartialEq, Eq, Clone, Copy, Debug, TypeInfo)]
pub struct SolutionRanges {
pub current: u64,
pub next: Option<u64>,
pub voting_current: u64,
pub voting_next: Option<u64>,
}
impl Default for SolutionRanges {
#[inline]
fn default() -> Self {
Self {
current: u64::MAX,
next: None,
voting_current: u64::MAX,
voting_next: None,
}
}
}
#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy, TypeInfo)]
pub enum ChainConstants {
#[codec(index = 0)]
V0 {
confirmation_depth_k: BlockNumber,
block_authoring_delay: Slot,
era_duration: BlockNumber,
slot_probability: (u64, u64),
slot_duration: SlotDuration,
recent_segments: HistorySize,
recent_history_fraction: (HistorySize, HistorySize),
min_sector_lifetime: HistorySize,
},
}
impl ChainConstants {
pub fn confirmation_depth_k(&self) -> BlockNumber {
let Self::V0 {
confirmation_depth_k,
..
} = self;
*confirmation_depth_k
}
pub fn era_duration(&self) -> BlockNumber {
let Self::V0 { era_duration, .. } = self;
*era_duration
}
pub fn block_authoring_delay(&self) -> Slot {
let Self::V0 {
block_authoring_delay,
..
} = self;
*block_authoring_delay
}
pub fn slot_probability(&self) -> (u64, u64) {
let Self::V0 {
slot_probability, ..
} = self;
*slot_probability
}
pub fn slot_duration(&self) -> SlotDuration {
let Self::V0 { slot_duration, .. } = self;
*slot_duration
}
pub fn recent_segments(&self) -> HistorySize {
let Self::V0 {
recent_segments, ..
} = self;
*recent_segments
}
pub fn recent_history_fraction(&self) -> (HistorySize, HistorySize) {
let Self::V0 {
recent_history_fraction,
..
} = self;
*recent_history_fraction
}
pub fn min_sector_lifetime(&self) -> HistorySize {
let Self::V0 {
min_sector_lifetime,
..
} = self;
*min_sector_lifetime
}
}
#[derive(Debug, Encode, Decode)]
pub struct WrappedSolution(Solution<()>);
impl<RewardAddress> From<&Solution<RewardAddress>> for WrappedSolution {
#[inline]
fn from(solution: &Solution<RewardAddress>) -> Self {
Self(Solution {
public_key: solution.public_key,
reward_address: (),
sector_index: solution.sector_index,
history_size: solution.history_size,
piece_offset: solution.piece_offset,
record_commitment: solution.record_commitment,
record_witness: solution.record_witness,
chunk: solution.chunk,
chunk_witness: solution.chunk_witness,
proof_of_space: solution.proof_of_space,
})
}
}
impl PassBy for WrappedSolution {
type PassBy = pass_by::Codec<Self>;
}
#[derive(Debug, Encode, Decode)]
pub struct WrappedVerifySolutionParams<'a>(Cow<'a, VerifySolutionParams>);
impl<'a> From<&'a VerifySolutionParams> for WrappedVerifySolutionParams<'a> {
#[inline]
fn from(value: &'a VerifySolutionParams) -> Self {
Self(Cow::Borrowed(value))
}
}
impl PassBy for WrappedVerifySolutionParams<'_> {
type PassBy = pass_by::Codec<Self>;
}
#[derive(Debug, Encode, Decode)]
pub struct WrappedPotOutput(PotOutput);
impl From<PotOutput> for WrappedPotOutput {
#[inline]
fn from(value: PotOutput) -> Self {
Self(value)
}
}
impl PassBy for WrappedPotOutput {
type PassBy = pass_by::Codec<Self>;
}
#[cfg(feature = "std")]
sp_externalities::decl_extension! {
pub struct KzgExtension(Kzg);
}
#[cfg(feature = "std")]
impl KzgExtension {
pub fn new(kzg: Kzg) -> Self {
Self(kzg)
}
}
#[cfg(feature = "std")]
sp_externalities::decl_extension! {
pub struct PosExtension(PosTableType);
}
#[cfg(feature = "std")]
impl PosExtension {
pub fn new<PosTable>() -> Self
where
PosTable: Table,
{
Self(PosTable::TABLE_TYPE)
}
}
#[cfg(feature = "std")]
sp_externalities::decl_extension! {
pub struct PotExtension(Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>);
}
#[cfg(feature = "std")]
impl PotExtension {
pub fn new(
verifier: Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>,
) -> Self {
Self(verifier)
}
}
#[runtime_interface]
pub trait Consensus {
fn verify_solution(
&mut self,
solution: WrappedSolution,
slot: SlotNumber,
params: WrappedVerifySolutionParams<'_>,
) -> Result<SolutionRange, String> {
use sp_externalities::ExternalitiesExt;
use subspace_proof_of_space::PosTableType;
let pos_table_type = self
.extension::<PosExtension>()
.expect("No `PosExtension` associated for the current context!")
.0;
let kzg = &self
.extension::<KzgExtension>()
.expect("No `KzgExtension` associated for the current context!")
.0;
match pos_table_type {
PosTableType::Chia => subspace_verification::verify_solution::<ChiaTable, _>(
&solution.0,
slot,
¶ms.0,
kzg,
)
.map_err(|error| error.to_string()),
PosTableType::Shim => subspace_verification::verify_solution::<ShimTable, _>(
&solution.0,
slot,
¶ms.0,
kzg,
)
.map_err(|error| error.to_string()),
}
}
fn is_proof_of_time_valid(
&mut self,
parent_hash: BlockHash,
slot: SlotNumber,
proof_of_time: WrappedPotOutput,
quick_verification: bool,
) -> bool {
use sp_externalities::ExternalitiesExt;
let verifier = &self
.extension::<PotExtension>()
.expect("No `PotExtension` associated for the current context!")
.0;
verifier(parent_hash, slot, proof_of_time.0, quick_verification)
}
}
#[derive(Debug, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub enum PotParameters {
V0 {
slot_iterations: NonZeroU32,
next_change: Option<PotParametersChange>,
},
}
impl PotParameters {
pub fn slot_iterations(&self) -> NonZeroU32 {
let Self::V0 {
slot_iterations, ..
} = self;
*slot_iterations
}
pub fn next_parameters_change(&self) -> Option<PotParametersChange> {
let Self::V0 { next_change, .. } = self;
*next_change
}
}
sp_api::decl_runtime_apis! {
pub trait SubspaceApi<RewardAddress: Encode + Decode> {
fn pot_parameters() -> PotParameters;
fn solution_ranges() -> SolutionRanges;
fn submit_vote_extrinsic(
signed_vote: SignedVote<
<<Block as BlockT>::Header as HeaderT>::Number,
Block::Hash,
RewardAddress,
>,
);
fn history_size() -> HistorySize;
fn max_pieces_in_sector() -> u16;
fn segment_commitment(segment_index: SegmentIndex) -> Option<SegmentCommitment>;
fn extract_segment_headers(ext: &Block::Extrinsic) -> Option<Vec<SegmentHeader >>;
fn is_inherent(ext: &Block::Extrinsic) -> bool;
fn root_plot_public_key() -> Option<PublicKey>;
fn should_adjust_solution_range() -> bool;
fn chain_constants() -> ChainConstants;
}
}