diff --git a/Cargo.lock b/Cargo.lock index d068061d0a9..afac2616adc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9078,13 +9078,13 @@ dependencies = [ "schnorrkel", "sp-api", "sp-application-crypto", - "sp-arithmetic", - "sp-consensus", "sp-consensus-slots", "sp-core", + "sp-externalities", "sp-inherents", "sp-io", "sp-runtime", + "sp-runtime-interface", "sp-std", "sp-timestamp", "subspace-archiving", @@ -10166,6 +10166,7 @@ dependencies = [ "sp-consensus-subspace", "sp-core", "sp-domains", + "sp-externalities", "sp-objects", "sp-offchain", "sp-receipts", diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index d1d0041c046..74ac4088ff6 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -18,6 +18,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![warn(unused_must_use, unsafe_code, unused_variables, unused_must_use)] +extern crate alloc; + mod default_weights; pub mod equivocation; @@ -26,6 +28,7 @@ mod mock; #[cfg(all(feature = "std", test))] mod tests; +use alloc::string::String; use codec::{Decode, Encode, MaxEncodedLen}; use core::num::NonZeroU64; use equivocation::{HandleEquivocation, SubspaceEquivocationOffence}; @@ -38,6 +41,7 @@ pub use pallet::*; use scale_info::TypeInfo; use schnorrkel::SignatureError; use sp_consensus_slots::Slot; +use sp_consensus_subspace::consensus::verify_solution; use sp_consensus_subspace::digests::CompatibleDigestItem; use sp_consensus_subspace::offence::{OffenceDetails, OffenceError, OnOffenceHandler}; use sp_consensus_subspace::{ @@ -52,16 +56,14 @@ use sp_runtime::transaction_validity::{ use sp_runtime::DispatchError; use sp_std::collections::btree_map::BTreeMap; use sp_std::prelude::*; -use subspace_core_primitives::crypto::kzg; -use subspace_core_primitives::crypto::kzg::Kzg; use subspace_core_primitives::{ PublicKey, Randomness, RewardSignature, RootBlock, SectorId, SectorIndex, SegmentIndex, SolutionRange, PIECES_IN_SEGMENT, PIECE_SIZE, RECORDED_HISTORY_SEGMENT_SIZE, RECORD_SIZE, }; use subspace_solving::REWARD_SIGNING_CONTEXT; use subspace_verification::{ - check_reward_signature, derive_next_solution_range, derive_randomness, verify_solution, - Error as VerificationError, PieceCheckParams, VerifySolutionParams, + check_reward_signature, derive_next_solution_range, derive_randomness, PieceCheckParams, + VerifySolutionParams, }; pub trait WeightInfo { @@ -1197,7 +1199,7 @@ enum CheckVoteError { SlotInThePast, BadRewardSignature(SignatureError), UnknownRecordsRoot, - InvalidSolution(VerificationError), + InvalidSolution(String), DuplicateVote, Equivocated(SubspaceEquivocationOffence), } @@ -1358,20 +1360,18 @@ fn check_vote( return Err(CheckVoteError::UnknownRecordsRoot); }; - let kzg = Kzg::new(kzg::test_public_parameters()); - - if let Err(error) = verify_solution::( - solution, + if let Err(error) = verify_solution( + solution.into(), slot.into(), - VerifySolutionParams { - global_randomness: &vote_verification_data.global_randomness, + (&VerifySolutionParams { + global_randomness: vote_verification_data.global_randomness, solution_range: vote_verification_data.solution_range, piece_check_params: Some(PieceCheckParams { - records_root: &records_root, + records_root, pieces_in_segment, }), - }, - Some(&kzg), + }) + .into(), ) { debug!( target: "runtime::subspace", diff --git a/crates/pallet-subspace/src/mock.rs b/crates/pallet-subspace/src/mock.rs index b514aca0294..b3de0c88699 100644 --- a/crates/pallet-subspace/src/mock.rs +++ b/crates/pallet-subspace/src/mock.rs @@ -28,16 +28,17 @@ use rand::Rng; use schnorrkel::Keypair; use sp_consensus_slots::Slot; use sp_consensus_subspace::digests::{CompatibleDigestItem, PreDigest}; -use sp_consensus_subspace::{FarmerSignature, SignedVote, Vote}; +use sp_consensus_subspace::{FarmerSignature, KzgExtension, SignedVote, Vote}; use sp_core::crypto::UncheckedFrom; use sp_core::H256; +use sp_io::TestExternalities; use sp_runtime::testing::{Digest, DigestItem, Header, TestXt}; use sp_runtime::traits::{Block as BlockT, Header as _, IdentityLookup}; use sp_runtime::Perbill; use std::num::NonZeroU64; use std::sync::Once; use subspace_archiving::archiver::{ArchivedSegment, Archiver}; -use subspace_core_primitives::crypto::kzg::{Kzg, Witness}; +use subspace_core_primitives::crypto::kzg::{test_public_parameters, Kzg, Witness}; use subspace_core_primitives::crypto::{blake2b_256_254_hash, kzg}; use subspace_core_primitives::{ ArchivedBlockProgress, Blake2b256Hash, LastArchivedBlock, Piece, Randomness, RecordsRoot, @@ -230,20 +231,27 @@ pub fn make_pre_digest( Digest { logs: vec![log] } } -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestExternalities { static INITIALIZE_LOGGER: Once = Once::new(); INITIALIZE_LOGGER.call_once(|| { let _ = env_logger::try_init_from_env(env_logger::Env::new().default_filter_or("error")); }); - let mut t = frame_system::GenesisConfig::default() + let mut storage = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); - GenesisBuild::::assimilate_storage(&pallet_subspace::GenesisConfig::default(), &mut t) - .unwrap(); + GenesisBuild::::assimilate_storage( + &pallet_subspace::GenesisConfig::default(), + &mut storage, + ) + .unwrap(); + + let mut ext = TestExternalities::from(storage); + + ext.register_extension(KzgExtension::new(Kzg::new(test_public_parameters()))); - t.into() + ext } /// Creates an equivocation at the current block, by generating two headers. diff --git a/crates/pallet-subspace/src/tests.rs b/crates/pallet-subspace/src/tests.rs index 0bb662f1b7d..627fd30d4ab 100644 --- a/crates/pallet-subspace/src/tests.rs +++ b/crates/pallet-subspace/src/tests.rs @@ -31,7 +31,7 @@ use frame_support::dispatch::{GetDispatchInfo, Pays}; use frame_support::weights::Weight; use frame_support::{assert_err, assert_ok}; use frame_system::{EventRecord, Phase}; -use schnorrkel::Keypair; +use schnorrkel::{Keypair, SignatureError}; use sp_consensus_slots::Slot; use sp_consensus_subspace::{ FarmerPublicKey, FarmerSignature, GlobalRandomnesses, SolutionRanges, Vote, @@ -1003,10 +1003,10 @@ fn vote_outside_of_solution_range() { 1, ); - assert_matches!( + assert_eq!( super::check_vote::(&signed_vote, false), Err(CheckVoteError::InvalidSolution( - VerificationError::OutsideSolutionRange + VerificationError::OutsideSolutionRange.to_string() )) ); }); @@ -1055,10 +1055,11 @@ fn vote_invalid_solution_signature() { .to_bytes(), ); - assert_matches!( + assert_eq!( super::check_vote::(&signed_vote, false), Err(CheckVoteError::InvalidSolution( - VerificationError::InvalidSolutionSignature(_) + VerificationError::InvalidSolutionSignature(SignatureError::ScalarFormatError) + .to_string() )) ); }); diff --git a/crates/sc-consensus-subspace/src/lib.rs b/crates/sc-consensus-subspace/src/lib.rs index b0bd180f182..260759039e6 100644 --- a/crates/sc-consensus-subspace/src/lib.rs +++ b/crates/sc-consensus-subspace/src/lib.rs @@ -677,8 +677,8 @@ where VerificationParams { header: block.header.clone(), slot_now: slot_now + 1, - verify_solution_params: VerifySolutionParams { - global_randomness: &subspace_digest_items.global_randomness, + verify_solution_params: &VerifySolutionParams { + global_randomness: subspace_digest_items.global_randomness, solution_range: subspace_digest_items.solution_range, piece_check_params: None, }, diff --git a/crates/sc-consensus-subspace/src/slot_worker.rs b/crates/sc-consensus-subspace/src/slot_worker.rs index 9559b166797..0818f23f6b9 100644 --- a/crates/sc-consensus-subspace/src/slot_worker.rs +++ b/crates/sc-consensus-subspace/src/slot_worker.rs @@ -238,11 +238,11 @@ where let solution_verification_result = verify_solution( &solution, slot.into(), - VerifySolutionParams { - global_randomness: &global_randomness, + &VerifySolutionParams { + global_randomness, solution_range: voting_solution_range, piece_check_params: Some(PieceCheckParams { - records_root: &records_root, + records_root, pieces_in_segment: PIECES_IN_SEGMENT, }), diff --git a/crates/sp-consensus-subspace/Cargo.toml b/crates/sp-consensus-subspace/Cargo.toml index 7acd912f1fb..9d6222a85dc 100644 --- a/crates/sp-consensus-subspace/Cargo.toml +++ b/crates/sp-consensus-subspace/Cargo.toml @@ -20,13 +20,13 @@ scale-info = { version = "2.3.1", default-features = false, features = ["derive" schnorrkel = { version = "0.9.1", default-features = false, features = ["u64_backend"] } sp-api = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-application-crypto = { version = "7.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } -sp-arithmetic = { version = "6.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } -sp-consensus = { version = "0.10.0-dev", optional = true, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-consensus-slots = { version = "0.10.0-dev", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-core = { version = "7.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } +sp-externalities = { version = "0.13.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-inherents = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-io = { version = "7.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-runtime = { version = "7.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } +sp-runtime-interface = { version = "7.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-std = { version = "5.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-timestamp = { version = "4.0.0-dev", git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232", default-features = false } subspace-archiving = { version = "0.1.0", path = "../subspace-archiving", default-features = false } @@ -45,13 +45,13 @@ std = [ "schnorrkel/std", "sp-api/std", "sp-application-crypto/std", - "sp-arithmetic/std", - "sp-consensus", "sp-consensus-slots/std", "sp-core/std", + "sp-externalities/std", "sp-inherents/std", "sp-io/std", "sp-runtime/std", + "sp-runtime-interface/std", "sp-std/std", "sp-timestamp/std", "subspace-archiving/std", diff --git a/crates/sp-consensus-subspace/src/lib.rs b/crates/sp-consensus-subspace/src/lib.rs index 1d89d628c61..e854cc6f285 100644 --- a/crates/sp-consensus-subspace/src/lib.rs +++ b/crates/sp-consensus-subspace/src/lib.rs @@ -19,7 +19,7 @@ #![forbid(unsafe_code, missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -extern crate core; +extern crate alloc; pub mod digests; pub mod inherents; @@ -28,6 +28,8 @@ pub mod offence; mod tests; use crate::digests::{CompatibleDigestItem, PreDigest}; +use alloc::borrow::Cow; +use alloc::string::String; use codec::{Decode, Encode, MaxEncodedLen}; use core::num::NonZeroU64; use core::time::Duration; @@ -39,6 +41,8 @@ use sp_core::crypto::KeyTypeId; use sp_core::H256; use sp_io::hashing; use sp_runtime::{ConsensusEngineId, DigestItem}; +use sp_runtime_interface::pass_by::PassBy; +use sp_runtime_interface::{pass_by, runtime_interface}; use sp_std::vec::Vec; use subspace_core_primitives::crypto::kzg::Kzg; use subspace_core_primitives::{ @@ -335,6 +339,81 @@ impl ChainConstants { } } +/// Wrapped solution for the purposes of runtime interface. +#[derive(Debug, Encode, Decode)] +pub struct WrappedSolution(Solution); + +impl From<&Solution> for WrappedSolution { + fn from(solution: &Solution) -> Self { + Self(Solution { + public_key: solution.public_key.clone(), + reward_address: (), + sector_index: solution.sector_index, + total_pieces: solution.total_pieces, + piece_offset: solution.piece_offset, + piece_record_hash: solution.piece_record_hash, + piece_witness: solution.piece_witness, + chunk_offset: solution.chunk_offset, + chunk: solution.chunk, + chunk_signature: solution.chunk_signature, + }) + } +} + +impl PassBy for WrappedSolution { + type PassBy = pass_by::Codec; +} + +/// Wrapped solution verification parameters for the purposes of runtime interface. +#[derive(Debug, Encode, Decode)] +pub struct WrappedVerifySolutionParams<'a>(Cow<'a, VerifySolutionParams>); + +impl<'a> From<&'a VerifySolutionParams> for WrappedVerifySolutionParams<'a> { + fn from(value: &'a VerifySolutionParams) -> Self { + Self(Cow::Borrowed(value)) + } +} + +impl<'a> PassBy for WrappedVerifySolutionParams<'a> { + type PassBy = pass_by::Codec; +} + +#[cfg(feature = "std")] +sp_externalities::decl_extension! { + /// A KZG extension. + pub struct KzgExtension(Kzg); +} + +#[cfg(feature = "std")] +impl KzgExtension { + /// Create new instance. + pub fn new(kzg: Kzg) -> Self { + Self(kzg) + } +} + +/// Consensus-related runtime interface +#[runtime_interface] +pub trait Consensus { + /// Verify whether solution is valid. + fn verify_solution( + &mut self, + solution: WrappedSolution, + slot: u64, + params: WrappedVerifySolutionParams<'_>, + ) -> Result<(), String> { + use sp_externalities::ExternalitiesExt; + + let kzg = &self + .extension::() + .expect("No `KzgExtension` associated for the current context!") + .0; + + subspace_verification::verify_solution(&solution.0, slot, ¶ms.0, Some(kzg)) + .map_err(|error| error.to_string()) + } +} + sp_api::decl_runtime_apis! { /// API necessary for block authorship with Subspace. pub trait SubspaceApi { @@ -436,7 +515,7 @@ where /// The slot number of the current time. pub slot_now: Slot, /// Parameters for solution verification - pub verify_solution_params: VerifySolutionParams<'a>, + pub verify_solution_params: &'a VerifySolutionParams, /// Signing context for reward signature pub reward_signing_context: &'a SigningContext, } diff --git a/crates/sp-lightclient/src/lib.rs b/crates/sp-lightclient/src/lib.rs index a9000f9a2de..a6dcd56f0e4 100644 --- a/crates/sp-lightclient/src/lib.rs +++ b/crates/sp-lightclient/src/lib.rs @@ -23,6 +23,7 @@ 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, @@ -34,16 +35,13 @@ use sp_runtime::ArithmeticError; use sp_std::cmp::Ordering; use sp_std::collections::btree_map::BTreeMap; use sp_std::marker::PhantomData; -use subspace_core_primitives::crypto::kzg; -use subspace_core_primitives::crypto::kzg::Kzg; use subspace_core_primitives::{ BlockWeight, PublicKey, Randomness, RecordsRoot, RewardSignature, SectorId, SegmentIndex, SolutionRange, PIECES_IN_SEGMENT, }; use subspace_solving::{derive_global_challenge, REWARD_SIGNING_CONTEXT}; use subspace_verification::{ - check_reward_signature, derive_audit_chunk, verify_solution, PieceCheckParams, - VerifySolutionParams, + check_reward_signature, derive_audit_chunk, PieceCheckParams, VerifySolutionParams, }; #[cfg(test)] @@ -239,7 +237,7 @@ pub enum ImportError { /// Block signature is invalid. InvalidBlockSignature, /// Solution present in the header is invalid. - InvalidSolution(subspace_verification::Error), + InvalidSolution(String), /// Arithmetic error. ArithmeticError(ArithmeticError), /// Switched to different fork beyond archiving depth. @@ -349,21 +347,18 @@ impl> HeaderImporter { let records_root = self.find_records_root_for_segment_index(segment_index, parent_header.header.hash())?; - // TODO: Probably should have public parameters in chain constants instead - let kzg = Kzg::new(kzg::test_public_parameters()); - verify_solution( - &header_digests.pre_digest.solution, + (&header_digests.pre_digest.solution).into(), header_digests.pre_digest.slot.into(), - VerifySolutionParams { - global_randomness: &header_digests.global_randomness, + (&VerifySolutionParams { + global_randomness: header_digests.global_randomness, solution_range: header_digests.solution_range, piece_check_params: Some(PieceCheckParams { - records_root: &records_root, + records_root, pieces_in_segment: PIECES_IN_SEGMENT, }), - }, - Some(&kzg), + }) + .into(), ) .map_err(ImportError::InvalidSolution)?; diff --git a/crates/sp-lightclient/src/mock.rs b/crates/sp-lightclient/src/mock.rs index 4aa002c9767..0c18d8d4c6c 100644 --- a/crates/sp-lightclient/src/mock.rs +++ b/crates/sp-lightclient/src/mock.rs @@ -1,9 +1,12 @@ use crate::{ChainConstants, HashOf, HeaderExt, NumberOf, Storage}; use codec::{Decode, Encode}; +use frame_support::sp_io::TestExternalities; use scale_info::TypeInfo; use sp_arithmetic::traits::Zero; +use sp_consensus_subspace::KzgExtension; use sp_runtime::traits::{BlakeTwo256, Header as HeaderT}; use std::collections::{BTreeMap, HashMap}; +use subspace_core_primitives::crypto::kzg::{test_public_parameters, Kzg}; use subspace_core_primitives::{BlockWeight, RecordsRoot, SegmentIndex, SolutionRange}; pub(crate) type Header = sp_runtime::generic::Header; @@ -178,3 +181,11 @@ impl MockStorage { self.0.records_roots.insert(segment_index, records_root); } } + +pub fn new_test_ext() -> TestExternalities { + let mut ext = TestExternalities::new_empty(); + + ext.register_extension(KzgExtension::new(Kzg::new(test_public_parameters()))); + + ext +} diff --git a/crates/sp-lightclient/src/tests.rs b/crates/sp-lightclient/src/tests.rs index 569674de073..1e7e6fd8a75 100644 --- a/crates/sp-lightclient/src/tests.rs +++ b/crates/sp-lightclient/src/tests.rs @@ -1,4 +1,4 @@ -use crate::mock::{Header, MockStorage}; +use crate::mock::{new_test_ext, Header, MockStorage}; use crate::{ ChainConstants, DigestError, HashOf, HeaderExt, HeaderImporter, ImportError, NextDigestItems, NumberOf, Storage, StorageBound, @@ -480,755 +480,783 @@ fn ensure_finalized_heads_have_no_forks(store: &MockStorage, finalized_number: N #[test] fn test_header_import_missing_parent() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - let constants = default_test_constants(); - let (mut store, _genesis_hash) = initialize_store(constants, true, None); - let randomness = default_randomness(); - let (header, _, segment_index, records_root) = valid_header(ValidHeaderParams { - parent_hash: Default::default(), - number: 1, - slot: 1, - keypair: &keypair, - randomness, - farmer: &farmer, + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + let constants = default_test_constants(); + let (mut store, _genesis_hash) = initialize_store(constants, true, None); + let randomness = default_randomness(); + let (header, _, segment_index, records_root) = valid_header(ValidHeaderParams { + parent_hash: Default::default(), + number: 1, + slot: 1, + keypair: &keypair, + randomness, + farmer: &farmer, + }); + store.store_records_root(segment_index, records_root); + let mut importer = HeaderImporter::new(store); + assert_err!( + importer.import_header(header.clone()), + ImportError::MissingParent(header.hash()) + ); }); - store.store_records_root(segment_index, records_root); - let mut importer = HeaderImporter::new(store); - assert_err!( - importer.import_header(header.clone()), - ImportError::MissingParent(header.hash()) - ); } #[test] fn test_header_import_non_canonical() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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, - ); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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); + 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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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, - ); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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); + 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_chain_reorg_to_longer_chain() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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 - ); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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 - ); + 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, - ); + // 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); + // 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) + // now the finalized header must be 5 + ensure_finalized_heads_have_no_forks(&importer.store, 5); + }); } #[test] fn test_reorg_to_heavier_smaller_chain() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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 - ); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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); - 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); + 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); + 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); + // 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, segment_index, records_root) = - 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, - randomness: digests_at_2.global_randomness, - farmer: &farmer, - }); - seal_header(&keypair, &mut header); - let digests: SubspaceDigestItems = - extract_subspace_digest_items(&header).unwrap(); - let sector_id = SectorId::new( - &(&digests.pre_digest.solution.public_key).into(), - digests.pre_digest.solution.sector_index, - ); - let new_weight = - HeaderImporter::::calculate_block_weight(§or_id, &digests); - importer - .store - .override_solution_range(header_at_2.header.hash(), solution_range); - importer - .store - .store_records_root(segment_index, records_root); - importer - .store - .override_cumulative_weight(importer.store.best_header().header.hash(), new_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) + // 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, segment_index, records_root) = + 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, + randomness: digests_at_2.global_randomness, + farmer: &farmer, + }); + seal_header(&keypair, &mut header); + let digests: SubspaceDigestItems = + extract_subspace_digest_items(&header).unwrap(); + let sector_id = SectorId::new( + &(&digests.pre_digest.solution.public_key).into(), + digests.pre_digest.solution.sector_index, + ); + let new_weight = + HeaderImporter::::calculate_block_weight(§or_id, &digests); + importer + .store + .override_solution_range(header_at_2.header.hash(), solution_range); + importer + .store + .store_records_root(segment_index, records_root); + importer + .store + .override_cumulative_weight(importer.store.best_header().header.hash(), new_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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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 - ); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); + let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer); + 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, + // 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, segment_index, records_root) = + 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, + randomness: digests_at_4.global_randomness, + farmer: &farmer, + }); + seal_header(&keypair, &mut header); + importer + .store + .override_solution_range(header_at_4.header.hash(), solution_range); + importer + .store + .store_records_root(segment_index, records_root); + 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( + &PublicKey::from(&pre_digest.solution.public_key), + &pre_digest.solution.chunk.to_bytes(), + &pre_digest.solution.chunk_signature, ) .unwrap(); - let (mut header, solution_range, segment_index, records_root) = - 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, - randomness: digests_at_4.global_randomness, - farmer: &farmer, - }); - seal_header(&keypair, &mut header); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_records_root(segment_index, records_root); - 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( - &PublicKey::from(&pre_digest.solution.public_key), - &pre_digest.solution.chunk.to_bytes(), - &pre_digest.solution.chunk_signature, - ) - .unwrap(); - 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()); + 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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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 - ); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); + let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer); + 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, segment_index, records_root) = - 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, - randomness: digests_at_4.global_randomness, - farmer: &farmer, - }); - seal_header(&keypair, &mut header); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_records_root(segment_index, records_root); - 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( - u64::from(header_at_4.era_start_slot), - u64::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()); + // 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, segment_index, records_root) = + 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, + randomness: digests_at_4.global_randomness, + farmer: &farmer, + }); + seal_header(&keypair, &mut header); + importer + .store + .override_solution_range(header_at_4.header.hash(), solution_range); + importer + .store + .store_records_root(segment_index, records_root); + 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( + u64::from(header_at_4.era_start_slot), + u64::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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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 - ); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); + let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer); + 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, segment_index, records_root) = - 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, - randomness: digests_at_4.global_randomness, - farmer: &farmer, - }); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_records_root(segment_index, records_root); - 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); + // 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, segment_index, records_root) = + 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, + randomness: digests_at_4.global_randomness, + farmer: &farmer, + }); + importer + .store + .override_solution_range(header_at_4.header.hash(), solution_range); + importer + .store + .store_records_root(segment_index, records_root); + 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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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); - 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); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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 + ); - // 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, segment_index, records_root) = - 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, - randomness: digests_at_4.global_randomness, - farmer: &farmer, - }); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_records_root(segment_index, records_root); - 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( - u64::from(header_at_4.era_start_slot), - u64::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); + let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer); + 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, segment_index, records_root) = + 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, + randomness: digests_at_4.global_randomness, + farmer: &farmer, + }); + importer + .store + .override_solution_range(header_at_4.header.hash(), solution_range); + importer + .store + .store_records_root(segment_index, records_root); + 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( + u64::from(header_at_4.era_start_slot), + u64::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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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); - 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); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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 + ); - // 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, segment_index, records_root) = - 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, - randomness: digests_at_3.global_randomness, - farmer: &farmer, - }); - importer - .store - .override_solution_range(header_at_3.header.hash(), solution_range); - importer - .store - .store_records_root(segment_index, records_root); - 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) - ); + let hash_of_3 = add_headers_to_chain(&mut importer, &keypair, 3, None, &farmer); + 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, segment_index, records_root) = + 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, + randomness: digests_at_3.global_randomness, + farmer: &farmer, + }); + importer + .store + .override_solution_range(header_at_3.header.hash(), solution_range); + importer + .store + .store_records_root(segment_index, records_root); + 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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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); - 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); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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 + ); - // 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, segment_index, records_root) = - 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, - randomness: digests_at_4.global_randomness, - farmer: &farmer, - }); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_records_root(segment_index, records_root); - 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); + let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer); + 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, segment_index, records_root) = + 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, + randomness: digests_at_4.global_randomness, + farmer: &farmer, + }); + importer + .store + .override_solution_range(header_at_4.header.hash(), solution_range); + importer + .store + .store_records_root(segment_index, records_root); + 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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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 - ); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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); - assert_eq!(importer.store.best_header().header.hash(), hash_of_4); + let hash_of_4 = add_headers_to_chain(&mut importer, &keypair, 4, None, &farmer); + 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, segment_index, records_root) = - 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, - randomness: digests_at_4.global_randomness, - farmer: &farmer, - }); - importer - .store - .override_solution_range(header_at_4.header.hash(), solution_range); - importer - .store - .store_records_root(segment_index, records_root); - 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 - )) - ); + // 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, segment_index, records_root) = + 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, + randomness: digests_at_4.global_randomness, + farmer: &farmer, + }); + importer + .store + .override_solution_range(header_at_4.header.hash(), solution_range); + importer + .store + .store_records_root(segment_index, records_root); + 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
) { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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; - } + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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()); + assert!(importer.store.headers_at_number(0).is_empty()); + }); } #[test] @@ -1248,163 +1276,171 @@ fn test_storage_bound_with_headers_beyond_k_depth_is_more_than_one() { #[test] fn test_block_author_different_farmer() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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 randomness = default_randomness(); - let (mut header, solution_range, segment_index, records_root) = - valid_header(ValidHeaderParams { - parent_hash: genesis_hash, - number: 1, - slot: 1, - keypair: &keypair_disallowed, - randomness, - farmer: &farmer, - }); - seal_header(&keypair_disallowed, &mut header); - constants.genesis_digest_items.next_solution_range = solution_range; - importer.store.override_constants(constants); - importer - .store - .store_records_root(segment_index, records_root); - 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() - )) - ) + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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 randomness = default_randomness(); + let (mut header, solution_range, segment_index, records_root) = + valid_header(ValidHeaderParams { + parent_hash: genesis_hash, + number: 1, + slot: 1, + keypair: &keypair_disallowed, + randomness, + farmer: &farmer, + }); + seal_header(&keypair_disallowed, &mut header); + constants.genesis_digest_items.next_solution_range = solution_range; + importer.store.override_constants(constants); + importer + .store + .store_records_root(segment_index, records_root); + 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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); - 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); + 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 randomness = default_randomness(); - let (mut header, solution_range, segment_index, records_root) = - valid_header(ValidHeaderParams { - parent_hash: genesis_hash, - number: 1, - slot: 1, - keypair: &keypair, - randomness, - farmer: &farmer, - }); - 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_records_root(segment_index, records_root); - 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)) + // try import header with first farmer + let randomness = default_randomness(); + let (mut header, solution_range, segment_index, records_root) = + valid_header(ValidHeaderParams { + parent_hash: genesis_hash, + number: 1, + slot: 1, + keypair: &keypair, + randomness, + farmer: &farmer, + }); + 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_records_root(segment_index, records_root); + 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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); - 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); + 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 randomness = default_randomness(); - let (mut header, solution_range, segment_index, records_root) = - valid_header(ValidHeaderParams { - parent_hash: genesis_hash, - number: 1, - slot: 1, - keypair: &keypair, - randomness, - farmer: &farmer, - }); - 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_records_root(segment_index, records_root); - 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) + // try to import header authored by different farmer + let randomness = default_randomness(); + let (mut header, solution_range, segment_index, records_root) = + valid_header(ValidHeaderParams { + parent_hash: genesis_hash, + number: 1, + slot: 1, + keypair: &keypair, + randomness, + farmer: &farmer, + }); + 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_records_root(segment_index, records_root); + 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() { - let keypair = Keypair::generate(); - let farmer = Farmer::new(&keypair); - - 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 randomness = default_randomness(); - let (mut header, solution_range, segment_index, records_root) = - valid_header(ValidHeaderParams { - parent_hash: genesis_hash, - number: 1, - slot: 1, - keypair: &keypair_allowed, - randomness, - farmer: &farmer, - }); - 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_records_root(segment_index, records_root); - importer.store.override_cumulative_weight(genesis_hash, 0); - let res = importer.import_header(header); - assert_err!( - res, - ImportError::DigestError(DigestError::NextDigestVerificationError( - ErrorDigestType::RootPlotPublicKeyUpdate - )) - ) + new_test_ext().execute_with(|| { + let keypair = Keypair::generate(); + let farmer = Farmer::new(&keypair); + + 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 randomness = default_randomness(); + let (mut header, solution_range, segment_index, records_root) = + valid_header(ValidHeaderParams { + parent_hash: genesis_hash, + number: 1, + slot: 1, + keypair: &keypair_allowed, + randomness, + farmer: &farmer, + }); + 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_records_root(segment_index, records_root); + importer.store.override_cumulative_weight(genesis_hash, 0); + let res = importer.import_header(header); + assert_err!( + res, + ImportError::DigestError(DigestError::NextDigestVerificationError( + ErrorDigestType::RootPlotPublicKeyUpdate + )) + ); + }); } diff --git a/crates/subspace-core-primitives/src/crypto/kzg.rs b/crates/subspace-core-primitives/src/crypto/kzg.rs index b842095f168..6bc2e087749 100644 --- a/crates/subspace-core-primitives/src/crypto/kzg.rs +++ b/crates/subspace-core-primitives/src/crypto/kzg.rs @@ -19,7 +19,7 @@ use dusk_plonk::fft::domain::EvaluationDomain; use dusk_plonk::fft::evaluations::Evaluations; use dusk_plonk::fft::polynomial::Polynomial as PlonkPolynomial; use dusk_plonk::prelude::BlsScalar; -use parity_scale_codec::{Decode, Encode, EncodeLike, Input}; +use parity_scale_codec::{Decode, Encode, EncodeLike, Input, MaxEncodedLen}; use scale_info::{Type, TypeInfo}; const TEST_PUBLIC_PARAMETERS: &[u8] = include_bytes!("kzg/test-public-parameters.bin"); @@ -134,6 +134,12 @@ impl Encode for Commitment { impl EncodeLike for Commitment {} +impl MaxEncodedLen for Commitment { + fn max_encoded_len() -> usize { + COMMITMENT_SIZE + } +} + impl Decode for Commitment { fn decode(input: &mut I) -> Result { Self::try_from_bytes(&Decode::decode(input)?).map_err(|error| { @@ -225,6 +231,12 @@ impl Encode for Witness { impl EncodeLike for Witness {} +impl MaxEncodedLen for Witness { + fn max_encoded_len() -> usize { + COMMITMENT_SIZE + } +} + impl Decode for Witness { fn decode(input: &mut I) -> Result { Self::try_from_bytes(&Decode::decode(input)?).map_err(|error| { diff --git a/crates/subspace-core-primitives/src/lib.rs b/crates/subspace-core-primitives/src/lib.rs index 8be419308a9..9930b44ffe7 100644 --- a/crates/subspace-core-primitives/src/lib.rs +++ b/crates/subspace-core-primitives/src/lib.rs @@ -551,7 +551,7 @@ impl Solution { sector_index, total_pieces, piece_offset, - piece_record_hash: piece_commitment, + piece_record_hash, piece_witness, chunk_offset, chunk, @@ -563,7 +563,7 @@ impl Solution { sector_index, total_pieces, piece_offset, - piece_record_hash: piece_commitment, + piece_record_hash, piece_witness, chunk_offset, chunk, diff --git a/crates/subspace-farmer-components/Cargo.toml b/crates/subspace-farmer-components/Cargo.toml index b7f15fec642..fa05dd47068 100644 --- a/crates/subspace-farmer-components/Cargo.toml +++ b/crates/subspace-farmer-components/Cargo.toml @@ -32,7 +32,7 @@ subspace-solving = { version = "0.1.0", path = "../subspace-solving" } subspace-core-primitives = { version = "0.1.0", path = "../subspace-core-primitives" } subspace-verification = { version = "0.1.0", path = "../subspace-verification" } thiserror = "1.0.38" -tokio = { version = "1.25.0", features = ["macros", "parking_lot", "rt-multi-thread", "signal"] } +tokio = { version = "1.25.0", features = ["macros", "parking_lot", "rt-multi-thread", "signal", "sync"] } tracing = "0.1.37" [dev-dependencies] diff --git a/crates/subspace-node/src/lib.rs b/crates/subspace-node/src/lib.rs index 1843b057c57..86b6db13683 100644 --- a/crates/subspace-node/src/lib.rs +++ b/crates/subspace-node/src/lib.rs @@ -44,10 +44,13 @@ pub struct ExecutorDispatch; impl NativeExecutionDispatch for ExecutorDispatch { /// Only enable the benchmarking host functions when we actually want to benchmark. #[cfg(feature = "runtime-benchmarks")] - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + type ExtendHostFunctions = ( + frame_benchmarking::benchmarking::HostFunctions, + sp_consensus_subspace::consensus::HostFunctions, + ); /// Otherwise we only use the default Substrate host functions. #[cfg(not(feature = "runtime-benchmarks"))] - type ExtendHostFunctions = (); + type ExtendHostFunctions = sp_consensus_subspace::consensus::HostFunctions; fn dispatch(method: &str, data: &[u8]) -> Option> { subspace_runtime::api::dispatch(method, data) diff --git a/crates/subspace-service/Cargo.toml b/crates/subspace-service/Cargo.toml index faa5012d452..f055365528d 100644 --- a/crates/subspace-service/Cargo.toml +++ b/crates/subspace-service/Cargo.toml @@ -52,6 +52,7 @@ sp-consensus-slots = { version = "0.10.0-dev", git = "https://github.com/subspac sp-consensus-subspace = { version = "0.1.0", path = "../sp-consensus-subspace" } sp-core = { version = "7.0.0", git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-domains = { version = "0.1.0", path = "../sp-domains" } +sp-externalities = { version = "0.13.0", git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-objects = { version = "0.1.0", path = "../sp-objects" } sp-offchain = { version = "4.0.0-dev", git = "https://github.com/subspace/substrate", rev = "456cfad45a178617f6886ec400c312f2fea59232" } sp-receipts = { version = "0.1.0", path = "../sp-receipts" } diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index 8d3096e9d11..33e87d6c84f 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -36,7 +36,10 @@ use futures::StreamExt; use jsonrpsee::RpcModule; use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi; use sc_basic_authorship::ProposerFactory; -use sc_client_api::{BlockBackend, BlockchainEvents, HeaderBackend, StateBackendFor}; +use sc_client_api::execution_extensions::ExtensionsFactory; +use sc_client_api::{ + BlockBackend, BlockchainEvents, ExecutorProvider, HeaderBackend, StateBackendFor, +}; use sc_consensus::{BlockImport, DefaultImportQueue}; use sc_consensus_slots::SlotProportion; use sc_consensus_subspace::notification::SubspaceNotificationStream; @@ -56,18 +59,21 @@ use sp_block_builder::BlockBuilder; use sp_blockchain::HeaderMetadata; use sp_consensus::{Error as ConsensusError, SyncOracle}; use sp_consensus_slots::Slot; -use sp_consensus_subspace::{FarmerPublicKey, SubspaceApi}; +use sp_consensus_subspace::{FarmerPublicKey, KzgExtension, SubspaceApi}; +use sp_core::offchain; use sp_core::traits::SpawnEssentialNamed; use sp_domains::transaction::PreValidationObjectApi; use sp_domains::ExecutorApi; +use sp_externalities::Extensions; use sp_objects::ObjectsApi; use sp_offchain::OffchainWorkerApi; use sp_receipts::ReceiptsApi; -use sp_runtime::traits::{Block as BlockT, BlockIdTo}; +use sp_runtime::traits::{Block as BlockT, BlockIdTo, NumberFor}; use sp_session::SessionKeys; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; use std::num::NonZeroUsize; use std::sync::{Arc, Mutex}; +use subspace_core_primitives::crypto::kzg::{test_public_parameters, Kzg}; use subspace_core_primitives::PIECES_IN_SEGMENT; use subspace_fraud_proof::VerifyFraudProof; use subspace_networking::libp2p::multiaddr::Protocol; @@ -171,6 +177,26 @@ pub struct SubspaceConfiguration { pub sync_from_dsn: bool, } +struct SubspaceExtensionsFactory { + kzg: Kzg, +} + +impl ExtensionsFactory for SubspaceExtensionsFactory +where + Block: BlockT, +{ + fn extensions_for( + &self, + _block_hash: Block::Hash, + _block_number: NumberFor, + _capabilities: offchain::Capabilities, + ) -> Extensions { + let mut exts = Extensions::new(); + exts.register(KzgExtension::new(self.kzg.clone())); + exts + } +} + /// Creates `PartialComponents` for Subspace client. #[allow(clippy::type_complexity)] pub fn new_partial( @@ -243,6 +269,12 @@ where executor.clone(), )?; + client + .execution_extensions() + .set_extensions_factory(SubspaceExtensionsFactory { + kzg: Kzg::new(test_public_parameters()), + }); + let client = Arc::new(client); let telemetry = telemetry.map(|(worker, telemetry)| { diff --git a/crates/subspace-verification/src/lib.rs b/crates/subspace-verification/src/lib.rs index 668f9b1dc34..323db1f4f50 100644 --- a/crates/subspace-verification/src/lib.rs +++ b/crates/subspace-verification/src/lib.rs @@ -19,6 +19,7 @@ #![warn(rust_2018_idioms, missing_debug_implementations, missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] +use codec::{Decode, Encode, MaxEncodedLen}; use schnorrkel::context::SigningContext; use schnorrkel::vrf::VRFOutput; use schnorrkel::{SignatureError, SignatureResult}; @@ -113,25 +114,25 @@ pub fn is_within_solution_range( } /// Parameters for checking piece validity -#[derive(Debug)] -pub struct PieceCheckParams<'a> { +#[derive(Debug, Clone, Encode, Decode, MaxEncodedLen)] +pub struct PieceCheckParams { /// Records root of segment to which piece belongs - pub records_root: &'a RecordsRoot, + pub records_root: RecordsRoot, /// Number of pieces in a segment pub pieces_in_segment: u32, } /// Parameters for solution verification -#[derive(Debug)] -pub struct VerifySolutionParams<'a> { +#[derive(Debug, Clone, Encode, Decode, MaxEncodedLen)] +pub struct VerifySolutionParams { /// Global randomness - pub global_randomness: &'a Randomness, + pub global_randomness: Randomness, /// Solution range pub solution_range: SolutionRange, /// Parameters for checking piece validity. /// /// If `None`, piece validity check will be skipped. - pub piece_check_params: Option>, + pub piece_check_params: Option, } /// Solution verification. @@ -140,8 +141,8 @@ pub struct VerifySolutionParams<'a> { pub fn verify_solution<'a, FarmerPublicKey, RewardAddress>( solution: &'a Solution, slot: u64, - params: VerifySolutionParams<'_>, - kzg: Option<&Kzg>, + params: &'a VerifySolutionParams, + kzg: Option<&'a Kzg>, ) -> Result<(), Error> where PublicKey: From<&'a FarmerPublicKey>, @@ -164,7 +165,7 @@ where if !is_within_solution_range( local_challenge, derive_audit_chunk(&chunk_bytes), - solution_range, + *solution_range, ) { return Err(Error::OutsideSolutionRange); } @@ -187,7 +188,7 @@ where { let audit_piece_offset: PieceIndex = local_challenge % PIECES_IN_SECTOR; let piece_index = sector_id.derive_piece_index(audit_piece_offset, solution.total_pieces); - let position = u32::try_from(piece_index % u64::from(pieces_in_segment)) + let position = u32::try_from(piece_index % u64::from(*pieces_in_segment)) .expect("Position within segment always fits into u32; qed"); // TODO: Check that chunk belongs to the encoded piece @@ -197,7 +198,7 @@ where return Err(Error::MissingKzgInstance); } }; - check_piece(kzg, pieces_in_segment, records_root, position, solution)?; + check_piece(kzg, *pieces_in_segment, records_root, position, solution)?; } Ok(()) diff --git a/test/subspace-test-client/src/lib.rs b/test/subspace-test-client/src/lib.rs index 4f68ac17c1a..754314c5ad1 100644 --- a/test/subspace-test-client/src/lib.rs +++ b/test/subspace-test-client/src/lib.rs @@ -56,7 +56,7 @@ pub struct TestExecutorDispatch; impl sc_executor::NativeExecutionDispatch for TestExecutorDispatch { /// Otherwise we only use the default Substrate host functions. - type ExtendHostFunctions = (); + type ExtendHostFunctions = sp_consensus_subspace::consensus::HostFunctions; fn dispatch(method: &str, data: &[u8]) -> Option> { subspace_test_runtime::api::dispatch(method, data)