From 75b710a8f2d2738f7850c4d42a06f4324fe2f4c6 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Fri, 1 Sep 2023 11:43:07 +0300 Subject: [PATCH 1/7] Fix compilation with latest `main` changes --- crates/sp-consensus-subspace/src/lib.rs | 3 +++ crates/subspace-runtime/src/lib.rs | 8 ++++++++ test/subspace-test-runtime/src/lib.rs | 8 ++++++++ 3 files changed, 19 insertions(+) diff --git a/crates/sp-consensus-subspace/src/lib.rs b/crates/sp-consensus-subspace/src/lib.rs index 8f9ee2949d..aaeb5a1523 100644 --- a/crates/sp-consensus-subspace/src/lib.rs +++ b/crates/sp-consensus-subspace/src/lib.rs @@ -725,6 +725,9 @@ sp_api::decl_runtime_apis! { /// Returns `Vec` if a given extrinsic has them. fn extract_segment_headers(ext: &Block::Extrinsic) -> Option>; + /// Checks if the extrinsic is an inherent. + fn is_inherent(ext: &Block::Extrinsic) -> bool; + /// Returns root plot public key in case block authoring is restricted. fn root_plot_public_key() -> Option; diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 1bc86855b5..c441d56d07 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -1336,6 +1336,14 @@ impl_runtime_apis! { extract_segment_headers(ext) } + fn is_inherent(ext: &::Extrinsic) -> bool { + match &ext.function { + RuntimeCall::Subspace(call) => Subspace::is_inherent(call), + RuntimeCall::Timestamp(call) => Timestamp::is_inherent(call), + _ => false, + } + } + fn root_plot_public_key() -> Option { Subspace::root_plot_public_key() } diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 7dfc8a79f7..c0cd8abf1a 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -1635,6 +1635,14 @@ impl_runtime_apis! { extract_segment_headers(ext) } + fn is_inherent(ext: &::Extrinsic) -> bool { + match &ext.function { + RuntimeCall::Subspace(call) => Subspace::is_inherent(call), + RuntimeCall::Timestamp(call) => Timestamp::is_inherent(call), + _ => false, + } + } + fn root_plot_public_key() -> Option { Subspace::root_plot_public_key() } From c402bdd777a2da654d3d79cc68ad214ad47e35a0 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Wed, 30 Aug 2023 15:52:37 +0300 Subject: [PATCH 2/7] Replace initial key with external entropy to unify PoT key derivation everywhere, simplify proving/verifying API further, fix benches to show real numbers --- Cargo.lock | 1 + crates/pallet-subspace/src/lib.rs | 21 +++++++------------ crates/sc-proof-of-time/src/source.rs | 21 +++++++------------ crates/sp-consensus-subspace/src/digests.rs | 7 ++++--- crates/subspace-core-primitives/src/lib.rs | 11 +++++----- crates/subspace-node/Cargo.toml | 1 + crates/subspace-node/src/bin/subspace-node.rs | 18 ++++++++-------- crates/subspace-node/src/chain_spec.rs | 8 +++---- crates/subspace-node/src/lib.rs | 15 ++++++------- crates/subspace-proof-of-time/benches/pot.rs | 13 +++++------- crates/subspace-proof-of-time/src/lib.rs | 13 ++++-------- crates/subspace-service/src/lib.rs | 2 +- 12 files changed, 57 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0c2f64ffa..451e8c3704 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11482,6 +11482,7 @@ dependencies = [ "frame-benchmarking-cli", "frame-support", "futures", + "hex", "hex-literal", "log", "once_cell", diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index 146816d2e8..f38f79c1cc 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -69,12 +69,12 @@ use sp_runtime::DispatchError; use sp_std::collections::btree_map::BTreeMap; use sp_std::prelude::*; use subspace_core_primitives::crypto::Scalar; +#[cfg(feature = "pot")] +use subspace_core_primitives::PotProof; use subspace_core_primitives::{ ArchivedHistorySegment, HistorySize, PublicKey, Randomness, RewardSignature, SectorId, SectorIndex, SegmentHeader, SegmentIndex, SolutionRange, }; -#[cfg(feature = "pot")] -use subspace_core_primitives::{PotProof, PotSeed}; use subspace_solving::REWARD_SIGNING_CONTEXT; #[cfg(not(feature = "pot"))] use subspace_verification::derive_randomness; @@ -789,20 +789,13 @@ impl Pallet { fn do_initialize(block_number: T::BlockNumber) { #[cfg(feature = "pot")] - frame_system::Pallet::::deposit_log(DigestItem::future_pot_seed( - if block_number.is_one() { - PotSeed::from_genesis_block_hash( - frame_system::Pallet::::block_hash(T::BlockNumber::zero()) - .as_ref() - .try_into() - .expect("Genesis block hash length must match, panic otherwise"), - ) - } else { + if !block_number.is_one() { + frame_system::Pallet::::deposit_log(DigestItem::future_pot_seed( FuturePotSeed::::get().expect( "Is set at the end of `do_initialize` of every block after genesis; qed", - ) - }, - )); + ), + )); + } let pre_digest = >::digest() .logs diff --git a/crates/sc-proof-of-time/src/source.rs b/crates/sc-proof-of-time/src/source.rs index 005aa03cbd..a7f3409c00 100644 --- a/crates/sc-proof-of-time/src/source.rs +++ b/crates/sc-proof-of-time/src/source.rs @@ -11,7 +11,7 @@ use std::marker::PhantomData; use std::num::NonZeroU32; use std::sync::Arc; use std::thread; -use subspace_core_primitives::{BlockHash, PotCheckpoints, PotKey, PotSeed, SlotNumber}; +use subspace_core_primitives::{PotCheckpoints, PotSeed, SlotNumber}; use subspace_proof_of_time::PotError; use tracing::{debug, error}; @@ -28,12 +28,12 @@ pub struct PotSlotInfo { pub struct PotSlotInfoStream(mpsc::Receiver); /// Configuration for proof of time source -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct PotSourceConfig { /// Is this node a Timekeeper pub is_timekeeper: bool, - /// PoT key used initially when PoT chain starts - pub initial_key: PotKey, + /// External entropy, used initially when PoT chain starts to derive the first seed + pub external_entropy: Vec, } /// Source of proofs of time. @@ -51,7 +51,6 @@ pub struct PotSource { impl PotSource where Block: BlockT, - BlockHash: From, Client: ProvideRuntimeApi + HeaderBackend, Client::Api: SubspaceRuntimeApi, { @@ -62,13 +61,12 @@ where let PotSourceConfig { // TODO: Respect this boolean flag is_timekeeper: _, - initial_key, + external_entropy, } = config; let info = client.info(); // TODO: All 3 are incorrect and should be able to continue after node restart let start_slot = SlotNumber::MIN; - let start_seed = PotSeed::from_genesis_block_hash(BlockHash::from(info.genesis_hash)); - let start_key = initial_key; + let start_seed = PotSeed::from_genesis(info.genesis_hash.as_ref(), &external_entropy); #[cfg(feature = "pot")] let best_hash = info.best_hash; #[cfg(feature = "pot")] @@ -85,8 +83,7 @@ where thread::Builder::new() .name("timekeeper".to_string()) .spawn(move || { - if let Err(error) = - run_timekeeper(start_seed, start_key, start_slot, iterations, slot_sender) + if let Err(error) = run_timekeeper(start_seed, start_slot, iterations, slot_sender) { error!(%error, "Timekeeper exited with an error"); } @@ -113,16 +110,14 @@ where /// Runs timekeeper, must be running on a fast dedicated CPU core fn run_timekeeper( mut seed: PotSeed, - mut key: PotKey, mut slot: SlotNumber, iterations: NonZeroU32, mut slot_sender: mpsc::Sender, ) -> Result<(), PotError> { loop { - let checkpoints = subspace_proof_of_time::prove(seed, key, iterations)?; + let checkpoints = subspace_proof_of_time::prove(seed, iterations)?; seed = checkpoints.output().seed(); - key = seed.key(); let slot_info = PotSlotInfo { slot: Slot::from(slot), diff --git a/crates/sp-consensus-subspace/src/digests.rs b/crates/sp-consensus-subspace/src/digests.rs index 41545e2ba6..fc9944dd44 100644 --- a/crates/sp-consensus-subspace/src/digests.rs +++ b/crates/sp-consensus-subspace/src/digests.rs @@ -583,8 +583,10 @@ pub struct SubspaceDigestItems { /// Root plot public key was updated pub root_plot_public_key_update: Option>, /// Future proof of time seed, essentially output of parent block's future proof of time. + /// + /// This value is missing in block #1, but present in all blocks after that. #[cfg(feature = "pot")] - pub future_pot_seed: PotSeed, + pub future_pot_seed: Option, } /// Extract the Subspace global randomness from the given header. @@ -799,8 +801,7 @@ where enable_solution_range_adjustment_and_override: maybe_enable_and_override_solution_range, root_plot_public_key_update: maybe_root_plot_public_key_update, #[cfg(feature = "pot")] - future_pot_seed: maybe_future_pot_seed - .ok_or(Error::Missing(ErrorDigestType::FuturePotSeed))?, + future_pot_seed: maybe_future_pot_seed, }) } diff --git a/crates/subspace-core-primitives/src/lib.rs b/crates/subspace-core-primitives/src/lib.rs index 90d7958495..8a01c1cc0b 100644 --- a/crates/subspace-core-primitives/src/lib.rs +++ b/crates/subspace-core-primitives/src/lib.rs @@ -43,7 +43,8 @@ extern crate alloc; use crate::crypto::kzg::{Commitment, Witness}; use crate::crypto::{ - blake2b_256_hash, blake2b_256_hash_list, blake2b_256_hash_with_key, blake3_hash, Scalar, + blake2b_256_hash, blake2b_256_hash_list, blake2b_256_hash_with_key, blake3_hash, + blake3_hash_list, Scalar, }; #[cfg(feature = "serde")] use ::serde::{Deserialize, Serialize}; @@ -141,9 +142,6 @@ pub type SolutionRange = u64; /// The closer solution's tag is to the target, the heavier it is. pub type BlockWeight = u128; -/// Block hash (the bytes from H256) -pub type BlockHash = [u8; 32]; - // TODO: New type /// Segment commitment type. pub type SegmentCommitment = Commitment; @@ -302,9 +300,10 @@ impl PotSeed { /// Derive initial PoT seed from genesis block hash #[inline] - pub fn from_genesis_block_hash(block_hash: BlockHash) -> Self { + pub fn from_genesis(genesis_block_hash: &[u8], external_entropy: &[u8]) -> Self { + let hash = blake3_hash_list(&[genesis_block_hash, external_entropy]); let mut seed = Self::default(); - seed.copy_from_slice(&block_hash[..Self::SIZE]); + seed.copy_from_slice(&hash[..Self::SIZE]); seed } diff --git a/crates/subspace-node/Cargo.toml b/crates/subspace-node/Cargo.toml index 029251d518..636b11d463 100644 --- a/crates/subspace-node/Cargo.toml +++ b/crates/subspace-node/Cargo.toml @@ -34,6 +34,7 @@ frame-benchmarking = { version = "4.0.0-dev", git = "https://github.com/subspace frame-benchmarking-cli = { version = "4.0.0-dev", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71", default-features = false } frame-support = { version = "4.0.0-dev", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" } futures = "0.3.28" +hex = "0.4.3" hex-literal = "0.4.0" log = "0.4.19" once_cell = "1.18.0" diff --git a/crates/subspace-node/src/bin/subspace-node.rs b/crates/subspace-node/src/bin/subspace-node.rs index fb8d701889..9e2d756e60 100644 --- a/crates/subspace-node/src/bin/subspace-node.rs +++ b/crates/subspace-node/src/bin/subspace-node.rs @@ -425,10 +425,10 @@ fn main() -> Result<(), Error> { }; #[cfg(feature = "pot")] - let maybe_chain_spec_pot_initial_key = consensus_chain_config + let maybe_chain_spec_pot_external_entropy = consensus_chain_config .chain_spec .properties() - .get("potInitialKey") + .get("potExternalEntropy") .map(|d| serde_json::from_value(d.clone())) .transpose() .map_err(|error| { @@ -438,20 +438,20 @@ fn main() -> Result<(), Error> { })? .flatten(); #[cfg(feature = "pot")] - if maybe_chain_spec_pot_initial_key.is_some() - && cli.pot_initial_key.is_some() - && maybe_chain_spec_pot_initial_key != cli.pot_initial_key + if maybe_chain_spec_pot_external_entropy.is_some() + && cli.pot_external_entropy.is_some() + && maybe_chain_spec_pot_external_entropy != cli.pot_external_entropy { warn!( - "--pot-initial-key CLI argument was ignored due to chain spec having a \ - different explicit value" + "--pot-external-entropy CLI argument was ignored due to chain spec having \ + a different explicit value" ); } #[cfg(feature = "pot")] let pot_source_config = PotSourceConfig { is_timekeeper: cli.timekeeper, - initial_key: maybe_chain_spec_pot_initial_key - .or(cli.pot_initial_key) + external_entropy: maybe_chain_spec_pot_external_entropy + .or(cli.pot_external_entropy) .unwrap_or_default(), }; diff --git a/crates/subspace-node/src/chain_spec.rs b/crates/subspace-node/src/chain_spec.rs index b9eeb045a3..e645a665e7 100644 --- a/crates/subspace-node/src/chain_spec.rs +++ b/crates/subspace-node/src/chain_spec.rs @@ -173,7 +173,7 @@ pub fn gemini_3f_compiled() -> Result, String> Some({ let mut properties = chain_spec_properties(); properties.insert( - "potInitialKey".to_string(), + "potExternalEntropy".to_string(), serde_json::to_value(None::).expect("Serialization is not infallible; qed"), ); properties @@ -271,7 +271,7 @@ pub fn devnet_config_compiled() -> Result, Str Some({ let mut properties = chain_spec_properties(); properties.insert( - "potInitialKey".to_string(), + "potExternalEntropy".to_string(), serde_json::to_value(None::).expect("Serialization is not infallible; qed"), ); properties @@ -326,7 +326,7 @@ pub fn dev_config() -> Result, String> { Some({ let mut properties = chain_spec_properties(); properties.insert( - "potInitialKey".to_string(), + "potExternalEntropy".to_string(), serde_json::to_value(None::).expect("Serialization is not infallible; qed"), ); properties @@ -389,7 +389,7 @@ pub fn local_config() -> Result, String> { Some({ let mut properties = chain_spec_properties(); properties.insert( - "potInitialKey".to_string(), + "potExternalEntropy".to_string(), serde_json::to_value(None::).expect("Serialization is not infallible; qed"), ); properties diff --git a/crates/subspace-node/src/lib.rs b/crates/subspace-node/src/lib.rs index 309e484123..d1905bf243 100644 --- a/crates/subspace-node/src/lib.rs +++ b/crates/subspace-node/src/lib.rs @@ -30,8 +30,6 @@ use sc_telemetry::serde_json; use serde_json::Value; use std::io::Write; use std::{fs, io}; -#[cfg(feature = "pot")] -use subspace_core_primitives::PotKey; use subspace_networking::libp2p::Multiaddr; /// Executor dispatch for subspace runtime @@ -180,6 +178,11 @@ pub enum Subcommand { Benchmark(frame_benchmarking_cli::BenchmarkCmd), } +#[cfg(feature = "pot")] +fn parse_pot_external_entropy(s: &str) -> Result, hex::FromHexError> { + hex::decode(s) +} + /// Subspace Cli. #[derive(Debug, Parser)] #[clap( @@ -265,12 +268,10 @@ pub struct Cli { #[cfg(feature = "pot")] pub timekeeper: bool, - /// Initial PoT key (unless specified in chain spec already). - /// - /// Key is a 16-byte hex string. - #[arg(long)] + /// External entropy, used initially when PoT chain starts to derive the first seed + #[arg(long, value_parser = parse_pot_external_entropy)] #[cfg(feature = "pot")] - pub pot_initial_key: Option, + pub pot_external_entropy: Option>, } impl SubstrateCli for Cli { diff --git a/crates/subspace-proof-of-time/benches/pot.rs b/crates/subspace-proof-of-time/benches/pot.rs index fdeae1b2c5..8d43981d36 100644 --- a/crates/subspace-proof-of-time/benches/pot.rs +++ b/crates/subspace-proof-of-time/benches/pot.rs @@ -2,35 +2,32 @@ use core::num::NonZeroU32; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use rand::{thread_rng, Rng}; use std::num::NonZeroU64; -use subspace_core_primitives::{PotKey, PotSeed}; +use subspace_core_primitives::PotSeed; use subspace_proof_of_time::{prove, verify}; fn criterion_benchmark(c: &mut Criterion) { let mut seed = PotSeed::default(); thread_rng().fill(seed.as_mut()); - let mut key = PotKey::default(); - thread_rng().fill(key.as_mut()); // About 1s on 5.5 GHz Raptor Lake CPU let pot_iterations = NonZeroU32::new(183_270_000).expect("Not zero; qed"); c.bench_function("prove", |b| { b.iter(|| { - prove(black_box(seed), black_box(key), black_box(pot_iterations)).unwrap(); + black_box(prove(black_box(seed), black_box(pot_iterations))).unwrap(); }) }); - let checkpoints = prove(seed, key, pot_iterations).unwrap(); + let checkpoints = prove(seed, pot_iterations).unwrap(); let pot_iterations = NonZeroU64::from(pot_iterations); c.bench_function("verify", |b| { b.iter(|| { - verify( + black_box(verify( black_box(seed), - black_box(key), black_box(pot_iterations), black_box(&*checkpoints), - ) + )) .unwrap(); }) }); diff --git a/crates/subspace-proof-of-time/src/lib.rs b/crates/subspace-proof-of-time/src/lib.rs index 3397577b29..17cc641c89 100644 --- a/crates/subspace-proof-of-time/src/lib.rs +++ b/crates/subspace-proof-of-time/src/lib.rs @@ -4,7 +4,7 @@ mod aes; use core::num::{NonZeroU32, NonZeroU64}; -use subspace_core_primitives::{PotCheckpoints, PotKey, PotProof, PotSeed}; +use subspace_core_primitives::{PotCheckpoints, PotProof, PotSeed}; /// Proof of time error #[derive(Debug)] @@ -30,11 +30,7 @@ pub enum PotError { /// /// Returns error if `iterations` is not a multiple of checkpoints times two. #[inline] -pub fn prove( - seed: PotSeed, - key: PotKey, - iterations: NonZeroU32, -) -> Result { +pub fn prove(seed: PotSeed, iterations: NonZeroU32) -> Result { if iterations.get() % u32::from(PotCheckpoints::NUM_CHECKPOINTS.get() * 2) != 0 { return Err(PotError::NotMultipleOfCheckpoints { iterations: NonZeroU64::from(iterations), @@ -44,7 +40,7 @@ pub fn prove( Ok(aes::create( seed, - key, + seed.key(), iterations.get() / u32::from(PotCheckpoints::NUM_CHECKPOINTS.get()), )) } @@ -55,7 +51,6 @@ pub fn prove( #[inline] pub fn verify( seed: PotSeed, - key: PotKey, iterations: NonZeroU64, checkpoints: &[PotProof], ) -> Result { @@ -69,7 +64,7 @@ pub fn verify( Ok(aes::verify_sequential( seed, - key, + seed.key(), checkpoints, iterations.get() / num_checkpoints, )) diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index e3001c50fc..6f1ce03b0c 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -765,7 +765,7 @@ where if config.role.is_authority() || config.force_new_slot_notifications { #[cfg(feature = "pot")] let (pot_source, pot_slot_info_stream) = - PotSource::new(config.pot_source_config, client.clone()) + PotSource::new(config.pot_source_config.clone(), client.clone()) .map_err(|error| Error::Other(error.into()))?; #[cfg(feature = "pot")] { From ab6764f043895c72fde87503482d6b738efd2887 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Thu, 31 Aug 2023 15:02:49 +0300 Subject: [PATCH 3/7] Small refactoring --- crates/pallet-subspace/src/lib.rs | 2 +- crates/sc-consensus-subspace/src/lib.rs | 41 +++++++++++-------- .../sc-consensus-subspace/src/slot_worker.rs | 26 ++++++------ crates/sc-proof-of-time/src/source.rs | 13 +++--- crates/sp-consensus-subspace/src/lib.rs | 10 ++--- 5 files changed, 52 insertions(+), 40 deletions(-) diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index f38f79c1cc..ec3494f2e6 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -1098,7 +1098,7 @@ impl Pallet { #[cfg(feature = "pot")] pub fn pot_parameters() -> PotParameters { PotParameters::V0 { - iterations: PotSlotIterations::::get() + slot_iterations: PotSlotIterations::::get() .expect("Always instantiated during genesis; qed"), // TODO: This is where adjustment for number of iterations and entropy injection will // happen for runtime API calls diff --git a/crates/sc-consensus-subspace/src/lib.rs b/crates/sc-consensus-subspace/src/lib.rs index 545dc70adc..a5586dc042 100644 --- a/crates/sc-consensus-subspace/src/lib.rs +++ b/crates/sc-consensus-subspace/src/lib.rs @@ -459,6 +459,8 @@ where block_proposal_slot_portion, max_block_proposal_slot_portion, telemetry, + chain_constants: get_chain_constants(client.as_ref()) + .map_err(|error| sp_consensus::Error::Other(error.into()))?, segment_headers_store, #[cfg(feature = "pot")] pending_solutions: Default::default(), @@ -588,6 +590,7 @@ where SubspaceNotificationSender>, subspace_link: SubspaceLink, create_inherent_data_providers: CIDP, + chain_constants: ChainConstants, segment_headers_store: SegmentHeadersStore, _pos_table: PhantomData, } @@ -606,6 +609,7 @@ where block_importing_notification_sender: self.block_importing_notification_sender.clone(), subspace_link: self.subspace_link.clone(), create_inherent_data_providers: self.create_inherent_data_providers.clone(), + chain_constants: self.chain_constants, segment_headers_store: self.segment_headers_store.clone(), _pos_table: PhantomData, } @@ -630,14 +634,16 @@ where >, subspace_link: SubspaceLink, create_inherent_data_providers: CIDP, + chain_constants: ChainConstants, segment_headers_store: SegmentHeadersStore, ) -> Self { - SubspaceBlockImport { + Self { client, inner: block_import, block_importing_notification_sender, subspace_link, create_inherent_data_providers, + chain_constants, segment_headers_store, _pos_table: PhantomData, } @@ -757,7 +763,6 @@ where pre_digest.solution().sector_index, ); - let chain_constants = get_chain_constants(self.client.as_ref())?; // TODO: Below `skip_runtime_access` has no impact on this, but ideally it // should (though we don't support fast sync yet, so doesn't matter in // practice) @@ -769,8 +774,8 @@ where pre_digest.solution().piece_offset, pre_digest.solution().history_size, max_pieces_in_sector, - chain_constants.recent_segments(), - chain_constants.recent_history_fraction(), + self.chain_constants.recent_segments(), + self.chain_constants.recent_history_fraction(), ); let segment_index = piece_index.segment_index(); @@ -787,7 +792,7 @@ where .pre_digest .solution() .history_size - .sector_expiration_check(chain_constants.min_sector_lifetime()) + .sector_expiration_check(self.chain_constants.min_sector_lifetime()) .ok_or(Error::InvalidHistorySize)? .segment_index(), ) @@ -808,9 +813,9 @@ where piece_check_params: Some(PieceCheckParams { max_pieces_in_sector, segment_commitment, - recent_segments: chain_constants.recent_segments(), - recent_history_fraction: chain_constants.recent_history_fraction(), - min_sector_lifetime: chain_constants.min_sector_lifetime(), + recent_segments: self.chain_constants.recent_segments(), + recent_history_fraction: self.chain_constants.recent_history_fraction(), + min_sector_lifetime: self.chain_constants.min_sector_lifetime(), // TODO: Below `skip_runtime_access` has no impact on this, but ideally it // should (though we don't support fast sync yet, so doesn't matter in // practice) @@ -1075,10 +1080,13 @@ pub fn block_import( kzg: Kzg, create_inherent_data_providers: CIDP, segment_headers_store: SegmentHeadersStore, -) -> ClientResult<( - SubspaceBlockImport, - SubspaceLink, -)> +) -> Result< + ( + SubspaceBlockImport, + SubspaceLink, + ), + sp_blockchain::Error, +> where PosTable: Table, Block: BlockT, @@ -1097,9 +1105,8 @@ where let (block_importing_notification_sender, block_importing_notification_stream) = notification::channel("subspace_block_importing_notification_stream"); - let confirmation_depth_k = get_chain_constants(client.as_ref()) - .expect("Must always be able to get chain constants") - .confirmation_depth_k(); + let chain_constants = get_chain_constants(client.as_ref()) + .map_err(|error| sp_blockchain::Error::Application(error.into()))?; let link = SubspaceLink { slot_duration, @@ -1114,7 +1121,8 @@ where // TODO: Consider making `confirmation_depth_k` non-zero segment_headers: Arc::new(Mutex::new(LruCache::new( NonZeroUsize::new( - (FINALIZATION_DEPTH_IN_SEGMENTS + 1).max(confirmation_depth_k as usize), + (FINALIZATION_DEPTH_IN_SEGMENTS + 1) + .max(chain_constants.confirmation_depth_k() as usize), ) .expect("Confirmation depth of zero is not supported"), ))), @@ -1127,6 +1135,7 @@ where block_importing_notification_sender, link.clone(), create_inherent_data_providers, + chain_constants, segment_headers_store, ); diff --git a/crates/sc-consensus-subspace/src/slot_worker.rs b/crates/sc-consensus-subspace/src/slot_worker.rs index e71912bda1..f268853cc0 100644 --- a/crates/sc-consensus-subspace/src/slot_worker.rs +++ b/crates/sc-consensus-subspace/src/slot_worker.rs @@ -17,8 +17,8 @@ use crate::archiver::SegmentHeadersStore; use crate::{ - get_chain_constants, BlockImportingNotification, NewSlotInfo, NewSlotNotification, - RewardSigningNotification, SubspaceLink, + BlockImportingNotification, NewSlotInfo, NewSlotNotification, RewardSigningNotification, + SubspaceLink, }; use futures::channel::mpsc; use futures::{StreamExt, TryFutureExt}; @@ -45,7 +45,9 @@ use sp_consensus_subspace::digests::PreDigestPotInfo; use sp_consensus_subspace::digests::{extract_pre_digest, CompatibleDigestItem, PreDigest}; #[cfg(feature = "pot")] use sp_consensus_subspace::SubspaceJustification; -use sp_consensus_subspace::{FarmerPublicKey, FarmerSignature, SignedVote, SubspaceApi, Vote}; +use sp_consensus_subspace::{ + ChainConstants, FarmerPublicKey, FarmerSignature, SignedVote, SubspaceApi, Vote, +}; use sp_core::crypto::ByteArray; use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Header, One, Saturating, Zero}; @@ -126,6 +128,7 @@ where pub(super) block_proposal_slot_portion: SlotProportion, pub(super) max_block_proposal_slot_portion: Option, pub(super) telemetry: Option, + pub(super) chain_constants: ChainConstants, pub(super) segment_headers_store: SegmentHeadersStore, /// Solution receivers for challenges that were sent to farmers and expected to be received /// eventually @@ -266,7 +269,6 @@ where slot: Slot, _aux_data: &Self::AuxData, ) -> Option { - let chain_constants = get_chain_constants(self.client.as_ref()).ok()?; let parent_pre_digest = match extract_pre_digest(parent_header) { Ok(pre_digest) => pre_digest, Err(error) => { @@ -309,8 +311,8 @@ where let proof_of_time = pot_checkpoints.get(&slot)?.output(); // Future slot for which proof must be available before authoring block at this slot - let future_slot = slot + chain_constants.block_authoring_delay(); - let parent_future_slot = parent_slot + chain_constants.block_authoring_delay(); + let future_slot = slot + self.chain_constants.block_authoring_delay(); + let parent_future_slot = parent_slot + self.chain_constants.block_authoring_delay(); let future_proof_of_time = pot_checkpoints.get(&future_slot)?.output(); // New checkpoints that were produced since parent block's future slot up to current @@ -403,8 +405,8 @@ where solution.piece_offset, solution.history_size, max_pieces_in_sector, - chain_constants.recent_segments(), - chain_constants.recent_history_fraction(), + self.chain_constants.recent_segments(), + self.chain_constants.recent_history_fraction(), ) .segment_index(); let maybe_segment_commitment = self @@ -426,7 +428,7 @@ where }; let sector_expiration_check_segment_index = match solution .history_size - .sector_expiration_check(chain_constants.min_sector_lifetime()) + .sector_expiration_check(self.chain_constants.min_sector_lifetime()) { Some(sector_expiration_check) => sector_expiration_check.segment_index(), None => { @@ -449,9 +451,9 @@ where piece_check_params: Some(PieceCheckParams { max_pieces_in_sector, segment_commitment, - recent_segments: chain_constants.recent_segments(), - recent_history_fraction: chain_constants.recent_history_fraction(), - min_sector_lifetime: chain_constants.min_sector_lifetime(), + recent_segments: self.chain_constants.recent_segments(), + recent_history_fraction: self.chain_constants.recent_history_fraction(), + min_sector_lifetime: self.chain_constants.min_sector_lifetime(), current_history_size: history_size, sector_expiration_check_segment_commitment, }), diff --git a/crates/sc-proof-of-time/src/source.rs b/crates/sc-proof-of-time/src/source.rs index a7f3409c00..a46bbeb717 100644 --- a/crates/sc-proof-of-time/src/source.rs +++ b/crates/sc-proof-of-time/src/source.rs @@ -72,18 +72,19 @@ where #[cfg(feature = "pot")] let runtime_api = client.runtime_api(); #[cfg(feature = "pot")] - let iterations = runtime_api + let slot_iterations = runtime_api .pot_parameters(best_hash)? - .iterations(Slot::from(start_slot)); + .slot_iterations(Slot::from(start_slot)); #[cfg(not(feature = "pot"))] - let iterations = NonZeroU32::new(100_000_000).expect("Not zero; qed"); + let slot_iterations = NonZeroU32::new(100_000_000).expect("Not zero; qed"); // TODO: Correct capacity let (slot_sender, slot_receiver) = mpsc::channel(10); thread::Builder::new() .name("timekeeper".to_string()) .spawn(move || { - if let Err(error) = run_timekeeper(start_seed, start_slot, iterations, slot_sender) + if let Err(error) = + run_timekeeper(start_seed, start_slot, slot_iterations, slot_sender) { error!(%error, "Timekeeper exited with an error"); } @@ -111,11 +112,11 @@ where fn run_timekeeper( mut seed: PotSeed, mut slot: SlotNumber, - iterations: NonZeroU32, + slot_iterations: NonZeroU32, mut slot_sender: mpsc::Sender, ) -> Result<(), PotError> { loop { - let checkpoints = subspace_proof_of_time::prove(seed, iterations)?; + let checkpoints = subspace_proof_of_time::prove(seed, slot_iterations)?; seed = checkpoints.output().seed(); diff --git a/crates/sp-consensus-subspace/src/lib.rs b/crates/sp-consensus-subspace/src/lib.rs index aaeb5a1523..193e96de20 100644 --- a/crates/sp-consensus-subspace/src/lib.rs +++ b/crates/sp-consensus-subspace/src/lib.rs @@ -651,8 +651,8 @@ sp_api::decl_runtime_apis! { pub enum PotParameters { /// Initial version of the parameters V0 { - /// Base number of iterations - iterations: NonZeroU32, + /// Base number of iterations per slot + slot_iterations: NonZeroU32, /// Optional next scheduled change of parameters next_change: Option, }, @@ -661,9 +661,9 @@ pub enum PotParameters { #[cfg(feature = "pot")] impl PotParameters { /// Number of iterations for proof of time per slot, taking into account potential future change - pub fn iterations(&self, slot: Slot) -> NonZeroU32 { + pub fn slot_iterations(&self, slot: Slot) -> NonZeroU32 { let Self::V0 { - iterations, + slot_iterations, next_change, } = self; @@ -673,7 +673,7 @@ impl PotParameters { } } - *iterations + *slot_iterations } } From 2b45e15fb7640be83ed791c256c052a8ed098f70 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Thu, 31 Aug 2023 13:56:50 +0300 Subject: [PATCH 4/7] Remove unnecessary `FuturePotSeed` consensus log item --- crates/pallet-subspace/src/lib.rs | 19 +------ crates/sp-consensus-subspace/src/digests.rs | 57 +-------------------- crates/sp-consensus-subspace/src/lib.rs | 8 +-- 3 files changed, 5 insertions(+), 79 deletions(-) diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index ec3494f2e6..b36663397f 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -154,7 +154,7 @@ mod pallet { use sp_std::prelude::*; use subspace_core_primitives::crypto::Scalar; use subspace_core_primitives::{ - HistorySize, PotSeed, Randomness, SectorIndex, SegmentHeader, SegmentIndex, SolutionRange, + HistorySize, Randomness, SectorIndex, SegmentHeader, SegmentIndex, SolutionRange, }; pub(super) struct InitialSolutionRanges { @@ -490,10 +490,6 @@ mod pallet { #[pallet::getter(fn root_plot_public_key)] pub(super) type RootPlotPublicKey = StorageValue<_, FarmerPublicKey>; - /// Future proof of time seed, essentially output of parent block's future proof of time. - #[pallet::storage] - pub(super) type FuturePotSeed = StorageValue<_, PotSeed>; - #[pallet::hooks] impl Hooks for Pallet { fn on_initialize(block_number: T::BlockNumber) -> Weight { @@ -788,15 +784,6 @@ impl Pallet { } fn do_initialize(block_number: T::BlockNumber) { - #[cfg(feature = "pot")] - if !block_number.is_one() { - frame_system::Pallet::::deposit_log(DigestItem::future_pot_seed( - FuturePotSeed::::get().expect( - "Is set at the end of `do_initialize` of every block after genesis; qed", - ), - )); - } - let pre_digest = >::digest() .logs .iter() @@ -937,10 +924,6 @@ impl Pallet { frame_system::Pallet::::deposit_log(DigestItem::pot_slot_iterations( PotSlotIterations::::get().expect("Always instantiated during genesis; qed"), )); - // TODO: Once we have entropy injection, it might take effect right here and should be - // accounted for - #[cfg(feature = "pot")] - FuturePotSeed::::put(pre_digest.pot_info().future_proof_of_time().seed()); } fn do_finalize(_block_number: T::BlockNumber) { diff --git a/crates/sp-consensus-subspace/src/digests.rs b/crates/sp-consensus-subspace/src/digests.rs index fc9944dd44..f976d596ba 100644 --- a/crates/sp-consensus-subspace/src/digests.rs +++ b/crates/sp-consensus-subspace/src/digests.rs @@ -30,10 +30,10 @@ use sp_std::collections::btree_map::{BTreeMap, Entry}; use sp_std::fmt; #[cfg(feature = "pot")] use sp_std::num::NonZeroU32; +#[cfg(feature = "pot")] +use subspace_core_primitives::PotProof; #[cfg(not(feature = "pot"))] use subspace_core_primitives::Randomness; -#[cfg(feature = "pot")] -use subspace_core_primitives::{PotProof, PotSeed}; use subspace_core_primitives::{SegmentCommitment, SegmentIndex, Solution, SolutionRange}; #[cfg(not(feature = "pot"))] use subspace_verification::derive_randomness; @@ -201,15 +201,6 @@ pub trait CompatibleDigestItem: Sized { /// If this item is a Subspace update of root plot public key, return it. fn as_root_plot_public_key_update(&self) -> Option>; - - /// Construct a digest item which contains future proof of time seed, essentially output of - /// parent block's future proof of time. - #[cfg(feature = "pot")] - fn future_pot_seed(pot_seed: PotSeed) -> Self; - - /// If this item is a Subspace future proof of time seed, return it. - #[cfg(feature = "pot")] - fn as_future_pot_seed(&self) -> Option; } impl CompatibleDigestItem for DigestItem { @@ -402,25 +393,6 @@ impl CompatibleDigestItem for DigestItem { } }) } - - #[cfg(feature = "pot")] - fn future_pot_seed(pot_seed: PotSeed) -> Self { - Self::Consensus( - SUBSPACE_ENGINE_ID, - ConsensusLog::FuturePotSeed(pot_seed).encode(), - ) - } - - #[cfg(feature = "pot")] - fn as_future_pot_seed(&self) -> Option { - self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| { - if let ConsensusLog::FuturePotSeed(pot_seed) = c { - Some(pot_seed) - } else { - None - } - }) - } } /// Various kinds of digest types used in errors @@ -454,9 +426,6 @@ pub enum ErrorDigestType { EnableSolutionRangeAdjustmentAndOverride, /// Root plot public key was updated RootPlotPublicKeyUpdate, - /// Future proof of time seed, essentially output of parent block's future proof of time. - #[cfg(feature = "pot")] - FuturePotSeed, } impl fmt::Display for ErrorDigestType { @@ -502,10 +471,6 @@ impl fmt::Display for ErrorDigestType { ErrorDigestType::RootPlotPublicKeyUpdate => { write!(f, "RootPlotPublicKeyUpdate") } - #[cfg(feature = "pot")] - ErrorDigestType::FuturePotSeed => { - write!(f, "FuturePotSeed") - } } } } @@ -582,11 +547,6 @@ pub struct SubspaceDigestItems { pub enable_solution_range_adjustment_and_override: Option>, /// Root plot public key was updated pub root_plot_public_key_update: Option>, - /// Future proof of time seed, essentially output of parent block's future proof of time. - /// - /// This value is missing in block #1, but present in all blocks after that. - #[cfg(feature = "pot")] - pub future_pot_seed: Option, } /// Extract the Subspace global randomness from the given header. @@ -614,8 +574,6 @@ where let mut segment_commitments = BTreeMap::new(); let mut maybe_enable_and_override_solution_range = None; let mut maybe_root_plot_public_key_update = None; - #[cfg(feature = "pot")] - let mut maybe_future_pot_seed = None; for log in header.digest().logs() { match log { @@ -744,15 +702,6 @@ where } } } - #[cfg(feature = "pot")] - ConsensusLog::FuturePotSeed(pot_seed) => match maybe_future_pot_seed { - Some(_) => { - return Err(Error::Duplicate(ErrorDigestType::FuturePotSeed)); - } - None => { - maybe_future_pot_seed.replace(pot_seed); - } - }, } } DigestItem::Seal(id, data) => { @@ -800,8 +749,6 @@ where segment_commitments, enable_solution_range_adjustment_and_override: maybe_enable_and_override_solution_range, root_plot_public_key_update: maybe_root_plot_public_key_update, - #[cfg(feature = "pot")] - future_pot_seed: maybe_future_pot_seed, }) } diff --git a/crates/sp-consensus-subspace/src/lib.rs b/crates/sp-consensus-subspace/src/lib.rs index 193e96de20..6e04e6c8e4 100644 --- a/crates/sp-consensus-subspace/src/lib.rs +++ b/crates/sp-consensus-subspace/src/lib.rs @@ -44,10 +44,10 @@ use sp_runtime_interface::{pass_by, runtime_interface}; use sp_std::num::NonZeroU32; use sp_std::vec::Vec; use subspace_core_primitives::crypto::kzg::Kzg; +#[cfg(feature = "pot")] +use subspace_core_primitives::Blake2b256Hash; #[cfg(not(feature = "pot"))] use subspace_core_primitives::Randomness; -#[cfg(feature = "pot")] -use subspace_core_primitives::{Blake2b256Hash, PotSeed}; use subspace_core_primitives::{ BlockNumber, HistorySize, PotCheckpoints, PublicKey, RewardSignature, SegmentCommitment, SegmentHeader, SegmentIndex, Solution, SolutionRange, PUBLIC_KEY_LENGTH, @@ -181,10 +181,6 @@ enum ConsensusLog { /// Root plot public key was updated. #[codec(index = 6)] RootPlotPublicKeyUpdate(Option), - /// Future proof of time seed, essentially output of parent block's future proof of time. - #[codec(index = 7)] - #[cfg(feature = "pot")] - FuturePotSeed(PotSeed), } /// Farmer vote. From 36a63dacbdaecd9179fb3fe2db7af546dd5d2ddd Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Wed, 30 Aug 2023 03:54:05 +0300 Subject: [PATCH 5/7] Prepare for PoT verification, remove irrelevant checks --- .../sc-consensus-subspace/src/import_queue.rs | 29 ++++++++++++------- crates/sc-consensus-subspace/src/lib.rs | 1 + crates/subspace-service/src/lib.rs | 1 + 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/crates/sc-consensus-subspace/src/import_queue.rs b/crates/sc-consensus-subspace/src/import_queue.rs index 9f28340eff..8491fcc0e0 100644 --- a/crates/sc-consensus-subspace/src/import_queue.rs +++ b/crates/sc-consensus-subspace/src/import_queue.rs @@ -9,7 +9,9 @@ use sc_consensus::import_queue::{ BasicQueue, BoxJustificationImport, DefaultImportQueue, Verifier, }; use sc_consensus_slots::check_equivocation; -use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE}; +#[cfg(not(feature = "pot"))] +use sc_telemetry::CONSENSUS_DEBUG; +use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_TRACE}; use schnorrkel::context::SigningContext; use sp_api::{ApiExt, BlockT, HeaderT, ProvideRuntimeApi, TransactionFor}; use sp_block_builder::BlockBuilder as BlockBuilderApi; @@ -109,6 +111,7 @@ pub enum VerificationError { enum CheckedHeader { /// A header which has slot in the future. this is the full header (not stripped) /// and the slot in which it should be processed. + #[cfg(not(feature = "pot"))] Deferred(H, Slot), /// A header which is fully checked, including signature. This is the pre-header /// accompanied by the seal components. @@ -125,6 +128,8 @@ where /// The header being verified. header: Header, /// The slot number of the current time. + // TODO: Remove field once PoT is the only option + #[cfg(not(feature = "pot"))] slot_now: Slot, /// Parameters for solution verification verify_solution_params: &'a VerifySolutionParams, @@ -145,6 +150,7 @@ struct SubspaceVerifier { client: Arc, kzg: Kzg, select_chain: SelectChain, + // TODO: Remove field once PoT is the only option slot_now: SN, telemetry: Option, reward_signing_context: SigningContext, @@ -173,14 +179,15 @@ where /// /// `pre_digest` argument is optional in case it is available to avoid doing the work of /// extracting it from the header twice. - fn check_header( + async fn check_header( &self, - params: VerificationParams, + params: VerificationParams<'_, Block::Header>, pre_digest: PreDigest, ) -> Result, VerificationError> { let VerificationParams { mut header, + #[cfg(not(feature = "pot"))] slot_now, verify_solution_params, reward_signing_context, @@ -200,11 +207,14 @@ where // The pre-hash of the header doesn't include the seal and that's what we sign let pre_hash = header.hash(); + #[cfg(not(feature = "pot"))] if slot > slot_now { header.digest_mut().push(seal); return Ok(CheckedHeader::Deferred(header, slot)); } + // TODO: PoT verification here + // Verify that block is signed properly if check_reward_signature( pre_hash.as_ref(), @@ -365,13 +375,11 @@ where // from the header are checked against expected correct values during block import as well // as whether piece in the header corresponds to the actual archival history of the // blockchain. - let checked_header = { - // We add one to the current slot to allow for some small drift. - // FIXME https://github.com/paritytech/substrate/issues/1019 in the future, alter this - // queue to allow deferring of headers - self.check_header( + let checked_header = self + .check_header( VerificationParams { header: block.header.clone(), + #[cfg(not(feature = "pot"))] slot_now: slot_now + 1, verify_solution_params: &VerifySolutionParams { #[cfg(not(feature = "pot"))] @@ -385,8 +393,8 @@ where }, pre_digest, ) - .map_err(Error::::from)? - }; + .await + .map_err(Error::::from)?; match checked_header { CheckedHeader::Checked(pre_header, verified_info) => { @@ -426,6 +434,7 @@ where Ok(block) } + #[cfg(not(feature = "pot"))] CheckedHeader::Deferred(a, b) => { debug!(target: "subspace", "Checking {:?} failed; {:?}, {:?}.", hash, a, b); telemetry!( diff --git a/crates/sc-consensus-subspace/src/lib.rs b/crates/sc-consensus-subspace/src/lib.rs index a5586dc042..2d84683252 100644 --- a/crates/sc-consensus-subspace/src/lib.rs +++ b/crates/sc-consensus-subspace/src/lib.rs @@ -149,6 +149,7 @@ pub enum Error { DigestItemError(#[from] DigestError), /// Header rejected: too far in the future #[error("Header {0:?} rejected: too far in the future")] + #[cfg(not(feature = "pot"))] TooFarInFuture(Header::Hash), /// Parent unavailable. Cannot import #[error("Parent ({0}) of {1} unavailable. Cannot import")] diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index 6f1ce03b0c..3b1e403476 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -428,6 +428,7 @@ where client.clone(), kzg, select_chain.clone(), + // TODO: Remove use current best slot known from PoT verifier in PoT case move || { let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); From bfee50647e4779e2b3990bef0179d44c819f501d Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Thu, 31 Aug 2023 15:02:53 +0300 Subject: [PATCH 6/7] Basic PoT verification --- Cargo.lock | 44 ++- crates/pallet-subspace/src/lib.rs | 1 + crates/sc-consensus-subspace-rpc/Cargo.toml | 2 +- crates/sc-consensus-subspace/Cargo.toml | 2 +- .../sc-consensus-subspace/src/import_queue.rs | 87 ++++-- crates/sc-consensus-subspace/src/lib.rs | 78 +++++- crates/sc-proof-of-time/Cargo.toml | 3 + crates/sc-proof-of-time/src/lib.rs | 1 + crates/sc-proof-of-time/src/source.rs | 93 +++--- crates/sc-proof-of-time/src/verifier.rs | 265 ++++++++++++++++++ crates/sc-proof-of-time/src/verifier/tests.rs | 105 +++++++ crates/sc-subspace-block-relay/Cargo.toml | 2 +- crates/subspace-core-primitives/src/lib.rs | 19 ++ crates/subspace-farmer-components/Cargo.toml | 2 +- crates/subspace-farmer/Cargo.toml | 2 +- crates/subspace-networking/Cargo.toml | 2 +- crates/subspace-node/src/bin/subspace-node.rs | 104 ++++--- crates/subspace-proof-of-time/benches/pot.rs | 3 - crates/subspace-proof-of-time/src/aes.rs | 4 +- crates/subspace-proof-of-time/src/lib.rs | 14 +- crates/subspace-service/src/lib.rs | 37 ++- 21 files changed, 728 insertions(+), 142 deletions(-) create mode 100644 crates/sc-proof-of-time/src/verifier.rs create mode 100644 crates/sc-proof-of-time/src/verifier/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 451e8c3704..c3f1208cca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,6 +375,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -646,9 +652,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener", ] @@ -4212,6 +4218,16 @@ dependencies = [ "ahash 0.8.3", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] + [[package]] name = "heck" version = "0.4.1" @@ -6015,6 +6031,15 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "lru" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eedb2bdbad7e0634f83989bf596f497b070130daaa398ab22d84c39e266deec5" +dependencies = [ + "hashbrown 0.14.0", +] + [[package]] name = "lru-cache" version = "0.1.2" @@ -9009,7 +9034,7 @@ dependencies = [ "futures", "futures-timer", "log", - "lru 0.10.0", + "lru 0.11.0", "parity-scale-codec", "parking_lot 0.12.1", "rand 0.8.5", @@ -9051,7 +9076,7 @@ dependencies = [ "futures", "futures-timer", "jsonrpsee", - "lru 0.10.0", + "lru 0.11.0", "parity-scale-codec", "parking_lot 0.12.1", "sc-client-api", @@ -9378,11 +9403,14 @@ dependencies = [ name = "sc-proof-of-time" version = "0.1.0" dependencies = [ + "async-lock", "async-trait", "derive_more", "futures", + "lru 0.11.0", "parity-scale-codec", "parking_lot 0.12.1", + "rayon", "sc-client-api", "sc-consensus-slots", "sc-network", @@ -9602,7 +9630,7 @@ dependencies = [ "async-channel", "async-trait", "futures", - "lru 0.10.0", + "lru 0.11.0", "parity-scale-codec", "parking_lot 0.12.1", "sc-client-api", @@ -11322,7 +11350,7 @@ dependencies = [ "hex", "jemallocator", "jsonrpsee", - "lru 0.10.0", + "lru 0.11.0", "memmap2 0.7.1", "parity-db", "parity-scale-codec", @@ -11366,7 +11394,7 @@ dependencies = [ "futures", "hex", "libc", - "lru 0.10.0", + "lru 0.11.0", "memmap2 0.7.1", "parity-scale-codec", "parking_lot 0.12.1", @@ -11444,7 +11472,7 @@ dependencies = [ "libp2p-kad 0.44.4", "libp2p-quic 0.8.0-alpha", "libp2p-swarm-test", - "lru 0.10.0", + "lru 0.11.0", "memmap2 0.7.1", "nohash-hasher", "parity-scale-codec", diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index b36663397f..a8ff3940e1 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -920,6 +920,7 @@ impl Pallet { )); } + // TODO: Take adjustment of iterations into account once we have it #[cfg(feature = "pot")] frame_system::Pallet::::deposit_log(DigestItem::pot_slot_iterations( PotSlotIterations::::get().expect("Always instantiated during genesis; qed"), diff --git a/crates/sc-consensus-subspace-rpc/Cargo.toml b/crates/sc-consensus-subspace-rpc/Cargo.toml index 1e2f566986..a47528ad57 100644 --- a/crates/sc-consensus-subspace-rpc/Cargo.toml +++ b/crates/sc-consensus-subspace-rpc/Cargo.toml @@ -17,7 +17,7 @@ async-oneshot = "0.5.0" futures = "0.3.28" futures-timer = "3.0.2" jsonrpsee = { version = "0.16.2", features = ["server", "macros"] } -lru = "0.10.0" +lru = "0.11.0" parity-scale-codec = "3.6.5" parking_lot = "0.12.1" sc-client-api = { version = "4.0.0-dev", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" } diff --git a/crates/sc-consensus-subspace/Cargo.toml b/crates/sc-consensus-subspace/Cargo.toml index 47536f5211..0e125dc454 100644 --- a/crates/sc-consensus-subspace/Cargo.toml +++ b/crates/sc-consensus-subspace/Cargo.toml @@ -20,7 +20,7 @@ fork-tree = { version = "3.0.0", git = "https://github.com/subspace/substrate", futures = "0.3.28" futures-timer = "3.0.2" log = "0.4.19" -lru = "0.10.0" +lru = "0.11.0" parking_lot = "0.12.1" prometheus-endpoint = { package = "substrate-prometheus-endpoint", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71", version = "0.10.0-dev" } rand = "0.8.5" diff --git a/crates/sc-consensus-subspace/src/import_queue.rs b/crates/sc-consensus-subspace/src/import_queue.rs index 8491fcc0e0..c7a054437e 100644 --- a/crates/sc-consensus-subspace/src/import_queue.rs +++ b/crates/sc-consensus-subspace/src/import_queue.rs @@ -1,5 +1,7 @@ //! Subspace block import implementation +#[cfg(feature = "pot")] +use crate::get_chain_constants; use crate::Error; use log::{debug, info, trace, warn}; use prometheus_endpoint::Registry; @@ -9,18 +11,22 @@ use sc_consensus::import_queue::{ BasicQueue, BoxJustificationImport, DefaultImportQueue, Verifier, }; use sc_consensus_slots::check_equivocation; +#[cfg(feature = "pot")] +use sc_proof_of_time::verifier::PotVerifier; #[cfg(not(feature = "pot"))] use sc_telemetry::CONSENSUS_DEBUG; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_TRACE}; use schnorrkel::context::SigningContext; use sp_api::{ApiExt, BlockT, HeaderT, ProvideRuntimeApi, TransactionFor}; use sp_block_builder::BlockBuilder as BlockBuilderApi; -use sp_blockchain::{HeaderBackend, Result as ClientResult}; +use sp_blockchain::HeaderBackend; use sp_consensus::{BlockOrigin, Error as ConsensusError}; use sp_consensus_slots::Slot; use sp_consensus_subspace::digests::{ - extract_subspace_digest_items, CompatibleDigestItem, PreDigest, + extract_subspace_digest_items, CompatibleDigestItem, PreDigest, SubspaceDigestItems, }; +#[cfg(feature = "pot")] +use sp_consensus_subspace::ChainConstants; use sp_consensus_subspace::{FarmerPublicKey, FarmerSignature, SubspaceApi}; use sp_runtime::DigestItem; use std::marker::PhantomData; @@ -53,7 +59,8 @@ pub fn import_queue( registry: Option<&Registry>, telemetry: Option, is_authoring_blocks: bool, -) -> ClientResult> + #[cfg(feature = "pot")] pot_verifier: PotVerifier, +) -> Result, sp_blockchain::Error> where PosTable: Table, Inner: BlockImport> @@ -65,14 +72,22 @@ where SelectChain: sp_consensus::SelectChain + 'static, SN: Fn() -> Slot + Send + Sync + 'static, { + #[cfg(feature = "pot")] + let chain_constants = get_chain_constants(client.as_ref()) + .map_err(|error| sp_blockchain::Error::Application(error.into()))?; + let verifier = SubspaceVerifier { client, kzg, select_chain, slot_now, telemetry, + #[cfg(feature = "pot")] + chain_constants, reward_signing_context: schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT), is_authoring_blocks, + #[cfg(feature = "pot")] + pot_verifier, _pos_table: PhantomData::, _block: PhantomData, }; @@ -87,23 +102,23 @@ where } /// Errors encountered by the Subspace authorship task. -#[derive(Debug, Eq, PartialEq)] -#[cfg_attr(feature = "thiserror", derive(thiserror::Error))] +#[derive(Debug, Eq, PartialEq, thiserror::Error)] pub enum VerificationError { /// Header has a bad seal - #[cfg_attr(feature = "thiserror", error("Header {0:?} has a bad seal"))] + #[error("Header {0:?} has a bad seal")] HeaderBadSeal(Header::Hash), /// Header is unsealed - #[cfg_attr(feature = "thiserror", error("Header {0:?} is unsealed"))] + #[error("Header {0:?} is unsealed")] HeaderUnsealed(Header::Hash), /// Bad reward signature - #[cfg_attr(feature = "thiserror", error("Bad reward signature on {0:?}"))] + #[error("Bad reward signature on {0:?}")] BadRewardSignature(Header::Hash), + /// Invalid proof of time + #[cfg(feature = "pot")] + #[error("Invalid proof of time")] + InvalidProofOfTime, /// Verification error - #[cfg_attr( - feature = "thiserror", - error("Verification error on slot {0:?}: {1:?}") - )] + #[error("Verification error on slot {0:?}: {1:?}")] VerificationError(Slot, subspace_verification::Error), } @@ -133,8 +148,6 @@ where slot_now: Slot, /// Parameters for solution verification verify_solution_params: &'a VerifySolutionParams, - /// Signing context for reward signature - reward_signing_context: &'a SigningContext, } /// Information from verified header @@ -153,8 +166,12 @@ struct SubspaceVerifier { // TODO: Remove field once PoT is the only option slot_now: SN, telemetry: Option, + #[cfg(feature = "pot")] + chain_constants: ChainConstants, reward_signing_context: SigningContext, is_authoring_blocks: bool, + #[cfg(feature = "pot")] + pot_verifier: PotVerifier, _pos_table: PhantomData, _block: PhantomData, } @@ -182,7 +199,11 @@ where async fn check_header( &self, params: VerificationParams<'_, Block::Header>, - pre_digest: PreDigest, + subspace_digest_items: SubspaceDigestItems< + FarmerPublicKey, + FarmerPublicKey, + FarmerSignature, + >, ) -> Result, VerificationError> { let VerificationParams { @@ -190,9 +211,9 @@ where #[cfg(not(feature = "pot"))] slot_now, verify_solution_params, - reward_signing_context, } = params; + let pre_digest = subspace_digest_items.pre_digest; let slot = pre_digest.slot(); let seal = header @@ -213,14 +234,28 @@ where return Ok(CheckedHeader::Deferred(header, slot)); } - // TODO: PoT verification here + // TODO: Extend/optimize this check once we have checkpoints in justifications + // Check proof of time between slot of the block and future proof of time + #[cfg(feature = "pot")] + if !self + .pot_verifier + .is_proof_valid( + pre_digest.pot_info().proof_of_time().seed(), + subspace_digest_items.pot_slot_iterations, + self.chain_constants.block_authoring_delay(), + pre_digest.pot_info().future_proof_of_time(), + ) + .await + { + return Err(VerificationError::InvalidProofOfTime); + } // Verify that block is signed properly if check_reward_signature( pre_hash.as_ref(), &RewardSignature::from(&signature), &PublicKey::from(&pre_digest.solution().public_key), - reward_signing_context, + &self.reward_signing_context, ) .is_err() { @@ -335,7 +370,6 @@ where FarmerSignature, >(&block.header) .map_err(Error::::from)?; - let pre_digest = subspace_digest_items.pre_digest; // Check if farmer's plot is burned. // TODO: Add to header and store in aux storage? @@ -344,7 +378,7 @@ where .runtime_api() .is_in_block_list( *block.header.parent_hash(), - &pre_digest.solution().public_key, + &subspace_digest_items.pre_digest.solution().public_key, ) .or_else(|error| { if block.state_action.skip_execution_checks() { @@ -357,11 +391,15 @@ where warn!( target: "subspace", "Verifying block with solution provided by farmer in block list: {}", - pre_digest.solution().public_key + subspace_digest_items.pre_digest.solution().public_key ); return Err(Error::::FarmerInBlockList( - pre_digest.solution().public_key.clone(), + subspace_digest_items + .pre_digest + .solution() + .public_key + .clone(), ) .into()); } @@ -385,13 +423,12 @@ where #[cfg(not(feature = "pot"))] global_randomness: subspace_digest_items.global_randomness, #[cfg(feature = "pot")] - proof_of_time: pre_digest.pot_info().proof_of_time(), + proof_of_time: subspace_digest_items.pre_digest.pot_info().proof_of_time(), solution_range: subspace_digest_items.solution_range, piece_check_params: None, }, - reward_signing_context: &self.reward_signing_context, }, - pre_digest, + subspace_digest_items, ) .await .map_err(Error::::from)?; diff --git a/crates/sc-consensus-subspace/src/lib.rs b/crates/sc-consensus-subspace/src/lib.rs index 2d84683252..81dcd3308f 100644 --- a/crates/sc-consensus-subspace/src/lib.rs +++ b/crates/sc-consensus-subspace/src/lib.rs @@ -47,6 +47,8 @@ use sc_consensus::JustificationSyncLink; use sc_consensus_slots::{BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SlotProportion}; #[cfg(feature = "pot")] use sc_proof_of_time::source::PotSlotInfoStream; +#[cfg(feature = "pot")] +use sc_proof_of_time::verifier::PotVerifier; use sc_telemetry::TelemetryHandle; use sc_utils::mpsc::TracingUnboundedSender; use sp_api::{ApiError, ApiExt, BlockT, HeaderT, NumberFor, ProvideRuntimeApi, TransactionFor}; @@ -169,6 +171,10 @@ pub enum Error { /// Bad reward signature #[error("Bad reward signature on {0:?}")] BadRewardSignature(Header::Hash), + /// Invalid proof of time + #[cfg(feature = "pot")] + #[error("Invalid proof of time")] + InvalidProofOfTime, /// Solution is outside of solution range #[error( "Solution distance {solution_distance} is outside of solution range \ @@ -208,6 +214,7 @@ pub enum Error { #[error("Parent block of {0} has no associated weight")] ParentBlockNoAssociatedWeight(Header::Hash), /// Block has invalid associated global randomness + #[cfg(not(feature = "pot"))] #[error("Invalid global randomness for block {0}")] InvalidGlobalRandomness(Header::Hash), /// Block has invalid associated solution range @@ -274,6 +281,8 @@ where VerificationError::BadRewardSignature(block_hash) => { Error::BadRewardSignature(block_hash) } + #[cfg(feature = "pot")] + VerificationError::InvalidProofOfTime => Error::InvalidProofOfTime, VerificationError::VerificationError(slot, error) => match error { VerificationPrimitiveError::InvalidPieceOffset { piece_offset, @@ -593,6 +602,8 @@ where create_inherent_data_providers: CIDP, chain_constants: ChainConstants, segment_headers_store: SegmentHeadersStore, + #[cfg(feature = "pot")] + pot_verifier: PotVerifier, _pos_table: PhantomData, } @@ -612,6 +623,8 @@ where create_inherent_data_providers: self.create_inherent_data_providers.clone(), chain_constants: self.chain_constants, segment_headers_store: self.segment_headers_store.clone(), + #[cfg(feature = "pot")] + pot_verifier: self.pot_verifier.clone(), _pos_table: PhantomData, } } @@ -627,6 +640,8 @@ where AS: AuxStore + Send + Sync + 'static, BlockNumber: From<<::Header as HeaderT>::Number>, { + // TODO: Create a struct for these parameters + #[allow(clippy::too_many_arguments)] fn new( client: Arc, block_import: I, @@ -637,6 +652,7 @@ where create_inherent_data_providers: CIDP, chain_constants: ChainConstants, segment_headers_store: SegmentHeadersStore, + #[cfg(feature = "pot")] pot_verifier: PotVerifier, ) -> Self { Self { client, @@ -646,6 +662,8 @@ where create_inherent_data_providers, chain_constants, segment_headers_store, + #[cfg(feature = "pot")] + pot_verifier, _pos_table: PhantomData, } } @@ -706,9 +724,27 @@ where .header(parent_hash)? .ok_or(Error::ParentUnavailable(parent_hash, block_hash))?; + let parent_slot = extract_pre_digest(&parent_header).map(|d| d.slot())?; + + // Make sure that slot number is strictly increasing + #[cfg_attr(not(feature = "pot"), allow(unused_variables))] + let slots_since_parent = match pre_digest.slot().checked_sub(*parent_slot) { + Some(slots_since_parent) => { + if slots_since_parent > 0 { + Slot::from(slots_since_parent) + } else { + return Err(Error::SlotMustIncrease(parent_slot, pre_digest.slot())); + } + } + None => { + return Err(Error::SlotMustIncrease(parent_slot, pre_digest.slot())); + } + }; + #[cfg(not(feature = "pot"))] let correct_global_randomness; - #[cfg_attr(feature = "pot", allow(clippy::needless_late_init))] + #[cfg(feature = "pot")] + let pot_seed; let correct_solution_range; if block_number.is_one() { @@ -721,6 +757,11 @@ where parent_hash, )?; } + #[cfg(feature = "pot")] + { + pot_seed = self.pot_verifier.genesis_seed(); + } + correct_solution_range = slot_worker::extract_solution_ranges_for_block(self.client.as_ref(), parent_hash)? .0; @@ -740,6 +781,14 @@ where None => parent_subspace_digest_items.global_randomness, }; } + #[cfg(feature = "pot")] + { + pot_seed = parent_subspace_digest_items + .pre_digest + .pot_info() + .proof_of_time() + .seed(); + } correct_solution_range = match parent_subspace_digest_items.next_solution_range { Some(solution_range) => solution_range, @@ -747,13 +796,24 @@ where }; } - // TODO: This should be checking PoT instead #[cfg(not(feature = "pot"))] if subspace_digest_items.global_randomness != correct_global_randomness { - // TODO: There is some kind of mess with PoT randomness right now that doesn't make a - // lot of sense, restore this check once that is resolved return Err(Error::InvalidGlobalRandomness(block_hash)); } + #[cfg(feature = "pot")] + // TODO: Extend/optimize this check once we have checkpoints in justifications + if !self + .pot_verifier + .is_proof_valid( + pot_seed, + subspace_digest_items.pot_slot_iterations, + slots_since_parent, + subspace_digest_items.pre_digest.pot_info().proof_of_time(), + ) + .await + { + return Err(Error::InvalidProofOfTime); + } if subspace_digest_items.solution_range != correct_solution_range { return Err(Error::InvalidSolutionRange(block_hash)); @@ -828,13 +888,6 @@ where ) .map_err(|error| VerificationError::VerificationError(pre_digest.slot(), error))?; - let parent_slot = extract_pre_digest(&parent_header).map(|d| d.slot())?; - - // Make sure that slot number is strictly increasing - if pre_digest.slot() <= parent_slot { - return Err(Error::SlotMustIncrease(parent_slot, pre_digest.slot())); - } - if !skip_runtime_access { // If the body is passed through, we need to use the runtime to check that the // internally-set timestamp in the inherents actually matches the slot set in the seal @@ -1081,6 +1134,7 @@ pub fn block_import( kzg: Kzg, create_inherent_data_providers: CIDP, segment_headers_store: SegmentHeadersStore, + #[cfg(feature = "pot")] pot_verifier: PotVerifier, ) -> Result< ( SubspaceBlockImport, @@ -1138,6 +1192,8 @@ where create_inherent_data_providers, chain_constants, segment_headers_store, + #[cfg(feature = "pot")] + pot_verifier, ); Ok((import, link)) diff --git a/crates/sc-proof-of-time/Cargo.toml b/crates/sc-proof-of-time/Cargo.toml index 8e13938b11..9ca26fdb31 100644 --- a/crates/sc-proof-of-time/Cargo.toml +++ b/crates/sc-proof-of-time/Cargo.toml @@ -11,9 +11,11 @@ include = [ ] [dependencies] +async-lock = "2.8.0" async-trait = "0.1.68" derive_more = "0.99.17" futures = "0.3.28" +lru = "0.11.0" parity-scale-codec = { version = "3.6.1", features = ["derive"] } sc-client-api = { version = "4.0.0-dev", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" } sc-consensus-slots = { version = "0.10.0-dev", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" } @@ -30,6 +32,7 @@ sp-runtime = { version = "24.0.0", git = "https://github.com/subspace/substrate" subspace-core-primitives = { version = "0.1.0", path = "../subspace-core-primitives" } subspace-proof-of-time = { version = "0.1.0", path = "../subspace-proof-of-time" } parking_lot = "0.12.1" +rayon = "1.7.0" thiserror = "1.0.38" tokio = { version = "1.28.2", features = ["time"] } tracing = "0.1.37" diff --git a/crates/sc-proof-of-time/src/lib.rs b/crates/sc-proof-of-time/src/lib.rs index 0780983c19..05c2f55825 100644 --- a/crates/sc-proof-of-time/src/lib.rs +++ b/crates/sc-proof-of-time/src/lib.rs @@ -6,6 +6,7 @@ // pub mod gossip; mod slots; pub mod source; +pub mod verifier; // mod state_manager; // mod time_keeper; diff --git a/crates/sc-proof-of-time/src/source.rs b/crates/sc-proof-of-time/src/source.rs index a46bbeb717..e0c61590a1 100644 --- a/crates/sc-proof-of-time/src/source.rs +++ b/crates/sc-proof-of-time/src/source.rs @@ -1,7 +1,8 @@ +use crate::verifier::PotVerifier; use derive_more::{Deref, DerefMut, From}; use futures::channel::mpsc; use futures::executor::block_on; -use futures::SinkExt; +use futures::{SinkExt, StreamExt}; use sp_api::{ApiError, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_consensus_slots::Slot; @@ -23,19 +24,22 @@ pub struct PotSlotInfo { pub checkpoints: PotCheckpoints, } +/// Proof of time slot information +struct NewCheckpoints { + /// Proof of time seed + seed: PotSeed, + /// Iterations per slot + slot_iterations: NonZeroU32, + /// Slot number + slot: Slot, + /// Proof of time checkpoints + checkpoints: PotCheckpoints, +} + /// Stream with proof of time slots #[derive(Debug, Deref, DerefMut, From)] pub struct PotSlotInfoStream(mpsc::Receiver); -/// Configuration for proof of time source -#[derive(Debug, Clone)] -pub struct PotSourceConfig { - /// Is this node a Timekeeper - pub is_timekeeper: bool, - /// External entropy, used initially when PoT chain starts to derive the first seed - pub external_entropy: Vec, -} - /// Source of proofs of time. /// /// Depending on configuration may produce proofs of time locally, send/receive via gossip and keep @@ -45,6 +49,9 @@ pub struct PotSource { // TODO: Use this in `fn run` #[allow(dead_code)] client: Arc, + pot_verifier: PotVerifier, + local_proofs_receiver: mpsc::Receiver, + slot_sender: mpsc::Sender, _block: PhantomData, } @@ -55,36 +62,32 @@ where Client::Api: SubspaceRuntimeApi, { pub fn new( - config: PotSourceConfig, + // TODO: Respect this boolean flag + _is_timekeeper: bool, client: Arc, + pot_verifier: PotVerifier, ) -> Result<(Self, PotSlotInfoStream), ApiError> { - let PotSourceConfig { - // TODO: Respect this boolean flag - is_timekeeper: _, - external_entropy, - } = config; - let info = client.info(); - // TODO: All 3 are incorrect and should be able to continue after node restart - let start_slot = SlotNumber::MIN; - let start_seed = PotSeed::from_genesis(info.genesis_hash.as_ref(), &external_entropy); + // TODO: Both of following are incorrect and should be able to continue after node restart + let start_slot = 1; + let start_seed = pot_verifier.genesis_seed(); #[cfg(feature = "pot")] - let best_hash = info.best_hash; + let best_hash = client.info().best_hash; #[cfg(feature = "pot")] - let runtime_api = client.runtime_api(); - #[cfg(feature = "pot")] - let slot_iterations = runtime_api + let slot_iterations = client + .runtime_api() .pot_parameters(best_hash)? .slot_iterations(Slot::from(start_slot)); #[cfg(not(feature = "pot"))] let slot_iterations = NonZeroU32::new(100_000_000).expect("Not zero; qed"); // TODO: Correct capacity + let (local_proofs_sender, local_proofs_receiver) = mpsc::channel(10); let (slot_sender, slot_receiver) = mpsc::channel(10); thread::Builder::new() .name("timekeeper".to_string()) .spawn(move || { if let Err(error) = - run_timekeeper(start_seed, start_slot, slot_iterations, slot_sender) + run_timekeeper(start_seed, start_slot, slot_iterations, local_proofs_sender) { error!(%error, "Timekeeper exited with an error"); } @@ -94,6 +97,9 @@ where Ok(( Self { client, + pot_verifier, + local_proofs_receiver, + slot_sender, _block: PhantomData, }, PotSlotInfoStream(slot_receiver), @@ -101,10 +107,25 @@ where } /// Run proof of time source - pub async fn run(self) { - // TODO: Aggregate multiple sources of proofs of time (multiple timekeepers, gossip, - // blockchain itself) - std::future::pending().await + pub async fn run(mut self) { + // TODO: More sources of checkpoints (block import, gossip) + while let Some(new_checkpoints) = self.local_proofs_receiver.next().await { + let NewCheckpoints { + seed, + slot_iterations, + slot, + checkpoints, + } = new_checkpoints; + + self.pot_verifier + .inject_verified_checkpoints(seed, slot_iterations, checkpoints); + + // It doesn't matter if receiver is dropped + let _ = self + .slot_sender + .send(PotSlotInfo { slot, checkpoints }) + .await; + } } } @@ -113,20 +134,22 @@ fn run_timekeeper( mut seed: PotSeed, mut slot: SlotNumber, slot_iterations: NonZeroU32, - mut slot_sender: mpsc::Sender, + mut proofs_sender: mpsc::Sender, ) -> Result<(), PotError> { loop { let checkpoints = subspace_proof_of_time::prove(seed, slot_iterations)?; - seed = checkpoints.output().seed(); - - let slot_info = PotSlotInfo { + let slot_info = NewCheckpoints { + seed, + slot_iterations, slot: Slot::from(slot), checkpoints, }; - if let Err(error) = slot_sender.try_send(slot_info) { - if let Err(error) = block_on(slot_sender.send(error.into_inner())) { + seed = checkpoints.output().seed(); + + if let Err(error) = proofs_sender.try_send(slot_info) { + if let Err(error) = block_on(proofs_sender.send(error.into_inner())) { debug!(%error, "Couldn't send checkpoints, channel is closed"); return Ok(()); } diff --git a/crates/sc-proof-of-time/src/verifier.rs b/crates/sc-proof-of-time/src/verifier.rs new file mode 100644 index 0000000000..2d6b1d2dbd --- /dev/null +++ b/crates/sc-proof-of-time/src/verifier.rs @@ -0,0 +1,265 @@ +//! Proof of time verifier + +#[cfg(test)] +mod tests; + +use async_lock::Mutex as AsyncMutex; +use futures::channel::oneshot; +use lru::LruCache; +use parking_lot::Mutex; +use sp_consensus_slots::Slot; +use std::num::{NonZeroU32, NonZeroUsize}; +use std::sync::Arc; +use subspace_core_primitives::{PotCheckpoints, PotProof, PotSeed}; +use subspace_proof_of_time::{prove, verify}; +use tracing::trace; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +struct CacheKey { + seed: PotSeed, + slot_iterations: NonZeroU32, +} + +#[derive(Debug, Clone)] +struct CacheValue { + checkpoints: Arc>>, +} + +/// Verifier data structure that verifies and caches results of PoT verification +#[derive(Debug, Clone)] +pub struct PotVerifier { + genesis_seed: PotSeed, + cache: Arc>>, +} + +impl PotVerifier { + pub fn new(genesis_seed: PotSeed, cache_size: NonZeroUsize) -> Self { + Self { + genesis_seed, + cache: Arc::new(Mutex::new(LruCache::new(cache_size))), + } + } + + /// Inject known good checkpoints into verifier + pub fn inject_verified_checkpoints( + &self, + seed: PotSeed, + slot_iterations: NonZeroU32, + checkpoints: PotCheckpoints, + ) { + self.cache.lock().push( + CacheKey { + seed, + slot_iterations, + }, + CacheValue { + checkpoints: Arc::new(AsyncMutex::new(Some(checkpoints))), + }, + ); + } + + /// Get genesis seed + pub fn genesis_seed(&self) -> PotSeed { + self.genesis_seed + } + + /// Verify a single proof of time that is `slots` slots away from `seed`. + /// + /// NOTE: Potentially much slower than checkpoints, prefer [`Self::verify_checkpoints()`] + /// whenever possible. + pub async fn is_proof_valid( + &self, + mut seed: PotSeed, + slot_iterations: NonZeroU32, + slots: Slot, + proof: PotProof, + ) -> bool { + let mut slots = u64::from(slots); + + loop { + if slots == 0 { + return proof.seed() == seed; + } + + // TODO: This "proxy" is a workaround for https://github.com/rust-lang/rust/issues/57478 + let (result_sender, result_receiver) = oneshot::channel(); + std::thread::spawn({ + let verifier = self.clone(); + + move || { + futures::executor::block_on({ + async move { + // Result doesn't matter here + let _ = result_sender + .send(verifier.derive_next_seed(seed, slot_iterations).await); + } + }); + } + }); + + seed = match result_receiver.await { + Ok(Some(seed)) => seed, + _ => { + return false; + } + }; + + slots -= 1; + } + } + + /// Derive next seed, proving might be used if necessary + // TODO: False-positive, lock is not actually held over await point, remove suppression once + // fixed upstream + #[allow(clippy::await_holding_lock)] + async fn derive_next_seed( + &self, + seed: PotSeed, + slot_iterations: NonZeroU32, + ) -> Option { + let cache_key = CacheKey { + seed, + slot_iterations, + }; + + loop { + let mut cache = self.cache.lock(); + let maybe_cache_value = cache.peek(&cache_key).cloned(); + if let Some(cache_value) = maybe_cache_value { + drop(cache); + let correct_checkpoints = cache_value.checkpoints.lock().await; + if let Some(correct_checkpoints) = correct_checkpoints.as_ref() { + return Some(correct_checkpoints.output().seed()); + } + + // There was another verification for these inputs and it wasn't successful, + // retry + continue; + } + + let cache_value = CacheValue { + checkpoints: Arc::default(), + }; + let checkpoints = Arc::clone(&cache_value.checkpoints); + let mut checkpoints = checkpoints + .try_lock() + .expect("No one can access this mutex yet; qed"); + // Store pending verification entry in cache + cache.push(cache_key, cache_value); + // Take a lock before anyone else + // Cache lock is no longer necessary, other callers should be able to access cache + // too + drop(cache); + + let (result_sender, result_receiver) = oneshot::channel(); + + rayon::spawn(move || { + let result = prove(seed, slot_iterations); + + if let Err(_error) = result_sender.send(result) { + trace!("Verification result receiver is gone before result was sent"); + } + }); + + let Ok(Ok(generated_checkpoints)) = result_receiver.await else { + // Avoid deadlock when taking a lock below + drop(checkpoints); + + // Proving failed, remove pending entry from cache such that retries can happen + let maybe_removed_cache_value = self.cache.lock().pop(&cache_key); + if let Some(removed_cache_value) = maybe_removed_cache_value { + // It is possible that we have removed a verified value that we have not + // inserted, check for this and restore if that was the case + let removed_verified_value = + removed_cache_value.checkpoints.lock().await.is_some(); + if removed_verified_value { + self.cache.lock().push(cache_key, removed_cache_value); + } + } + return None; + }; + + let seed = generated_checkpoints.output().seed(); + checkpoints.replace(generated_checkpoints); + return Some(seed); + } + } + + /// Verify proof of time checkpoints + // TODO: False-positive, lock is not actually held over await point, remove suppression once + // fixed upstream + #[allow(clippy::await_holding_lock)] + pub async fn verify_checkpoints( + &self, + seed: PotSeed, + slot_iterations: NonZeroU32, + checkpoints: &PotCheckpoints, + ) -> bool { + let cache_key = CacheKey { + seed, + slot_iterations, + }; + + loop { + let mut cache = self.cache.lock(); + if let Some(cache_value) = cache.peek(&cache_key).cloned() { + drop(cache); + let correct_checkpoints = cache_value.checkpoints.lock().await; + if let Some(correct_checkpoints) = correct_checkpoints.as_ref() { + return checkpoints == correct_checkpoints; + } + + // There was another verification for these inputs and it wasn't successful, retry + continue; + } + + let cache_value = CacheValue { + checkpoints: Arc::default(), + }; + let correct_checkpoints = Arc::clone(&cache_value.checkpoints); + let mut correct_checkpoints = correct_checkpoints + .try_lock() + .expect("No one can access this mutex yet; qed"); + // Store pending verification entry in cache + cache.push(cache_key, cache_value); + // Take a lock before anyone else + // Cache lock is no longer necessary, other callers should be able to access cache too + drop(cache); + + let (result_sender, result_receiver) = oneshot::channel(); + + let checkpoints = *checkpoints; + rayon::spawn(move || { + let result = + verify(seed, slot_iterations, checkpoints.as_slice()).unwrap_or_default(); + + if let Err(_error) = result_sender.send(result) { + trace!("Verification result receiver is gone before result was sent"); + } + }); + + if !result_receiver.await.unwrap_or_default() { + // Avoid deadlock when taking a lock below + drop(correct_checkpoints); + + // Verification failed, remove pending entry from cache such that retries can happen + let maybe_removed_cache_value = self.cache.lock().pop(&cache_key); + if let Some(removed_cache_value) = maybe_removed_cache_value { + // It is possible that we have removed a verified value that we have not + // inserted, check for this and restore if that was the case + let removed_verified_value = + removed_cache_value.checkpoints.lock().await.is_some(); + if removed_verified_value { + self.cache.lock().push(cache_key, removed_cache_value); + } + } + return false; + } + + // Store known good checkpoints in cache + correct_checkpoints.replace(checkpoints); + + return true; + } + } +} diff --git a/crates/sc-proof-of-time/src/verifier/tests.rs b/crates/sc-proof-of-time/src/verifier/tests.rs new file mode 100644 index 0000000000..9c6c9445be --- /dev/null +++ b/crates/sc-proof-of-time/src/verifier/tests.rs @@ -0,0 +1,105 @@ +use crate::verifier::PotVerifier; +use futures::executor::block_on; +use sp_consensus_slots::Slot; +use std::num::{NonZeroU32, NonZeroUsize}; +use subspace_core_primitives::PotSeed; +use subspace_proof_of_time::prove; + +const SEED: [u8; 16] = [ + 0xd6, 0x66, 0xcc, 0xd8, 0xd5, 0x93, 0xc2, 0x3d, 0xa8, 0xdb, 0x6b, 0x5b, 0x14, 0x13, 0xb1, 0x3a, +]; + +#[test] +fn test_basic() { + let genesis_seed = PotSeed::from(SEED); + let slot_iterations = NonZeroU32::new(512).unwrap(); + let checkpoints_1 = prove(genesis_seed, slot_iterations).unwrap(); + + let verifier = PotVerifier::new(genesis_seed, NonZeroUsize::new(1000).unwrap()); + + // Expected to be valid + assert!(block_on(verifier.is_proof_valid( + genesis_seed, + slot_iterations, + Slot::from(1), + checkpoints_1.output() + ))); + assert!(block_on(verifier.verify_checkpoints( + genesis_seed, + slot_iterations, + &checkpoints_1 + ))); + + // Invalid number of slots + assert!(!block_on(verifier.is_proof_valid( + genesis_seed, + slot_iterations, + Slot::from(2), + checkpoints_1.output() + ))); + // Invalid seed + assert!(!block_on(verifier.is_proof_valid( + checkpoints_1.output().seed(), + slot_iterations, + Slot::from(1), + checkpoints_1.output() + ))); + // Invalid number of iterations + assert!(!block_on( + verifier.verify_checkpoints( + genesis_seed, + slot_iterations + .checked_mul(NonZeroU32::new(2).unwrap()) + .unwrap(), + &checkpoints_1 + ) + )); + + let seed_1 = checkpoints_1.output().seed(); + let checkpoints_2 = prove(seed_1, slot_iterations).unwrap(); + + // Expected to be valid + assert!(block_on(verifier.is_proof_valid( + seed_1, + slot_iterations, + Slot::from(1), + checkpoints_2.output() + ))); + assert!(block_on(verifier.is_proof_valid( + genesis_seed, + slot_iterations, + Slot::from(2), + checkpoints_2.output() + ))); + assert!(block_on(verifier.verify_checkpoints( + seed_1, + slot_iterations, + &checkpoints_2 + ))); + + // Invalid number of slots + assert!(!block_on(verifier.is_proof_valid( + seed_1, + slot_iterations, + Slot::from(2), + checkpoints_2.output() + ))); + // Invalid seed + assert!(!block_on(verifier.is_proof_valid( + seed_1, + slot_iterations, + Slot::from(2), + checkpoints_2.output() + ))); + // Invalid number of iterations + assert!(!block_on( + verifier.is_proof_valid( + genesis_seed, + slot_iterations + .checked_mul(NonZeroU32::new(2).unwrap()) + .unwrap(), + Slot::from(2), + checkpoints_2.output() + ) + )); +} diff --git a/crates/sc-subspace-block-relay/Cargo.toml b/crates/sc-subspace-block-relay/Cargo.toml index 98f95da33b..7fbbed327e 100644 --- a/crates/sc-subspace-block-relay/Cargo.toml +++ b/crates/sc-subspace-block-relay/Cargo.toml @@ -15,7 +15,7 @@ async-channel = "1.8.0" async-trait = "0.1.68" codec = { package = "parity-scale-codec", version = "3.6.5", default-features = false, features = ["derive"] } futures = "0.3.28" -lru = "0.10.0" +lru = "0.11.0" parking_lot = "0.12.1" sc-client-api = { version = "4.0.0-dev", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" } sc-network = { version = "0.10.0-dev", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" } diff --git a/crates/subspace-core-primitives/src/lib.rs b/crates/subspace-core-primitives/src/lib.rs index 8a01c1cc0b..450a781960 100644 --- a/crates/subspace-core-primitives/src/lib.rs +++ b/crates/subspace-core-primitives/src/lib.rs @@ -256,6 +256,12 @@ impl PosProof { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PotKey(#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] [u8; Self::SIZE]); +impl fmt::Display for PotKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + impl FromStr for PotKey { type Err = hex::FromHexError; @@ -281,6 +287,7 @@ impl PotKey { Clone, Eq, PartialEq, + Hash, From, AsRef, AsMut, @@ -294,6 +301,12 @@ impl PotKey { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PotSeed(#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] [u8; Self::SIZE]); +impl fmt::Display for PotSeed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + impl PotSeed { /// Size of proof of time seed in bytes pub const SIZE: usize = 16; @@ -337,6 +350,12 @@ impl PotSeed { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PotProof(#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] [u8; Self::SIZE]); +impl fmt::Display for PotProof { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + impl PotProof { /// Size of proof of time proof in bytes pub const SIZE: usize = 16; diff --git a/crates/subspace-farmer-components/Cargo.toml b/crates/subspace-farmer-components/Cargo.toml index 54a34c56fa..9051dcb2b7 100644 --- a/crates/subspace-farmer-components/Cargo.toml +++ b/crates/subspace-farmer-components/Cargo.toml @@ -23,7 +23,7 @@ fs2 = "0.4.3" futures = "0.3.28" hex = "0.4.3" libc = "0.2.146" -lru = "0.10.0" +lru = "0.11.0" parity-scale-codec = "3.6.5" parking_lot = "0.12.1" rand = "0.8.5" diff --git a/crates/subspace-farmer/Cargo.toml b/crates/subspace-farmer/Cargo.toml index deee889be6..3f12c6fced 100644 --- a/crates/subspace-farmer/Cargo.toml +++ b/crates/subspace-farmer/Cargo.toml @@ -27,7 +27,7 @@ fdlimit = "0.2" futures = "0.3.28" hex = { version = "0.4.3", features = ["serde"] } jsonrpsee = { version = "0.16.2", features = ["client"] } -lru = "0.10.0" +lru = "0.11.0" memmap2 = "0.7.1" parity-db = "0.4.6" parity-scale-codec = "3.6.5" diff --git a/crates/subspace-networking/Cargo.toml b/crates/subspace-networking/Cargo.toml index 6482ce941e..4747a31d70 100644 --- a/crates/subspace-networking/Cargo.toml +++ b/crates/subspace-networking/Cargo.toml @@ -29,7 +29,7 @@ fs2 = "0.4.3" futures = "0.3.28" futures-timer = "3.0.2" hex = "0.4.3" -lru = "0.10.0" +lru = "0.11.0" memmap2 = "0.7.1" nohash-hasher = "0.2.0" parity-scale-codec = "3.6.5" diff --git a/crates/subspace-node/src/bin/subspace-node.rs b/crates/subspace-node/src/bin/subspace-node.rs index 9e2d756e60..ffb9ff2819 100644 --- a/crates/subspace-node/src/bin/subspace-node.rs +++ b/crates/subspace-node/src/bin/subspace-node.rs @@ -26,7 +26,7 @@ use log::warn; use sc_cli::{ChainSpec, CliConfiguration, SubstrateCli}; use sc_consensus_slots::SlotProportion; #[cfg(feature = "pot")] -use sc_proof_of_time::source::PotSourceConfig; +use sc_service::Configuration; use sc_service::PartialComponents; use sc_storage_monitor::StorageMonitorService; use sc_utils::mpsc::tracing_unbounded; @@ -93,6 +93,35 @@ fn set_default_ss58_version>(chain_spec: C) { } } +#[cfg(feature = "pot")] +fn pot_external_entropy( + consensus_chain_config: &Configuration, + cli: &Cli, +) -> Result, sc_service::Error> { + let maybe_chain_spec_pot_external_entropy = consensus_chain_config + .chain_spec + .properties() + .get("potExternalEntropy") + .map(|d| serde_json::from_value(d.clone())) + .transpose() + .map_err(|error| { + sc_service::Error::Other(format!("Failed to decode PoT initial key: {error:?}")) + })? + .flatten(); + if maybe_chain_spec_pot_external_entropy.is_some() + && cli.pot_external_entropy.is_some() + && maybe_chain_spec_pot_external_entropy != cli.pot_external_entropy + { + warn!( + "--pot-external-entropy CLI argument was ignored due to chain spec having a different \ + explicit value" + ); + } + Ok(maybe_chain_spec_pot_external_entropy + .or(cli.pot_external_entropy.clone()) + .unwrap_or_default()) +} + fn main() -> Result<(), Error> { let cli = Cli::from_args(); @@ -112,7 +141,10 @@ fn main() -> Result<(), Error> { task_manager, .. } = subspace_service::new_partial::( - &config, None, + &config, + None, + #[cfg(feature = "pot")] + &pot_external_entropy(&config, &cli)?, )?; Ok(( cmd.run(client, import_queue).map_err(Error::SubstrateCli), @@ -129,7 +161,10 @@ fn main() -> Result<(), Error> { task_manager, .. } = subspace_service::new_partial::( - &config, None, + &config, + None, + #[cfg(feature = "pot")] + &pot_external_entropy(&config, &cli)?, )?; Ok(( cmd.run(client, config.database) @@ -147,7 +182,10 @@ fn main() -> Result<(), Error> { task_manager, .. } = subspace_service::new_partial::( - &config, None, + &config, + None, + #[cfg(feature = "pot")] + &pot_external_entropy(&config, &cli)?, )?; Ok(( cmd.run(client, config.chain_spec) @@ -166,7 +204,10 @@ fn main() -> Result<(), Error> { task_manager, .. } = subspace_service::new_partial::( - &config, None, + &config, + None, + #[cfg(feature = "pot")] + &pot_external_entropy(&config, &cli)?, )?; Ok(( cmd.run(client, import_queue).map_err(Error::SubstrateCli), @@ -230,7 +271,10 @@ fn main() -> Result<(), Error> { task_manager, .. } = subspace_service::new_partial::( - &config, None, + &config, + None, + #[cfg(feature = "pot")] + &pot_external_entropy(&config, &cli)?, )?; Ok(( cmd.run(client, backend, None).map_err(Error::SubstrateCli), @@ -266,7 +310,10 @@ fn main() -> Result<(), Error> { RuntimeApi, ExecutorDispatch, >( - &config, None + &config, + None, + #[cfg(feature = "pot")] + &pot_external_entropy(&config, &cli)?, )?; cmd.run(client) @@ -275,7 +322,10 @@ fn main() -> Result<(), Error> { let PartialComponents { client, backend, .. } = subspace_service::new_partial::( - &config, None, + &config, + None, + #[cfg(feature = "pot")] + &pot_external_entropy(&config, &cli)?, )?; let db = backend.expose_db(); let storage = backend.expose_storage(); @@ -364,6 +414,9 @@ fn main() -> Result<(), Error> { ); let _enter = span.enter(); + #[cfg(feature = "pot")] + let pot_external_entropy = pot_external_entropy(&consensus_chain_config, &cli)?; + let dsn_config = { let network_keypair = consensus_chain_config .network @@ -424,37 +477,6 @@ fn main() -> Result<(), Error> { } }; - #[cfg(feature = "pot")] - let maybe_chain_spec_pot_external_entropy = consensus_chain_config - .chain_spec - .properties() - .get("potExternalEntropy") - .map(|d| serde_json::from_value(d.clone())) - .transpose() - .map_err(|error| { - sc_service::Error::Other(format!( - "Failed to decode PoT initial key: {error:?}" - )) - })? - .flatten(); - #[cfg(feature = "pot")] - if maybe_chain_spec_pot_external_entropy.is_some() - && cli.pot_external_entropy.is_some() - && maybe_chain_spec_pot_external_entropy != cli.pot_external_entropy - { - warn!( - "--pot-external-entropy CLI argument was ignored due to chain spec having \ - a different explicit value" - ); - } - #[cfg(feature = "pot")] - let pot_source_config = PotSourceConfig { - is_timekeeper: cli.timekeeper, - external_entropy: maybe_chain_spec_pot_external_entropy - .or(cli.pot_external_entropy) - .unwrap_or_default(), - }; - let consensus_chain_config = SubspaceConfiguration { base: consensus_chain_config, // Domain node needs slots notifications for bundle production. @@ -463,7 +485,7 @@ fn main() -> Result<(), Error> { sync_from_dsn: cli.sync_from_dsn, enable_subspace_block_relay: cli.enable_subspace_block_relay, #[cfg(feature = "pot")] - pot_source_config, + is_timekeeper: cli.timekeeper, }; let construct_domain_genesis_block_builder = @@ -474,6 +496,8 @@ fn main() -> Result<(), Error> { subspace_service::new_partial::( &consensus_chain_config, Some(&construct_domain_genesis_block_builder), + #[cfg(feature = "pot")] + &pot_external_entropy, ) .map_err(|error| { sc_service::Error::Other(format!( diff --git a/crates/subspace-proof-of-time/benches/pot.rs b/crates/subspace-proof-of-time/benches/pot.rs index 8d43981d36..97ab9a7401 100644 --- a/crates/subspace-proof-of-time/benches/pot.rs +++ b/crates/subspace-proof-of-time/benches/pot.rs @@ -1,7 +1,6 @@ use core::num::NonZeroU32; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use rand::{thread_rng, Rng}; -use std::num::NonZeroU64; use subspace_core_primitives::PotSeed; use subspace_proof_of_time::{prove, verify}; @@ -19,8 +18,6 @@ fn criterion_benchmark(c: &mut Criterion) { let checkpoints = prove(seed, pot_iterations).unwrap(); - let pot_iterations = NonZeroU64::from(pot_iterations); - c.bench_function("verify", |b| { b.iter(|| { black_box(verify( diff --git a/crates/subspace-proof-of-time/src/aes.rs b/crates/subspace-proof-of-time/src/aes.rs index d9b09b2a82..94b780f9dc 100644 --- a/crates/subspace-proof-of-time/src/aes.rs +++ b/crates/subspace-proof-of-time/src/aes.rs @@ -49,7 +49,7 @@ pub(crate) fn verify_sequential( seed: PotSeed, key: PotKey, checkpoints: &[PotProof], - checkpoint_iterations: u64, + checkpoint_iterations: u32, ) -> bool { assert_eq!(checkpoint_iterations % 2, 0); @@ -111,8 +111,6 @@ mod tests { assert_eq!(checkpoints, generic_checkpoints); } - let checkpoint_iterations = u64::from(checkpoint_iterations); - assert!(verify_sequential( seed, key, diff --git a/crates/subspace-proof-of-time/src/lib.rs b/crates/subspace-proof-of-time/src/lib.rs index 17cc641c89..06cf167d19 100644 --- a/crates/subspace-proof-of-time/src/lib.rs +++ b/crates/subspace-proof-of-time/src/lib.rs @@ -3,7 +3,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod aes; -use core::num::{NonZeroU32, NonZeroU64}; +use core::num::NonZeroU32; use subspace_core_primitives::{PotCheckpoints, PotProof, PotSeed}; /// Proof of time error @@ -20,9 +20,9 @@ pub enum PotError { )] NotMultipleOfCheckpoints { /// Slot iterations provided - iterations: NonZeroU64, + iterations: NonZeroU32, /// Number of checkpoints - num_checkpoints: u64, + num_checkpoints: u32, }, } @@ -33,8 +33,8 @@ pub enum PotError { pub fn prove(seed: PotSeed, iterations: NonZeroU32) -> Result { if iterations.get() % u32::from(PotCheckpoints::NUM_CHECKPOINTS.get() * 2) != 0 { return Err(PotError::NotMultipleOfCheckpoints { - iterations: NonZeroU64::from(iterations), - num_checkpoints: u64::from(PotCheckpoints::NUM_CHECKPOINTS.get()), + iterations, + num_checkpoints: u32::from(PotCheckpoints::NUM_CHECKPOINTS.get()), }); } @@ -51,10 +51,10 @@ pub fn prove(seed: PotSeed, iterations: NonZeroU32) -> Result Result { - let num_checkpoints = checkpoints.len() as u64; + let num_checkpoints = checkpoints.len() as u32; if iterations.get() % (num_checkpoints * 2) != 0 { return Err(PotError::NotMultipleOfCheckpoints { iterations, diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index 3b1e403476..7237793221 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -16,6 +16,7 @@ //! Service and ServiceFactory implementation. Specialized wrapper over substrate service. #![feature( + const_option, impl_trait_in_assoc_type, int_roundings, type_alias_impl_trait, @@ -56,7 +57,9 @@ use sc_consensus_subspace::{ use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; use sc_network::NetworkService; #[cfg(feature = "pot")] -use sc_proof_of_time::source::{PotSource, PotSourceConfig}; +use sc_proof_of_time::source::PotSource; +#[cfg(feature = "pot")] +use sc_proof_of_time::verifier::PotVerifier; use sc_service::error::Error as ServiceError; use sc_service::{Configuration, NetworkStarter, SpawnTasksParams, TaskManager}; use sc_subspace_block_relay::{build_consensus_relay, NetworkWrapper}; @@ -79,8 +82,12 @@ use sp_session::SessionKeys; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; use static_assertions::const_assert; use std::marker::PhantomData; +#[cfg(feature = "pot")] +use std::num::NonZeroUsize; use std::sync::Arc; use subspace_core_primitives::crypto::kzg::{embedded_kzg_settings, Kzg}; +#[cfg(feature = "pot")] +use subspace_core_primitives::PotSeed; use subspace_fraud_proof::verifier_api::VerifierClient; use subspace_networking::libp2p::multiaddr::Protocol; use subspace_networking::libp2p::Multiaddr; @@ -95,6 +102,11 @@ use tracing::{debug, error, info, Instrument}; // compile otherwise const_assert!(std::mem::size_of::() >= std::mem::size_of::()); +/// This is over 15 minutes of slots assuming there are no forks, should be both sufficient and not +/// too large to handle +#[cfg(feature = "pot")] +const POT_VERIFIER_CACHE_SIZE: NonZeroUsize = NonZeroUsize::new(10_000).expect("Not zero; qed"); + /// Error type for Subspace service. #[derive(thiserror::Error, Debug)] pub enum Error { @@ -198,9 +210,9 @@ pub struct SubspaceConfiguration { /// Use the block request handler implementation from subspace /// instead of the default substrate handler. pub enable_subspace_block_relay: bool, - /// Proof of time source config + /// Is this node a Timekeeper #[cfg(feature = "pot")] - pub pot_source_config: PotSourceConfig, + pub is_timekeeper: bool, } struct SubspaceExtensionsFactory { @@ -252,6 +264,9 @@ where pub subspace_link: SubspaceLink, /// Segment headers store pub segment_headers_store: SegmentHeadersStore>, + /// Proof of time verifier + #[cfg(feature = "pot")] + pub pot_verifier: PotVerifier, /// Telemetry pub telemetry: Option, } @@ -283,6 +298,7 @@ pub fn new_partial( NativeElseWasmExecutor, ) -> Arc, >, + #[cfg(feature = "pot")] pot_external_entropy: &[u8], ) -> Result, ServiceError> where PosTable: Table, @@ -380,6 +396,11 @@ where let fraud_proof_block_import = sc_consensus_fraud_proof::block_import(client.clone(), client.clone(), proof_verifier); + #[cfg(feature = "pot")] + let pot_verifier = PotVerifier::new( + PotSeed::from_genesis(client.info().genesis_hash.as_ref(), pot_external_entropy), + POT_VERIFIER_CACHE_SIZE, + ); let (block_import, subspace_link) = sc_consensus_subspace::block_import::< PosTable, _, @@ -419,6 +440,8 @@ where } }, segment_headers_store.clone(), + #[cfg(feature = "pot")] + pot_verifier.clone(), )?; let slot_duration = subspace_link.slot_duration(); @@ -438,12 +461,16 @@ where config.prometheus_registry(), telemetry.as_ref().map(|x| x.handle()), config.role.is_authority(), + #[cfg(feature = "pot")] + pot_verifier.clone(), )?; let other = OtherPartialComponents { block_import: Box::new(block_import), subspace_link, segment_headers_store, + #[cfg(feature = "pot")] + pot_verifier, telemetry, }; @@ -553,6 +580,8 @@ where block_import, subspace_link, segment_headers_store, + #[cfg(feature = "pot")] + pot_verifier, mut telemetry, } = other; @@ -766,7 +795,7 @@ where if config.role.is_authority() || config.force_new_slot_notifications { #[cfg(feature = "pot")] let (pot_source, pot_slot_info_stream) = - PotSource::new(config.pot_source_config.clone(), client.clone()) + PotSource::new(config.is_timekeeper, client.clone(), pot_verifier) .map_err(|error| Error::Other(error.into()))?; #[cfg(feature = "pot")] { From 99dac3eaba24d451b2662c51632de0ebf0466045 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 4 Sep 2023 17:12:28 +0300 Subject: [PATCH 7/7] Move comments to correct place --- crates/sc-proof-of-time/src/verifier.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sc-proof-of-time/src/verifier.rs b/crates/sc-proof-of-time/src/verifier.rs index 2d6b1d2dbd..b689faf7e9 100644 --- a/crates/sc-proof-of-time/src/verifier.rs +++ b/crates/sc-proof-of-time/src/verifier.rs @@ -141,12 +141,12 @@ impl PotVerifier { checkpoints: Arc::default(), }; let checkpoints = Arc::clone(&cache_value.checkpoints); + // Take a lock before anyone else let mut checkpoints = checkpoints .try_lock() .expect("No one can access this mutex yet; qed"); // Store pending verification entry in cache cache.push(cache_key, cache_value); - // Take a lock before anyone else // Cache lock is no longer necessary, other callers should be able to access cache // too drop(cache); @@ -217,12 +217,12 @@ impl PotVerifier { checkpoints: Arc::default(), }; let correct_checkpoints = Arc::clone(&cache_value.checkpoints); + // Take a lock before anyone else let mut correct_checkpoints = correct_checkpoints .try_lock() .expect("No one can access this mutex yet; qed"); // Store pending verification entry in cache cache.push(cache_key, cache_value); - // Take a lock before anyone else // Cache lock is no longer necessary, other callers should be able to access cache too drop(cache);