diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index ad53d124759..203fbceafb0 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -378,6 +378,7 @@ mod pallet { pub(super) type GlobalRandomnesses = StorageValue<_, sp_consensus_subspace::GlobalRandomnesses, ValueQuery>; + // TODO: Clarify when this value is updated (when it is updated, right now it is not) /// Number of iterations for proof of time per slot #[pallet::storage] pub(super) type PotSlotIterations = StorageValue<_, NonZeroU32>; diff --git a/crates/sc-consensus-subspace/src/import_queue.rs b/crates/sc-consensus-subspace/src/import_queue.rs index c7a054437ea..2a0aec1e6cd 100644 --- a/crates/sc-consensus-subspace/src/import_queue.rs +++ b/crates/sc-consensus-subspace/src/import_queue.rs @@ -234,16 +234,47 @@ where return Ok(CheckedHeader::Deferred(header, slot)); } + #[cfg(feature = "pot")] + let slot_iterations; + #[cfg(feature = "pot")] + let pot_seed; + #[cfg(feature = "pot")] + let next_slot = slot + Slot::from(1); + #[cfg(feature = "pot")] + // The change to number of iterations might have happened before `next_slot` + if let Some(parameters_change) = subspace_digest_items.pot_parameters_change + && parameters_change.slot <= next_slot + { + slot_iterations = parameters_change.slot_iterations; + // Only if entropy injection happens exactly on next slot we need to mix it in + if parameters_change.slot == next_slot { + pot_seed = pre_digest + .pot_info() + .proof_of_time() + .seed_with_entropy(¶meters_change.entropy); + } else { + pot_seed = pre_digest.pot_info().proof_of_time().seed(); + } + } else { + slot_iterations = subspace_digest_items.pot_slot_iterations; + pot_seed = pre_digest.pot_info().proof_of_time().seed(); + } + // 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 + // Here during stateless verification we do not have access to parent block, thus only + // verify proofs after proof of time of at current slot up until future proof of time + // (inclusive), during block import we verify the rest. #[cfg(feature = "pot")] if !self .pot_verifier .is_proof_valid( - pre_digest.pot_info().proof_of_time().seed(), - subspace_digest_items.pot_slot_iterations, + next_slot, + pot_seed, + slot_iterations, self.chain_constants.block_authoring_delay(), pre_digest.pot_info().future_proof_of_time(), + subspace_digest_items.pot_parameters_change, ) .await { diff --git a/crates/sc-consensus-subspace/src/lib.rs b/crates/sc-consensus-subspace/src/lib.rs index 81dcd3308fd..03dd8553f53 100644 --- a/crates/sc-consensus-subspace/src/lib.rs +++ b/crates/sc-consensus-subspace/src/lib.rs @@ -16,7 +16,7 @@ // along with this program. If not, see . #![doc = include_str!("../README.md")] -#![feature(try_blocks)] +#![feature(let_chains, try_blocks)] #![forbid(unsafe_code)] #![warn(missing_docs)] @@ -401,6 +401,10 @@ where /// Handle use to report telemetries. pub telemetry: Option, + /// Proof of time verifier + #[cfg(feature = "pot")] + pub pot_verifier: PotVerifier, + /// Stream with proof of time slots. #[cfg(feature = "pot")] pub pot_slot_info_stream: PotSlotInfoStream, @@ -424,6 +428,8 @@ pub fn start_subspace, ) -> Result @@ -476,6 +482,8 @@ where pending_solutions: Default::default(), #[cfg(feature = "pot")] pot_checkpoints: Default::default(), + #[cfg(feature = "pot")] + pot_verifier, _pos_table: PhantomData::, }; @@ -745,6 +753,8 @@ where let correct_global_randomness; #[cfg(feature = "pot")] let pot_seed; + #[cfg(feature = "pot")] + let slot_iterations; let correct_solution_range; if block_number.is_one() { @@ -759,6 +769,11 @@ where } #[cfg(feature = "pot")] { + slot_iterations = self + .client + .runtime_api() + .pot_parameters(parent_hash)? + .slot_iterations(); pot_seed = self.pot_verifier.genesis_seed(); } @@ -782,7 +797,19 @@ where }; } #[cfg(feature = "pot")] + // In case parameters change in the very first slot after slot of the parent block, + // account for them + if let Some(parameters_change) = subspace_digest_items.pot_parameters_change + && parameters_change.slot == (parent_slot + Slot::from(1)) { + slot_iterations = parameters_change.slot_iterations; + pot_seed = parent_subspace_digest_items + .pre_digest + .pot_info() + .proof_of_time() + .seed_with_entropy(¶meters_change.entropy); + } else { + slot_iterations = subspace_digest_items.pot_slot_iterations; pot_seed = parent_subspace_digest_items .pre_digest .pot_info() @@ -802,13 +829,21 @@ where } #[cfg(feature = "pot")] // TODO: Extend/optimize this check once we have checkpoints in justifications + // Here we check that there is continuity from parent block's proof of time (but not future + // entropy since this block may be produced before slot corresponding to parent block's + // future proof of time) to current block's proof of time. During stateless verification we + // do not have access to parent block, thus only verify proofs after proof of time of at + // current slot up until future proof of time (inclusive), here during block import we + // verify the rest. if !self .pot_verifier .is_proof_valid( + parent_slot + Slot::from(1), pot_seed, - subspace_digest_items.pot_slot_iterations, + slot_iterations, slots_since_parent, subspace_digest_items.pre_digest.pot_info().proof_of_time(), + subspace_digest_items.pot_parameters_change, ) .await { diff --git a/crates/sc-consensus-subspace/src/slot_worker.rs b/crates/sc-consensus-subspace/src/slot_worker.rs index f268853cc0e..304ef7d6611 100644 --- a/crates/sc-consensus-subspace/src/slot_worker.rs +++ b/crates/sc-consensus-subspace/src/slot_worker.rs @@ -32,6 +32,8 @@ use sc_consensus_slots::{ BackoffAuthoringBlocksStrategy, SimpleSlotWorker, SlotInfo, SlotLenienceType, SlotProportion, }; #[cfg(feature = "pot")] +use sc_proof_of_time::verifier::PotVerifier; +#[cfg(feature = "pot")] use sc_proof_of_time::PotSlotWorker; use sc_telemetry::TelemetryHandle; use sc_utils::mpsc::tracing_unbounded; @@ -140,6 +142,8 @@ where #[cfg(feature = "pot")] // TODO: Substrate should make `fn claim_slot` take `&mut self`, the we'll not need `Mutex` pub(super) pot_checkpoints: Mutex>, + #[cfg(feature = "pot")] + pub(super) pot_verifier: PotVerifier, pub(super) _pos_table: PhantomData, } @@ -153,7 +157,14 @@ where SO: SyncOracle + Send + Sync, { fn on_proof(&mut self, slot: Slot, checkpoints: PotCheckpoints) { - self.pot_checkpoints.lock().insert(slot, checkpoints); + { + let mut pot_checkpoints = self.pot_checkpoints.lock(); + + // Remove checkpoints from future slots, if present they are out of date anyway + pot_checkpoints.retain(|&stored_slot, _checkpoints| stored_slot < slot); + + pot_checkpoints.insert(slot, checkpoints); + } if self.sync_oracle.is_major_syncing() { debug!( @@ -303,27 +314,90 @@ where #[cfg(feature = "pot")] let (proof_of_time, future_proof_of_time, new_checkpoints) = { - let mut pot_checkpoints = self.pot_checkpoints.lock(); + // TODO: These variables and code block below are only necessary to work around + // https://github.com/rust-lang/rust/issues/57478 + let proof_of_time; + let future_slot; + let future_proof_of_time; + let new_checkpoints; + { + let mut pot_checkpoints = self.pot_checkpoints.lock(); + + // Remove checkpoints from old slots we will not need anymore + pot_checkpoints.retain(|&stored_slot, _checkpoints| stored_slot > parent_slot); + + proof_of_time = pot_checkpoints.get(&slot)?.output(); + + // Future slot for which proof must be available before authoring block at this slot + future_slot = slot + self.chain_constants.block_authoring_delay(); + let parent_future_slot = parent_slot + self.chain_constants.block_authoring_delay(); + future_proof_of_time = pot_checkpoints.get(&future_slot)?.output(); + + // New checkpoints that were produced since parent block's future slot up to current + // future slot (inclusive) + new_checkpoints = pot_checkpoints + .iter() + .filter_map(|(&stored_slot, &checkpoints)| { + (stored_slot > parent_future_slot && stored_slot <= future_slot) + .then_some(checkpoints) + }) + .collect::>(); + } - // Remove checkpoints from old slots we will not need anymore - pot_checkpoints.retain(|&stored_slot, _checkpoints| stored_slot > parent_slot); - - 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 + 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 - // future slot (inclusive) - let new_checkpoints = pot_checkpoints - .iter() - .filter_map(|(&stored_slot, &checkpoints)| { - (stored_slot > parent_future_slot && stored_slot <= future_slot) - .then_some(checkpoints) - }) - .collect::>(); + let pot_parameters = runtime_api.pot_parameters(parent_hash).ok()?; + let slot_iterations; + let pot_seed; + let after_parent_slot = parent_slot + Slot::from(1); + + if parent_header.number().is_zero() { + slot_iterations = pot_parameters.slot_iterations(); + pot_seed = self.pot_verifier.genesis_seed(); + } else { + let pot_info = parent_pre_digest.pot_info(); + // The change to number of iterations might have happened before + // `after_parent_slot` + if let Some(parameters_change) = pot_parameters.next_parameters_change() + && parameters_change.slot <= after_parent_slot + { + slot_iterations = parameters_change.slot_iterations; + // Only if entropy injection happens exactly after parent slot we need to \ + // mix it in + if parameters_change.slot == after_parent_slot { + pot_seed = pot_info + .proof_of_time() + .seed_with_entropy(¶meters_change.entropy); + } else { + pot_seed = pot_info + .proof_of_time().seed(); + } + } else { + slot_iterations = pot_parameters.slot_iterations(); + pot_seed = pot_info + .proof_of_time() + .seed(); + } + }; + + // Ensure proof of time and future proof of time included in upcoming block are valid + if !self + .pot_verifier + .is_proof_valid( + after_parent_slot, + pot_seed, + slot_iterations, + Slot::from(u64::from(future_slot) - u64::from(parent_slot)), + future_proof_of_time, + pot_parameters.next_parameters_change(), + ) + .await + { + warn!( + target: "subspace", + "Proof of time or future proof of time is invalid, skipping block \ + production at slot {slot:?}" + ); + return None; + } (proof_of_time, future_proof_of_time, new_checkpoints) }; diff --git a/crates/sc-proof-of-time/src/lib.rs b/crates/sc-proof-of-time/src/lib.rs index e874d9e0ca0..f09e3ee5061 100644 --- a/crates/sc-proof-of-time/src/lib.rs +++ b/crates/sc-proof-of-time/src/lib.rs @@ -1,6 +1,7 @@ //! Subspace proof of time implementation. -pub mod gossip; +#![feature(let_chains, stmt_expr_attributes)] + mod slots; pub mod source; pub mod verifier; diff --git a/crates/sc-proof-of-time/src/source.rs b/crates/sc-proof-of-time/src/source.rs index 8e3f49917a9..9404067f06b 100644 --- a/crates/sc-proof-of-time/src/source.rs +++ b/crates/sc-proof-of-time/src/source.rs @@ -1,11 +1,15 @@ -use crate::gossip::{GossipCheckpoints, PotGossipWorker}; +pub mod gossip; +mod state; +mod timekeeper; + +use crate::source::gossip::{GossipCheckpoints, PotGossipWorker}; +use crate::source::state::{NextSlotInput, PotState}; +use crate::source::timekeeper::run_timekeeper; use crate::verifier::PotVerifier; -use atomic::Atomic; -use derive_more::{Deref, DerefMut, From}; +use derive_more::{Deref, DerefMut}; use futures::channel::mpsc; -use futures::executor::block_on; -use futures::{select, SinkExt, StreamExt}; -use sc_client_api::{BlockImportNotification, BlockchainEvents}; +use futures::{select, StreamExt}; +use sc_client_api::BlockchainEvents; use sc_network::PeerId; use sc_network_gossip::{Network as GossipNetwork, Syncing as GossipSyncing}; use sp_api::{ApiError, ProvideRuntimeApi}; @@ -14,24 +18,30 @@ use sp_consensus_slots::Slot; #[cfg(feature = "pot")] use sp_consensus_subspace::digests::extract_pre_digest; #[cfg(feature = "pot")] +use sp_consensus_subspace::digests::extract_subspace_digest_items; +#[cfg(feature = "pot")] use sp_consensus_subspace::ChainConstants; +#[cfg(feature = "pot")] +use sp_consensus_subspace::FarmerSignature; use sp_consensus_subspace::{FarmerPublicKey, SubspaceApi as SubspaceRuntimeApi}; use sp_runtime::traits::Block as BlockT; #[cfg(feature = "pot")] -use sp_runtime::traits::{Header, Zero}; +use sp_runtime::traits::Header as HeaderT; +#[cfg(feature = "pot")] +use sp_runtime::traits::Zero; use std::marker::PhantomData; use std::num::NonZeroU32; -#[cfg(feature = "pot")] -use std::sync::atomic::Ordering; use std::sync::Arc; use std::thread; -use subspace_core_primitives::{PotCheckpoints, PotSeed, SlotNumber}; -use subspace_proof_of_time::PotError; +use subspace_core_primitives::{PotCheckpoints, PotSeed}; +#[cfg(feature = "pot")] +use tracing::warn; use tracing::{debug, error}; const LOCAL_PROOFS_CHANNEL_CAPACITY: usize = 10; const SLOTS_CHANNEL_CAPACITY: usize = 10; const GOSSIP_OUTGOING_CHANNEL_CAPACITY: usize = 10; +const GOSSIP_INCOMING_CHANNEL_CAPACITY: usize = 10; /// Proof of time slot information pub struct PotSlotInfo { @@ -54,16 +64,16 @@ struct TimekeeperCheckpoints { } /// Stream with proof of time slots -#[derive(Debug, Deref, DerefMut, From)] +#[derive(Debug, Deref, DerefMut)] pub struct PotSlotInfoStream(mpsc::Receiver); -/// Source of proofs of time. +/// Worker producing proofs of time. /// /// Depending on configuration may produce proofs of time locally, send/receive via gossip and keep /// up to day with blockchain reorgs. #[derive(Debug)] #[must_use = "Proof of time source doesn't do anything unless run() method is called"] -pub struct PotSource { +pub struct PotSourceWorker { client: Arc, #[cfg(feature = "pot")] chain_constants: ChainConstants, @@ -71,15 +81,11 @@ pub struct PotSource { outgoing_messages_sender: mpsc::Sender, incoming_messages_receiver: mpsc::Receiver<(PeerId, GossipCheckpoints)>, slot_sender: mpsc::Sender, - #[cfg(feature = "pot")] - current_slot_iterations: Arc>, - // TODO: Make this shared with Timekeeper instead so it can follow latest parameters - // automatically, this will implement Timekeeper "reset" - next_slot_and_seed: (Slot, PotSeed), + state: Arc, _block: PhantomData, } -impl PotSource +impl PotSourceWorker where Block: BlockT, Client: BlockchainEvents + HeaderBackend + ProvideRuntimeApi, @@ -98,13 +104,16 @@ where { #[cfg(feature = "pot")] let chain_constants; + #[cfg(feature = "pot")] + let mut maybe_next_parameters_change; let start_slot; let start_seed; - let current_slot_iterations; + let slot_iterations; #[cfg(feature = "pot")] { let best_hash = client.info().best_hash; - chain_constants = client.runtime_api().chain_constants(best_hash)?; + let runtime_api = client.runtime_api(); + chain_constants = runtime_api.chain_constants(best_hash)?; let best_header = client.header(best_hash)?.ok_or_else(|| { ApiError::UnknownBlock(format!("Parent block {best_hash} not found")) @@ -118,63 +127,76 @@ where // Next slot after the best one seen best_pre_digest.slot() + chain_constants.block_authoring_delay() + Slot::from(1) }; - // TODO: Support parameters change - start_seed = if best_header.number().is_zero() { - pot_verifier.genesis_seed() + + let pot_parameters = runtime_api.pot_parameters(best_hash)?; + maybe_next_parameters_change = pot_parameters.next_parameters_change(); + + if let Some(parameters_change) = maybe_next_parameters_change + && parameters_change.slot == start_slot + { + start_seed = best_pre_digest.pot_info().future_proof_of_time().seed_with_entropy(¶meters_change.entropy); + slot_iterations = parameters_change.slot_iterations; + maybe_next_parameters_change.take(); } else { - best_pre_digest.pot_info().future_proof_of_time().seed() - }; - // TODO: Support parameters change - current_slot_iterations = client - .runtime_api() - .pot_parameters(best_hash)? - .slot_iterations(start_slot); + start_seed = if best_header.number().is_zero() { + pot_verifier.genesis_seed() + } else { + best_pre_digest.pot_info().future_proof_of_time().seed() + }; + slot_iterations = pot_parameters.slot_iterations(); + } } #[cfg(not(feature = "pot"))] { start_slot = Slot::from(1); start_seed = pot_verifier.genesis_seed(); - current_slot_iterations = NonZeroU32::new(100_000_000).expect("Not zero; qed"); + slot_iterations = NonZeroU32::new(100_000_000).expect("Not zero; qed"); } + let state = Arc::new(PotState::new( + NextSlotInput { + slot: start_slot, + slot_iterations, + seed: start_seed, + }, + #[cfg(feature = "pot")] + maybe_next_parameters_change, + pot_verifier.clone(), + )); + let (timekeeper_checkpoints_sender, timekeeper_checkpoints_receiver) = mpsc::channel(LOCAL_PROOFS_CHANNEL_CAPACITY); let (slot_sender, slot_receiver) = mpsc::channel(SLOTS_CHANNEL_CAPACITY); if is_timekeeper { + let state = Arc::clone(&state); let pot_verifier = pot_verifier.clone(); thread::Builder::new() .name("timekeeper".to_string()) .spawn(move || { - if let Err(error) = run_timekeeper( - start_seed, - start_slot, - current_slot_iterations, - pot_verifier, - timekeeper_checkpoints_sender, - ) { + if let Err(error) = + run_timekeeper(state, pot_verifier, timekeeper_checkpoints_sender) + { error!(%error, "Timekeeper exited with an error"); } }) .expect("Thread creation must not panic"); } - let current_slot_iterations = Arc::new(Atomic::new(current_slot_iterations)); - let (outgoing_messages_sender, outgoing_messages_receiver) = mpsc::channel(GOSSIP_OUTGOING_CHANNEL_CAPACITY); let (incoming_messages_sender, incoming_messages_receiver) = - mpsc::channel(GOSSIP_OUTGOING_CHANNEL_CAPACITY); - let gossip = PotGossipWorker::new( + mpsc::channel(GOSSIP_INCOMING_CHANNEL_CAPACITY); + let gossip_worker = PotGossipWorker::new( outgoing_messages_receiver, incoming_messages_sender, pot_verifier, - Arc::clone(¤t_slot_iterations), + Arc::clone(&state), network, sync, ); - let source = Self { + let source_worker = Self { client, #[cfg(feature = "pot")] chain_constants, @@ -182,13 +204,13 @@ where outgoing_messages_sender, incoming_messages_receiver, slot_sender, - #[cfg(feature = "pot")] - current_slot_iterations, - next_slot_and_seed: (start_slot, start_seed), + state, _block: PhantomData, }; - Ok((source, gossip, PotSlotInfoStream(slot_receiver))) + let pot_slot_info_stream = PotSlotInfoStream(slot_receiver); + + Ok((source_worker, gossip_worker, pot_slot_info_stream)) } /// Run proof of time source @@ -199,12 +221,12 @@ where select! { // List of blocks that the client has finalized. timekeeper_checkpoints = self.timekeeper_checkpoints_receiver.select_next_some() => { - self.handle_timekeeper_checkpoints(timekeeper_checkpoints).await; + self.handle_timekeeper_checkpoints(timekeeper_checkpoints); } // List of blocks that the client has finalized. maybe_gossip_checkpoints = self.incoming_messages_receiver.next() => { if let Some((sender, gossip_checkpoints)) = maybe_gossip_checkpoints { - self.handle_gossip_checkpoints(sender, gossip_checkpoints).await; + self.handle_gossip_checkpoints(sender, gossip_checkpoints); } else { debug!("Incoming gossip messages stream ended, exiting"); return; @@ -212,7 +234,10 @@ where } maybe_import_notification = import_notification_stream.next() => { if let Some(import_notification) = maybe_import_notification { - self.handle_import_notification(import_notification).await; + self.handle_block_import_notification( + import_notification.hash, + &import_notification.header, + ); } else { debug!("Import notifications stream ended, exiting"); return; @@ -222,17 +247,22 @@ where } } - async fn handle_timekeeper_checkpoints( - &mut self, - timekeeper_checkpoints: TimekeeperCheckpoints, - ) { + fn handle_timekeeper_checkpoints(&mut self, timekeeper_checkpoints: TimekeeperCheckpoints) { let TimekeeperCheckpoints { + slot, seed, slot_iterations, - slot, checkpoints, } = timekeeper_checkpoints; + debug!( + ?slot, + %seed, + %slot_iterations, + output = %checkpoints.output(), + "Received timekeeper proof", + ); + if self .outgoing_messages_sender .try_send(GossipCheckpoints { @@ -246,130 +276,96 @@ where debug!(%slot, "Gossip is not able to keep-up with slot production"); } - // It doesn't matter if receiver is dropped - let _ = self - .slot_sender - .send(PotSlotInfo { slot, checkpoints }) - .await; - - self.next_slot_and_seed = (slot + Slot::from(1), checkpoints.output().seed()); + // We don't care if block production is too slow or block production is not enabled on this + // node at all + let _ = self.slot_sender.try_send(PotSlotInfo { slot, checkpoints }); } // TODO: Follow both verified and unverified checkpoints to start secondary timekeeper ASAP in // case verification succeeds - async fn handle_gossip_checkpoints( + fn handle_gossip_checkpoints( &mut self, _sender: PeerId, gossip_checkpoints: GossipCheckpoints, ) { - let (next_slot, next_seed) = self.next_slot_and_seed; - if gossip_checkpoints.slot == next_slot && gossip_checkpoints.seed == next_seed { - // It doesn't matter if receiver is dropped - let _ = self - .slot_sender - .send(PotSlotInfo { - slot: gossip_checkpoints.slot, - checkpoints: gossip_checkpoints.checkpoints, - }) - .await; + let expected_next_slot_input = NextSlotInput { + slot: gossip_checkpoints.slot, + slot_iterations: gossip_checkpoints.slot_iterations, + seed: gossip_checkpoints.seed, + }; - self.next_slot_and_seed = ( - gossip_checkpoints.slot + Slot::from(1), - gossip_checkpoints.checkpoints.output().seed(), - ); + if self + .state + .try_extend( + expected_next_slot_input, + gossip_checkpoints.slot, + gossip_checkpoints.checkpoints.output(), + #[cfg(feature = "pot")] + None, + ) + .is_ok() + { + // We don't care if block production is too slow or block production is not enabled on + // this node at all + let _ = self.slot_sender.try_send(PotSlotInfo { + slot: gossip_checkpoints.slot, + checkpoints: gossip_checkpoints.checkpoints, + }); } } #[cfg(not(feature = "pot"))] - async fn handle_import_notification( + fn handle_block_import_notification( &mut self, - _import_notification: BlockImportNotification, + _block_hash: Block::Hash, + _header: &Block::Header, ) { } #[cfg(feature = "pot")] - async fn handle_import_notification( - &mut self, - import_notification: BlockImportNotification, - ) { - let pre_digest = match extract_pre_digest(&import_notification.header) { - Ok(pre_digest) => pre_digest, - Err(error) => { - error!( - %error, - block_number = %import_notification.header.number(), - block_hash = %import_notification.hash, - "Failed to extract pre-digest from header" - ); - return; - } - }; - let pot_parameters = match self - .client - .runtime_api() - .pot_parameters(import_notification.hash) + fn handle_block_import_notification(&self, block_hash: Block::Hash, header: &Block::Header) { + let subspace_digest_items = match extract_subspace_digest_items::< + Block::Header, + FarmerPublicKey, + FarmerPublicKey, + FarmerSignature, + >(header) { - Ok(pot_parameters) => pot_parameters, + Ok(pre_digest) => pre_digest, Err(error) => { error!( %error, - block_number = %import_notification.header.number(), - block_hash = %import_notification.hash, - "Failed to get proof of time parameters" + block_number = %header.number(), + %block_hash, + "Failed to extract Subspace digest items from header" ); return; } }; - let next_slot = - pre_digest.slot() + self.chain_constants.block_authoring_delay() + Slot::from(1); - self.current_slot_iterations - .store(pot_parameters.slot_iterations(next_slot), Ordering::Relaxed); - - // In case block import is ahead of timekeeper and gossip, update `next_slot_and_seed` - if next_slot >= self.next_slot_and_seed.0 { - // TODO: Account for entropy injection here - self.next_slot_and_seed = ( - next_slot, - pre_digest.pot_info().future_proof_of_time().seed(), - ); - - // TODO: Try to get higher time slot using verifier, we are behind and need to catch up - // and may have already received newer proofs via gossip - } - } -} - -/// Runs timekeeper, must be running on a fast dedicated CPU core -fn run_timekeeper( - mut seed: PotSeed, - slot: Slot, - slot_iterations: NonZeroU32, - pot_verifier: PotVerifier, - mut proofs_sender: mpsc::Sender, -) -> Result<(), PotError> { - let mut slot = SlotNumber::from(slot); - loop { - let checkpoints = subspace_proof_of_time::prove(seed, slot_iterations)?; - - pot_verifier.inject_verified_checkpoints(seed, slot_iterations, checkpoints); - - let slot_info = TimekeeperCheckpoints { - seed, - slot_iterations, - slot: Slot::from(slot), - checkpoints, - }; - - 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(()); - } + let best_slot = + subspace_digest_items.pre_digest.slot() + self.chain_constants.block_authoring_delay(); + let best_proof = subspace_digest_items + .pre_digest + .pot_info() + .future_proof_of_time(); + + // This will do one of 3 things depending on circumstances: + // * if block import is ahead of timekeeper and gossip, it will update next slot input + // * if block import is on a different PoT chain, it will update next slot input to the + // correct fork + // * if block import is on the same PoT chain this will essentially do nothing + if self + .state + .update( + best_slot, + best_proof, + #[cfg(feature = "pot")] + Some(subspace_digest_items.pot_parameters_change), + ) + .is_some() + { + warn!("Proof of time chain reorg happened"); } - - slot += 1; } } diff --git a/crates/sc-proof-of-time/src/gossip.rs b/crates/sc-proof-of-time/src/source/gossip.rs similarity index 90% rename from crates/sc-proof-of-time/src/gossip.rs rename to crates/sc-proof-of-time/src/source/gossip.rs index 8d7a561f870..a0559ebb53c 100644 --- a/crates/sc-proof-of-time/src/gossip.rs +++ b/crates/sc-proof-of-time/src/source/gossip.rs @@ -1,7 +1,7 @@ //! PoT gossip functionality. +use crate::source::state::PotState; use crate::verifier::PotVerifier; -use atomic::Atomic; use futures::channel::mpsc; use futures::{FutureExt, SinkExt, StreamExt}; use parity_scale_codec::{Decode, Encode}; @@ -31,15 +31,15 @@ pub fn pot_gossip_peers_set_config() -> NonDefaultSetConfig { } #[derive(Debug, Copy, Clone, Encode, Decode)] -pub(crate) struct GossipCheckpoints { +pub(super) struct GossipCheckpoints { /// Slot number - pub(crate) slot: Slot, + pub(super) slot: Slot, /// Proof of time seed - pub(crate) seed: PotSeed, + pub(super) seed: PotSeed, /// Iterations per slot - pub(crate) slot_iterations: NonZeroU32, + pub(super) slot_iterations: NonZeroU32, /// Proof of time checkpoints - pub(crate) checkpoints: PotCheckpoints, + pub(super) checkpoints: PotCheckpoints, } /// PoT gossip worker @@ -59,11 +59,11 @@ where Block: BlockT, { /// Instantiate gossip worker - pub(crate) fn new( + pub(super) fn new( outgoing_messages_receiver: mpsc::Receiver, incoming_messages_sender: mpsc::Sender<(PeerId, GossipCheckpoints)>, pot_verifier: PotVerifier, - current_slot_iterations: Arc>, + state: Arc, network: Network, sync: Arc, ) -> Self @@ -73,11 +73,7 @@ where { let topic = <::Hashing as HashT>::hash(b"checkpoints"); - let validator = Arc::new(PotGossipValidator::new( - pot_verifier, - current_slot_iterations, - topic, - )); + let validator = Arc::new(PotGossipValidator::new(pot_verifier, state, topic)); let engine = GossipEngine::new(network, sync, GOSSIP_PROTOCOL, validator, None); Self { @@ -146,7 +142,7 @@ where Block: BlockT, { pot_verifier: PotVerifier, - current_slot_iterations: Arc>, + state: Arc, topic: Block::Hash, } @@ -155,14 +151,10 @@ where Block: BlockT, { /// Creates the validator. - fn new( - pot_verifier: PotVerifier, - current_slot_iterations: Arc>, - topic: Block::Hash, - ) -> Self { + fn new(pot_verifier: PotVerifier, state: Arc, topic: Block::Hash) -> Self { Self { pot_verifier, - current_slot_iterations, + state, topic, } } @@ -184,7 +176,10 @@ where Ok(message) => { // TODO: Gossip validation should be non-blocking! // TODO: Check that slot number is not too far in the past of future - let current_slot_iterations = self.current_slot_iterations.load(Ordering::Relaxed); + let current_slot_iterations = self + .state + .next_slot_input(Ordering::Relaxed) + .slot_iterations; // Check that number of slot iterations is between 2/3 and 1.5 of current slot // iterations, otherwise ignore diff --git a/crates/sc-proof-of-time/src/source/state.rs b/crates/sc-proof-of-time/src/source/state.rs new file mode 100644 index 00000000000..d7328c64fba --- /dev/null +++ b/crates/sc-proof-of-time/src/source/state.rs @@ -0,0 +1,187 @@ +use crate::verifier::PotVerifier; +use atomic::Atomic; +use sp_consensus_slots::Slot; +#[cfg(feature = "pot")] +use sp_consensus_subspace::PotParametersChange; +use std::num::NonZeroU32; +use std::sync::atomic::Ordering; +use subspace_core_primitives::{PotProof, PotSeed}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(super) struct NextSlotInput { + pub(super) slot: Slot, + pub(super) slot_iterations: NonZeroU32, + pub(super) seed: PotSeed, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +struct InnerState { + next_slot_input: NextSlotInput, + #[cfg(feature = "pot")] + parameters_change: Option, +} + +impl InnerState { + pub(super) fn update( + mut self, + mut best_slot: Slot, + mut best_proof: PotProof, + #[cfg(feature = "pot")] maybe_updated_parameters_change: Option< + Option, + >, + pot_verifier: &PotVerifier, + ) -> Self { + #[cfg(feature = "pot")] + if let Some(updated_parameters_change) = maybe_updated_parameters_change { + self.parameters_change = updated_parameters_change; + } + + loop { + let next_slot = best_slot + Slot::from(1); + let next_slot_iterations; + let next_seed; + + #[cfg(feature = "pot")] + // The change to number of iterations might have happened before `next_slot` + if let Some(parameters_change) = self.parameters_change + && parameters_change.slot <= next_slot + { + next_slot_iterations = parameters_change.slot_iterations; + // Only if entropy injection happens on this exact slot we need to mix it in + if parameters_change.slot == next_slot { + next_seed = best_proof.seed_with_entropy(¶meters_change.entropy); + } else { + next_seed = best_proof.seed(); + } + } else { + next_slot_iterations = self.next_slot_input.slot_iterations; + next_seed = best_proof.seed(); + } + #[cfg(not(feature = "pot"))] + { + next_slot_iterations = self.next_slot_input.slot_iterations; + next_seed = best_proof.seed(); + } + + self.next_slot_input = NextSlotInput { + slot: next_slot, + slot_iterations: next_slot_iterations, + seed: next_seed, + }; + + // Advance further as far as possible using previously verified proofs/checkpoints + if let Some(checkpoints) = pot_verifier.get_checkpoints(next_seed, next_slot_iterations) + { + best_slot = best_slot + Slot::from(1); + best_proof = checkpoints.output(); + } else { + break; + } + } + + self + } +} + +#[derive(Debug)] +pub(super) struct PotState { + inner_state: Atomic, + verifier: PotVerifier, +} + +impl PotState { + pub(super) fn new( + next_slot_input: NextSlotInput, + #[cfg(feature = "pot")] parameters_change: Option, + verifier: PotVerifier, + ) -> Self { + let inner = InnerState { + next_slot_input, + #[cfg(feature = "pot")] + parameters_change, + }; + + Self { + inner_state: Atomic::new(inner), + verifier, + } + } + + pub(super) fn next_slot_input(&self, ordering: Ordering) -> NextSlotInput { + self.inner_state.load(ordering).next_slot_input + } + + /// Extend state if it matches provided expected next slot input. + /// + /// Returns `Ok(new_next_slot_input)` if state was extended successfully and + /// `Err(existing_next_slot_input)` in case state was changed in the meantime. + pub(super) fn try_extend( + &self, + expected_previous_next_slot_input: NextSlotInput, + best_slot: Slot, + best_proof: PotProof, + #[cfg(feature = "pot")] maybe_updated_parameters_change: Option< + Option, + >, + ) -> Result { + let old_inner_state = self.inner_state.load(Ordering::Acquire); + if expected_previous_next_slot_input != old_inner_state.next_slot_input { + return Err(old_inner_state.next_slot_input); + } + + let new_inner_state = old_inner_state.update( + best_slot, + best_proof, + #[cfg(feature = "pot")] + maybe_updated_parameters_change, + &self.verifier, + ); + + // Use `compare_exchange` to ensure we only update previously known value and not + // accidentally override something that doesn't match expectations anymore + self.inner_state + .compare_exchange( + old_inner_state, + new_inner_state, + Ordering::AcqRel, + // We don't care about the value read in case of failure + Ordering::Acquire, + ) + .map(|_old_inner_state| new_inner_state.next_slot_input) + .map_err(|existing_inner_state| existing_inner_state.next_slot_input) + } + + /// Update state, overriding PoT chain if it doesn't match provided values. + /// + /// Returns `Some(next_slot_input)` if reorg happened. + #[cfg(feature = "pot")] + pub(super) fn update( + &self, + best_slot: Slot, + best_proof: PotProof, + #[cfg(feature = "pot")] maybe_updated_parameters_change: Option< + Option, + >, + ) -> Option { + let mut best_state = None; + // Use `fetch_update` such that we don't accidentally downgrade best slot to smaller value + let previous_best_state = self + .inner_state + .fetch_update(Ordering::AcqRel, Ordering::Acquire, |inner_state| { + best_state = Some(inner_state.update( + best_slot, + best_proof, + #[cfg(feature = "pot")] + maybe_updated_parameters_change, + &self.verifier, + )); + + best_state + }) + .expect("Callback always returns `Some`; qed"); + let best_state = best_state.expect("Replaced with `Some` above; qed"); + + (previous_best_state.next_slot_input != best_state.next_slot_input) + .then_some(best_state.next_slot_input) + } +} diff --git a/crates/sc-proof-of-time/src/source/timekeeper.rs b/crates/sc-proof-of-time/src/source/timekeeper.rs new file mode 100644 index 00000000000..8d393d1b8d8 --- /dev/null +++ b/crates/sc-proof-of-time/src/source/timekeeper.rs @@ -0,0 +1,54 @@ +use crate::source::state::PotState; +use crate::source::TimekeeperCheckpoints; +use crate::verifier::PotVerifier; +use futures::channel::mpsc; +use futures::executor::block_on; +use futures::SinkExt; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use subspace_proof_of_time::PotError; +use tracing::debug; + +/// Runs timekeeper, must be running on a fast dedicated CPU core +pub(super) fn run_timekeeper( + state: Arc, + pot_verifier: PotVerifier, + mut proofs_sender: mpsc::Sender, +) -> Result<(), PotError> { + let mut next_slot_input = state.next_slot_input(Ordering::Acquire); + + loop { + let checkpoints = + subspace_proof_of_time::prove(next_slot_input.seed, next_slot_input.slot_iterations)?; + + let slot_info = TimekeeperCheckpoints { + seed: next_slot_input.seed, + slot_iterations: next_slot_input.slot_iterations, + slot: next_slot_input.slot, + checkpoints, + }; + + pot_verifier.inject_verified_checkpoints( + next_slot_input.seed, + next_slot_input.slot_iterations, + checkpoints, + ); + + next_slot_input = state + .try_extend( + next_slot_input, + next_slot_input.slot, + checkpoints.output(), + #[cfg(feature = "pot")] + None, + ) + .unwrap_or_else(|next_slot_input| next_slot_input); + + 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 index b949f11ebd5..0b7d2e58b27 100644 --- a/crates/sc-proof-of-time/src/verifier.rs +++ b/crates/sc-proof-of-time/src/verifier.rs @@ -8,6 +8,8 @@ use futures::channel::oneshot; use lru::LruCache; use parking_lot::Mutex; use sp_consensus_slots::Slot; +#[cfg(feature = "pot")] +use sp_consensus_subspace::PotParametersChange; use std::num::{NonZeroU32, NonZeroUsize}; use std::sync::Arc; use subspace_core_primitives::{PotCheckpoints, PotProof, PotSeed}; @@ -62,24 +64,43 @@ impl PotVerifier { self.genesis_seed } - /// Verify a single proof of time that is `slots` slots away from `seed`. + pub fn get_checkpoints( + &self, + seed: PotSeed, + slot_iterations: NonZeroU32, + ) -> Option { + let cache_key = CacheKey { + seed, + slot_iterations, + }; + + self.cache + .lock() + .peek(&cache_key) + .and_then(|value| value.checkpoints.try_lock()?.as_ref().copied()) + } + + /// Verify sequence of proofs of time that covers `slots` slots starting at `slot` with provided + /// initial `seed`. + /// + /// In case `maybe_parameters_change` is present, it will not affect provided `seed` and + /// `slot_iterations`, meaning if parameters change occurred at `slot`, provided `seed` and + /// `slot_iterations` must already account for that. /// /// NOTE: Potentially much slower than checkpoints, prefer [`Self::verify_checkpoints()`] /// whenever possible. pub async fn is_proof_valid( &self, + #[cfg(feature = "pot")] mut slot: Slot, mut seed: PotSeed, - slot_iterations: NonZeroU32, + #[cfg_attr(not(feature = "pot"), allow(unused_mut))] mut slot_iterations: NonZeroU32, slots: Slot, proof: PotProof, + #[cfg(feature = "pot")] mut maybe_parameters_change: Option, ) -> 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({ @@ -90,20 +111,41 @@ impl PotVerifier { async move { // Result doesn't matter here let _ = result_sender - .send(verifier.derive_next_seed(seed, slot_iterations).await); + .send(verifier.calculate_proof(seed, slot_iterations).await); } }); } }); - seed = match result_receiver.await { - Ok(Some(seed)) => seed, - _ => { - return false; - } + let Ok(Some(calculated_proof)) = result_receiver.await else { + return false; }; slots -= 1; + + if slots == 0 { + return proof == calculated_proof; + } + + #[cfg(feature = "pot")] + { + slot = slot + Slot::from(1); + } + + #[cfg(feature = "pot")] + if let Some(parameters_change) = maybe_parameters_change + && parameters_change.slot == slot + { + slot_iterations = parameters_change.slot_iterations; + seed = calculated_proof.seed_with_entropy(¶meters_change.entropy); + maybe_parameters_change.take(); + } else { + seed = calculated_proof.seed(); + } + #[cfg(not(feature = "pot"))] + { + seed = calculated_proof.seed(); + } } } @@ -111,11 +153,11 @@ impl PotVerifier { // 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( + async fn calculate_proof( &self, seed: PotSeed, slot_iterations: NonZeroU32, - ) -> Option { + ) -> Option { let cache_key = CacheKey { seed, slot_iterations, @@ -128,7 +170,7 @@ impl PotVerifier { 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()); + return Some(correct_checkpoints.output()); } // There was another verification for these inputs and it wasn't successful, @@ -178,9 +220,9 @@ impl PotVerifier { return None; }; - let seed = generated_checkpoints.output().seed(); + let proof = generated_checkpoints.output(); checkpoints.replace(generated_checkpoints); - return Some(seed); + return Some(proof); } } diff --git a/crates/sc-proof-of-time/src/verifier/tests.rs b/crates/sc-proof-of-time/src/verifier/tests.rs index 56c3d178c26..5795f3f894e 100644 --- a/crates/sc-proof-of-time/src/verifier/tests.rs +++ b/crates/sc-proof-of-time/src/verifier/tests.rs @@ -1,7 +1,13 @@ use crate::verifier::PotVerifier; use futures::executor::block_on; use sp_consensus_slots::Slot; +#[cfg(feature = "pot")] +use sp_consensus_subspace::PotParametersChange; +#[cfg(feature = "pot")] +use std::mem; use std::num::{NonZeroU32, NonZeroUsize}; +#[cfg(feature = "pot")] +use subspace_core_primitives::Blake3Hash; use subspace_core_primitives::PotSeed; const SEED: [u8; 16] = [ @@ -18,10 +24,14 @@ fn test_basic() { // Expected to be valid assert!(block_on(verifier.is_proof_valid( + #[cfg(feature = "pot")] + Slot::from(1), genesis_seed, slot_iterations, Slot::from(1), - checkpoints_1.output() + checkpoints_1.output(), + #[cfg(feature = "pot")] + None ))); assert!(block_on(verifier.verify_checkpoints( genesis_seed, @@ -31,17 +41,25 @@ fn test_basic() { // Invalid number of slots assert!(!block_on(verifier.is_proof_valid( + #[cfg(feature = "pot")] + Slot::from(1), genesis_seed, slot_iterations, Slot::from(2), - checkpoints_1.output() + checkpoints_1.output(), + #[cfg(feature = "pot")] + None ))); // Invalid seed assert!(!block_on(verifier.is_proof_valid( + #[cfg(feature = "pot")] + Slot::from(1), checkpoints_1.output().seed(), slot_iterations, Slot::from(1), - checkpoints_1.output() + checkpoints_1.output(), + #[cfg(feature = "pot")] + None ))); // Invalid number of iterations assert!(!block_on( @@ -59,16 +77,24 @@ fn test_basic() { // Expected to be valid assert!(block_on(verifier.is_proof_valid( + #[cfg(feature = "pot")] + Slot::from(2), seed_1, slot_iterations, Slot::from(1), - checkpoints_2.output() + checkpoints_2.output(), + #[cfg(feature = "pot")] + None ))); assert!(block_on(verifier.is_proof_valid( + #[cfg(feature = "pot")] + Slot::from(1), genesis_seed, slot_iterations, Slot::from(2), - checkpoints_2.output() + checkpoints_2.output(), + #[cfg(feature = "pot")] + None ))); assert!(block_on(verifier.verify_checkpoints( seed_1, @@ -78,27 +104,134 @@ fn test_basic() { // Invalid number of slots assert!(!block_on(verifier.is_proof_valid( + #[cfg(feature = "pot")] + Slot::from(1), seed_1, slot_iterations, Slot::from(2), - checkpoints_2.output() + checkpoints_2.output(), + #[cfg(feature = "pot")] + None ))); // Invalid seed assert!(!block_on(verifier.is_proof_valid( + #[cfg(feature = "pot")] + Slot::from(1), seed_1, slot_iterations, Slot::from(2), - checkpoints_2.output() + checkpoints_2.output(), + #[cfg(feature = "pot")] + None ))); // Invalid number of iterations assert!(!block_on( verifier.is_proof_valid( + #[cfg(feature = "pot")] + Slot::from(1), genesis_seed, slot_iterations .checked_mul(NonZeroU32::new(2).unwrap()) .unwrap(), Slot::from(2), - checkpoints_2.output() + checkpoints_2.output(), + #[cfg(feature = "pot")] + None ) )); } + +#[cfg(feature = "pot")] +#[test] +fn parameters_change() { + let genesis_seed = PotSeed::from(SEED); + let slot_iterations_1 = NonZeroU32::new(512).unwrap(); + let entropy = [1; mem::size_of::()]; + let checkpoints_1 = subspace_proof_of_time::prove(genesis_seed, slot_iterations_1).unwrap(); + let slot_iterations_2 = slot_iterations_1.saturating_mul(NonZeroU32::new(2).unwrap()); + let checkpoints_2 = subspace_proof_of_time::prove( + checkpoints_1.output().seed_with_entropy(&entropy), + slot_iterations_2, + ) + .unwrap(); + let checkpoints_3 = + subspace_proof_of_time::prove(checkpoints_2.output().seed(), slot_iterations_2).unwrap(); + + let verifier = PotVerifier::new(genesis_seed, NonZeroUsize::new(1000).unwrap()); + + // Changing parameters after first slot + assert!(block_on(verifier.is_proof_valid( + Slot::from(1), + genesis_seed, + slot_iterations_1, + Slot::from(1), + checkpoints_1.output(), + Some(PotParametersChange { + slot: Slot::from(2), + slot_iterations: slot_iterations_2, + entropy, + }) + ))); + // Changing parameters in the middle + assert!(block_on(verifier.is_proof_valid( + Slot::from(1), + genesis_seed, + slot_iterations_1, + Slot::from(3), + checkpoints_3.output(), + Some(PotParametersChange { + slot: Slot::from(2), + slot_iterations: slot_iterations_2, + entropy, + }) + ))); + // Changing parameters on last slot + assert!(block_on(verifier.is_proof_valid( + Slot::from(1), + genesis_seed, + slot_iterations_1, + Slot::from(2), + checkpoints_2.output(), + Some(PotParametersChange { + slot: Slot::from(2), + slot_iterations: slot_iterations_2, + entropy, + }) + ))); + // Not changing parameters because changes apply to the very first slot that is verified + assert!(block_on(verifier.is_proof_valid( + Slot::from(2), + checkpoints_1.output().seed_with_entropy(&entropy), + slot_iterations_2, + Slot::from(2), + checkpoints_3.output(), + Some(PotParametersChange { + slot: Slot::from(2), + slot_iterations: slot_iterations_2, + entropy, + }) + ))); + + // Missing parameters change + assert!(!block_on(verifier.is_proof_valid( + Slot::from(1), + genesis_seed, + slot_iterations_1, + Slot::from(3), + checkpoints_3.output(), + None + ))); + // Invalid slot + assert!(!block_on(verifier.is_proof_valid( + Slot::from(2), + genesis_seed, + slot_iterations_1, + Slot::from(3), + checkpoints_3.output(), + Some(PotParametersChange { + slot: Slot::from(2), + slot_iterations: slot_iterations_2, + entropy, + }) + ))); +} diff --git a/crates/sp-consensus-subspace/src/digests.rs b/crates/sp-consensus-subspace/src/digests.rs index f976d596bae..70adec72572 100644 --- a/crates/sp-consensus-subspace/src/digests.rs +++ b/crates/sp-consensus-subspace/src/digests.rs @@ -133,7 +133,8 @@ pub trait CompatibleDigestItem: Sized { /// If this item is a Subspace signature, return the signature. fn as_subspace_seal(&self) -> Option; - /// Number of iterations for proof of time per slot + /// Number of iterations for proof of time per slot, corresponds to slot that directly follows + /// parent block's slot and can change before slot for which block is produced #[cfg(feature = "pot")] fn pot_slot_iterations(pot_slot_iterations: NonZeroU32) -> Self; @@ -525,7 +526,8 @@ pub struct SubspaceDigestItems { pub pre_digest: PreDigest, /// Signature (seal) if present pub signature: Option, - /// Number of iterations for proof of time per slot + /// Number of iterations for proof of time per slot, corresponds to slot that directly follows + /// parent block's slot and can change before slot for which block is produced #[cfg(feature = "pot")] pub pot_slot_iterations: NonZeroU32, /// Global randomness diff --git a/crates/sp-consensus-subspace/src/lib.rs b/crates/sp-consensus-subspace/src/lib.rs index 6e04e6c8e4d..9905371c76b 100644 --- a/crates/sp-consensus-subspace/src/lib.rs +++ b/crates/sp-consensus-subspace/src/lib.rs @@ -45,7 +45,7 @@ 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; +use subspace_core_primitives::Blake3Hash; #[cfg(not(feature = "pot"))] use subspace_core_primitives::Randomness; use subspace_core_primitives::{ @@ -136,21 +136,22 @@ pub type EquivocationProof
= sp_consensus_slots::EquivocationProof, @@ -656,21 +658,22 @@ 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 slot_iterations(&self, slot: Slot) -> NonZeroU32 { + /// Number of iterations for proof of time per slot, corresponds to slot that directly follows + /// parent block's slot and can change before slot for which block is produced + pub fn slot_iterations(&self) -> NonZeroU32 { let Self::V0 { - slot_iterations, - next_change, + slot_iterations, .. } = self; - if let Some(next_change) = next_change { - if next_change.slot >= slot { - return next_change.iterations; - } - } - *slot_iterations } + + /// Get next proof of time parameters change if any + pub fn next_parameters_change(&self) -> Option { + let Self::V0 { next_change, .. } = self; + + *next_change + } } #[cfg(feature = "pot")] diff --git a/crates/subspace-core-primitives/src/lib.rs b/crates/subspace-core-primitives/src/lib.rs index 450a781960f..5af21014766 100644 --- a/crates/subspace-core-primitives/src/lib.rs +++ b/crates/subspace-core-primitives/src/lib.rs @@ -371,6 +371,15 @@ impl PotProof { pub fn seed(&self) -> PotSeed { PotSeed(self.0) } + + /// Derive seed from proof of time with entropy injection + #[inline] + pub fn seed_with_entropy(&self, entropy: &Blake3Hash) -> PotSeed { + let hash = blake3_hash_list(&[entropy, &self.0]); + let mut seed = PotSeed::default(); + seed.copy_from_slice(&hash[..Self::SIZE]); + seed + } } /// Proof of time checkpoints, result of proving diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index d3d3032b5cc..ae075081caf 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -57,9 +57,9 @@ use sc_consensus_subspace::{ use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; use sc_network::NetworkService; #[cfg(feature = "pot")] -use sc_proof_of_time::gossip::pot_gossip_peers_set_config; +use sc_proof_of_time::source::gossip::pot_gossip_peers_set_config; #[cfg(feature = "pot")] -use sc_proof_of_time::source::PotSource; +use sc_proof_of_time::source::PotSourceWorker; #[cfg(feature = "pot")] use sc_proof_of_time::verifier::PotVerifier; use sc_service::error::Error as ServiceError; @@ -795,17 +795,17 @@ where #[cfg(feature = "pot")] let pot_slot_info_stream = { - let (pot_source, pot_gossip_worker, pot_slot_info_stream) = PotSource::new( + let (pot_source_worker, pot_gossip_worker, pot_slot_info_stream) = PotSourceWorker::new( config.is_timekeeper, client.clone(), - pot_verifier, + pot_verifier.clone(), network_service.clone(), sync_service.clone(), ) .map_err(|error| Error::Other(error.into()))?; let spawn_essential_handle = task_manager.spawn_essential_handle(); - spawn_essential_handle.spawn("pot-source", Some("pot"), pot_source.run()); + spawn_essential_handle.spawn("pot-source", Some("pot"), pot_source_worker.run()); spawn_essential_handle.spawn_blocking("pot-gossip", Some("pot"), pot_gossip_worker.run()); pot_slot_info_stream @@ -863,6 +863,8 @@ where max_block_proposal_slot_portion: None, telemetry: None, #[cfg(feature = "pot")] + pot_verifier, + #[cfg(feature = "pot")] pot_slot_info_stream, };