From c647b9814759b3eda37d5469c6c1ddb5a3e66af4 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 24 Sep 2024 07:42:00 +0300 Subject: [PATCH] Remove `sp-lightclient` --- Cargo.toml | 4 - crates/sp-lightclient/Cargo.toml | 54 - crates/sp-lightclient/README.md | 1 - crates/sp-lightclient/src/lib.rs | 912 ----------------- crates/sp-lightclient/src/mock.rs | 221 ---- crates/sp-lightclient/src/tests.rs | 1497 ---------------------------- 6 files changed, 2689 deletions(-) delete mode 100644 crates/sp-lightclient/Cargo.toml delete mode 100644 crates/sp-lightclient/README.md delete mode 100644 crates/sp-lightclient/src/lib.rs delete mode 100644 crates/sp-lightclient/src/mock.rs delete mode 100644 crates/sp-lightclient/src/tests.rs diff --git a/Cargo.toml b/Cargo.toml index f982230963..8a28b195f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,6 @@ members = [ "test/subspace-test-runtime", "test/subspace-test-service", ] -exclude = [ - # TODO: We need to figure out what to do with sp-lightclient before unlocking it again - "crates/sp-lightclient", -] # The list of dependencies below (which can be both direct and indirect dependencies) are crates # that are suspected to be CPU-intensive, and that are unlikely to require debugging (as some of diff --git a/crates/sp-lightclient/Cargo.toml b/crates/sp-lightclient/Cargo.toml deleted file mode 100644 index bdd6bd506f..0000000000 --- a/crates/sp-lightclient/Cargo.toml +++ /dev/null @@ -1,54 +0,0 @@ -[package] -name = "sp-lightclient" -version = "0.1.0" -authors = ["Vedhavyas Singareddi "] -edition = "2021" -license = "Apache-2.0" -homepage = "https://subspace.network" -repository = "https://github.com/autonomys/subspace" -description = "Light client substrate primitives for Subspace" -include = [ - "/src", - "/Cargo.toml", - "/README.md", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.1.2", default-features = false } -scale-info = { version = "2.11.2", default-features = false, features = ["derive"] } -schnorrkel = { version = "0.11.4", default-features = false } -sp-arithmetic = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "5626154d0781ac9a6ffd5a6207ed237f425ae631" } -sp-consensus-slots = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "5626154d0781ac9a6ffd5a6207ed237f425ae631" } -sp-consensus-subspace = { version = "0.1.0", path = "../sp-consensus-subspace", default-features = false } -sp-runtime = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "5626154d0781ac9a6ffd5a6207ed237f425ae631" } -sp-std = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "5626154d0781ac9a6ffd5a6207ed237f425ae631" } -subspace-core-primitives = { version = "0.1.0", path = "../subspace-core-primitives", default-features = false } -subspace-erasure-coding = { version = "0.1.0", path = "../subspace-erasure-coding", default-features = false } -subspace-verification = { version = "0.1.0", path = "../subspace-verification", default-features = false } - -[dev-dependencies] -frame-support = { git = "https://github.com/subspace/polkadot-sdk", rev = "5626154d0781ac9a6ffd5a6207ed237f425ae631" } -futures = "0.3.29" -rand = { version = "0.8.5", features = ["min_const_gen"] } -sp-io = { git = "https://github.com/subspace/polkadot-sdk", rev = "5626154d0781ac9a6ffd5a6207ed237f425ae631" } -subspace-archiving = { version = "0.1.0", path = "../subspace-archiving" } -subspace-core-primitives = { version = "0.1.0", path = "../subspace-core-primitives" } -subspace-farmer-components = { version = "0.1.0", path = "../subspace-farmer-components" } -subspace-proof-of-space = { version = "0.1.0", path = "../subspace-proof-of-space" } - -[features] -default = ["std"] -std = [ - "codec/std", - "scale-info/std", - "schnorrkel/std", - "sp-arithmetic/std", - "sp-consensus-slots/std", - "sp-consensus-subspace/std", - "sp-runtime/std", - "sp-std/std", - "subspace-core-primitives/std", - "subspace-verification/std" -] diff --git a/crates/sp-lightclient/README.md b/crates/sp-lightclient/README.md deleted file mode 100644 index 866ed17091..0000000000 --- a/crates/sp-lightclient/README.md +++ /dev/null @@ -1 +0,0 @@ -# Light client substrate primitives for Subspace diff --git a/crates/sp-lightclient/src/lib.rs b/crates/sp-lightclient/src/lib.rs deleted file mode 100644 index ad0d631a4c..0000000000 --- a/crates/sp-lightclient/src/lib.rs +++ /dev/null @@ -1,912 +0,0 @@ -// Copyright (C) 2021 Subspace Labs, Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Light client substrate primitives for Subspace. -#![forbid(unsafe_code)] -#![warn(rust_2018_idioms, missing_docs)] -#![cfg_attr(not(feature = "std"), no_std)] - -// TODO: Unlock tests for PoT as well once PoT implementation settled (there are multiple items with -// this conditional compilation in the file -#[cfg(all(test, not(feature = "pot")))] -mod mock; -#[cfg(all(test, not(feature = "pot")))] -mod tests; - -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_arithmetic::traits::{CheckedAdd, CheckedSub, One, Zero}; -use sp_consensus_slots::Slot; -use sp_consensus_subspace::consensus::verify_solution; -use sp_consensus_subspace::digests::{ - extract_pre_digest, extract_subspace_digest_items, verify_next_digests, CompatibleDigestItem, - Error as DigestError, ErrorDigestType, NextDigestsVerificationParams, PreDigest, - SubspaceDigestItems, -}; -use sp_consensus_subspace::{FarmerPublicKey, FarmerSignature}; -use sp_runtime::traits::Header as HeaderT; -use sp_runtime::ArithmeticError; -use sp_std::cmp::Ordering; -use sp_std::collections::btree_map::BTreeMap; -use sp_std::marker::PhantomData; -use sp_std::num::NonZeroU64; -#[cfg(not(feature = "pot"))] -use subspace_core_primitives::Randomness; -use subspace_core_primitives::{ - ArchivedHistorySegment, BlockWeight, HistorySize, PublicKey, RewardSignature, SectorId, - SegmentCommitment, SegmentIndex, SolutionRange, REWARD_SIGNING_CONTEXT, -}; -use subspace_verification::{ - calculate_block_weight, check_reward_signature, PieceCheckParams, VerifySolutionParams, -}; - -/// Chain constants. -#[derive(Debug, Encode, Decode, Clone, TypeInfo)] -pub struct ChainConstants { - /// K Depth at which we finalize the heads. - pub k_depth: NumberOf
, - /// Genesis digest items at the start of the chain since the genesis block will not have any - /// digests to verify the Block #1 digests. - pub genesis_digest_items: NextDigestItems, - /// Genesis block segment commitments to verify the Block #1 and other block solutions until - /// Block #1 is finalized. - /// When Block #1 is finalized, these segment commitments are present in Block #1 are stored in - /// the storage. - pub genesis_segment_commitments: BTreeMap, - /// Defines interval at which randomness is updated. - #[cfg(not(feature = "pot"))] - pub global_randomness_interval: NumberOf
, - /// Era duration at which solution range is updated. - pub era_duration: NumberOf
, - /// Slot probability. - pub slot_probability: (u64, u64), - /// Storage bound for the light client store. - pub storage_bound: StorageBound>, - /// Number of latest archived segments that are considered "recent history". - pub recent_segments: HistorySize, - /// Fraction of pieces from the "recent history" (`recent_segments`) in each sector. - pub recent_history_fraction: (HistorySize, HistorySize), - /// Minimum lifetime of a plotted sector, measured in archived segment. - pub min_sector_lifetime: HistorySize, -} - -/// Defines the storage bound for the light client store. -#[derive(Default, Debug, Encode, Decode, TypeInfo, Clone)] -pub enum StorageBound { - /// Keeps all the headers in the storage. - #[default] - Unbounded, - /// Keeps only # number of headers beyond K depth. - NumberOfHeaderToKeepBeyondKDepth(Number), -} - -/// HeaderExt describes an extended block chain header at a specific height along with some computed -/// values. -#[derive(Default, Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] -pub struct HeaderExt
{ - /// Actual header of the subspace block chain at a specific number. - pub header: Header, - /// Cumulative weight of chain until this header. - pub total_weight: BlockWeight, - /// Slot at which current era started. - pub era_start_slot: Slot, - /// Should adjust solution range on era change. - pub should_adjust_solution_range: bool, - /// Solution range override for the current era. - pub maybe_current_solution_range_override: Option, - /// Solution range override for the next era. - pub maybe_next_solution_range_override: Option, - /// Restrict block authoring to this public key. - pub maybe_root_plot_public_key: Option, - - #[cfg(all(test, not(feature = "pot")))] - test_overrides: mock::TestOverrides, -} - -/// Type to hold next digest items present in parent header that are used to verify the immediate -/// descendant. -#[derive(Default, Debug, Encode, Decode, Clone, TypeInfo)] -pub struct NextDigestItems { - #[cfg(not(feature = "pot"))] - next_global_randomness: Randomness, - next_solution_range: SolutionRange, -} - -impl NextDigestItems { - /// Constructs self with provided next digest items. - pub fn new( - #[cfg(not(feature = "pot"))] next_global_randomness: Randomness, - next_solution_range: SolutionRange, - ) -> Self { - Self { - #[cfg(not(feature = "pot"))] - next_global_randomness, - next_solution_range, - } - } -} - -impl HeaderExt
{ - /// Extracts the next digest items Randomness, Solution range, and Salt present in the Header. - /// If next digests are not present, then we fallback to the current ones. - fn extract_next_digest_items(&self) -> Result> { - let SubspaceDigestItems { - #[cfg(not(feature = "pot"))] - global_randomness, - solution_range, - #[cfg(not(feature = "pot"))] - next_global_randomness, - next_solution_range, - .. - } = extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - &self.header, - )?; - - // if there is override for solution range for current era, override it - let solution_range = self - .maybe_current_solution_range_override - .unwrap_or(solution_range); - - #[cfg(all(test, not(feature = "pot")))] - let solution_range = { - if self.test_overrides.solution_range.is_some() { - self.test_overrides.solution_range.unwrap() - } else { - solution_range - } - }; - - #[cfg(all(test, not(feature = "pot")))] - let next_solution_range = { - if self.test_overrides.next_solution_range.is_some() { - self.test_overrides.next_solution_range - } else { - next_solution_range - } - }; - - Ok(NextDigestItems { - #[cfg(not(feature = "pot"))] - next_global_randomness: next_global_randomness.unwrap_or(global_randomness), - next_solution_range: next_solution_range.unwrap_or(solution_range), - }) - } -} - -type HashOf = ::Hash; -type NumberOf = ::Number; - -/// Storage responsible for storing headers. -pub trait Storage { - /// Returns the chain constants. - fn chain_constants(&self) -> ChainConstants
; - - /// Queries a header at a specific block number or block hash. - fn header(&self, hash: HashOf
) -> Option>; - - /// Stores the extended header. - /// `as_best_header` signifies of the header we are importing is considered best. - fn store_header(&mut self, header_ext: HeaderExt
, as_best_header: bool); - - /// Returns the best known tip of the chain. - fn best_header(&self) -> HeaderExt
; - - /// Returns headers at a given number. - fn headers_at_number(&self, number: NumberOf
) -> Vec>; - - /// Prunes header with hash. - fn prune_header(&mut self, hash: HashOf
); - - /// Marks a given header with hash as finalized. - fn finalize_header(&mut self, hash: HashOf
); - - /// Returns the latest finalized header. - fn finalized_header(&self) -> HeaderExt
; - - /// Stores segment commitments for fast retrieval by segment index at or below finalized header. - fn store_segment_commitments( - &mut self, - segment_commitments: BTreeMap, - ); - - /// Returns a segment commitment for a given segment index. - fn segment_commitment(&self, segment_index: SegmentIndex) -> Option; - - /// Returns the stored segment count. - // TODO: Ideally should use `HistorySize` instead of `u64` - fn number_of_segments(&self) -> u64; - - /// How many pieces one sector is supposed to contain (max) - fn max_pieces_in_sector(&self) -> u16; -} - -/// Error type that holds the current finalized number and the header number we are trying to import. -#[derive(Debug, PartialEq, Eq)] -pub struct HeaderBelowArchivingDepthError { - current_finalized_number: NumberOf
, - header_number: NumberOf
, -} - -/// Error during the header import. -#[derive(Debug, PartialEq, Eq)] -pub enum ImportError { - /// Header already imported. - HeaderAlreadyImported, - /// Missing parent header. - MissingParent(HashOf
), - /// Missing header associated with hash. - MissingHeader(HashOf
), - /// Missing ancestor header at the number. - MissingAncestorHeader(HashOf
, NumberOf
), - /// Error while extracting digests from header. - DigestError(DigestError), - /// Invalid digest in the header. - InvalidDigest(ErrorDigestType), - /// Invalid slot when compared with parent header. - InvalidSlot, - /// Block signature is invalid. - InvalidBlockSignature, - /// Solution present in the header is invalid. - InvalidSolution(String), - /// Arithmetic error. - ArithmeticError(ArithmeticError), - /// Switched to different fork beyond archiving depth. - SwitchedToForkBelowArchivingDepth, - /// Header being imported is below the archiving depth. - HeaderIsBelowArchivingDepth(HeaderBelowArchivingDepthError
), - /// Missing segment commitment for a given segment index. - MissingSegmentCommitment(SegmentIndex), - /// Incorrect block author. - IncorrectBlockAuthor(FarmerPublicKey), - /// Segment commitment history is empty - EmptySegmentCommitmentHistory, - /// Invalid history size - InvalidHistorySize, -} - -impl From for ImportError
{ - #[inline] - fn from(error: DigestError) -> Self { - ImportError::DigestError(error) - } -} - -/// Verifies and import headers. -#[derive(Debug)] -pub struct HeaderImporter> { - store: Store, - _phantom: PhantomData
, -} - -impl> HeaderImporter { - /// Returns a new instance of HeaderImporter with provided Storage impls - pub fn new(store: Store) -> Self { - HeaderImporter { - store, - _phantom: Default::default(), - } - } - - /// Verifies header, computes consensus values for block progress and stores the HeaderExt. - pub fn import_header(&mut self, mut header: Header) -> Result<(), ImportError
> { - // check if the header is already imported - match self.store.header(header.hash()) { - Some(_) => Err(ImportError::HeaderAlreadyImported), - None => Ok(()), - }?; - - // only try and import headers above the finalized number - let current_finalized_number = *self.store.finalized_header().header.number(); - if *header.number() <= current_finalized_number { - return Err(ImportError::HeaderIsBelowArchivingDepth( - HeaderBelowArchivingDepthError { - current_finalized_number, - header_number: *header.number(), - }, - )); - } - - // fetch parent header - let parent_header = self - .store - .header(*header.parent_hash()) - .ok_or_else(|| ImportError::MissingParent(header.hash()))?; - - // verify global randomness and solution range from the parent header - let header_digests = self.verify_header_digest_with_parent(&parent_header, &header)?; - - // verify next digest items - let constants = self.store.chain_constants(); - let mut maybe_root_plot_public_key = parent_header.maybe_root_plot_public_key; - if let Some(root_plot_public_key) = &maybe_root_plot_public_key { - if root_plot_public_key != &header_digests.pre_digest.solution().public_key { - return Err(ImportError::IncorrectBlockAuthor( - header_digests.pre_digest.solution().public_key.clone(), - )); - } - } - - let mut should_adjust_solution_range = parent_header.should_adjust_solution_range; - let mut maybe_next_solution_range_override = - parent_header.maybe_next_solution_range_override; - verify_next_digests::
(NextDigestsVerificationParams { - number: *header.number(), - header_digests: &header_digests, - #[cfg(not(feature = "pot"))] - global_randomness_interval: constants.global_randomness_interval, - era_duration: constants.era_duration, - slot_probability: constants.slot_probability, - era_start_slot: parent_header.era_start_slot, - should_adjust_solution_range: &mut should_adjust_solution_range, - maybe_next_solution_range_override: &mut maybe_next_solution_range_override, - maybe_root_plot_public_key: &mut maybe_root_plot_public_key, - })?; - - // slot must be strictly increasing from the parent header - Self::verify_slot(&parent_header.header, &header_digests.pre_digest)?; - - // verify block signature - Self::verify_block_signature( - &mut header, - &header_digests.pre_digest.solution().public_key, - )?; - - // verify solution - let sector_id = SectorId::new( - PublicKey::from(&header_digests.pre_digest.solution().public_key).hash(), - header_digests.pre_digest.solution().sector_index, - ); - - let max_pieces_in_sector = self.store.max_pieces_in_sector(); - - let segment_index = sector_id - .derive_piece_index( - header_digests.pre_digest.solution().piece_offset, - header_digests.pre_digest.solution().history_size, - max_pieces_in_sector, - constants.recent_segments, - constants.recent_history_fraction, - ) - .segment_index(); - - let segment_commitment = self - .find_segment_commitment_for_segment_index(segment_index, parent_header.header.hash())? - .ok_or(ImportError::MissingSegmentCommitment(segment_index))?; - let current_history_size = HistorySize::new( - NonZeroU64::try_from(self.store.number_of_segments()) - .map_err(|_error| ImportError::EmptySegmentCommitmentHistory)?, - ); - let sector_expiration_check_segment_commitment = self - .find_segment_commitment_for_segment_index( - header_digests - .pre_digest - .solution() - .history_size - .sector_expiration_check(constants.min_sector_lifetime) - .ok_or(ImportError::InvalidHistorySize)? - .segment_index(), - parent_header.header.hash(), - )?; - - verify_solution( - header_digests.pre_digest.solution().into(), - header_digests.pre_digest.slot().into(), - (&VerifySolutionParams { - #[cfg(not(feature = "pot"))] - global_randomness: header_digests.global_randomness, - #[cfg(feature = "pot")] - proof_of_time: header_digests.pre_digest.pot_info().proof_of_time(), - solution_range: header_digests.solution_range, - piece_check_params: Some(PieceCheckParams { - max_pieces_in_sector, - segment_commitment, - recent_segments: constants.recent_segments, - recent_history_fraction: constants.recent_history_fraction, - min_sector_lifetime: constants.min_sector_lifetime, - current_history_size, - sector_expiration_check_segment_commitment, - }), - }) - .into(), - ) - .map_err(ImportError::InvalidSolution)?; - - let added_weight = calculate_block_weight(header_digests.solution_range); - let total_weight = parent_header.total_weight + added_weight; - - // last best header should ideally be parent header. if not check for forks and pick the best chain - let last_best_header = self.store.best_header(); - let last_best_weight = last_best_header.total_weight; - let is_best_header = total_weight > last_best_weight; - - // check if era has changed - let era_start_slot = if Self::has_era_changed(&header, constants.era_duration) { - header_digests.pre_digest.slot() - } else { - parent_header.era_start_slot - }; - - // check if we should update current solution range override - let mut maybe_current_solution_range_override = - parent_header.maybe_current_solution_range_override; - - // if there is override of solution range in this header, use it - if let Some(current_solution_range_override) = - header_digests.enable_solution_range_adjustment_and_override - { - maybe_current_solution_range_override = current_solution_range_override; - } - - // check if the era has changed and there is a current solution range override, reset it - if maybe_current_solution_range_override.is_some() - && Self::has_era_changed(&header, constants.era_duration) - { - maybe_current_solution_range_override = None - } - - // store header - let header_ext = HeaderExt { - header, - total_weight, - era_start_slot, - should_adjust_solution_range, - maybe_current_solution_range_override, - maybe_next_solution_range_override, - maybe_root_plot_public_key, - - #[cfg(all(test, not(feature = "pot")))] - test_overrides: Default::default(), - }; - - self.store.store_header(header_ext, is_best_header); - - // finalize, prune forks, and ensure storage is bounded if the chain has progressed - if is_best_header { - self.finalize_header_at_k_depth()?; - self.ensure_storage_bound(); - } - - Ok(()) - } - - fn has_era_changed(header: &Header, era_duration: NumberOf
) -> bool { - // special case when the current header is one, then first era begins - // or - // era duration interval has reached, so era has changed - header.number().is_one() || *header.number() % era_duration == Zero::zero() - } - - /// Verifies if the header digests matches with logs from the parent header. - fn verify_header_digest_with_parent( - &self, - parent_header: &HeaderExt
, - header: &Header, - ) -> Result< - SubspaceDigestItems, - ImportError
, - > { - // extract digest items from the header - let pre_digest_items = extract_subspace_digest_items(header)?; - // extract next digest items from the parent header - let next_digest_items = { - // if the header we are verifying is #1, then parent header, genesis, wont have the next digests - // instead fetch them from the constants provided by the store - if header.number() == &One::one() { - self.store.chain_constants().genesis_digest_items - } else { - parent_header.extract_next_digest_items()? - } - }; - - // check the digest items against the next digest items from parent header - #[cfg(not(feature = "pot"))] - if pre_digest_items.global_randomness != next_digest_items.next_global_randomness { - return Err(ImportError::InvalidDigest( - ErrorDigestType::GlobalRandomness, - )); - } - - if pre_digest_items.solution_range != next_digest_items.next_solution_range { - return Err(ImportError::InvalidDigest(ErrorDigestType::SolutionRange)); - } - - Ok(pre_digest_items) - } - - /// Verifies that slot present in the header is strictly increasing from the slot in the parent. - fn verify_slot( - parent_header: &Header, - pre_digest: &PreDigest, - ) -> Result<(), ImportError
> { - let parent_pre_digest = extract_pre_digest(parent_header)?; - - if pre_digest.slot() <= parent_pre_digest.slot() { - return Err(ImportError::InvalidSlot); - } - - Ok(()) - } - - /// Verifies the block signature present in the last digest log. - fn verify_block_signature( - header: &mut Header, - public_key: &FarmerPublicKey, - ) -> Result<(), ImportError
> { - let seal = - header - .digest_mut() - .pop() - .ok_or(ImportError::DigestError(DigestError::Missing( - ErrorDigestType::Seal, - )))?; - - let signature = seal - .as_subspace_seal() - .ok_or(ImportError::InvalidDigest(ErrorDigestType::Seal))?; - - // the pre-hash of the header doesn't include the seal and that's what we sign - let pre_hash = header.hash(); - - // verify that block is signed properly - check_reward_signature( - pre_hash.as_ref(), - &RewardSignature::from(&signature), - &PublicKey::from(public_key), - &schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT), - ) - .map_err(|_| ImportError::InvalidBlockSignature)?; - - // push the seal back into the header - header.digest_mut().push(seal); - Ok(()) - } - - /// Returns the ancestor of the header at number. - fn find_ancestor_of_header_at_number( - &self, - hash: HashOf
, - ancestor_number: NumberOf
, - ) -> Option> { - let header = self.store.header(hash)?; - - // header number must be greater than the ancestor number - if *header.header.number() < ancestor_number { - return None; - } - - let headers_at_ancestor_number = self.store.headers_at_number(ancestor_number); - - // short circuit if there are no fork headers at the ancestor number - if headers_at_ancestor_number.len() == 1 { - return headers_at_ancestor_number.into_iter().next(); - } - - // start tree route till the ancestor - let mut header = header; - while *header.header.number() > ancestor_number { - header = self.store.header(*header.header.parent_hash())?; - } - - Some(header) - } - - /// Prunes header and its descendant header chain(s). - fn prune_header_and_its_descendants( - &mut self, - header: HeaderExt
, - ) -> Result<(), ImportError
> { - // prune the header - self.store.prune_header(header.header.hash()); - - // start pruning all the descendant headers from the current header - // header(at number n) - // / \ - // descendant-1 descendant-2 - // / - // descendant-3 - let mut pruned_parent_hashes = vec![header.header.hash()]; - let mut current_number = *header.header.number(); - - while !pruned_parent_hashes.is_empty() { - current_number = current_number - .checked_add(&One::one()) - .ok_or(ImportError::ArithmeticError(ArithmeticError::Overflow))?; - - // get headers at the current number and filter the headers descended from the pruned parents - let descendant_header_hashes = self - .store - .headers_at_number(current_number) - .into_iter() - .filter(|descendant_header| { - pruned_parent_hashes.contains(descendant_header.header.parent_hash()) - }) - .map(|header| header.header.hash()) - .collect::>>(); - - // prune the descendant headers - descendant_header_hashes - .iter() - .for_each(|hash| self.store.prune_header(*hash)); - - pruned_parent_hashes = descendant_header_hashes; - } - - Ok(()) - } - - /// Returns the total pieces on chain where chain_tip is the hash of the tip of the chain. - /// We count the total segments to calculate total pieces as follows, - /// - Fetch the segment count from the store. - /// - Count the segments from each header that is not finalized. - // TODO: This function will become useful in the future for verifying sector expiration - #[allow(dead_code)] - fn total_pieces(&self, chain_tip: HashOf
) -> Result> { - // fetch the segment count from the store - let segment_commitments_count_till_finalized_header = self.store.number_of_segments(); - - let finalized_header = self.store.finalized_header(); - let mut segment_commitments_count = segment_commitments_count_till_finalized_header; - - // special case when Block #1 is not finalized yet, then include the genesis segment count - if finalized_header.header.number().is_zero() { - segment_commitments_count += self - .store - .chain_constants() - .genesis_segment_commitments - .len() as u64; - } - - // calculate segment count present in each header from header till finalized header - let mut header = self - .store - .header(chain_tip) - .ok_or(ImportError::MissingHeader(chain_tip))?; - - while header.header.hash() != finalized_header.header.hash() { - let digest_items = extract_subspace_digest_items::< - _, - FarmerPublicKey, - FarmerPublicKey, - FarmerSignature, - >(&header.header)?; - segment_commitments_count += digest_items.segment_commitments.len() as u64; - - header = self - .store - .header(*header.header.parent_hash()) - .ok_or_else(|| ImportError::MissingParent(header.header.hash()))?; - } - - Ok(segment_commitments_count * ArchivedHistorySegment::NUM_PIECES as u64) - } - - /// Finds a segment commitment mapped against a segment index in the chain with chain_tip as the - /// tip of the chain. - /// We try to find the segment commitment as follows: - /// - Find segment commitment from the store and return if found. - /// - Find segment commitment from the genesis segment commitment and return if found. - /// - Find the segment commitment present in the non finalized headers. - fn find_segment_commitment_for_segment_index( - &self, - segment_index: SegmentIndex, - chain_tip: HashOf
, - ) -> Result, ImportError
> { - // check if the segment commitment is already in the store - if let Some(segment_commitment) = self.store.segment_commitment(segment_index) { - return Ok(Some(segment_commitment)); - }; - - // special case: check the genesis segment commitments if the Block #1 is not finalized yet - if let Some(segment_commitment) = self - .store - .chain_constants() - .genesis_segment_commitments - .get(&segment_index) - { - return Ok(Some(*segment_commitment)); - } - - // find the segment commitment from the headers which are not finalized yet. - let finalized_header = self.store.finalized_header(); - let mut header = self - .store - .header(chain_tip) - .ok_or(ImportError::MissingHeader(chain_tip))?; - - while header.header.hash() != finalized_header.header.hash() { - let digest_items = extract_subspace_digest_items::< - _, - FarmerPublicKey, - FarmerPublicKey, - FarmerSignature, - >(&header.header)?; - - if let Some(segment_commitment) = digest_items.segment_commitments.get(&segment_index) { - return Ok(Some(*segment_commitment)); - } - - header = self - .store - .header(*header.header.parent_hash()) - .ok_or_else(|| ImportError::MissingParent(header.header.hash()))?; - } - - Ok(None) - } - - /// Stores finalized header and segment commitments present in the header. - fn store_finalized_header_and_segment_commitments( - &mut self, - header: &Header, - ) -> Result<(), ImportError
> { - let digests_items = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - header, - )?; - - // mark header as finalized - self.store.finalize_header(header.hash()); - - // store the segment commitments present in the header digests - self.store - .store_segment_commitments(digests_items.segment_commitments); - Ok(()) - } - - /// Finalize the header at K-depth from the best block and prune remaining forks at that number. - /// We want to finalize the header from the current finalized header until the K-depth number of the best. - /// 1. In an ideal scenario, the current finalized head is one number less than number to be finalized. - /// 2. If there was a re-org to longer chain when new header was imported, we do not want to miss - /// pruning fork headers between current and to be finalized number. So we go number by number and prune fork headers. - /// 3. If there was a re-org to a shorter chain and to be finalized header was below the current finalized head, - /// fail and let user know. - fn finalize_header_at_k_depth(&mut self) -> Result<(), ImportError
> { - let k_depth = self.store.chain_constants().k_depth; - let current_finalized_header = self.store.finalized_header(); - - // ensure we have imported at least K-depth number of headers - let number_to_finalize = match self - .store - .best_header() - .header - .number() - .checked_sub(&k_depth) - { - // we have not progressed that far to finalize yet - None => { - // if the chain re-org happened to smaller chain and if there was any finalized heads, - // fail and let the user decide what to do - if *current_finalized_header.header.number() > Zero::zero() { - return Err(ImportError::SwitchedToForkBelowArchivingDepth); - } - - return Ok(()); - } - - Some(number) => number, - }; - - match number_to_finalize.cmp(current_finalized_header.header.number()) { - Ordering::Less => Err(ImportError::SwitchedToForkBelowArchivingDepth), - // nothing to do as we finalized the header already - Ordering::Equal => Ok(()), - // finalize heads one after the other and prune any forks - Ordering::Greater => { - let mut current_finalized_number = *current_finalized_header.header.number(); - - while current_finalized_number < number_to_finalize { - current_finalized_number = current_finalized_number - .checked_add(&One::one()) - .ok_or(ImportError::ArithmeticError(ArithmeticError::Overflow))?; - - // find the headers at the number to be finalized - let headers_at_number_to_be_finalized = - self.store.headers_at_number(current_finalized_number); - // if there is just one header at that number, we mark that header as finalized and move one - if headers_at_number_to_be_finalized.len() == 1 { - let header_to_finalize = headers_at_number_to_be_finalized - .first() - .expect("First item must exist as the len is 1."); - - self.store_finalized_header_and_segment_commitments( - &header_to_finalize.header, - )? - } else { - // there are multiple headers at the number to be finalized. - // find the correct ancestor header of the current best header. - // finalize it and prune all the remaining fork headers. - let current_best_header = self.store.best_header(); - let (current_best_hash, current_best_number) = ( - current_best_header.header.hash(), - *current_best_header.header.number(), - ); - - let header_to_finalize = self - .find_ancestor_of_header_at_number( - current_best_hash, - current_finalized_number, - ) - .ok_or(ImportError::MissingAncestorHeader( - current_best_hash, - current_best_number, - ))?; - - // filter fork headers and prune them - let headers_to_prune = headers_at_number_to_be_finalized - .into_iter() - .filter(|header| { - header.header.hash() != header_to_finalize.header.hash() - }) - .collect::>>(); - - for header_to_prune in headers_to_prune { - self.prune_header_and_its_descendants(header_to_prune)?; - } - - // mark the header as finalized - self.store_finalized_header_and_segment_commitments( - &header_to_finalize.header, - )? - } - } - - Ok(()) - } - } - } - - /// Ensure light client storage is bounded by the defined storage bound constant. - /// If unbounded, we keep all the finalized headers in the store. - /// If bounded, we fetch the finalized head and then prune all the headers - /// beyond K depth as per bounded value. - /// If finalized head is at x and storage is bounded to keep y headers beyond, then - /// prune all headers at and below (x - y - 1) - fn ensure_storage_bound(&mut self) { - let storage_bound = self.store.chain_constants().storage_bound; - let number_of_headers_to_keep_beyond_k_depth = match storage_bound { - // unbounded storage, so return - StorageBound::Unbounded => return, - // bounded storage, keep only # number of headers beyond K depth - StorageBound::NumberOfHeaderToKeepBeyondKDepth(number_of_headers_to_keep) => { - number_of_headers_to_keep - } - }; - - let finalized_head_number = *self.store.finalized_header().header.number(); - // (finalized_number - bound_value - 1) - let mut maybe_prune_headers_from_number = finalized_head_number - .checked_sub(&number_of_headers_to_keep_beyond_k_depth) - .and_then(|number| number.checked_sub(&One::one())); - - let mut headers_to_prune = maybe_prune_headers_from_number - .map(|number| self.store.headers_at_number(number)) - .unwrap_or_default(); - - while !headers_to_prune.is_empty() { - // loop and prune even though there should be only 1 head beyond finalized head - for header in headers_to_prune { - self.store.prune_header(header.header.hash()) - } - - maybe_prune_headers_from_number = - maybe_prune_headers_from_number.and_then(|number| number.checked_sub(&One::one())); - - headers_to_prune = maybe_prune_headers_from_number - .map(|number| self.store.headers_at_number(number)) - .unwrap_or_default(); - } - } -} diff --git a/crates/sp-lightclient/src/mock.rs b/crates/sp-lightclient/src/mock.rs deleted file mode 100644 index 88b2ceef21..0000000000 --- a/crates/sp-lightclient/src/mock.rs +++ /dev/null @@ -1,221 +0,0 @@ -use crate::{ChainConstants, HashOf, HeaderExt, NumberOf, Storage}; -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_arithmetic::traits::Zero; -#[cfg(feature = "pot")] -use sp_consensus_subspace::PotExtension; -use sp_consensus_subspace::{KzgExtension, PosExtension}; -use sp_io::TestExternalities; -use sp_runtime::traits::{BlakeTwo256, Header as HeaderT}; -use std::collections::{BTreeMap, HashMap}; -use std::sync::OnceLock; -use subspace_core_primitives::crypto::kzg::{embedded_kzg_settings, Kzg}; -use subspace_core_primitives::{BlockWeight, SegmentCommitment, SegmentIndex, SolutionRange}; -use subspace_proof_of_space::shim::ShimTable; - -pub(crate) type PosTable = ShimTable; - -pub(crate) type Header = sp_runtime::generic::Header; - -// Smaller value for testing purposes -const MAX_PIECES_IN_SECTOR: u16 = 32; - -pub(crate) fn kzg_instance() -> &'static Kzg { - static KZG: OnceLock = OnceLock::new(); - - KZG.get_or_init(|| Kzg::new(embedded_kzg_settings())) -} - -#[derive(Debug)] -struct StorageData { - constants: ChainConstants
, - headers: HashMap, HeaderExt
>, - number_to_hashes: HashMap, Vec>>, - best_header: (NumberOf
, HashOf
), - finalized_head: Option<(NumberOf
, HashOf
)>, - segment_commitments: BTreeMap, -} - -#[derive(Default, Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] -pub(crate) struct TestOverrides { - pub(crate) solution_range: Option, - pub(crate) next_solution_range: Option, -} - -#[derive(Debug)] -pub(crate) struct MockStorage(StorageData); - -impl Storage
for MockStorage { - fn chain_constants(&self) -> ChainConstants
{ - self.0.constants.clone() - } - - fn header(&self, query: HashOf
) -> Option> { - self.0.headers.get(&query).cloned() - } - - fn store_header(&mut self, header_ext: HeaderExt
, as_best_header: bool) { - let (number, hash) = (*header_ext.header.number(), header_ext.header.hash()); - if self.0.headers.insert(hash, header_ext).is_none() { - let mut set = self - .0 - .number_to_hashes - .get(&number) - .cloned() - .unwrap_or_default(); - set.push(hash); - self.0.number_to_hashes.insert(number, set); - } - if as_best_header { - self.0.best_header = (number, hash) - } - } - - fn best_header(&self) -> HeaderExt
{ - let (_, hash) = self.0.best_header; - self.0.headers.get(&hash).cloned().unwrap() - } - - fn headers_at_number(&self, number: NumberOf
) -> Vec> { - self.0 - .number_to_hashes - .get(&number) - .unwrap_or(&vec![]) - .iter() - .map(|hash| self.0.headers.get(hash).cloned().unwrap()) - .collect() - } - - fn prune_header(&mut self, pruned_hash: HashOf
) { - if let Some(pruned_header) = self.0.headers.remove(&pruned_hash) { - let number_to_hashes = self - .0 - .number_to_hashes - .remove(pruned_header.header.number()) - .unwrap_or_default() - .into_iter() - .filter(|hash| *hash != pruned_hash) - .collect(); - - self.0 - .number_to_hashes - .insert(*pruned_header.header.number(), number_to_hashes); - } - } - - fn finalize_header(&mut self, hash: HashOf
) { - let header = self.0.headers.get(&hash).unwrap(); - self.0.finalized_head = Some((*header.header.number(), header.header.hash())) - } - - fn finalized_header(&self) -> HeaderExt
{ - self.0 - .finalized_head - .and_then(|(_, hash)| self.0.headers.get(&hash).cloned()) - .unwrap_or_else(|| { - self.0 - .headers - .get( - self.0 - .number_to_hashes - .get(&Zero::zero()) - .cloned() - .unwrap() - .get(0) - .unwrap(), - ) - .cloned() - .unwrap() - }) - } - - fn store_segment_commitments( - &mut self, - mut segment_commitments: BTreeMap, - ) { - self.0.segment_commitments.append(&mut segment_commitments) - } - - fn segment_commitment(&self, segment_index: SegmentIndex) -> Option { - self.0.segment_commitments.get(&segment_index).cloned() - } - - fn number_of_segments(&self) -> u64 { - self.0.segment_commitments.len() as u64 - } - - fn max_pieces_in_sector(&self) -> u16 { - MAX_PIECES_IN_SECTOR - } -} - -impl MockStorage { - pub(crate) fn new(constants: ChainConstants
) -> Self { - MockStorage(StorageData { - constants, - headers: Default::default(), - number_to_hashes: Default::default(), - best_header: (Default::default(), Default::default()), - finalized_head: None, - segment_commitments: Default::default(), - }) - } - - // hack to adjust the solution range - pub(crate) fn override_solution_range( - &mut self, - hash: HashOf
, - solution_range: SolutionRange, - ) { - let mut header = self.0.headers.remove(&hash).unwrap(); - header.test_overrides.solution_range = Some(solution_range); - self.0.headers.insert(hash, header); - } - - // hack to adjust the next solution range - pub(crate) fn override_next_solution_range( - &mut self, - hash: HashOf
, - next_solution_range: SolutionRange, - ) { - let mut header = self.0.headers.remove(&hash).unwrap(); - header.test_overrides.next_solution_range = Some(next_solution_range); - self.0.headers.insert(hash, header); - } - - // hack to adjust constants when importing Block #1 - pub(crate) fn override_constants(&mut self, constants: ChainConstants
) { - self.0.constants = constants; - } - - // hack to adjust the cumulative weight - pub(crate) fn override_cumulative_weight(&mut self, hash: HashOf
, weight: BlockWeight) { - let mut header = self.0.headers.remove(&hash).unwrap(); - header.total_weight = weight; - self.0.headers.insert(hash, header); - } - - // hack to store segment commitments - pub(crate) fn store_segment_commitment( - &mut self, - segment_index: SegmentIndex, - segment_commitment: SegmentCommitment, - ) { - self.0 - .segment_commitments - .insert(segment_index, segment_commitment); - } -} - -pub fn new_test_ext() -> TestExternalities { - let mut ext = TestExternalities::new_empty(); - - ext.register_extension(KzgExtension::new(kzg_instance().clone())); - ext.register_extension(PosExtension::new::()); - #[cfg(feature = "pot")] - ext.register_extension(PotExtension::new(Box::new( - |parent_hash, slot, proof_of_time| todo!(), - ))); - - ext -} diff --git a/crates/sp-lightclient/src/tests.rs b/crates/sp-lightclient/src/tests.rs deleted file mode 100644 index 96da40490d..0000000000 --- a/crates/sp-lightclient/src/tests.rs +++ /dev/null @@ -1,1497 +0,0 @@ -use crate::mock::{kzg_instance, new_test_ext, Header, MockStorage, PosTable}; -use crate::{ - ChainConstants, DigestError, HashOf, HeaderExt, HeaderImporter, ImportError, NextDigestItems, - NumberOf, Storage, StorageBound, -}; -use frame_support::{assert_err, assert_ok}; -use futures::executor::block_on; -use rand::rngs::StdRng; -use rand::{Rng, SeedableRng}; -use schnorrkel::Keypair; -use sp_consensus_slots::Slot; -#[cfg(not(feature = "pot"))] -use sp_consensus_subspace::digests::derive_next_global_randomness; -use sp_consensus_subspace::digests::{ - derive_next_solution_range, extract_pre_digest, extract_subspace_digest_items, - CompatibleDigestItem, DeriveNextSolutionRangeParams, ErrorDigestType, PreDigest, -}; -use sp_consensus_subspace::{FarmerPublicKey, FarmerSignature}; -use sp_runtime::app_crypto::UncheckedFrom; -use sp_runtime::testing::H256; -use sp_runtime::traits::Header as HeaderT; -use sp_runtime::{Digest, DigestItem}; -use std::iter; -use std::num::{NonZeroU64, NonZeroUsize}; -use std::sync::OnceLock; -use subspace_archiving::archiver::{Archiver, NewArchivedSegment}; -#[cfg(feature = "pot")] -use subspace_core_primitives::PotOutput; -use subspace_core_primitives::{ - BlockWeight, HistorySize, PublicKey, Randomness, Record, RecordedHistorySegment, - SegmentCommitment, SegmentIndex, SlotNumber, Solution, SolutionRange, REWARD_SIGNING_CONTEXT, -}; -use subspace_erasure_coding::ErasureCoding; -use subspace_farmer_components::auditing::audit_sector; -use subspace_farmer_components::plotting::plot_sector; -use subspace_farmer_components::sector::{sector_size, SectorMetadataChecksummed}; -use subspace_farmer_components::FarmerProtocolInfo; -use subspace_proof_of_space::Table; -#[cfg(not(feature = "pot"))] -use subspace_verification::derive_randomness; -use subspace_verification::{calculate_block_weight, verify_solution, VerifySolutionParams}; - -fn erasure_coding_instance() -> &'static ErasureCoding { - static ERASURE_CODING: OnceLock = OnceLock::new(); - - ERASURE_CODING.get_or_init(|| { - ErasureCoding::new( - NonZeroUsize::new(Record::NUM_S_BUCKETS.next_power_of_two().ilog2() as usize) - .expect("Not zero; qed"), - ) - .unwrap() - }) -} - -#[cfg(not(feature = "pot"))] -fn default_randomness() -> Randomness { - Randomness::from([1u8; 32]) -} - -fn default_test_constants() -> ChainConstants
{ - #[cfg(not(feature = "pot"))] - let global_randomness = default_randomness(); - ChainConstants { - k_depth: 7, - genesis_digest_items: NextDigestItems { - #[cfg(not(feature = "pot"))] - next_global_randomness: global_randomness, - next_solution_range: Default::default(), - }, - genesis_segment_commitments: Default::default(), - #[cfg(not(feature = "pot"))] - global_randomness_interval: 20, - era_duration: 20, - slot_probability: (1, 6), - storage_bound: Default::default(), - recent_segments: HistorySize::from(NonZeroU64::new(5).unwrap()), - recent_history_fraction: ( - HistorySize::from(NonZeroU64::new(1).unwrap()), - HistorySize::from(NonZeroU64::new(10).unwrap()), - ), - min_sector_lifetime: HistorySize::from(NonZeroU64::new(4).unwrap()), - } -} - -fn archived_segment() -> &'static NewArchivedSegment { - static ARCHIVED_SEGMENT: OnceLock = OnceLock::new(); - - ARCHIVED_SEGMENT.get_or_init(|| { - // we don't care about the block data - let mut rng = StdRng::seed_from_u64(0); - let mut block = vec![0u8; RecordedHistorySegment::SIZE]; - rng.fill(block.as_mut_slice()); - - let mut archiver = Archiver::new(kzg_instance().clone()).unwrap(); - - archiver - .add_block(block, Default::default(), true) - .into_iter() - .next() - .unwrap() - }) -} - -struct FarmerParameters { - farmer_protocol_info: FarmerProtocolInfo, -} - -impl FarmerParameters { - fn new() -> Self { - let farmer_protocol_info = FarmerProtocolInfo { - history_size: HistorySize::from(SegmentIndex::ZERO), - max_pieces_in_sector: 1, - recent_segments: HistorySize::from(NonZeroU64::new(5).unwrap()), - recent_history_fraction: ( - HistorySize::from(NonZeroU64::new(1).unwrap()), - HistorySize::from(NonZeroU64::new(10).unwrap()), - ), - min_sector_lifetime: HistorySize::from(NonZeroU64::new(4).unwrap()), - }; - - Self { - farmer_protocol_info, - } - } -} - -struct ValidHeaderParams<'a> { - parent_hash: HashOf
, - number: NumberOf
, - slot: u64, - keypair: &'a Keypair, - #[cfg(not(feature = "pot"))] - global_randomness: Randomness, - #[cfg(feature = "pot")] - proof_of_time: PotOutput, - #[cfg(feature = "pot")] - future_proof_of_time: PotOutput, - farmer_parameters: &'a FarmerParameters, -} - -fn valid_header( - params: ValidHeaderParams<'_>, -) -> ( - Header, - SolutionRange, - BlockWeight, - SegmentIndex, - SegmentCommitment, -) { - let ValidHeaderParams { - parent_hash, - number, - slot, - keypair, - #[cfg(not(feature = "pot"))] - global_randomness, - #[cfg(feature = "pot")] - proof_of_time, - #[cfg(feature = "pot")] - future_proof_of_time, - farmer_parameters, - } = params; - - let archived_segment = archived_segment(); - - let segment_index = archived_segment.segment_header.segment_index(); - let segment_commitment = archived_segment.segment_header.segment_commitment(); - let public_key = PublicKey::from(keypair.public.to_bytes()); - - let pieces_in_sector = farmer_parameters.farmer_protocol_info.max_pieces_in_sector; - let sector_size = sector_size(pieces_in_sector); - - let mut table_generator = PosTable::generator(); - - for sector_index in iter::from_fn(|| Some(rand::random())) { - let mut plotted_sector_bytes = Vec::new(); - let mut plotted_sector_metadata_bytes = Vec::new(); - - let plotted_sector = block_on(plot_sector( - &public_key, - sector_index, - &archived_segment.pieces, - &farmer_parameters.farmer_protocol_info, - kzg_instance(), - erasure_coding_instance(), - pieces_in_sector, - &mut plotted_sector_bytes, - &mut plotted_sector_metadata_bytes, - records_encoder: &mut CpuRecordsEncoder::::new( - slice::from_mut(&mut table_generator), - &erasure_coding, - &Default::default(), - ), - )) - .unwrap(); - - #[cfg(feature = "pot")] - let global_randomness = proof_of_time.derive_global_randomness(); - let global_challenge = global_randomness.derive_global_challenge(slot); - - let maybe_solution_candidates = audit_sector( - &public_key, - &global_challenge, - SolutionRange::MAX, - &plotted_sector_bytes, - &plotted_sector.sector_metadata, - ); - - let Some(solution_candidates) = maybe_solution_candidates else { - // Sector didn't have any solutions - continue; - }; - - let solution = solution_candidates - .into_iter::<_, PosTable>( - &public_key, - kzg_instance(), - erasure_coding_instance(), - &mut table_generator, - ) - .unwrap() - .next() - .unwrap() - .unwrap(); - - let solution = Solution { - public_key: FarmerPublicKey::unchecked_from(keypair.public.to_bytes()), - reward_address: solution.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, - }; - - let solution_distance = verify_solution::( - &solution, - slot, - &VerifySolutionParams { - #[cfg(not(feature = "pot"))] - global_randomness, - #[cfg(feature = "pot")] - proof_of_time, - solution_range: SolutionRange::MAX, - piece_check_params: None, - }, - kzg_instance(), - ) - .unwrap(); - let solution_range = solution_distance * 2; - let block_weight = calculate_block_weight(solution_range); - - let pre_digest = PreDigest::V0 { - slot: slot.into(), - solution, - #[cfg(feature = "pot")] - proof_of_time, - #[cfg(feature = "pot")] - future_proof_of_time, - }; - let digests = vec![ - #[cfg(not(feature = "pot"))] - DigestItem::global_randomness(global_randomness), - DigestItem::solution_range(solution_range), - DigestItem::subspace_pre_digest(&pre_digest), - ]; - - let header = Header { - parent_hash, - number, - state_root: Default::default(), - extrinsics_root: Default::default(), - digest: Digest { logs: digests }, - }; - - return ( - header, - solution_range, - block_weight, - segment_index, - segment_commitment, - ); - } - - unreachable!("Will find solution before exhausting u64") -} - -fn seal_header(keypair: &Keypair, header: &mut Header) { - let ctx = schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT); - let pre_hash = header.hash(); - let signature = - FarmerSignature::unchecked_from(keypair.sign(ctx.bytes(pre_hash.as_bytes())).to_bytes()); - header - .digest - .logs - .push(DigestItem::subspace_seal(signature)); -} - -fn remove_seal(header: &mut Header) { - let digests = header.digest_mut(); - digests.pop(); -} - -fn next_slot(slot_probability: (u64, u64), current_slot: Slot) -> Slot { - let mut rng = StdRng::seed_from_u64(current_slot.into()); - current_slot + rng.gen_range(slot_probability.0..=slot_probability.1) -} - -fn initialize_store( - constants: ChainConstants
, - should_adjust_solution_range: bool, - maybe_root_plot_public_key: Option, -) -> (MockStorage, HashOf
) { - let mut store = MockStorage::new(constants); - let mut rng = StdRng::seed_from_u64(0); - let mut state_root = vec![0u8; 32]; - rng.fill(state_root.as_mut_slice()); - let genesis_header = Header { - parent_hash: Default::default(), - number: 0, - state_root: H256::from_slice(&state_root), - extrinsics_root: Default::default(), - digest: Default::default(), - }; - - let genesis_hash = genesis_header.hash(); - let header = HeaderExt { - header: genesis_header, - total_weight: 0, - era_start_slot: Default::default(), - should_adjust_solution_range, - maybe_current_solution_range_override: None, - maybe_next_solution_range_override: None, - maybe_root_plot_public_key, - test_overrides: Default::default(), - }; - - store.store_header(header, true); - (store, genesis_hash) -} - -fn add_next_digests(store: &MockStorage, number: NumberOf
, header: &mut Header) { - let constants = store.chain_constants(); - let parent_header = store.header(*header.parent_hash()).unwrap(); - let digests = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - header, - ) - .unwrap(); - - let digest_logs = header.digest_mut(); - #[cfg(not(feature = "pot"))] - if let Some(next_randomness) = derive_next_global_randomness::
( - number, - constants.global_randomness_interval, - &digests.pre_digest, - ) { - digest_logs.push(DigestItem::next_global_randomness(next_randomness)); - } - - if let Some(next_solution_range) = - derive_next_solution_range::
(DeriveNextSolutionRangeParams { - number, - era_duration: constants.era_duration, - slot_probability: constants.slot_probability, - current_slot: digests.pre_digest.slot(), - current_solution_range: digests.solution_range, - era_start_slot: parent_header.era_start_slot, - should_adjust_solution_range: true, - maybe_next_solution_range_override: None, - }) - .unwrap() - { - digest_logs.push(DigestItem::next_solution_range(next_solution_range)); - } -} - -struct ForkAt { - parent_hash: HashOf
, - // if None, fork chain cumulative weight is equal to canonical chain weight - is_best: Option, -} - -fn add_headers_to_chain( - importer: &mut HeaderImporter, - keypair: &Keypair, - headers_to_add: NumberOf
, - maybe_fork_chain: Option, - farmer_parameters: &FarmerParameters, -) -> HashOf
{ - let best_header_ext = importer.store.best_header(); - let constants = importer.store.chain_constants(); - let (parent_hash, number, slot) = if let Some(ForkAt { parent_hash, .. }) = maybe_fork_chain { - let header = importer.store.header(parent_hash).unwrap(); - let digests = extract_pre_digest(&header.header).unwrap(); - - (parent_hash, *header.header.number(), digests.slot()) - } else { - let digests = extract_pre_digest(&best_header_ext.header).unwrap(); - ( - best_header_ext.header.hash(), - *best_header_ext.header.number(), - digests.slot(), - ) - }; - - let until_number = number + headers_to_add; - let mut parent_hash = parent_hash; - let mut number = number + 1; - let mut slot = next_slot(constants.slot_probability, slot); - let mut best_header_hash = best_header_ext.header.hash(); - while number <= until_number { - let (global_randomness, override_next_solution) = if number == 1 { - let randomness = default_randomness(); - (randomness, false) - } else { - let header = importer.store.header(parent_hash).unwrap(); - let digests = extract_subspace_digest_items::< - _, - FarmerPublicKey, - FarmerPublicKey, - FarmerSignature, - >(&header.header) - .unwrap(); - - let randomness = digests - .next_global_randomness - .unwrap_or(digests.global_randomness); - (randomness, digests.next_global_randomness.is_some()) - }; - - let (mut header, solution_range, block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash, - number, - slot: slot.into(), - keypair, - global_randomness, - farmer_parameters, - }); - importer.store.override_cumulative_weight(parent_hash, 0); - if number == 1 { - // adjust Chain constants for Block #1 - let mut constants = importer.store.chain_constants(); - constants.genesis_digest_items.next_solution_range = solution_range; - importer.store.override_constants(constants) - } else if override_next_solution { - importer - .store - .override_next_solution_range(parent_hash, solution_range); - } else { - importer - .store - .override_solution_range(parent_hash, solution_range); - } - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - if let Some(ForkAt { - is_best: maybe_best, - .. - }) = maybe_fork_chain - { - if let Some(is_best) = maybe_best { - if is_best { - importer - .store - .override_cumulative_weight(best_header_hash, block_weight - 1) - } else { - importer - .store - .override_cumulative_weight(best_header_hash, block_weight + 1) - } - } else { - importer - .store - .override_cumulative_weight(best_header_hash, block_weight) - } - } - - add_next_digests(&importer.store, number, &mut header); - seal_header(keypair, &mut header); - parent_hash = header.hash(); - slot = next_slot(constants.slot_probability, slot); - number += 1; - - assert_ok!(importer.import_header(header.clone())); - if let Some(ForkAt { - is_best: maybe_best, - .. - }) = maybe_fork_chain - { - if let Some(is_best) = maybe_best { - if is_best { - best_header_hash = header.hash() - } - } - } else { - best_header_hash = header.hash() - } - - assert_eq!(importer.store.best_header().header.hash(), best_header_hash); - } - - parent_hash -} - -fn ensure_finalized_heads_have_no_forks(store: &MockStorage, finalized_number: NumberOf
) { - let finalized_header = store.finalized_header(); - let (expected_finalized_number, hash) = ( - finalized_header.header.number, - finalized_header.header.hash(), - ); - assert_eq!(expected_finalized_number, finalized_number); - assert_eq!(store.headers_at_number(finalized_number).len(), 1); - if finalized_number < 1 { - return; - } - - let header = store.header(hash).unwrap(); - let mut parent_hash = header.header.parent_hash; - let mut finalized_number = finalized_number - 1; - while finalized_number > 0 { - assert_eq!(store.headers_at_number(finalized_number).len(), 1); - let hash = store.headers_at_number(finalized_number)[0].header.hash(); - assert_eq!(parent_hash, hash); - parent_hash = store.header(hash).unwrap().header.parent_hash; - finalized_number -= 1; - } -} - -#[test] -fn test_header_import_missing_parent() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let constants = default_test_constants(); - let (mut store, _genesis_hash) = initialize_store(constants, true, None); - let global_randomness = default_randomness(); - let (header, _solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: Default::default(), - number: 1, - slot: 1, - keypair: &keypair, - global_randomness, - farmer_parameters: &farmer_parameters, - }); - store.store_segment_commitment(segment_index, segment_commitment); - let mut importer = HeaderImporter::new(store); - assert_err!( - importer.import_header(header.clone()), - ImportError::MissingParent(header.hash()) - ); - }); -} - -#[test] -fn test_header_import_non_canonical() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer = FarmerParameters::new(); - - let constants = default_test_constants(); - let (store, _genesis_hash) = initialize_store(constants, true, None); - let mut importer = HeaderImporter::new(store); - let hash_of_2 = add_headers_to_chain(&mut importer, &keypair, 2, None, &farmer); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_2); - - // import canonical block 3 - let hash_of_3 = add_headers_to_chain(&mut importer, &keypair, 1, None, &farmer); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_3); - let best_header = importer.store.header(hash_of_3).unwrap(); - assert_eq!(importer.store.headers_at_number(3).len(), 1); - - // import non canonical block 3 - add_headers_to_chain( - &mut importer, - &keypair, - 1, - Some(ForkAt { - parent_hash: hash_of_2, - is_best: Some(false), - }), - &farmer, - ); - - let best_header_ext = importer.store.best_header(); - assert_eq!(best_header_ext.header, best_header.header); - // we still track the forks - assert_eq!(importer.store.headers_at_number(3).len(), 2); - }); -} - -#[test] -fn test_header_import_canonical() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer = FarmerParameters::new(); - - let constants = default_test_constants(); - let (store, _genesis_hash) = initialize_store(constants, true, None); - let mut importer = HeaderImporter::new(store); - let hash_of_5 = add_headers_to_chain(&mut importer, &keypair, 5, None, &farmer); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_5); - - // import some more canonical blocks - let hash_of_25 = add_headers_to_chain(&mut importer, &keypair, 20, None, &farmer); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_25); - assert_eq!(importer.store.headers_at_number(25).len(), 1); - }); -} - -#[test] -fn test_header_import_non_canonical_with_equal_block_weight() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer = FarmerParameters::new(); - - let constants = default_test_constants(); - let (store, _genesis_hash) = initialize_store(constants, true, None); - let mut importer = HeaderImporter::new(store); - let hash_of_2 = add_headers_to_chain(&mut importer, &keypair, 2, None, &farmer); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_2); - - // import canonical block 3 - let hash_of_3 = add_headers_to_chain(&mut importer, &keypair, 1, None, &farmer); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_3); - let best_header = importer.store.header(hash_of_3).unwrap(); - assert_eq!(importer.store.headers_at_number(3).len(), 1); - - // import non canonical block 3 - add_headers_to_chain( - &mut importer, - &keypair, - 1, - Some(ForkAt { - parent_hash: hash_of_2, - is_best: None, - }), - &farmer, - ); - - let best_header_ext = importer.store.best_header(); - assert_eq!(best_header_ext.header, best_header.header); - // we still track the forks - assert_eq!(importer.store.headers_at_number(3).len(), 2); - }); -} - -// TODO: This test doesn't actually reorg, but probably should -#[test] -fn test_chain_reorg_to_heavier_chain() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.k_depth = 4; - let (store, genesis_hash) = initialize_store(constants, true, None); - let mut importer = HeaderImporter::new(store); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_4); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - // create a fork chain of 4 headers from number 1 - add_headers_to_chain( - &mut importer, - &keypair, - 4, - Some(ForkAt { - parent_hash: genesis_hash, - is_best: Some(false), - }), - &farmer, - ); - assert_eq!(best_header.header.hash(), hash_of_4); - // block 0 is still finalized - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - ensure_finalized_heads_have_no_forks(&importer.store, 0); - - // add new best header at 5 - let hash_of_5 = add_headers_to_chain(&mut importer, &keypair, 1, None, &farmer); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_5); - - // block 1 should be finalized - assert_eq!(importer.store.finalized_header().header.number, 1); - ensure_finalized_heads_have_no_forks(&importer.store, 1); - - // create a fork chain from number 5 with block until 8 - let fork_hash_of_8 = add_headers_to_chain( - &mut importer, - &keypair, - 4, - Some(ForkAt { - parent_hash: hash_of_4, - is_best: Some(false), - }), - &farmer, - ); - - // best header should still be the same - assert_eq!(best_header.header, importer.store.best_header().header); - - // there must be 2 heads at 5 - assert_eq!(importer.store.headers_at_number(5).len(), 2); - - // block 1 should be finalized - assert_eq!(importer.store.finalized_header().header.number, 1); - ensure_finalized_heads_have_no_forks(&importer.store, 1); - - // import a new head to the fork chain and make it the best. - let hash_of_9 = add_headers_to_chain( - &mut importer, - &keypair, - 1, - Some(ForkAt { - parent_hash: fork_hash_of_8, - is_best: Some(true), - }), - &farmer, - ); - assert_eq!(importer.store.best_header().header.hash(), hash_of_9); - - // now the finalized header must be 5 - ensure_finalized_heads_have_no_forks(&importer.store, 5); - }); -} - -#[test] -fn test_reorg_to_heavier_smaller_chain() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.k_depth = 4; - let (store, genesis_hash) = initialize_store(constants, true, None); - let mut importer = HeaderImporter::new(store); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - let hash_of_5 = add_headers_to_chain(&mut importer, &keypair, 5, None, &farmer_parameters); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_5); - assert_eq!(importer.store.finalized_header().header.number, 1); - - // header count at the finalized head must be 1 - ensure_finalized_heads_have_no_forks(&importer.store, 1); - - // now import a fork header 3 that becomes canonical - let constants = importer.store.chain_constants(); - let header_at_2 = importer - .store - .headers_at_number(2) - .first() - .cloned() - .unwrap(); - let digests_at_2 = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - &header_at_2.header, - ) - .unwrap(); - let (mut header, solution_range, block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: header_at_2.header.hash(), - number: 3, - slot: next_slot(constants.slot_probability, digests_at_2.pre_digest.slot()).into(), - keypair: &keypair, - #[cfg(not(feature = "pot"))] - global_randomness: digests_at_2.global_randomness, - // TODO: Correct value - #[cfg(feature = "pot")] - proof_of_time: PotOutput::default(), - // TODO: Correct value - #[cfg(feature = "pot")] - future_proof_of_time: PotOutput::default(), - farmer_parameters: &farmer_parameters, - }); - seal_header(&keypair, &mut header); - importer - .store - .override_solution_range(header_at_2.header.hash(), solution_range); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer.store.override_cumulative_weight( - importer.store.best_header().header.hash(), - block_weight - 1, - ); - // override parent weight to 0 - importer - .store - .override_cumulative_weight(header_at_2.header.hash(), 0); - let res = importer.import_header(header); - assert_err!(res, ImportError::SwitchedToForkBelowArchivingDepth); - }); -} - -#[test] -fn test_next_global_randomness_digest() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.global_randomness_interval = 5; - let (store, genesis_hash) = initialize_store(constants, true, None); - let mut importer = HeaderImporter::new(store); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer_parameters); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); - - // try to import header with out next global randomness - let constants = importer.store.chain_constants(); - let header_at_4 = importer.store.header(hash_of_4).unwrap(); - let digests_at_4 = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - &header_at_4.header, - ) - .unwrap(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: header_at_4.header.hash(), - number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), - keypair: &keypair, - global_randomness: digests_at_4.global_randomness, - farmer_parameters: &farmer_parameters, - }); - seal_header(&keypair, &mut header); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer - .store - .override_cumulative_weight(header_at_4.header.hash(), 0); - let res = importer.import_header(header.clone()); - assert_err!( - res, - ImportError::DigestError(DigestError::NextDigestVerificationError( - ErrorDigestType::NextGlobalRandomness - )) - ); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); - - // add next global randomness - remove_seal(&mut header); - let pre_digest = extract_pre_digest(&header).unwrap(); - let randomness = derive_randomness(pre_digest.solution(), pre_digest.slot().into()); - let digests = header.digest_mut(); - digests.push(DigestItem::next_global_randomness(randomness)); - seal_header(&keypair, &mut header); - let res = importer.import_header(header.clone()); - assert_ok!(res); - assert_eq!(importer.store.best_header().header.hash(), header.hash()); - }); -} - -#[test] -fn test_next_solution_range_digest_with_adjustment_enabled() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.era_duration = 5; - let (store, genesis_hash) = initialize_store(constants, true, None); - let mut importer = HeaderImporter::new(store); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer_parameters); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); - - // try to import header with out next global randomness - let constants = importer.store.chain_constants(); - let header_at_4 = importer.store.header(hash_of_4).unwrap(); - let digests_at_4 = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - &header_at_4.header, - ) - .unwrap(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: header_at_4.header.hash(), - number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), - keypair: &keypair, - global_randomness: digests_at_4.global_randomness, - farmer_parameters: &farmer_parameters, - }); - seal_header(&keypair, &mut header); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer - .store - .override_cumulative_weight(header_at_4.header.hash(), 0); - let pre_digest = extract_pre_digest(&header).unwrap(); - let res = importer.import_header(header.clone()); - assert_err!( - res, - ImportError::DigestError(DigestError::NextDigestVerificationError( - ErrorDigestType::NextSolutionRange - )) - ); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); - - // add next solution range - remove_seal(&mut header); - let next_solution_range = subspace_verification::derive_next_solution_range( - SlotNumber::from(header_at_4.era_start_slot), - SlotNumber::from(pre_digest.slot()), - constants.slot_probability, - solution_range, - constants.era_duration, - ); - let digests = header.digest_mut(); - digests.push(DigestItem::next_solution_range(next_solution_range)); - seal_header(&keypair, &mut header); - let res = importer.import_header(header.clone()); - assert_ok!(res); - assert_eq!(importer.store.best_header().header.hash(), header.hash()); - }); -} - -#[test] -fn test_next_solution_range_digest_with_adjustment_disabled() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.era_duration = 5; - let (store, genesis_hash) = initialize_store(constants, false, None); - let mut importer = HeaderImporter::new(store); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer_parameters); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); - - // try to import header with out next global randomness - let constants = importer.store.chain_constants(); - let header_at_4 = importer.store.header(hash_of_4).unwrap(); - let digests_at_4 = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - &header_at_4.header, - ) - .unwrap(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: header_at_4.header.hash(), - number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), - keypair: &keypair, - global_randomness: digests_at_4.global_randomness, - farmer_parameters: &farmer_parameters, - }); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer - .store - .override_cumulative_weight(header_at_4.header.hash(), 0); - - // since solution range adjustment is disabled - // current solution range is used as next - let next_solution_range = solution_range; - let digests = header.digest_mut(); - digests.push(DigestItem::next_solution_range(next_solution_range)); - seal_header(&keypair, &mut header); - let res = importer.import_header(header.clone()); - assert_ok!(res); - assert_eq!(importer.store.best_header().header.hash(), header.hash()); - assert!(!importer.store.best_header().should_adjust_solution_range); - }); -} - -#[test] -fn test_enable_solution_range_adjustment_without_override() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.era_duration = 5; - let (store, genesis_hash) = initialize_store(constants, false, None); - let mut importer = HeaderImporter::new(store); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer_parameters); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); - // solution range adjustment is disabled - assert!(!importer.store.best_header().should_adjust_solution_range); - - // enable solution range adjustment in this header - let constants = importer.store.chain_constants(); - let header_at_4 = importer.store.header(hash_of_4).unwrap(); - let digests_at_4 = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - &header_at_4.header, - ) - .unwrap(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: header_at_4.header.hash(), - number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), - keypair: &keypair, - global_randomness: digests_at_4.global_randomness, - farmer_parameters: &farmer_parameters, - }); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer - .store - .override_cumulative_weight(header_at_4.header.hash(), 0); - let pre_digest = extract_pre_digest(&header).unwrap(); - let next_solution_range = subspace_verification::derive_next_solution_range( - SlotNumber::from(header_at_4.era_start_slot), - SlotNumber::from(pre_digest.slot()), - constants.slot_probability, - solution_range, - constants.era_duration, - ); - let digests = header.digest_mut(); - digests.push(DigestItem::next_solution_range(next_solution_range)); - digests.push(DigestItem::enable_solution_range_adjustment_and_override( - None, - )); - seal_header(&keypair, &mut header); - let res = importer.import_header(header.clone()); - assert_ok!(res); - assert_eq!(importer.store.best_header().header.hash(), header.hash()); - assert!(importer.store.best_header().should_adjust_solution_range); - assert_eq!(header_at_4.maybe_current_solution_range_override, None); - assert_eq!(header_at_4.maybe_next_solution_range_override, None); - }); -} - -#[test] -fn test_enable_solution_range_adjustment_with_override_between_update_intervals() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.era_duration = 5; - let (store, genesis_hash) = initialize_store(constants, false, None); - let mut importer = HeaderImporter::new(store); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - let hash_of_3 = add_headers_to_chain(&mut importer, &keypair, 3, None, &farmer_parameters); - assert_eq!(importer.store.best_header().header.hash(), hash_of_3); - // solution range adjustment is disabled - assert!(!importer.store.best_header().should_adjust_solution_range); - - // enable solution range adjustment with override in this header - let constants = importer.store.chain_constants(); - let header_at_3 = importer.store.header(hash_of_3).unwrap(); - let digests_at_3 = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - &header_at_3.header, - ) - .unwrap(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: header_at_3.header.hash(), - number: 4, - slot: next_slot(constants.slot_probability, digests_at_3.pre_digest.slot()).into(), - keypair: &keypair, - global_randomness: digests_at_3.global_randomness, - farmer_parameters: &farmer_parameters, - }); - importer - .store - .override_solution_range(header_at_3.header.hash(), solution_range); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer - .store - .override_cumulative_weight(header_at_3.header.hash(), 0); - let digests = header.digest_mut(); - let solution_range_override = 100; - digests.push(DigestItem::enable_solution_range_adjustment_and_override( - Some(solution_range_override), - )); - seal_header(&keypair, &mut header); - let res = importer.import_header(header.clone()); - assert_ok!(res); - let header_at_4 = importer.store.best_header(); - assert_eq!(header_at_4.header.hash(), header.hash()); - assert!(header_at_4.should_adjust_solution_range); - // current solution range override and next solution range overrides are updated - assert_eq!( - header_at_4.maybe_current_solution_range_override, - Some(solution_range_override) - ); - assert_eq!( - header_at_4.maybe_next_solution_range_override, - Some(solution_range_override) - ); - }); -} - -#[test] -fn test_enable_solution_range_adjustment_with_override_at_interval_change() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.era_duration = 5; - let (store, genesis_hash) = initialize_store(constants, false, None); - let mut importer = HeaderImporter::new(store); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer_parameters); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); - // solution range adjustment is disabled - assert!(!importer.store.best_header().should_adjust_solution_range); - - // enable solution range adjustment in this header - let constants = importer.store.chain_constants(); - let header_at_4 = importer.store.header(hash_of_4).unwrap(); - let digests_at_4 = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - &header_at_4.header, - ) - .unwrap(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: header_at_4.header.hash(), - number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), - keypair: &keypair, - global_randomness: digests_at_4.global_randomness, - farmer_parameters: &farmer_parameters, - }); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer - .store - .override_cumulative_weight(header_at_4.header.hash(), 0); - let solution_range_override = 100; - let next_solution_range = solution_range_override; - let digests = header.digest_mut(); - digests.push(DigestItem::next_solution_range(next_solution_range)); - digests.push(DigestItem::enable_solution_range_adjustment_and_override( - Some(solution_range_override), - )); - seal_header(&keypair, &mut header); - let res = importer.import_header(header.clone()); - assert_ok!(res); - assert_eq!(importer.store.best_header().header.hash(), header.hash()); - assert!(importer.store.best_header().should_adjust_solution_range); - assert_eq!(header_at_4.maybe_current_solution_range_override, None); - assert_eq!(header_at_4.maybe_next_solution_range_override, None); - }); -} - -#[test] -fn test_disallow_enable_solution_range_digest_when_solution_range_adjustment_is_already_enabled() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.era_duration = 5; - let (store, genesis_hash) = initialize_store(constants, true, None); - let mut importer = HeaderImporter::new(store); - assert_eq!( - importer.store.finalized_header().header.hash(), - genesis_hash - ); - - let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer_parameters); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); - - // try to import header with enable solution range adjustment digest - let constants = importer.store.chain_constants(); - let header_at_4 = importer.store.header(hash_of_4).unwrap(); - let digests_at_4 = - extract_subspace_digest_items::<_, FarmerPublicKey, FarmerPublicKey, FarmerSignature>( - &header_at_4.header, - ) - .unwrap(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: header_at_4.header.hash(), - number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), - keypair: &keypair, - global_randomness: digests_at_4.global_randomness, - farmer_parameters: &farmer_parameters, - }); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer - .store - .override_cumulative_weight(header_at_4.header.hash(), 0); - let digests = header.digest_mut(); - digests.push(DigestItem::enable_solution_range_adjustment_and_override( - None, - )); - seal_header(&keypair, &mut header); - let res = importer.import_header(header.clone()); - assert_err!( - res, - ImportError::DigestError(DigestError::NextDigestVerificationError( - ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride - )) - ); - }); -} - -fn ensure_store_is_storage_bounded(headers_to_keep_beyond_k_depth: NumberOf
) { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer = FarmerParameters::new(); - - let mut constants = default_test_constants(); - constants.k_depth = 7; - constants.storage_bound = - StorageBound::NumberOfHeaderToKeepBeyondKDepth(headers_to_keep_beyond_k_depth); - let (store, _genesis_hash) = initialize_store(constants, true, None); - let mut importer = HeaderImporter::new(store); - // import some more canonical blocks - let hash_of_50 = add_headers_to_chain(&mut importer, &keypair, 50, None, &farmer); - let best_header = importer.store.best_header(); - assert_eq!(best_header.header.hash(), hash_of_50); - - // check storage bound - let finalized_head = importer.store.finalized_header(); - assert_eq!(finalized_head.header.number, 43); - // there should be headers at and below (finalized_head - bound - 1) - let mut pruned_number = 43 - headers_to_keep_beyond_k_depth - 1; - while pruned_number != 0 { - assert!(importer.store.headers_at_number(pruned_number).is_empty()); - pruned_number -= 1; - } - - assert!(importer.store.headers_at_number(0).is_empty()); - }); -} - -#[test] -fn test_storage_bound_with_headers_beyond_k_depth_is_zero() { - ensure_store_is_storage_bounded(0) -} - -#[test] -fn test_storage_bound_with_headers_beyond_k_depth_is_one() { - ensure_store_is_storage_bounded(1) -} - -#[test] -fn test_storage_bound_with_headers_beyond_k_depth_is_more_than_one() { - ensure_store_is_storage_bounded(5) -} - -#[test] -fn test_block_author_different_farmer() { - new_test_ext().execute_with(|| { - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - let keypair_allowed = Keypair::generate(); - let pub_key = FarmerPublicKey::unchecked_from(keypair_allowed.public.to_bytes()); - let (store, genesis_hash) = initialize_store(constants.clone(), true, Some(pub_key)); - let mut importer = HeaderImporter::new(store); - - // try to import header authored by different farmer - let keypair_disallowed = Keypair::generate(); - let global_randomness = default_randomness(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: genesis_hash, - number: 1, - slot: 1, - keypair: &keypair_disallowed, - global_randomness, - farmer_parameters: &farmer_parameters, - }); - seal_header(&keypair_disallowed, &mut header); - constants.genesis_digest_items.next_solution_range = solution_range; - importer.store.override_constants(constants); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer.store.override_cumulative_weight(genesis_hash, 0); - let res = importer.import_header(header); - assert_err!( - res, - ImportError::IncorrectBlockAuthor(FarmerPublicKey::unchecked_from( - keypair_disallowed.public.to_bytes() - )) - ); - }); -} - -#[test] -fn test_block_author_first_farmer() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - let pub_key = FarmerPublicKey::unchecked_from(keypair.public.to_bytes()); - let (store, genesis_hash) = initialize_store(constants.clone(), true, None); - let mut importer = HeaderImporter::new(store); - - // try import header with first farmer - let global_randomness = default_randomness(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: genesis_hash, - number: 1, - slot: 1, - keypair: &keypair, - global_randomness, - farmer_parameters: &farmer_parameters, - }); - header - .digest - .logs - .push(DigestItem::root_plot_public_key_update(Some( - pub_key.clone(), - ))); - seal_header(&keypair, &mut header); - constants.genesis_digest_items.next_solution_range = solution_range; - importer.store.override_constants(constants); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer.store.override_cumulative_weight(genesis_hash, 0); - let res = importer.import_header(header.clone()); - assert_ok!(res); - let best_header = importer.store.best_header(); - assert_eq!(header.hash(), best_header.header.hash()); - assert_eq!(best_header.maybe_root_plot_public_key, Some(pub_key)); - }); -} - -#[test] -fn test_block_author_allow_any_farmer() { - new_test_ext().execute_with(|| { - let keypair = Keypair::generate(); - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - let pub_key = FarmerPublicKey::unchecked_from(keypair.public.to_bytes()); - let (store, genesis_hash) = initialize_store(constants.clone(), true, Some(pub_key)); - let mut importer = HeaderImporter::new(store); - - // try to import header authored by different farmer - let global_randomness = default_randomness(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: genesis_hash, - number: 1, - slot: 1, - keypair: &keypair, - global_randomness, - farmer_parameters: &farmer_parameters, - }); - header - .digest - .logs - .push(DigestItem::root_plot_public_key_update(None)); - seal_header(&keypair, &mut header); - constants.genesis_digest_items.next_solution_range = solution_range; - importer.store.override_constants(constants); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer.store.override_cumulative_weight(genesis_hash, 0); - let res = importer.import_header(header.clone()); - assert_ok!(res); - let best_header = importer.store.best_header(); - assert_eq!(header.hash(), best_header.header.hash()); - assert_eq!(best_header.maybe_root_plot_public_key, None); - }); -} - -#[test] -fn test_disallow_root_plot_public_key_override() { - new_test_ext().execute_with(|| { - let farmer_parameters = FarmerParameters::new(); - - let mut constants = default_test_constants(); - let keypair_allowed = Keypair::generate(); - let pub_key = FarmerPublicKey::unchecked_from(keypair_allowed.public.to_bytes()); - let (store, genesis_hash) = initialize_store(constants.clone(), true, Some(pub_key)); - let mut importer = HeaderImporter::new(store); - - // try to import header that contains root plot public key override - let global_randomness = default_randomness(); - let (mut header, solution_range, _block_weight, segment_index, segment_commitment) = - valid_header(ValidHeaderParams { - parent_hash: genesis_hash, - number: 1, - slot: 1, - keypair: &keypair_allowed, - global_randomness, - farmer_parameters: &farmer_parameters, - }); - let keypair_disallowed = Keypair::generate(); - let pub_key = FarmerPublicKey::unchecked_from(keypair_disallowed.public.to_bytes()); - header - .digest - .logs - .push(DigestItem::root_plot_public_key_update(Some(pub_key))); - seal_header(&keypair_allowed, &mut header); - constants.genesis_digest_items.next_solution_range = solution_range; - importer.store.override_constants(constants); - importer - .store - .store_segment_commitment(segment_index, segment_commitment); - importer.store.override_cumulative_weight(genesis_hash, 0); - let res = importer.import_header(header); - assert_err!( - res, - ImportError::DigestError(DigestError::NextDigestVerificationError( - ErrorDigestType::RootPlotPublicKeyUpdate - )) - ); - }); -} - -// TODO: Test for expired sector