Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b4004d6
Small refactoring with to reduce code duplication and prepare for PoT…
nazar-pc Sep 4, 2023
95fc104
Support parameters change in `PotSource` and block import/verificatio…
nazar-pc Sep 4, 2023
6cbde16
Make `PotSource` follow canonical fork of the blockchain and rebase P…
nazar-pc Sep 4, 2023
bd5051a
Refactor `update_next_slot_input` to take updated parameters change v…
nazar-pc Sep 4, 2023
96291ce
Small tweaks in `PotSource` to remove unnecessary `.await`
nazar-pc Sep 4, 2023
521878e
Move `update_next_slot_input` into new struct `PotState` and its meth…
nazar-pc Sep 4, 2023
76744ee
Introduce `PotState::try_extend` to generalize more of state management
nazar-pc Sep 4, 2023
efcc84c
Move `gossip` module under `source`
nazar-pc Sep 4, 2023
5010a4f
Modify `PotState` to become sharable data structure with updates usin…
nazar-pc Sep 4, 2023
fd89282
Move `run_timekeeper` function into `timekeeper` submodule
nazar-pc Sep 5, 2023
4932a4b
Make timekeeper rebase to latest proof of time available if necessary
nazar-pc Sep 5, 2023
cddb769
Fix verification
nazar-pc Sep 6, 2023
fcf7e4d
Tiny formatting change
nazar-pc Sep 6, 2023
e49a68a
Remove timekeeper reorg of PoT chain, only blockchain should do reorg…
nazar-pc Sep 6, 2023
105ac8e
Fix initial slot in proof of time chain verification and improve veri…
nazar-pc Sep 7, 2023
42d4b03
Clean up old checkpoints in slot worker in case PoT chain reorg happened
nazar-pc Sep 7, 2023
d63dd4d
Implementation of block import handling without runtime API in `PotSo…
nazar-pc Sep 7, 2023
1b9ed48
Check proof of time in slot worker to ensure bad proofs of time are n…
nazar-pc Sep 7, 2023
7ed2bd1
Relax reorg check in PoT state
nazar-pc Sep 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/pallet-subspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ mod pallet {
pub(super) type GlobalRandomnesses<T> =
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<T> = StorageValue<_, NonZeroU32>;
Expand Down
35 changes: 33 additions & 2 deletions crates/sc-consensus-subspace/src/import_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,16 +234,47 @@ where
return Ok(CheckedHeader::Deferred(header, slot));
}

#[cfg(feature = "pot")]
let slot_iterations;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put these three variables and two following if-statements under the same cfg-block?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that in some cases, here doing let (slot_iterations, pot_seed, next_slot) = {} would be hardly more readable.

#[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(&parameters_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
{
Expand Down
31 changes: 29 additions & 2 deletions crates/sc-consensus-subspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#![doc = include_str!("../README.md")]
#![feature(try_blocks)]
#![feature(let_chains, try_blocks)]
#![forbid(unsafe_code)]
#![warn(missing_docs)]

Expand Down Expand Up @@ -745,6 +745,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() {
Expand All @@ -759,6 +761,11 @@ where
}
#[cfg(feature = "pot")]
{
slot_iterations = self
.client
.runtime_api()
.pot_parameters(parent_hash)?
.slot_iterations();
pot_seed = self.pot_verifier.genesis_seed();
}

Expand All @@ -782,7 +789,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(&parameters_change.entropy);
} else {
slot_iterations = subspace_digest_items.pot_slot_iterations;
pot_seed = parent_subspace_digest_items
.pre_digest
.pot_info()
Expand All @@ -802,13 +821,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(
pre_digest.slot(),
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
{
Expand Down
2 changes: 1 addition & 1 deletion crates/sc-proof-of-time/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Subspace proof of time implementation.

#![feature(stmt_expr_attributes)]
#![feature(let_chains, stmt_expr_attributes)]

pub mod gossip;
mod slots;
Expand Down
75 changes: 57 additions & 18 deletions crates/sc-proof-of-time/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ pub struct PotSource<Block, Client> {
/// Rough current number of slot iterations used by gossip for verification purposes
#[cfg(feature = "pot")]
current_slot_iterations: Arc<Atomic<NonZeroU32>>,
// TODO: Make this shared with Timekeeper instead so it can follow latest parameters
// automatically, this will implement Timekeeper "reset"
// TODO: Make this shared with Timekeeper so it can follow latest parameters automatically,
// this will help implementing Timekeeper "reset"
next_slot_input: NextSlotInput,
#[cfg(feature = "pot")]
// TODO: Make this shared with Timekeeper so it can follow latest parameters automatically,
// this will help implementing Timekeeper "reset"
parameters_change: Option<PotParametersChange>,
_block: PhantomData<Block>,
}
Expand All @@ -110,13 +112,16 @@ where
{
#[cfg(feature = "pot")]
let chain_constants;
#[cfg(feature = "pot")]
let mut maybe_next_parameters_change;
let start_slot;
let start_seed;
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"))
Expand All @@ -130,17 +135,24 @@ 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(&parameters_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
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"))]
{
Expand Down Expand Up @@ -202,7 +214,7 @@ where
seed: start_seed,
},
#[cfg(feature = "pot")]
parameters_change: None,
parameters_change: maybe_next_parameters_change,
_block: PhantomData,
};

Expand Down Expand Up @@ -353,11 +365,38 @@ where
}

fn update_next_slot_input(&mut self, best_slot: Slot, best_proof: PotProof) {
// TODO: Take `self.parameters_change` into consideration
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(&parameters_change.entropy);

self.parameters_change.take();
} 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: best_slot + Slot::from(1),
slot_iterations: self.next_slot_input.slot_iterations,
seed: best_proof.seed(),
slot: next_slot,
slot_iterations: next_slot_iterations,
seed: next_seed,
};
#[cfg(feature = "pot")]
self.current_slot_iterations
Expand Down
45 changes: 36 additions & 9 deletions crates/sc-proof-of-time/src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -64,14 +66,20 @@ impl PotVerifier {

/// Verify a single proof of time that is `slots` slots away from `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<PotParametersChange>,
) -> bool {
let mut slots = u64::from(slots);

Expand All @@ -90,32 +98,51 @@ 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,
let proof = match result_receiver.await {
Ok(Some(proof)) => proof,
_ => {
return false;
}
};

slots -= 1;
#[cfg(feature = "pot")]
{
slot = slot + Slot::from(1);
}

#[cfg(feature = "pot")]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be probably joined with the previous cfg-block.

Copy link
Copy Markdown
Member Author

@nazar-pc nazar-pc Sep 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then another level of indentation would have to be added and I wanted to avoid that.

if let Some(parameters_change) = maybe_parameters_change
&& parameters_change.slot == slot
{
slot_iterations = parameters_change.slot_iterations;
seed = proof.seed_with_entropy(&parameters_change.entropy);
maybe_parameters_change.take();
} else {
seed = proof.seed();
}
#[cfg(not(feature = "pot"))]
{
seed = proof.seed();
}
}
}

/// 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(
async fn calculate_proof(
&self,
seed: PotSeed,
slot_iterations: NonZeroU32,
) -> Option<PotSeed> {
) -> Option<PotProof> {
let cache_key = CacheKey {
seed,
slot_iterations,
Expand All @@ -128,7 +155,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,
Expand Down Expand Up @@ -178,9 +205,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);
}
}

Expand Down
Loading