From 394d34e2cf53567719fc7b1465894f4b8a038b58 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 28 Aug 2023 13:16:37 +0300 Subject: [PATCH 01/10] Make `PreDigest` versioned --- crates/pallet-subspace/src/lib.rs | 18 +++--- crates/pallet-subspace/src/mock.rs | 2 +- crates/sc-consensus-subspace/src/lib.rs | 47 ++++++++-------- .../sc-consensus-subspace/src/slot_worker.rs | 11 ++-- crates/sp-consensus-subspace/src/digests.rs | 56 +++++++++++++++---- crates/sp-consensus-subspace/src/lib.rs | 16 +++--- crates/sp-consensus-subspace/src/tests.rs | 4 +- crates/sp-lightclient/src/lib.rs | 29 +++++----- crates/sp-lightclient/src/tests.rs | 36 ++++++------ crates/subspace-runtime/src/domains.rs | 2 +- test/subspace-test-runtime/src/lib.rs | 2 +- test/subspace-test-service/src/lib.rs | 2 +- 12 files changed, 134 insertions(+), 91 deletions(-) diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index 52e258d370..995ef116d5 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -785,15 +785,15 @@ impl Pallet { // On the first non-zero block (i.e. block #1) we need to adjust internal storage // accordingly. if *GenesisSlot::::get() == 0 { - GenesisSlot::::put(pre_digest.slot); + GenesisSlot::::put(pre_digest.slot()); debug_assert_ne!(*GenesisSlot::::get(), 0); } // The slot number of the current block being initialized. - CurrentSlot::::put(pre_digest.slot); + CurrentSlot::::put(pre_digest.slot()); { - let farmer_public_key = pre_digest.solution.public_key.clone(); + let farmer_public_key = pre_digest.solution().public_key.clone(); // Optional restriction for block authoring to the root user if !AllowAuthoringByAnyone::::get() { @@ -817,10 +817,10 @@ impl Pallet { let key = ( farmer_public_key, - pre_digest.solution.sector_index, - pre_digest.solution.chunk, - AuditChunkOffset(pre_digest.solution.audit_chunk_offset), - pre_digest.slot, + pre_digest.solution().sector_index, + pre_digest.solution().chunk, + AuditChunkOffset(pre_digest.solution().audit_chunk_offset), + pre_digest.slot(), ); if ParentBlockVoters::::get().contains_key(&key) { let (public_key, _sector_index, _chunk, _audit_chunk_offset, slot) = key; @@ -848,7 +848,7 @@ impl Pallet { chunk, audit_chunk_offset, slot, - pre_digest.solution.reward_address.clone(), + pre_digest.solution().reward_address.clone(), )); } } @@ -883,7 +883,7 @@ impl Pallet { // Extract PoR randomness from pre-digest. #[cfg(not(feature = "pot"))] - let por_randomness = derive_randomness(&pre_digest.solution, pre_digest.slot.into()); + let por_randomness = derive_randomness(pre_digest.solution(), pre_digest.slot().into()); // Store PoR randomness for block duration as it might be useful. #[cfg(not(feature = "pot"))] PorRandomness::::put(por_randomness); diff --git a/crates/pallet-subspace/src/mock.rs b/crates/pallet-subspace/src/mock.rs index ca7ecfa628..ebb634185c 100644 --- a/crates/pallet-subspace/src/mock.rs +++ b/crates/pallet-subspace/src/mock.rs @@ -260,7 +260,7 @@ pub fn make_pre_digest( slot: Slot, solution: Solution::AccountId>, ) -> Digest { - let log = DigestItem::subspace_pre_digest(&PreDigest { + let log = DigestItem::subspace_pre_digest(&PreDigest::V0 { slot, solution, #[cfg(feature = "pot")] diff --git a/crates/sc-consensus-subspace/src/lib.rs b/crates/sc-consensus-subspace/src/lib.rs index 3b82d076de..e761ec4423 100644 --- a/crates/sc-consensus-subspace/src/lib.rs +++ b/crates/sc-consensus-subspace/src/lib.rs @@ -710,7 +710,10 @@ where if self .client .runtime_api() - .is_in_block_list(*block.header.parent_hash(), &pre_digest.solution.public_key) + .is_in_block_list( + *block.header.parent_hash(), + &pre_digest.solution().public_key, + ) .or_else(|error| { if block.state_action.skip_execution_checks() { Ok(false) @@ -722,11 +725,11 @@ where warn!( target: "subspace", "Verifying block with solution provided by farmer in block list: {}", - pre_digest.solution.public_key + pre_digest.solution().public_key ); return Err(Error::::FarmerInBlockList( - pre_digest.solution.public_key.clone(), + pre_digest.solution().public_key.clone(), ) .into()); } @@ -752,7 +755,7 @@ where #[cfg(not(feature = "pot"))] global_randomness: subspace_digest_items.global_randomness, #[cfg(feature = "pot")] - proof_of_time: pre_digest.proof_of_time, + proof_of_time: pre_digest.proof_of_time(), solution_range: subspace_digest_items.solution_range, piece_check_params: None, }, @@ -766,7 +769,7 @@ where match checked_header { CheckedHeader::Checked(pre_header, verified_info) => { - let slot = verified_info.pre_digest.slot; + let slot = verified_info.pre_digest.slot(); // the header is valid but let's check if there was something else already // proposed at the same slot by the given author. if there was, we will @@ -776,7 +779,7 @@ where slot_now, slot, &block.header, - &verified_info.pre_digest.solution.public_key, + &verified_info.pre_digest.solution().public_key, &block.origin, ) .await @@ -902,7 +905,7 @@ where let pre_digest = &subspace_digest_items.pre_digest; if let Some(root_plot_public_key) = root_plot_public_key { - if &pre_digest.solution.public_key != root_plot_public_key { + if &pre_digest.solution().public_key != root_plot_public_key { // Only root plot public key is allowed. return Err(Error::OnlyRootPlotPublicKeyAllowed); } @@ -913,7 +916,7 @@ where if self .client .runtime_api() - .is_in_block_list(parent_hash, &pre_digest.solution.public_key) + .is_in_block_list(parent_hash, &pre_digest.solution().public_key) .or_else(|error| { if skip_runtime_access { Ok(false) @@ -925,11 +928,11 @@ where warn!( target: "subspace", "Ignoring block with solution provided by farmer in block list: {}", - pre_digest.solution.public_key + pre_digest.solution().public_key ); return Err(Error::FarmerInBlockList( - pre_digest.solution.public_key.clone(), + pre_digest.solution().public_key.clone(), )); } @@ -992,8 +995,8 @@ where } let sector_id = SectorId::new( - PublicKey::from(&pre_digest.solution.public_key).hash(), - pre_digest.solution.sector_index, + PublicKey::from(&pre_digest.solution().public_key).hash(), + pre_digest.solution().sector_index, ); let chain_constants = get_chain_constants(self.client.as_ref())?; @@ -1005,8 +1008,8 @@ where .runtime_api() .max_pieces_in_sector(parent_hash)?; let piece_index = sector_id.derive_piece_index( - pre_digest.solution.piece_offset, - pre_digest.solution.history_size, + pre_digest.solution().piece_offset, + pre_digest.solution().history_size, max_pieces_in_sector, chain_constants.recent_segments(), chain_constants.recent_history_fraction(), @@ -1024,7 +1027,7 @@ where .get_segment_header( subspace_digest_items .pre_digest - .solution + .solution() .history_size .sector_expiration_check(chain_constants.min_sector_lifetime()) .ok_or(Error::InvalidHistorySize)? @@ -1035,14 +1038,14 @@ where // Piece is not checked during initial block verification because it requires access to // segment header and runtime, check it now. subspace_verification::verify_solution::( - &pre_digest.solution, + pre_digest.solution(), // Slot was already checked during initial block verification - pre_digest.slot.into(), + pre_digest.slot().into(), &VerifySolutionParams { #[cfg(not(feature = "pot"))] global_randomness: subspace_digest_items.global_randomness, #[cfg(feature = "pot")] - proof_of_time: subspace_digest_items.pre_digest.proof_of_time, + proof_of_time: subspace_digest_items.pre_digest.proof_of_time(), solution_range: subspace_digest_items.solution_range, piece_check_params: Some(PieceCheckParams { max_pieces_in_sector, @@ -1059,13 +1062,13 @@ where }, &self.subspace_link.kzg, ) - .map_err(|error| VerificationError::VerificationError(pre_digest.slot, error))?; + .map_err(|error| VerificationError::VerificationError(pre_digest.slot(), error))?; - let parent_slot = extract_pre_digest(&parent_header).map(|d| d.slot)?; + 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 pre_digest.slot() <= parent_slot { + return Err(Error::SlotMustIncrease(parent_slot, pre_digest.slot())); } if !skip_runtime_access { diff --git a/crates/sc-consensus-subspace/src/slot_worker.rs b/crates/sc-consensus-subspace/src/slot_worker.rs index 50f3413703..07e1eff6cb 100644 --- a/crates/sc-consensus-subspace/src/slot_worker.rs +++ b/crates/sc-consensus-subspace/src/slot_worker.rs @@ -276,7 +276,7 @@ where return None; } }; - let parent_slot = parent_pre_digest.slot; + let parent_slot = parent_pre_digest.slot(); if slot <= parent_slot { debug!( @@ -464,7 +464,7 @@ where // block reward is claimed if maybe_pre_digest.is_none() && solution_distance <= solution_range / 2 { info!(target: "subspace", "🚜 Claimed block at slot {slot}"); - maybe_pre_digest.replace(PreDigest { + maybe_pre_digest.replace(PreDigest::V0 { slot, solution, #[cfg(feature = "pot")] @@ -541,7 +541,7 @@ where let signature = self .sign_reward( H256::from_slice(header_hash.as_ref()), - &pre_digest.solution.public_key, + &pre_digest.solution().public_key, ) .await?; @@ -568,7 +568,8 @@ where fn should_backoff(&self, slot: Slot, chain_head: &Block::Header) -> bool { if let Some(ref strategy) = self.backoff_authoring_blocks { - if let Ok(chain_head_slot) = extract_pre_digest(chain_head).map(|digest| digest.slot) { + if let Ok(chain_head_slot) = extract_pre_digest(chain_head).map(|digest| digest.slot()) + { return strategy.should_backoff( *chain_head.number(), chain_head_slot, @@ -604,7 +605,7 @@ where fn proposing_remaining_duration(&self, slot_info: &SlotInfo) -> std::time::Duration { let parent_slot = extract_pre_digest(&slot_info.chain_head) .ok() - .map(|d| d.slot); + .map(|d| d.slot()); sc_consensus_slots::proposing_remaining_duration( parent_slot, diff --git a/crates/sp-consensus-subspace/src/digests.rs b/crates/sp-consensus-subspace/src/digests.rs index 151366f7b1..8d4fbb0a47 100644 --- a/crates/sp-consensus-subspace/src/digests.rs +++ b/crates/sp-consensus-subspace/src/digests.rs @@ -37,17 +37,53 @@ use subspace_verification::derive_randomness; /// A Subspace pre-runtime digest. This contains all data required to validate a block and for the /// Subspace runtime module. #[derive(Debug, Clone, Encode, Decode)] -pub struct PreDigest { +pub enum PreDigest { + /// Initial version of the pre-digest + #[codec(index = 0)] + V0 { + /// Slot + slot: Slot, + /// Solution (includes PoR) + solution: Solution, + /// Proof of time for this slot + #[cfg(feature = "pot")] + proof_of_time: PotCheckpoint, + /// Future proof of time + #[cfg(feature = "pot")] + future_proof_of_time: PotCheckpoint, + }, +} + +impl PreDigest { /// Slot - pub slot: Slot, + #[inline] + pub fn slot(&self) -> Slot { + let Self::V0 { slot, .. } = self; + *slot + } /// Solution (includes PoR) - pub solution: Solution, + #[inline] + pub fn solution(&self) -> &Solution { + let Self::V0 { solution, .. } = self; + solution + } /// Proof of time for this slot #[cfg(feature = "pot")] - pub proof_of_time: PotCheckpoint, + #[inline] + pub fn proof_of_time(&self) -> PotCheckpoint { + let Self::V0 { proof_of_time, .. } = self; + *proof_of_time + } /// Future proof of time #[cfg(feature = "pot")] - pub future_proof_of_time: PotCheckpoint, + #[inline] + pub fn future_proof_of_time(&self) -> PotCheckpoint { + let Self::V0 { + future_proof_of_time, + .. + } = self; + *future_proof_of_time + } } /// A digest item which is usable with Subspace consensus. @@ -593,7 +629,7 @@ where // genesis block doesn't contain a pre digest so let's generate a // dummy one to not break any invariants in the rest of the code if header.number().is_zero() { - return Ok(PreDigest { + return Ok(PreDigest::V0 { slot: Slot::from(0), solution: Solution::genesis_solution( FarmerPublicKey::unchecked_from([0u8; 32]), @@ -632,8 +668,8 @@ pub fn derive_next_global_randomness( } Some(derive_randomness( - &pre_digest.solution, - pre_digest.slot.into(), + pre_digest.solution(), + pre_digest.slot().into(), )) } @@ -779,7 +815,7 @@ pub fn verify_next_digests( number, era_duration, slot_probability, - current_slot: header_digests.pre_digest.slot, + current_slot: header_digests.pre_digest.slot(), current_solution_range: header_digests.solution_range, era_start_slot, should_adjust_solution_range: *should_adjust_solution_range, @@ -801,7 +837,7 @@ pub fn verify_next_digests( Some(updated_root_plot_public_key) => { if number.is_one() && root_plot_public_key.is_none() - && &header_digests.pre_digest.solution.public_key + && &header_digests.pre_digest.solution().public_key == updated_root_plot_public_key { root_plot_public_key.replace(updated_root_plot_public_key.clone()); diff --git a/crates/sp-consensus-subspace/src/lib.rs b/crates/sp-consensus-subspace/src/lib.rs index eb9ab94823..c5e892b850 100644 --- a/crates/sp-consensus-subspace/src/lib.rs +++ b/crates/sp-consensus-subspace/src/lib.rs @@ -279,17 +279,17 @@ where // both headers must be targeting the same slot and it must // be the same as the one in the proof. - if !(proof.slot == first_pre_digest.slot && proof.slot == second_pre_digest.slot) { + if !(proof.slot == first_pre_digest.slot() && proof.slot == second_pre_digest.slot()) { return false; } // both headers must have the same sector index - if first_pre_digest.solution.sector_index != second_pre_digest.solution.sector_index { + if first_pre_digest.solution().sector_index != second_pre_digest.solution().sector_index { return false; } // both headers must have been authored by the same farmer - if first_pre_digest.solution.public_key != second_pre_digest.solution.public_key { + if first_pre_digest.solution().public_key != second_pre_digest.solution().public_key { return false; } @@ -761,7 +761,7 @@ where None => find_pre_digest::(&header) .ok_or(VerificationError::NoPreRuntimeDigest)?, }; - let slot = pre_digest.slot; + let slot = pre_digest.slot(); let seal = header .digest_mut() @@ -775,16 +775,16 @@ where // The pre-hash of the header doesn't include the seal and that's what we sign let pre_hash = header.hash(); - if pre_digest.slot > slot_now { + if pre_digest.slot() > slot_now { header.digest_mut().push(seal); - return Ok(CheckedHeader::Deferred(header, pre_digest.slot)); + return Ok(CheckedHeader::Deferred(header, pre_digest.slot())); } // Verify that block is signed properly if check_reward_signature( pre_hash.as_ref(), &RewardSignature::from(&signature), - &PublicKey::from(&pre_digest.solution.public_key), + &PublicKey::from(&pre_digest.solution().public_key), reward_signing_context, ) .is_err() @@ -794,7 +794,7 @@ where // Verify that solution is valid verify_solution::( - &pre_digest.solution, + pre_digest.solution(), slot.into(), verify_solution_params, kzg, diff --git a/crates/sp-consensus-subspace/src/tests.rs b/crates/sp-consensus-subspace/src/tests.rs index a0ed44731a..ec70c30a95 100644 --- a/crates/sp-consensus-subspace/src/tests.rs +++ b/crates/sp-consensus-subspace/src/tests.rs @@ -39,7 +39,7 @@ fn test_is_equivocation_proof_valid() { state_root: Default::default(), extrinsics_root: Default::default(), digest: Digest { - logs: vec![DigestItem::subspace_pre_digest(&PreDigest { + logs: vec![DigestItem::subspace_pre_digest(&PreDigest::V0 { slot, solution: solution.clone(), #[cfg(feature = "pot")] @@ -67,7 +67,7 @@ fn test_is_equivocation_proof_valid() { state_root: Default::default(), extrinsics_root: Default::default(), digest: Digest { - logs: vec![DigestItem::subspace_pre_digest(&PreDigest { + logs: vec![DigestItem::subspace_pre_digest(&PreDigest::V0 { slot, solution, #[cfg(feature = "pot")] diff --git a/crates/sp-lightclient/src/lib.rs b/crates/sp-lightclient/src/lib.rs index 8dbfdc75c5..c120755a49 100644 --- a/crates/sp-lightclient/src/lib.rs +++ b/crates/sp-lightclient/src/lib.rs @@ -333,9 +333,9 @@ impl> HeaderImporter { let constants = self.store.chain_constants(); let mut maybe_root_plot_public_key = parent_header.maybe_root_plot_public_key; if let Some(root_plot_public_key) = &maybe_root_plot_public_key { - if root_plot_public_key != &header_digests.pre_digest.solution.public_key { + if root_plot_public_key != &header_digests.pre_digest.solution().public_key { return Err(ImportError::IncorrectBlockAuthor( - header_digests.pre_digest.solution.public_key, + header_digests.pre_digest.solution().public_key.clone(), )); } } @@ -360,20 +360,23 @@ impl> HeaderImporter { Self::verify_slot(&parent_header.header, &header_digests.pre_digest)?; // verify block signature - Self::verify_block_signature(&mut header, &header_digests.pre_digest.solution.public_key)?; + Self::verify_block_signature( + &mut header, + &header_digests.pre_digest.solution().public_key, + )?; // verify solution let sector_id = SectorId::new( - PublicKey::from(&header_digests.pre_digest.solution.public_key).hash(), - header_digests.pre_digest.solution.sector_index, + PublicKey::from(&header_digests.pre_digest.solution().public_key).hash(), + header_digests.pre_digest.solution().sector_index, ); let max_pieces_in_sector = self.store.max_pieces_in_sector(); let segment_index = sector_id .derive_piece_index( - header_digests.pre_digest.solution.piece_offset, - header_digests.pre_digest.solution.history_size, + header_digests.pre_digest.solution().piece_offset, + header_digests.pre_digest.solution().history_size, max_pieces_in_sector, constants.recent_segments, constants.recent_history_fraction, @@ -391,7 +394,7 @@ impl> HeaderImporter { .find_segment_commitment_for_segment_index( header_digests .pre_digest - .solution + .solution() .history_size .sector_expiration_check(constants.min_sector_lifetime) .ok_or(ImportError::InvalidHistorySize)? @@ -400,13 +403,13 @@ impl> HeaderImporter { )?; verify_solution( - (&header_digests.pre_digest.solution).into(), - header_digests.pre_digest.slot.into(), + header_digests.pre_digest.solution().into(), + header_digests.pre_digest.slot().into(), (&VerifySolutionParams { #[cfg(not(feature = "pot"))] global_randomness: header_digests.global_randomness, #[cfg(feature = "pot")] - proof_of_time: header_digests.pre_digest.proof_of_time, + proof_of_time: header_digests.pre_digest.proof_of_time(), solution_range: header_digests.solution_range, piece_check_params: Some(PieceCheckParams { max_pieces_in_sector, @@ -432,7 +435,7 @@ impl> HeaderImporter { // check if era has changed let era_start_slot = if Self::has_era_changed(&header, constants.era_duration) { - header_digests.pre_digest.slot + header_digests.pre_digest.slot() } else { parent_header.era_start_slot }; @@ -531,7 +534,7 @@ impl> HeaderImporter { ) -> Result<(), ImportError
> { let parent_pre_digest = extract_pre_digest(parent_header)?; - if pre_digest.slot <= parent_pre_digest.slot { + if pre_digest.slot() <= parent_pre_digest.slot() { return Err(ImportError::InvalidSlot); } diff --git a/crates/sp-lightclient/src/tests.rs b/crates/sp-lightclient/src/tests.rs index 9a8a7f01dc..e102622dc2 100644 --- a/crates/sp-lightclient/src/tests.rs +++ b/crates/sp-lightclient/src/tests.rs @@ -29,7 +29,7 @@ use subspace_core_primitives::crypto::kzg::Kzg; use subspace_core_primitives::PotCheckpoint; use subspace_core_primitives::{ BlockWeight, HistorySize, PublicKey, Randomness, Record, RecordedHistorySegment, - SegmentCommitment, SegmentIndex, Solution, SolutionRange, + SegmentCommitment, SegmentIndex, SlotNumber, Solution, SolutionRange, }; use subspace_erasure_coding::ErasureCoding; use subspace_farmer_components::auditing::audit_sector; @@ -254,7 +254,7 @@ fn valid_header( let solution_range = solution_distance * 2; let block_weight = calculate_block_weight(solution_range); - let pre_digest = PreDigest { + let pre_digest = PreDigest::V0 { slot: slot.into(), solution, #[cfg(feature = "pot")] @@ -367,7 +367,7 @@ fn add_next_digests(store: &MockStorage, number: NumberOf
, header: &mut number, era_duration: constants.era_duration, slot_probability: constants.slot_probability, - current_slot: digests.pre_digest.slot, + current_slot: digests.pre_digest.slot(), current_solution_range: digests.solution_range, era_start_slot: parent_header.era_start_slot, should_adjust_solution_range: true, @@ -398,13 +398,13 @@ fn add_headers_to_chain( let header = importer.store.header(parent_hash).unwrap(); let digests = extract_pre_digest(&header.header).unwrap(); - (parent_hash, *header.header.number(), digests.slot) + (parent_hash, *header.header.number(), digests.slot()) } else { let digests = extract_pre_digest(&best_header_ext.header).unwrap(); ( best_header_ext.header.hash(), *best_header_ext.header.number(), - digests.slot, + digests.slot(), ) }; @@ -791,7 +791,7 @@ fn test_reorg_to_heavier_smaller_chain() { valid_header(ValidHeaderParams { parent_hash: header_at_2.header.hash(), number: 3, - slot: next_slot(constants.slot_probability, digests_at_2.pre_digest.slot).into(), + slot: next_slot(constants.slot_probability, digests_at_2.pre_digest.slot()).into(), keypair: &keypair, #[cfg(not(feature = "pot"))] global_randomness: digests_at_2.global_randomness, @@ -853,7 +853,7 @@ fn test_next_global_randomness_digest() { valid_header(ValidHeaderParams { parent_hash: header_at_4.header.hash(), number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot).into(), + slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), keypair: &keypair, global_randomness: digests_at_4.global_randomness, farmer_parameters: &farmer_parameters, @@ -880,7 +880,7 @@ fn test_next_global_randomness_digest() { // add next global randomness remove_seal(&mut header); let pre_digest = extract_pre_digest(&header).unwrap(); - let randomness = derive_randomness(&pre_digest.solution, pre_digest.slot.into()); + let randomness = derive_randomness(pre_digest.solution(), pre_digest.slot().into()); let digests = header.digest_mut(); digests.push(DigestItem::next_global_randomness(randomness)); seal_header(&keypair, &mut header); @@ -920,7 +920,7 @@ fn test_next_solution_range_digest_with_adjustment_enabled() { valid_header(ValidHeaderParams { parent_hash: header_at_4.header.hash(), number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot).into(), + slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), keypair: &keypair, global_randomness: digests_at_4.global_randomness, farmer_parameters: &farmer_parameters, @@ -948,8 +948,8 @@ fn test_next_solution_range_digest_with_adjustment_enabled() { // add next solution range remove_seal(&mut header); let next_solution_range = subspace_verification::derive_next_solution_range( - u64::from(header_at_4.era_start_slot), - u64::from(pre_digest.slot), + SlotNumber::from(header_at_4.era_start_slot), + SlotNumber::from(pre_digest.slot()), constants.slot_probability, solution_range, constants.era_duration, @@ -993,7 +993,7 @@ fn test_next_solution_range_digest_with_adjustment_disabled() { valid_header(ValidHeaderParams { parent_hash: header_at_4.header.hash(), number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot).into(), + slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), keypair: &keypair, global_randomness: digests_at_4.global_randomness, farmer_parameters: &farmer_parameters, @@ -1053,7 +1053,7 @@ fn test_enable_solution_range_adjustment_without_override() { valid_header(ValidHeaderParams { parent_hash: header_at_4.header.hash(), number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot).into(), + slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), keypair: &keypair, global_randomness: digests_at_4.global_randomness, farmer_parameters: &farmer_parameters, @@ -1069,8 +1069,8 @@ fn test_enable_solution_range_adjustment_without_override() { .override_cumulative_weight(header_at_4.header.hash(), 0); let pre_digest = extract_pre_digest(&header).unwrap(); let next_solution_range = subspace_verification::derive_next_solution_range( - u64::from(header_at_4.era_start_slot), - u64::from(pre_digest.slot), + SlotNumber::from(header_at_4.era_start_slot), + SlotNumber::from(pre_digest.slot()), constants.slot_probability, solution_range, constants.era_duration, @@ -1122,7 +1122,7 @@ fn test_enable_solution_range_adjustment_with_override_between_update_intervals( valid_header(ValidHeaderParams { parent_hash: header_at_3.header.hash(), number: 4, - slot: next_slot(constants.slot_probability, digests_at_3.pre_digest.slot).into(), + slot: next_slot(constants.slot_probability, digests_at_3.pre_digest.slot()).into(), keypair: &keypair, global_randomness: digests_at_3.global_randomness, farmer_parameters: &farmer_parameters, @@ -1191,7 +1191,7 @@ fn test_enable_solution_range_adjustment_with_override_at_interval_change() { valid_header(ValidHeaderParams { parent_hash: header_at_4.header.hash(), number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot).into(), + slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), keypair: &keypair, global_randomness: digests_at_4.global_randomness, farmer_parameters: &farmer_parameters, @@ -1252,7 +1252,7 @@ fn test_disallow_enable_solution_range_digest_when_solution_range_adjustment_is_ valid_header(ValidHeaderParams { parent_hash: header_at_4.header.hash(), number: 5, - slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot).into(), + slot: next_slot(constants.slot_probability, digests_at_4.pre_digest.slot()).into(), keypair: &keypair, global_randomness: digests_at_4.global_randomness, farmer_parameters: &farmer_parameters, diff --git a/crates/subspace-runtime/src/domains.rs b/crates/subspace-runtime/src/domains.rs index 824e6f6dd5..17aae52dbb 100644 --- a/crates/subspace-runtime/src/domains.rs +++ b/crates/subspace-runtime/src/domains.rs @@ -103,7 +103,7 @@ pub(crate) fn extrinsics_shuffling_seed(header: Block::Header) -> let pre_digest = pre_digest.expect("Header must contain one pre-runtime digest; qed"); let seed: &[u8] = b"extrinsics-shuffling-seed"; - let randomness = derive_randomness(&pre_digest.solution, pre_digest.slot.into()); + let randomness = derive_randomness(pre_digest.solution(), pre_digest.slot().into()); let mut data = Vec::with_capacity(seed.len() + randomness.len()); data.extend_from_slice(seed); data.extend_from_slice(randomness.as_ref()); diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 51b707310f..d0d4283ac9 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -1127,7 +1127,7 @@ fn extrinsics_shuffling_seed(header: Block::Header) -> Randomness let pre_digest = pre_digest.expect("Header must contain one pre-runtime digest; qed"); let seed: &[u8] = b"extrinsics-shuffling-seed"; - let randomness = derive_randomness(&pre_digest.solution, pre_digest.slot.into()); + let randomness = derive_randomness(pre_digest.solution(), pre_digest.slot().into()); let mut data = Vec::with_capacity(seed.len() + randomness.len()); data.extend_from_slice(seed); data.extend_from_slice(randomness.as_ref()); diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index 7bf7902d55..dab4a2f4db 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -596,7 +596,7 @@ impl MockConsensusNode { } fn mock_subspace_digest(&self, slot: Slot) -> Digest { - let pre_digest: PreDigest = PreDigest { + let pre_digest: PreDigest = PreDigest::V0 { slot, solution: self.mock_solution.clone(), #[cfg(feature = "pot")] From d39e77cd5c5c9380ab9fe1855ff599157541a56f Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 28 Aug 2023 14:15:19 +0300 Subject: [PATCH 02/10] Remove old `PotProof` and rename `PotCheckpoint` to `PotProof` --- crates/pallet-subspace/src/lib.rs | 4 +- crates/sp-consensus-subspace/src/digests.rs | 10 +-- crates/sp-lightclient/src/tests.rs | 10 +-- crates/subspace-core-primitives/src/lib.rs | 92 +-------------------- crates/subspace-proof-of-time/src/aes.rs | 10 +-- crates/subspace-proof-of-time/src/lib.rs | 4 +- crates/subspace-verification/src/lib.rs | 4 +- 7 files changed, 25 insertions(+), 109 deletions(-) diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index 995ef116d5..db3784399a 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -68,7 +68,7 @@ use sp_std::collections::btree_map::BTreeMap; use sp_std::prelude::*; use subspace_core_primitives::crypto::Scalar; #[cfg(feature = "pot")] -use subspace_core_primitives::PotCheckpoint; +use subspace_core_primitives::PotProof; use subspace_core_primitives::{ ArchivedHistorySegment, HistorySize, PublicKey, Randomness, RewardSignature, SectorId, SectorIndex, SegmentHeader, SegmentIndex, SolutionRange, @@ -1447,7 +1447,7 @@ fn check_vote( global_randomness: vote_verification_data.global_randomness, // TODO: This is incorrect, find a way to verify votes #[cfg(feature = "pot")] - proof_of_time: PotCheckpoint::default(), + proof_of_time: PotProof::default(), solution_range: vote_verification_data.solution_range, piece_check_params: Some(PieceCheckParams { max_pieces_in_sector: T::MaxPiecesInSector::get(), diff --git a/crates/sp-consensus-subspace/src/digests.rs b/crates/sp-consensus-subspace/src/digests.rs index 8d4fbb0a47..eb0db8b896 100644 --- a/crates/sp-consensus-subspace/src/digests.rs +++ b/crates/sp-consensus-subspace/src/digests.rs @@ -27,7 +27,7 @@ use sp_runtime::DigestItem; use sp_std::collections::btree_map::{BTreeMap, Entry}; use sp_std::fmt; #[cfg(feature = "pot")] -use subspace_core_primitives::PotCheckpoint; +use subspace_core_primitives::PotProof; #[cfg(not(feature = "pot"))] use subspace_core_primitives::Randomness; use subspace_core_primitives::{SegmentCommitment, SegmentIndex, Solution, SolutionRange}; @@ -47,10 +47,10 @@ pub enum PreDigest { solution: Solution, /// Proof of time for this slot #[cfg(feature = "pot")] - proof_of_time: PotCheckpoint, + proof_of_time: PotProof, /// Future proof of time #[cfg(feature = "pot")] - future_proof_of_time: PotCheckpoint, + future_proof_of_time: PotProof, }, } @@ -70,14 +70,14 @@ impl PreDigest { /// Proof of time for this slot #[cfg(feature = "pot")] #[inline] - pub fn proof_of_time(&self) -> PotCheckpoint { + pub fn proof_of_time(&self) -> PotProof { let Self::V0 { proof_of_time, .. } = self; *proof_of_time } /// Future proof of time #[cfg(feature = "pot")] #[inline] - pub fn future_proof_of_time(&self) -> PotCheckpoint { + pub fn future_proof_of_time(&self) -> PotProof { let Self::V0 { future_proof_of_time, .. diff --git a/crates/sp-lightclient/src/tests.rs b/crates/sp-lightclient/src/tests.rs index e102622dc2..21aa800a90 100644 --- a/crates/sp-lightclient/src/tests.rs +++ b/crates/sp-lightclient/src/tests.rs @@ -26,7 +26,7 @@ use subspace_archiving::archiver::{Archiver, NewArchivedSegment}; use subspace_core_primitives::crypto::kzg; use subspace_core_primitives::crypto::kzg::Kzg; #[cfg(feature = "pot")] -use subspace_core_primitives::PotCheckpoint; +use subspace_core_primitives::PotProof; use subspace_core_primitives::{ BlockWeight, HistorySize, PublicKey, Randomness, Record, RecordedHistorySegment, SegmentCommitment, SegmentIndex, SlotNumber, Solution, SolutionRange, @@ -130,9 +130,9 @@ struct ValidHeaderParams<'a> { #[cfg(not(feature = "pot"))] global_randomness: Randomness, #[cfg(feature = "pot")] - proof_of_time: PotCheckpoint, + proof_of_time: PotProof, #[cfg(feature = "pot")] - future_proof_of_time: PotCheckpoint, + future_proof_of_time: PotProof, farmer_parameters: &'a FarmerParameters, } @@ -797,10 +797,10 @@ fn test_reorg_to_heavier_smaller_chain() { global_randomness: digests_at_2.global_randomness, // TODO: Correct value #[cfg(feature = "pot")] - proof_of_time: PotCheckpoint::default(), + proof_of_time: PotProof::default(), // TODO: Correct value #[cfg(feature = "pot")] - future_proof_of_time: PotCheckpoint::default(), + future_proof_of_time: PotProof::default(), farmer_parameters: &farmer_parameters, }); seal_header(&keypair, &mut header); diff --git a/crates/subspace-core-primitives/src/lib.rs b/crates/subspace-core-primitives/src/lib.rs index cb261820f6..ab4437c5df 100644 --- a/crates/subspace-core-primitives/src/lib.rs +++ b/crates/subspace-core-primitives/src/lib.rs @@ -313,9 +313,9 @@ impl PotSeed { TypeInfo, MaxEncodedLen, )] -pub struct PotCheckpoint(PotBytes); +pub struct PotProof(PotBytes); -impl PotCheckpoint { +impl PotProof { /// Derives the global randomness from the output. pub fn derive_global_randomness(&self) -> Randomness { Randomness::from(blake2b_256_hash(&self.0)) @@ -337,7 +337,7 @@ impl PotCheckpoint { TypeInfo, MaxEncodedLen, )] -pub struct PotCheckpoints([PotCheckpoint; Self::NUM_CHECKPOINTS.get() as usize]); +pub struct PotCheckpoints([PotProof; Self::NUM_CHECKPOINTS.get() as usize]); impl PotCheckpoints { /// Number of PoT checkpoints produced (used to optimize verification) @@ -345,95 +345,11 @@ impl PotCheckpoints { /// Get proof of time output out of checkpoints (last checkpoint) #[inline] - pub fn output(&self) -> PotCheckpoint { + pub fn output(&self) -> PotProof { self.0[Self::NUM_CHECKPOINTS.get() as usize - 1] } } -/// Proof of time. -/// TODO: versioning. -#[derive(Debug, Clone, Encode, Decode, Eq, PartialEq)] -pub struct PotProof { - /// Slot the proof was evaluated for. - pub slot_number: SlotNumber, - - /// The seed used for evaluation. - pub seed: PotSeed, - - /// The key used for evaluation. - pub key: PotKey, - - /// The encrypted outputs from each stage. - pub checkpoints: PotCheckpoints, - - /// Hash of last block at injection point. - pub injected_block_hash: BlockHash, -} - -impl PotProof { - /// Create the proof. - pub fn new( - slot_number: SlotNumber, - seed: PotSeed, - key: PotKey, - checkpoints: PotCheckpoints, - injected_block_hash: BlockHash, - ) -> Self { - Self { - slot_number, - seed, - key, - checkpoints, - injected_block_hash, - } - } - - /// Get proof of time output out of checkpoints (last checkpoint) - #[inline] - pub fn output(&self) -> PotCheckpoint { - self.checkpoints.output() - } - - /// Derives the global randomness from the output. - pub fn derive_global_randomness(&self) -> Blake2b256Hash { - blake2b_256_hash(&PotBytes::from(self.output())) - } - - /// Derives the next seed based on the injected randomness. - pub fn next_seed(&self, injected_hash: Option) -> PotSeed { - match injected_hash { - Some(injected_hash) => { - // Next seed = Hash(last checkpoint + injected hash). - let hash = blake2b_256_hash_list(&[&self.output().0, &injected_hash]); - PotSeed::from(truncate_32_bytes(hash)) - } - None => { - // No injected randomness, next seed = last checkpoint. - PotSeed::from(self.output().0) - } - } - } - - /// Derives the next key from the hash of the current seed. - pub fn next_key(&self) -> PotKey { - PotKey::from(truncate_32_bytes(blake2b_256_hash(&self.seed.0))) - } -} - -impl fmt::Display for PotProof { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "PotProof: [slot={}, seed={}, key={}, injected={}, output={}]", - self.slot_number, - hex::encode(self.seed.0), - hex::encode(self.key.0), - hex::encode(self.injected_block_hash), - hex::encode(self.output().as_ref()) - ) - } -} - /// Helper to truncate the 32 bytes to 16 bytes. fn truncate_32_bytes(bytes: [u8; 32]) -> PotBytes { bytes[..core::mem::size_of::()] diff --git a/crates/subspace-proof-of-time/src/aes.rs b/crates/subspace-proof-of-time/src/aes.rs index 45f50f8f18..d6c65e5b21 100644 --- a/crates/subspace-proof-of-time/src/aes.rs +++ b/crates/subspace-proof-of-time/src/aes.rs @@ -9,7 +9,7 @@ extern crate alloc; use aes::cipher::generic_array::GenericArray; use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit}; use aes::Aes128; -use subspace_core_primitives::{PotBytes, PotCheckpoint, PotCheckpoints, PotKey, PotSeed}; +use subspace_core_primitives::{PotBytes, PotCheckpoints, PotKey, PotProof, PotSeed}; /// Creates the AES based proof. #[inline(always)] @@ -35,7 +35,7 @@ fn create_generic(seed: &PotSeed, key: &PotKey, checkpoint_iterations: u32) -> P // Encrypt in place to produce the next block. cipher.encrypt_block(&mut cur_block); } - *checkpoint = PotCheckpoint::from(PotBytes::from(cur_block)); + *checkpoint = PotProof::from(PotBytes::from(cur_block)); } checkpoints @@ -48,7 +48,7 @@ fn create_generic(seed: &PotSeed, key: &PotKey, checkpoint_iterations: u32) -> P pub(crate) fn verify_sequential( seed: &PotSeed, key: &PotKey, - checkpoints: &[PotCheckpoint], + checkpoints: &[PotProof], checkpoint_iterations: u64, ) -> bool { assert_eq!(checkpoint_iterations % 2, 0); @@ -77,7 +77,7 @@ pub(crate) fn verify_sequential( #[cfg(test)] mod tests { use super::*; - use subspace_core_primitives::{PotCheckpoint, PotKey, PotSeed}; + use subspace_core_primitives::{PotKey, PotProof, PotSeed}; const SEED: [u8; 16] = [ 0xd6, 0x66, 0xcc, 0xd8, 0xd5, 0x93, 0xc2, 0x3d, 0xa8, 0xdb, 0x6b, 0x5b, 0x14, 0x13, 0xb1, @@ -122,7 +122,7 @@ mod tests { // Decryption of invalid cipher text fails. let mut checkpoints_1 = checkpoints; - checkpoints_1[0] = PotCheckpoint::from(BAD_CIPHER); + checkpoints_1[0] = PotProof::from(BAD_CIPHER); 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 8f165c24f1..4225581ce0 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::{PotCheckpoint, PotCheckpoints, PotKey, PotSeed}; +use subspace_core_primitives::{PotCheckpoints, PotKey, PotProof, PotSeed}; /// Proof of time error #[derive(Debug)] @@ -57,7 +57,7 @@ pub fn verify( seed: PotSeed, key: PotKey, iterations: NonZeroU64, - checkpoints: &[PotCheckpoint], + checkpoints: &[PotProof], ) -> Result { let num_checkpoints = checkpoints.len() as u64; if iterations.get() % (num_checkpoints * 2) != 0 { diff --git a/crates/subspace-verification/src/lib.rs b/crates/subspace-verification/src/lib.rs index 5371d2cb97..ff9969506e 100644 --- a/crates/subspace-verification/src/lib.rs +++ b/crates/subspace-verification/src/lib.rs @@ -32,7 +32,7 @@ use subspace_core_primitives::crypto::{ blake2b_256_254_hash_to_scalar, blake2b_256_hash_list, blake2b_256_hash_with_key, }; #[cfg(feature = "pot")] -use subspace_core_primitives::PotCheckpoint; +use subspace_core_primitives::PotProof; use subspace_core_primitives::{ Blake2b256Hash, BlockNumber, BlockWeight, HistorySize, PublicKey, Randomness, Record, RewardSignature, SectorId, SectorSlotChallenge, SegmentCommitment, SlotNumber, Solution, @@ -169,7 +169,7 @@ pub struct VerifySolutionParams { pub global_randomness: Randomness, /// Proof of time for which solution is built #[cfg(feature = "pot")] - pub proof_of_time: PotCheckpoint, + pub proof_of_time: PotProof, /// Solution range pub solution_range: SolutionRange, /// Parameters for checking piece validity. From 8d01e579d55f24c4543ba7edba35bfe5b6a3a807 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 28 Aug 2023 14:44:46 +0300 Subject: [PATCH 03/10] Update proof of time data structures for better ergonomics, fix new timekeeper to derive seed and key properly between iterations --- crates/sc-proof-of-time/src/source.rs | 10 +-- crates/subspace-core-primitives/src/lib.rs | 74 +++++++++++++++------- crates/subspace-proof-of-time/src/aes.rs | 54 ++++++++-------- crates/subspace-proof-of-time/src/lib.rs | 8 +-- 4 files changed, 84 insertions(+), 62 deletions(-) diff --git a/crates/sc-proof-of-time/src/source.rs b/crates/sc-proof-of-time/src/source.rs index ad1bbb783d..135fa9a183 100644 --- a/crates/sc-proof-of-time/src/source.rs +++ b/crates/sc-proof-of-time/src/source.rs @@ -5,7 +5,7 @@ use futures::SinkExt; use sp_consensus_slots::Slot; use std::num::NonZeroU32; use std::thread; -use subspace_core_primitives::{PotBytes, PotCheckpoints, PotKey, PotSeed, SlotNumber}; +use subspace_core_primitives::{PotCheckpoints, PotKey, PotSeed, SlotNumber}; use subspace_proof_of_time::PotError; use tracing::{debug, error}; @@ -85,15 +85,11 @@ fn run_timekeeper( iterations: NonZeroU32, mut slot_sender: mpsc::Sender, ) -> Result<(), PotError> { - // TODO loop { let checkpoints = subspace_proof_of_time::prove(seed, key, iterations)?; - // TODO: Store checkpoints somewhere - - // TODO: These two are wrong and need to be updated - seed = PotSeed::from(PotBytes::from(checkpoints.output())); - key = PotKey::from(PotBytes::from(checkpoints.output())); + seed = checkpoints.output().seed(); + key = seed.key(); let slot_info = PotSlotInfo { slot: Slot::from(slot), diff --git a/crates/subspace-core-primitives/src/lib.rs b/crates/subspace-core-primitives/src/lib.rs index ab4437c5df..90d7958495 100644 --- a/crates/subspace-core-primitives/src/lib.rs +++ b/crates/subspace-core-primitives/src/lib.rs @@ -42,7 +42,9 @@ mod tests; extern crate alloc; use crate::crypto::kzg::{Commitment, Witness}; -use crate::crypto::{blake2b_256_hash, blake2b_256_hash_list, blake2b_256_hash_with_key, Scalar}; +use crate::crypto::{ + blake2b_256_hash, blake2b_256_hash_list, blake2b_256_hash_with_key, blake3_hash, Scalar, +}; #[cfg(feature = "serde")] use ::serde::{Deserialize, Serialize}; use alloc::boxed::Box; @@ -82,9 +84,6 @@ pub const BLAKE3_HASH_SIZE: usize = 32; /// BLAKE3 hash output pub type Blake3Hash = [u8; BLAKE3_HASH_SIZE]; -/// 128 bits for the proof of time data types. -pub type PotBytes = [u8; 16]; - /// Type of randomness. #[derive( Debug, @@ -247,29 +246,36 @@ impl PosProof { Eq, PartialEq, From, - Into, AsRef, AsMut, + Deref, + DerefMut, Encode, Decode, TypeInfo, MaxEncodedLen, )] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct PotKey(#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] [u8; 16]); +pub struct PotKey(#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] [u8; Self::SIZE]); impl FromStr for PotKey { type Err = hex::FromHexError; + #[inline] fn from_str(s: &str) -> Result { - let mut bytes = PotBytes::default(); - hex::decode_to_slice(s, &mut bytes)?; + let mut key = Self::default(); + hex::decode_to_slice(s, key.as_mut())?; - Ok(Self(bytes)) + Ok(key) } } -/// Proof of time seed (input to the encryption). +impl PotKey { + /// Size of proof of time key in bytes + pub const SIZE: usize = 16; +} + +/// Proof of time seed #[derive( Debug, Default, @@ -278,21 +284,36 @@ impl FromStr for PotKey { Eq, PartialEq, From, - Into, AsRef, AsMut, + Deref, + DerefMut, Encode, Decode, TypeInfo, MaxEncodedLen, )] -pub struct PotSeed(PotBytes); +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PotSeed(#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] [u8; Self::SIZE]); impl PotSeed { - /// Derive initial PoT seed from genesis block hash. + /// Size of proof of time seed in bytes + pub const SIZE: usize = 16; + + /// Derive initial PoT seed from genesis block hash #[inline] pub fn from_genesis_block_hash(block_hash: BlockHash) -> Self { - Self(truncate_32_bytes(block_hash)) + let mut seed = Self::default(); + seed.copy_from_slice(&block_hash[..Self::SIZE]); + seed + } + + /// Derive key from proof of time seed + #[inline] + pub fn key(&self) -> PotKey { + let mut key = PotKey::default(); + key.copy_from_slice(&blake3_hash(&self.0)[..Self::SIZE]); + key } } @@ -305,21 +326,33 @@ impl PotSeed { Eq, PartialEq, From, - Into, AsRef, AsMut, + Deref, + DerefMut, Encode, Decode, TypeInfo, MaxEncodedLen, )] -pub struct PotProof(PotBytes); +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PotProof(#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] [u8; Self::SIZE]); impl PotProof { - /// Derives the global randomness from the output. + /// Size of proof of time proof in bytes + pub const SIZE: usize = 16; + + /// Derives the global randomness from the output + #[inline] pub fn derive_global_randomness(&self) -> Randomness { Randomness::from(blake2b_256_hash(&self.0)) } + + /// Derive seed from proof of time in case entropy injection is not needed + #[inline] + pub fn seed(&self) -> PotSeed { + PotSeed(self.0) + } } /// Proof of time checkpoints, result of proving @@ -350,13 +383,6 @@ impl PotCheckpoints { } } -/// Helper to truncate the 32 bytes to 16 bytes. -fn truncate_32_bytes(bytes: [u8; 32]) -> PotBytes { - bytes[..core::mem::size_of::()] - .try_into() - .expect("Hash is longer than seed; qed") -} - /// A Ristretto Schnorr public key as bytes produced by `schnorrkel` crate. #[derive( Debug, diff --git a/crates/subspace-proof-of-time/src/aes.rs b/crates/subspace-proof-of-time/src/aes.rs index d6c65e5b21..d9b09b2a82 100644 --- a/crates/subspace-proof-of-time/src/aes.rs +++ b/crates/subspace-proof-of-time/src/aes.rs @@ -9,11 +9,11 @@ extern crate alloc; use aes::cipher::generic_array::GenericArray; use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit}; use aes::Aes128; -use subspace_core_primitives::{PotBytes, PotCheckpoints, PotKey, PotProof, PotSeed}; +use subspace_core_primitives::{PotCheckpoints, PotKey, PotProof, PotSeed}; /// Creates the AES based proof. #[inline(always)] -pub(crate) fn create(seed: &PotSeed, key: &PotKey, checkpoint_iterations: u32) -> PotCheckpoints { +pub(crate) fn create(seed: PotSeed, key: PotKey, checkpoint_iterations: u32) -> PotCheckpoints { #[cfg(target_arch = "x86_64")] { unsafe { x86_64::create(seed.as_ref(), key.as_ref(), checkpoint_iterations) } @@ -24,10 +24,10 @@ pub(crate) fn create(seed: &PotSeed, key: &PotKey, checkpoint_iterations: u32) - #[cfg(any(not(target_arch = "x86_64"), test))] #[inline(always)] -fn create_generic(seed: &PotSeed, key: &PotKey, checkpoint_iterations: u32) -> PotCheckpoints { - let key = GenericArray::from(PotBytes::from(*key)); +fn create_generic(seed: PotSeed, key: PotKey, checkpoint_iterations: u32) -> PotCheckpoints { + let key = GenericArray::from(*key); let cipher = Aes128::new(&key); - let mut cur_block = GenericArray::from(PotBytes::from(*seed)); + let mut cur_block = GenericArray::from(*seed); let mut checkpoints = PotCheckpoints::default(); for checkpoint in checkpoints.iter_mut() { @@ -35,7 +35,7 @@ fn create_generic(seed: &PotSeed, key: &PotKey, checkpoint_iterations: u32) -> P // Encrypt in place to produce the next block. cipher.encrypt_block(&mut cur_block); } - *checkpoint = PotProof::from(PotBytes::from(cur_block)); + checkpoint.copy_from_slice(&cur_block); } checkpoints @@ -46,24 +46,24 @@ fn create_generic(seed: &PotSeed, key: &PotKey, checkpoint_iterations: u32) -> P /// Panics if `checkpoint_iterations` is not a multiple of `2`. #[inline(always)] pub(crate) fn verify_sequential( - seed: &PotSeed, - key: &PotKey, + seed: PotSeed, + key: PotKey, checkpoints: &[PotProof], checkpoint_iterations: u64, ) -> bool { assert_eq!(checkpoint_iterations % 2, 0); - let key = GenericArray::from(PotBytes::from(*key)); + let key = GenericArray::from(*key); let cipher = Aes128::new(&key); let mut inputs = Vec::with_capacity(checkpoints.len()); - inputs.push(GenericArray::from(PotBytes::from(*seed))); - for checkpoint in checkpoints.iter().rev().skip(1).rev() { - inputs.push(GenericArray::from(PotBytes::from(*checkpoint))); + inputs.push(GenericArray::from(*seed)); + for &checkpoint in checkpoints.iter().rev().skip(1).rev() { + inputs.push(GenericArray::from(*checkpoint)); } let mut outputs = checkpoints .iter() - .map(|checkpoint| GenericArray::from(PotBytes::from(*checkpoint))) + .map(|&checkpoint| GenericArray::from(*checkpoint)) .collect::>(); for _ in 0..checkpoint_iterations / 2 { @@ -104,18 +104,18 @@ mod tests { let checkpoint_iterations = 100; // Can encrypt/decrypt. - let checkpoints = create(&seed, &key, checkpoint_iterations); + let checkpoints = create(seed, key, checkpoint_iterations); #[cfg(target_arch = "x86_64")] { - let generic_checkpoints = create_generic(&seed, &key, checkpoint_iterations); + let generic_checkpoints = create_generic(seed, key, checkpoint_iterations); assert_eq!(checkpoints, generic_checkpoints); } let checkpoint_iterations = u64::from(checkpoint_iterations); assert!(verify_sequential( - &seed, - &key, + seed, + key, &*checkpoints, checkpoint_iterations )); @@ -124,38 +124,38 @@ mod tests { let mut checkpoints_1 = checkpoints; checkpoints_1[0] = PotProof::from(BAD_CIPHER); assert!(!verify_sequential( - &seed, - &key, + seed, + key, &*checkpoints_1, checkpoint_iterations )); // Decryption with wrong number of iterations fails. assert!(!verify_sequential( - &seed, - &key, + seed, + key, &*checkpoints, checkpoint_iterations + 2 )); assert!(!verify_sequential( - &seed, - &key, + seed, + key, &*checkpoints, checkpoint_iterations - 2 )); // Decryption with wrong seed fails. assert!(!verify_sequential( - &PotSeed::from(SEED_1), - &key, + PotSeed::from(SEED_1), + key, &*checkpoints, checkpoint_iterations )); // Decryption with wrong key fails. assert!(!verify_sequential( - &seed, - &PotKey::from(KEY_1), + seed, + PotKey::from(KEY_1), &*checkpoints, checkpoint_iterations )); diff --git a/crates/subspace-proof-of-time/src/lib.rs b/crates/subspace-proof-of-time/src/lib.rs index 4225581ce0..3397577b29 100644 --- a/crates/subspace-proof-of-time/src/lib.rs +++ b/crates/subspace-proof-of-time/src/lib.rs @@ -43,8 +43,8 @@ pub fn prove( } Ok(aes::create( - &seed, - &key, + seed, + key, iterations.get() / u32::from(PotCheckpoints::NUM_CHECKPOINTS.get()), )) } @@ -68,8 +68,8 @@ pub fn verify( } Ok(aes::verify_sequential( - &seed, - &key, + seed, + key, checkpoints, iterations.get() / num_checkpoints, )) From 091316e1b1b9333290d20963712b79252721327e Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 28 Aug 2023 15:21:54 +0300 Subject: [PATCH 04/10] Introduce `PreDigestPotInfo` that holds PoT-related information for the block --- crates/pallet-subspace/src/mock.rs | 10 +++- crates/sc-consensus-subspace/src/lib.rs | 4 +- .../sc-consensus-subspace/src/slot_worker.rs | 13 ++++- crates/sp-consensus-subspace/src/digests.rs | 58 +++++++++++++------ crates/sp-consensus-subspace/src/lib.rs | 1 + crates/sp-consensus-subspace/src/tests.rs | 20 +++++-- crates/sp-lightclient/src/lib.rs | 2 +- test/subspace-test-service/src/lib.rs | 12 +++- 8 files changed, 83 insertions(+), 37 deletions(-) diff --git a/crates/pallet-subspace/src/mock.rs b/crates/pallet-subspace/src/mock.rs index ebb634185c..bdd477f9fe 100644 --- a/crates/pallet-subspace/src/mock.rs +++ b/crates/pallet-subspace/src/mock.rs @@ -28,6 +28,8 @@ use futures::executor::block_on; use rand::Rng; use schnorrkel::Keypair; use sp_consensus_slots::Slot; +#[cfg(feature = "pot")] +use sp_consensus_subspace::digests::PreDigestPotInfo; use sp_consensus_subspace::digests::{CompatibleDigestItem, PreDigest}; use sp_consensus_subspace::{FarmerSignature, KzgExtension, PosExtension, SignedVote, Vote}; use sp_core::crypto::UncheckedFrom; @@ -264,9 +266,11 @@ pub fn make_pre_digest( slot, solution, #[cfg(feature = "pot")] - proof_of_time: Default::default(), - #[cfg(feature = "pot")] - future_proof_of_time: Default::default(), + pot_info: PreDigestPotInfo::Regular { + iterations: NonZeroU32::new(100_000).unwrap(), + proof_of_time: Default::default(), + future_proof_of_time: Default::default(), + }, }); Digest { logs: vec![log] } } diff --git a/crates/sc-consensus-subspace/src/lib.rs b/crates/sc-consensus-subspace/src/lib.rs index e761ec4423..28c8798cdd 100644 --- a/crates/sc-consensus-subspace/src/lib.rs +++ b/crates/sc-consensus-subspace/src/lib.rs @@ -755,7 +755,7 @@ where #[cfg(not(feature = "pot"))] global_randomness: subspace_digest_items.global_randomness, #[cfg(feature = "pot")] - proof_of_time: pre_digest.proof_of_time(), + proof_of_time: pre_digest.pot_info().proof_of_time(), solution_range: subspace_digest_items.solution_range, piece_check_params: None, }, @@ -1045,7 +1045,7 @@ where #[cfg(not(feature = "pot"))] global_randomness: subspace_digest_items.global_randomness, #[cfg(feature = "pot")] - proof_of_time: subspace_digest_items.pre_digest.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: Some(PieceCheckParams { max_pieces_in_sector, diff --git a/crates/sc-consensus-subspace/src/slot_worker.rs b/crates/sc-consensus-subspace/src/slot_worker.rs index 07e1eff6cb..ede7755539 100644 --- a/crates/sc-consensus-subspace/src/slot_worker.rs +++ b/crates/sc-consensus-subspace/src/slot_worker.rs @@ -40,6 +40,8 @@ use sp_api::{ApiError, NumberFor, ProvideRuntimeApi, TransactionFor}; use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; use sp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SyncOracle}; use sp_consensus_slots::Slot; +#[cfg(feature = "pot")] +use sp_consensus_subspace::digests::PreDigestPotInfo; use sp_consensus_subspace::digests::{extract_pre_digest, CompatibleDigestItem, PreDigest}; #[cfg(feature = "pot")] use sp_consensus_subspace::SubspaceJustification; @@ -52,6 +54,8 @@ use sp_runtime::DigestItem; use std::collections::BTreeMap; use std::future::Future; use std::marker::PhantomData; +#[cfg(feature = "pot")] +use std::num::NonZeroU32; use std::pin::Pin; use std::sync::Arc; #[cfg(feature = "pot")] @@ -468,9 +472,12 @@ where slot, solution, #[cfg(feature = "pot")] - proof_of_time, - #[cfg(feature = "pot")] - future_proof_of_time, + pot_info: PreDigestPotInfo::Regular { + // TODO: Replace with correct value from runtime state + iterations: NonZeroU32::MIN, + proof_of_time, + future_proof_of_time, + }, }); } else if !parent_header.number().is_zero() { // Not sending vote on top of genesis block since segment headers since piece diff --git a/crates/sp-consensus-subspace/src/digests.rs b/crates/sp-consensus-subspace/src/digests.rs index eb0db8b896..27200799bd 100644 --- a/crates/sp-consensus-subspace/src/digests.rs +++ b/crates/sp-consensus-subspace/src/digests.rs @@ -27,6 +27,8 @@ use sp_runtime::DigestItem; 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; @@ -45,12 +47,9 @@ pub enum PreDigest { slot: Slot, /// Solution (includes PoR) solution: Solution, - /// Proof of time for this slot + /// Proof of time information #[cfg(feature = "pot")] - proof_of_time: PotProof, - /// Future proof of time - #[cfg(feature = "pot")] - future_proof_of_time: PotProof, + pot_info: PreDigestPotInfo, }, } @@ -61,28 +60,47 @@ impl PreDigest { let Self::V0 { slot, .. } = self; *slot } + /// Solution (includes PoR) #[inline] pub fn solution(&self) -> &Solution { let Self::V0 { solution, .. } = self; solution } - /// Proof of time for this slot + + /// Proof of time information #[cfg(feature = "pot")] #[inline] - pub fn proof_of_time(&self) -> PotProof { - let Self::V0 { proof_of_time, .. } = self; - *proof_of_time + pub fn pot_info(&self) -> &PreDigestPotInfo { + let Self::V0 { pot_info, .. } = self; + pot_info } - /// Future proof of time +} + +/// Proof of time information in pre-digest +#[cfg(feature = "pot")] +#[derive(Debug, Clone, Encode, Decode)] +pub enum PreDigestPotInfo { + /// Regular information + #[codec(index = 0)] + Regular { + /// Number of iterations per slot in proof of time + iterations: NonZeroU32, + /// Proof of time for this slot + proof_of_time: PotProof, + /// Future proof of time + future_proof_of_time: PotProof, + }, +} + +#[cfg(feature = "pot")] +impl PreDigestPotInfo { + /// Proof of time for this slot #[cfg(feature = "pot")] #[inline] - pub fn future_proof_of_time(&self) -> PotProof { - let Self::V0 { - future_proof_of_time, - .. - } = self; - *future_proof_of_time + pub fn proof_of_time(&self) -> PotProof { + let Self::Regular { proof_of_time, .. } = self; + *proof_of_time } } @@ -636,9 +654,11 @@ where FarmerPublicKey::unchecked_from([0u8; 32]), ), #[cfg(feature = "pot")] - proof_of_time: Default::default(), - #[cfg(feature = "pot")] - future_proof_of_time: Default::default(), + pot_info: PreDigestPotInfo::Regular { + iterations: NonZeroU32::MIN, + proof_of_time: Default::default(), + future_proof_of_time: Default::default(), + }, }); } diff --git a/crates/sp-consensus-subspace/src/lib.rs b/crates/sp-consensus-subspace/src/lib.rs index c5e892b850..7049af5d49 100644 --- a/crates/sp-consensus-subspace/src/lib.rs +++ b/crates/sp-consensus-subspace/src/lib.rs @@ -340,6 +340,7 @@ impl Default for SolutionRanges { #[derive(Debug, Encode, Decode, MaxEncodedLen, PartialEq, Eq, Clone, Copy, TypeInfo)] pub enum ChainConstants { /// V0 of the chain constants. + #[codec(index = 0)] V0 { /// Depth `K` after which a block enters the recorded history. confirmation_depth_k: BlockNumber, diff --git a/crates/sp-consensus-subspace/src/tests.rs b/crates/sp-consensus-subspace/src/tests.rs index ec70c30a95..b97829a9e9 100644 --- a/crates/sp-consensus-subspace/src/tests.rs +++ b/crates/sp-consensus-subspace/src/tests.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "pot")] +use crate::digests::PreDigestPotInfo; use crate::{ is_equivocation_proof_valid, CompatibleDigestItem, EquivocationProof, FarmerPublicKey, FarmerSignature, @@ -7,6 +9,8 @@ use sp_consensus_slots::Slot; use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::BlakeTwo256; use sp_runtime::{Digest, DigestItem}; +#[cfg(feature = "pot")] +use std::num::NonZeroU32; use std::num::NonZeroU64; use subspace_core_primitives::{HistorySize, PieceOffset, Solution}; use subspace_solving::REWARD_SIGNING_CONTEXT; @@ -43,9 +47,11 @@ fn test_is_equivocation_proof_valid() { slot, solution: solution.clone(), #[cfg(feature = "pot")] - proof_of_time: Default::default(), - #[cfg(feature = "pot")] - future_proof_of_time: Default::default(), + pot_info: PreDigestPotInfo::Regular { + iterations: NonZeroU32::MIN, + proof_of_time: Default::default(), + future_proof_of_time: Default::default(), + }, })], }, }; @@ -71,9 +77,11 @@ fn test_is_equivocation_proof_valid() { slot, solution, #[cfg(feature = "pot")] - proof_of_time: Default::default(), - #[cfg(feature = "pot")] - future_proof_of_time: Default::default(), + pot_info: PreDigestPotInfo::Regular { + iterations: NonZeroU32::MIN, + proof_of_time: Default::default(), + future_proof_of_time: Default::default(), + }, })], }, }; diff --git a/crates/sp-lightclient/src/lib.rs b/crates/sp-lightclient/src/lib.rs index c120755a49..981943a711 100644 --- a/crates/sp-lightclient/src/lib.rs +++ b/crates/sp-lightclient/src/lib.rs @@ -409,7 +409,7 @@ impl> HeaderImporter { #[cfg(not(feature = "pot"))] global_randomness: header_digests.global_randomness, #[cfg(feature = "pot")] - proof_of_time: header_digests.pre_digest.proof_of_time(), + proof_of_time: header_digests.pre_digest.pot_info().proof_of_time(), solution_range: header_digests.solution_range, piece_check_params: Some(PieceCheckParams { max_pieces_in_sector, diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index dab4a2f4db..f89090d2ff 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -52,6 +52,8 @@ use sp_application_crypto::UncheckedFrom; use sp_blockchain::HeaderBackend; use sp_consensus::{BlockOrigin, Error as ConsensusError}; use sp_consensus_slots::Slot; +#[cfg(feature = "pot")] +use sp_consensus_subspace::digests::PreDigestPotInfo; use sp_consensus_subspace::digests::{CompatibleDigestItem, PreDigest}; use sp_consensus_subspace::FarmerPublicKey; use sp_core::traits::SpawnEssentialNamed; @@ -66,6 +68,8 @@ use sp_runtime::{DigestItem, OpaqueExtrinsic}; use sp_timestamp::Timestamp; use std::error::Error; use std::marker::PhantomData; +#[cfg(feature = "pot")] +use std::num::NonZeroU32; use std::sync::Arc; use std::time; use subspace_core_primitives::{Randomness, Solution}; @@ -600,9 +604,11 @@ impl MockConsensusNode { slot, solution: self.mock_solution.clone(), #[cfg(feature = "pot")] - proof_of_time: Default::default(), - #[cfg(feature = "pot")] - future_proof_of_time: Default::default(), + pot_info: PreDigestPotInfo::Regular { + iterations: NonZeroU32::MIN, + proof_of_time: Default::default(), + future_proof_of_time: Default::default(), + }, }; let mut digest = Digest::default(); digest.push(DigestItem::subspace_pre_digest(&pre_digest)); From 975bdefb0ee77f75c83db893bf51b0f08eaddbf7 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 28 Aug 2023 20:50:19 +0300 Subject: [PATCH 05/10] Introduce notion of iterations for proof of time into `pallet-subspace` and tune it slightly lower for local/devnet for better dev experience --- Cargo.toml | 2 + crates/pallet-subspace/src/lib.rs | 37 ++++++++++++-- crates/pallet-subspace/src/mock.rs | 13 +++-- .../sc-consensus-subspace/src/slot_worker.rs | 8 ++- crates/sc-proof-of-time/src/lib.rs | 4 -- crates/sc-proof-of-time/src/source.rs | 49 +++++++++++++++---- crates/sp-consensus-subspace/src/lib.rs | 5 ++ crates/subspace-node/src/chain_spec.rs | 9 ++++ crates/subspace-runtime/src/lib.rs | 22 ++++++--- crates/subspace-service/src/lib.rs | 4 +- test/subspace-test-client/src/chain_spec.rs | 2 + test/subspace-test-runtime/src/lib.rs | 22 ++++++--- 12 files changed, 132 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 16ac35c337..b1f89fb27c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,12 +64,14 @@ sha2 = { opt-level = 3 } sha3 = { opt-level = 3 } smallvec = { opt-level = 3 } snow = { opt-level = 3 } +sc-proof-of-time = { opt-level = 3 } subspace-archiving = { opt-level = 3 } subspace-chiapos = { opt-level = 3 } subspace-core-primitives = { opt-level = 3 } subspace-erasure-coding = { opt-level = 3 } subspace-farmer-components = { opt-level = 3 } subspace-proof-of-space = { opt-level = 3 } +subspace-proof-of-time = { opt-level = 3 } twox-hash = { opt-level = 3 } uint = { opt-level = 3 } x25519-dalek = { opt-level = 3 } diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index db3784399a..2d0b92a342 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -65,6 +65,7 @@ use sp_runtime::transaction_validity::{ }; use sp_runtime::DispatchError; use sp_std::collections::btree_map::BTreeMap; +use sp_std::num::NonZeroU32; use sp_std::prelude::*; use subspace_core_primitives::crypto::Scalar; #[cfg(feature = "pot")] @@ -148,6 +149,7 @@ mod pallet { use sp_consensus_subspace::{EquivocationProof, FarmerPublicKey, FarmerSignature, SignedVote}; use sp_runtime::DigestItem; use sp_std::collections::btree_map::BTreeMap; + use sp_std::num::NonZeroU32; use sp_std::prelude::*; use subspace_core_primitives::crypto::Scalar; use subspace_core_primitives::{ @@ -299,16 +301,16 @@ mod pallet { pub enable_storage_access: bool, /// Who can author blocks at genesis. pub allow_authoring_by: AllowAuthoringBy, + /// Number of iterations for proof of time per slot + pub pot_slot_iterations: NonZeroU32, } impl Default for GenesisConfig { #[inline] fn default() -> Self { - Self { - enable_rewards: true, - enable_storage_access: true, - allow_authoring_by: AllowAuthoringBy::Anyone, - } + // TODO: Remove once https://github.com/paritytech/polkadot-sdk/pull/1221 is in our + // fork + unreachable!("Config must be initialized explicitly"); } } @@ -331,6 +333,7 @@ mod pallet { RootPlotPublicKey::::put(root_farmer.clone()); } } + PotSlotIterations::::put(self.pot_slot_iterations.get()); } } @@ -376,6 +379,22 @@ mod pallet { pub(super) type GlobalRandomnesses = StorageValue<_, sp_consensus_subspace::GlobalRandomnesses, ValueQuery>; + pub(super) struct DefaultPotSlotIterations {} + + // TODO: Replace with `NonZeroU32` once we can use it: + // https://github.com/paritytech/parity-scale-codec/pull/505 + impl Get for DefaultPotSlotIterations { + fn get() -> u32 { + unreachable!("Always instantiated during genesis; qed"); + } + } + + /// Number of iterations for proof of time per slot + #[pallet::storage] + // #[pallet::getter(fn pot_slot_iterations)] + pub(super) type PotSlotIterations = + StorageValue<_, u32, ValueQuery, DefaultPotSlotIterations>; + /// Solution ranges used for challenges. #[pallet::storage] #[pallet::getter(fn solution_ranges)] @@ -1064,6 +1083,14 @@ impl Pallet { Some(()) } + /// Number of iterations for proof of time per slot + // TODO: Remove once we can use `NonZeroU32` directly: + // https://github.com/paritytech/parity-scale-codec/pull/505 + pub fn pot_slot_iterations() -> NonZeroU32 { + NonZeroU32::new(PotSlotIterations::::get()) + .expect("Always initialized to non-zero value; qed") + } + /// Check if `farmer_public_key` is in block list (due to equivocation) pub fn is_in_block_list(farmer_public_key: &FarmerPublicKey) -> bool { BlockList::::contains_key(farmer_public_key) diff --git a/crates/pallet-subspace/src/mock.rs b/crates/pallet-subspace/src/mock.rs index bdd477f9fe..facd4a57fb 100644 --- a/crates/pallet-subspace/src/mock.rs +++ b/crates/pallet-subspace/src/mock.rs @@ -18,8 +18,8 @@ use crate::equivocation::EquivocationHandler; use crate::{ - self as pallet_subspace, Config, CurrentSlot, FarmerPublicKey, NormalEraChange, - NormalGlobalRandomnessInterval, + self as pallet_subspace, AllowAuthoringBy, Config, CurrentSlot, FarmerPublicKey, + NormalEraChange, NormalGlobalRandomnessInterval, }; use frame_support::pallet_prelude::Weight; use frame_support::parameter_types; @@ -39,7 +39,7 @@ use sp_runtime::testing::{Digest, DigestItem, Header, TestXt}; use sp_runtime::traits::{Block as BlockT, Header as _, IdentityLookup}; use sp_runtime::Perbill; use std::iter; -use std::num::NonZeroU64; +use std::num::{NonZeroU32, NonZeroU64}; use std::sync::Once; use subspace_archiving::archiver::{Archiver, NewArchivedSegment}; use subspace_core_primitives::crypto::kzg::{embedded_kzg_settings, Kzg}; @@ -286,7 +286,12 @@ pub fn new_test_ext() -> TestExternalities { .unwrap(); GenesisBuild::::assimilate_storage( - &pallet_subspace::GenesisConfig::default(), + &pallet_subspace::GenesisConfig { + enable_rewards: true, + enable_storage_access: true, + allow_authoring_by: AllowAuthoringBy::Anyone, + pot_slot_iterations: NonZeroU32::new(100_000).unwrap(), + }, &mut storage, ) .unwrap(); diff --git a/crates/sc-consensus-subspace/src/slot_worker.rs b/crates/sc-consensus-subspace/src/slot_worker.rs index ede7755539..46bcb1d483 100644 --- a/crates/sc-consensus-subspace/src/slot_worker.rs +++ b/crates/sc-consensus-subspace/src/slot_worker.rs @@ -54,8 +54,6 @@ use sp_runtime::DigestItem; use std::collections::BTreeMap; use std::future::Future; use std::marker::PhantomData; -#[cfg(feature = "pot")] -use std::num::NonZeroU32; use std::pin::Pin; use std::sync::Arc; #[cfg(feature = "pot")] @@ -299,9 +297,10 @@ where let (solution_range, voting_solution_range) = extract_solution_ranges_for_block(self.client.as_ref(), parent_hash).ok()?; + #[cfg(feature = "pot")] + let pot_slot_iterations = runtime_api.pot_slot_iterations(parent_hash).ok()?; let maybe_root_plot_public_key = runtime_api.root_plot_public_key(parent_hash).ok()?; - // TODO: Store `new_checkpoints` #[cfg(feature = "pot")] let (proof_of_time, future_proof_of_time, new_checkpoints) = { let mut pot_checkpoints = self.pot_checkpoints.lock(); @@ -473,8 +472,7 @@ where solution, #[cfg(feature = "pot")] pot_info: PreDigestPotInfo::Regular { - // TODO: Replace with correct value from runtime state - iterations: NonZeroU32::MIN, + iterations: pot_slot_iterations, proof_of_time, future_proof_of_time, }, diff --git a/crates/sc-proof-of-time/src/lib.rs b/crates/sc-proof-of-time/src/lib.rs index bf40501f87..0780983c19 100644 --- a/crates/sc-proof-of-time/src/lib.rs +++ b/crates/sc-proof-of-time/src/lib.rs @@ -107,10 +107,6 @@ pub async fn start_slot_worker( if let Some(slot_info) = slot_info_producer.produce_slot_info(slot_to_claim).await { let _ = worker.on_slot(slot_info).await; - - // TODO: Remove this hack, it restricts slot production with extremely low number of - // iterations - tokio::time::sleep(std::time::Duration::from_secs(1)).await; } } } diff --git a/crates/sc-proof-of-time/src/source.rs b/crates/sc-proof-of-time/src/source.rs index 135fa9a183..f604516b99 100644 --- a/crates/sc-proof-of-time/src/source.rs +++ b/crates/sc-proof-of-time/src/source.rs @@ -2,8 +2,14 @@ use derive_more::{Deref, DerefMut, From}; use futures::channel::mpsc; use futures::executor::block_on; use futures::SinkExt; +use sp_api::{ApiError, ProvideRuntimeApi}; +use sp_blockchain::HeaderBackend; use sp_consensus_slots::Slot; +use sp_consensus_subspace::{FarmerPublicKey, SubspaceApi as SubspaceRuntimeApi}; +use sp_runtime::traits::Block as BlockT; +use std::marker::PhantomData; use std::num::NonZeroU32; +use std::sync::Arc; use std::thread; use subspace_core_primitives::{PotCheckpoints, PotKey, PotSeed, SlotNumber}; use subspace_proof_of_time::PotError; @@ -35,18 +41,40 @@ pub struct PotSourceConfig { /// Depending on configuration may produce proofs of time locally, send/receive via gossip and keep /// up to day with blockchain reorgs. #[derive(Debug)] -pub struct PotSource { - // TODO +pub struct PotSource { + // TODO: Use this in `fn run` + #[allow(dead_code)] + client: Arc, + _block: PhantomData, } -impl PotSource { - pub fn new(config: PotSourceConfig) -> (Self, PotSlotInfoStream) { +impl PotSource +where + Block: BlockT, + Client: ProvideRuntimeApi + HeaderBackend, + Client::Api: SubspaceRuntimeApi, +{ + pub fn new( + config: PotSourceConfig, + client: Arc, + ) -> Result<(Self, PotSlotInfoStream), ApiError> { + let PotSourceConfig { + // TODO: Respect this boolean flag + is_timekeeper: _, + initial_key, + } = config; // TODO: All 3 are incorrect and should be able to continue after node restart let start_slot = SlotNumber::MIN; let start_seed = PotSeed::default(); - let start_key = config.initial_key; - // TODO: Change to correct values taken from blockchain - let iterations = NonZeroU32::new(1024).expect("Not zero; qed"); + let start_key = initial_key; + #[cfg(feature = "pot")] + let best_hash = client.info().best_hash; + #[cfg(feature = "pot")] + let runtime_api = client.runtime_api(); + #[cfg(feature = "pot")] + let iterations = runtime_api.pot_slot_iterations(best_hash)?; + #[cfg(not(feature = "pot"))] + let iterations = NonZeroU32::new(100_000_000).expect("Not zero; qed"); // TODO: Correct capacity let (slot_sender, slot_receiver) = mpsc::channel(10); @@ -61,12 +89,13 @@ impl PotSource { }) .expect("Thread creation must not panic"); - ( + Ok(( Self { - // TODO + client, + _block: PhantomData, }, PotSlotInfoStream(slot_receiver), - ) + )) } /// Run proof of time source diff --git a/crates/sp-consensus-subspace/src/lib.rs b/crates/sp-consensus-subspace/src/lib.rs index 7049af5d49..2c09cbbaf0 100644 --- a/crates/sp-consensus-subspace/src/lib.rs +++ b/crates/sp-consensus-subspace/src/lib.rs @@ -41,6 +41,8 @@ use sp_io::hashing; use sp_runtime::{ConsensusEngineId, DigestItem, Justification}; use sp_runtime_interface::pass_by::PassBy; use sp_runtime_interface::{pass_by, runtime_interface}; +#[cfg(feature = "pot")] +use sp_std::num::NonZeroU32; use sp_std::vec::Vec; use subspace_core_primitives::crypto::kzg::Kzg; #[cfg(not(feature = "pot"))] @@ -621,6 +623,9 @@ sp_api::decl_runtime_apis! { /// The slot duration in milliseconds for Subspace. fn slot_duration() -> SlotDuration; + /// Number of iterations for proof of time per slot + fn pot_slot_iterations() -> NonZeroU32; + /// Solution ranges. fn solution_ranges() -> SolutionRanges; diff --git a/crates/subspace-node/src/chain_spec.rs b/crates/subspace-node/src/chain_spec.rs index 0a979acb2a..944c53c6e3 100644 --- a/crates/subspace-node/src/chain_spec.rs +++ b/crates/subspace-node/src/chain_spec.rs @@ -25,6 +25,7 @@ use sp_consensus_subspace::FarmerPublicKey; use sp_core::crypto::{Ss58Codec, UncheckedFrom}; use sp_domains::RuntimeType; use sp_runtime::Percent; +use std::num::NonZeroU32; use subspace_core_primitives::PotKey; use subspace_runtime::{ AllowAuthoringBy, BalancesConfig, DomainsConfig, GenesisConfig, MaxDomainBlockSize, @@ -81,6 +82,7 @@ struct GenesisParams { enable_rewards: bool, enable_storage_access: bool, allow_authoring_by: AllowAuthoringBy, + pot_slot_iterations: NonZeroU32, enable_domains: bool, enable_transfer: bool, confirmation_depth_k: u32, @@ -149,6 +151,8 @@ pub fn gemini_3f_compiled() -> Result, String> "8aecbcf0b404590ddddc01ebacb205a562d12fdb5c2aa6a4035c1a20f23c9515" )), ), + // TODO: Adjust once we bench PoT on faster hardware + pot_slot_iterations: NonZeroU32::new(183_270_000).expect("Not zero; qed"), enable_domains: true, enable_transfer: false, confirmation_depth_k: 100, // TODO: Proper value here @@ -246,6 +250,7 @@ pub fn devnet_config_compiled() -> Result, Str enable_rewards: false, enable_storage_access: false, allow_authoring_by: AllowAuthoringBy::FirstFarmer, + pot_slot_iterations: NonZeroU32::new(100_000_000).expect("Not zero; qed"), enable_domains: true, enable_transfer: true, confirmation_depth_k: 100, // TODO: Proper value here @@ -303,6 +308,7 @@ pub fn dev_config() -> Result, String> { enable_rewards: false, enable_storage_access: false, allow_authoring_by: AllowAuthoringBy::Anyone, + pot_slot_iterations: NonZeroU32::new(150_000_000).expect("Not zero; qed"), enable_domains: true, enable_transfer: true, confirmation_depth_k: 5, @@ -365,6 +371,7 @@ pub fn local_config() -> Result, String> { enable_rewards: false, enable_storage_access: false, allow_authoring_by: AllowAuthoringBy::Anyone, + pot_slot_iterations: NonZeroU32::new(100_000_000).expect("Not zero; qed"), enable_domains: true, enable_transfer: true, confirmation_depth_k: 1, @@ -406,6 +413,7 @@ fn subspace_genesis_config( enable_rewards, enable_storage_access, allow_authoring_by, + pot_slot_iterations, enable_domains, enable_transfer, confirmation_depth_k, @@ -433,6 +441,7 @@ fn subspace_genesis_config( enable_rewards, enable_storage_access, allow_authoring_by, + pot_slot_iterations, }, vesting: VestingConfig { vesting }, runtime_configs: RuntimeConfigsConfig { diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 54fed02ee2..538ff17cae 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -78,6 +78,8 @@ use sp_runtime::transaction_validity::{TransactionSource, TransactionValidity}; use sp_runtime::{ create_runtime_str, generic, AccountId32, ApplyExtrinsicResult, Perbill, SaturatedConversion, }; +#[cfg(feature = "pot")] +use sp_std::num::NonZeroU32; use sp_std::marker::PhantomData; use sp_std::prelude::*; #[cfg(feature = "std")] @@ -1263,18 +1265,14 @@ impl_runtime_apis! { } impl sp_consensus_subspace::SubspaceApi for Runtime { - fn history_size() -> HistorySize { - >::history_size() - } - - fn max_pieces_in_sector() -> u16 { - MAX_PIECES_IN_SECTOR - } - fn slot_duration() -> SlotDuration { SlotDuration::from_millis(SLOT_DURATION) } + fn pot_slot_iterations() -> NonZeroU32 { + Subspace::pot_slot_iterations() + } + fn solution_ranges() -> SolutionRanges { Subspace::solution_ranges() } @@ -1313,6 +1311,14 @@ impl_runtime_apis! { Subspace::is_in_block_list(farmer_public_key) } + fn history_size() -> HistorySize { + >::history_size() + } + + fn max_pieces_in_sector() -> u16 { + MAX_PIECES_IN_SECTOR + } + fn segment_commitment(segment_index: SegmentIndex) -> Option { Subspace::segment_commitment(segment_index) } diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index 477a4100b2..0d481e8a81 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -764,7 +764,9 @@ 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); + let (pot_source, pot_slot_info_stream) = + PotSource::new(config.pot_source_config, client.clone()) + .map_err(|error| Error::Other(error.into()))?; #[cfg(feature = "pot")] { task_manager.spawn_essential_handle().spawn_blocking( diff --git a/test/subspace-test-client/src/chain_spec.rs b/test/subspace-test-client/src/chain_spec.rs index 0bed5402dd..103685afbe 100644 --- a/test/subspace-test-client/src/chain_spec.rs +++ b/test/subspace-test-client/src/chain_spec.rs @@ -6,6 +6,7 @@ use sp_core::{sr25519, Pair, Public}; use sp_domains::{GenesisDomain, OperatorPublicKey, RuntimeType}; use sp_runtime::traits::{IdentifyAccount, Verify}; use sp_runtime::Percent; +use std::num::NonZeroU32; use subspace_runtime_primitives::{AccountId, Balance, BlockNumber, Signature}; use subspace_test_runtime::{ AllowAuthoringBy, BalancesConfig, DomainsConfig, GenesisConfig, MaxDomainBlockSize, @@ -99,6 +100,7 @@ fn create_genesis_config( enable_rewards: false, enable_storage_access: false, allow_authoring_by: AllowAuthoringBy::Anyone, + pot_slot_iterations: NonZeroU32::new(50_000_000).expect("Not zero; qed"), }, vesting: VestingConfig { vesting }, domains: DomainsConfig { diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index d0d4283ac9..16ee5ae52d 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -77,6 +77,8 @@ use sp_runtime::{ }; use sp_std::iter::Peekable; use sp_std::marker::PhantomData; +#[cfg(feature = "pot")] +use sp_std::num::NonZeroU32; use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; @@ -1562,18 +1564,14 @@ impl_runtime_apis! { } impl sp_consensus_subspace::SubspaceApi for Runtime { - fn history_size() -> HistorySize { - >::history_size() - } - - fn max_pieces_in_sector() -> u16 { - MAX_PIECES_IN_SECTOR - } - fn slot_duration() -> SlotDuration { SlotDuration::from_millis(SLOT_DURATION) } + fn pot_slot_iterations() -> NonZeroU32 { + Subspace::pot_slot_iterations() + } + fn solution_ranges() -> SolutionRanges { Subspace::solution_ranges() } @@ -1612,6 +1610,14 @@ impl_runtime_apis! { Subspace::is_in_block_list(farmer_public_key) } + fn history_size() -> HistorySize { + >::history_size() + } + + fn max_pieces_in_sector() -> u16 { + MAX_PIECES_IN_SECTOR + } + fn segment_commitment(segment_index: SegmentIndex) -> Option { Subspace::segment_commitment(segment_index) } From 1dbd0ad4bb49f07bfff55c46df3cc441058e0b89 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 28 Aug 2023 20:57:37 +0300 Subject: [PATCH 06/10] Derive initial seed during PotSource startup from genesis hash --- crates/sc-proof-of-time/src/source.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/sc-proof-of-time/src/source.rs b/crates/sc-proof-of-time/src/source.rs index f604516b99..5b0b66b30c 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::{PotCheckpoints, PotKey, PotSeed, SlotNumber}; +use subspace_core_primitives::{BlockHash, PotCheckpoints, PotKey, PotSeed, SlotNumber}; use subspace_proof_of_time::PotError; use tracing::{debug, error}; @@ -51,6 +51,7 @@ pub struct PotSource { impl PotSource where Block: BlockT, + BlockHash: From, Client: ProvideRuntimeApi + HeaderBackend, Client::Api: SubspaceRuntimeApi, { @@ -63,12 +64,13 @@ where is_timekeeper: _, initial_key, } = 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::default(); + let start_seed = PotSeed::from_genesis_block_hash(BlockHash::from(info.genesis_hash)); let start_key = initial_key; #[cfg(feature = "pot")] - let best_hash = client.info().best_hash; + let best_hash = info.best_hash; #[cfg(feature = "pot")] let runtime_api = client.runtime_api(); #[cfg(feature = "pot")] From f1406581fd2bdba456f9cf209e760b1f3f15b805 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 29 Aug 2023 20:56:42 +0300 Subject: [PATCH 07/10] Fix PoT after recent domains changes --- crates/subspace-runtime/src/lib.rs | 54 +++++++++++++++++++++++++++ test/subspace-test-runtime/src/lib.rs | 54 +++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 538ff17cae..7da4245100 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -1409,6 +1409,14 @@ impl_runtime_apis! { fn non_empty_er_exists(domain_id: DomainId) -> bool { Domains::non_empty_er_exists(domain_id) } + + fn domain_best_number(domain_id: DomainId) -> Option { + Domains::domain_best_number(domain_id) + } + + fn domain_state_root(domain_id: DomainId, number: DomainNumber, hash: DomainHash) -> Option{ + Domains::domain_state_root(domain_id, number, hash) + } } impl sp_domains::BundleProducerElectionApi for Runtime { @@ -1460,6 +1468,52 @@ impl_runtime_apis! { } } + impl sp_messenger::MessengerApi for Runtime { + fn extract_xdm_proof_state_roots( + extrinsic: Vec, + ) -> Option::Hash, ::Hash>> { + extract_xdm_proof_state_roots(extrinsic) + } + + fn is_domain_info_confirmed( + domain_id: DomainId, + domain_block_info: BlockInfo::Hash>, + domain_state_root: ::Hash, + ) -> bool{ + Messenger::is_domain_info_confirmed(domain_id, domain_block_info, domain_state_root) + } + } + + impl sp_messenger::RelayerApi for Runtime { + fn chain_id() -> ChainId { + SelfChainId::get() + } + + fn relay_confirmation_depth() -> BlockNumber { + RelayConfirmationDepth::get() + } + + fn block_messages() -> BlockMessagesWithStorageKey { + Messenger::get_block_messages() + } + + fn outbox_message_unsigned(msg: CrossDomainMessage::Hash, ::Hash>) -> Option<::Extrinsic> { + Messenger::outbox_message_unsigned(msg) + } + + fn inbox_response_message_unsigned(msg: CrossDomainMessage::Hash, ::Hash>) -> Option<::Extrinsic> { + Messenger::inbox_response_message_unsigned(msg) + } + + fn should_relay_outbox_message(dst_chain_id: ChainId, msg_id: MessageId) -> bool { + Messenger::should_relay_outbox_message(dst_chain_id, msg_id) + } + + fn should_relay_inbox_message_response(dst_chain_id: ChainId, msg_id: MessageId) -> bool { + Messenger::should_relay_inbox_message_response(dst_chain_id, msg_id) + } + } + #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { fn benchmark_metadata(extra: bool) -> ( diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 16ee5ae52d..c2a7b04a15 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -1708,6 +1708,14 @@ impl_runtime_apis! { fn non_empty_er_exists(domain_id: DomainId) -> bool { Domains::non_empty_er_exists(domain_id) } + + fn domain_best_number(domain_id: DomainId) -> Option { + Domains::domain_best_number(domain_id) + } + + fn domain_state_root(domain_id: DomainId, number: DomainNumber, hash: DomainHash) -> Option{ + Domains::domain_state_root(domain_id, number, hash) + } } impl sp_domains::BundleProducerElectionApi for Runtime { @@ -1758,4 +1766,50 @@ impl_runtime_apis! { TransactionPayment::length_to_fee(length) } } + + impl sp_messenger::MessengerApi for Runtime { + fn extract_xdm_proof_state_roots( + extrinsic: Vec, + ) -> Option::Hash, ::Hash>> { + extract_xdm_proof_state_roots(extrinsic) + } + + fn is_domain_info_confirmed( + domain_id: DomainId, + domain_block_info: BlockInfo::Hash>, + domain_state_root: ::Hash, + ) -> bool{ + Messenger::is_domain_info_confirmed(domain_id, domain_block_info, domain_state_root) + } + } + + impl sp_messenger::RelayerApi for Runtime { + fn chain_id() -> ChainId { + SelfChainId::get() + } + + fn relay_confirmation_depth() -> BlockNumber { + RelayConfirmationDepth::get() + } + + fn block_messages() -> BlockMessagesWithStorageKey { + Messenger::get_block_messages() + } + + fn outbox_message_unsigned(msg: CrossDomainMessage::Hash, ::Hash>) -> Option<::Extrinsic> { + Messenger::outbox_message_unsigned(msg) + } + + fn inbox_response_message_unsigned(msg: CrossDomainMessage::Hash, ::Hash>) -> Option<::Extrinsic> { + Messenger::inbox_response_message_unsigned(msg) + } + + fn should_relay_outbox_message(dst_chain_id: ChainId, msg_id: MessageId) -> bool { + Messenger::should_relay_outbox_message(dst_chain_id, msg_id) + } + + fn should_relay_inbox_message_response(dst_chain_id: ChainId, msg_id: MessageId) -> bool { + Messenger::should_relay_inbox_message_response(dst_chain_id, msg_id) + } + } } From 24d77edf0c71686d4c41082b05073981c0aff87d Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 29 Aug 2023 16:19:12 +0300 Subject: [PATCH 08/10] Remove iterations from `PreDigestPotInfo` and introduce a few consensus log items instead for stateless verification purposes, prepare for future extension for entropy injection into seed too --- crates/pallet-subspace/src/lib.rs | 69 ++++-- crates/pallet-subspace/src/mock.rs | 2 +- .../sc-consensus-subspace/src/slot_worker.rs | 5 +- crates/sc-proof-of-time/src/source.rs | 4 +- crates/sp-consensus-subspace/src/digests.rs | 205 ++++++++++++++++-- crates/sp-consensus-subspace/src/lib.rs | 63 +++++- crates/sp-consensus-subspace/src/tests.rs | 8 +- crates/subspace-runtime/src/lib.rs | 8 +- test/subspace-test-runtime/src/lib.rs | 8 +- test/subspace-test-service/src/lib.rs | 5 +- 10 files changed, 314 insertions(+), 63 deletions(-) diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index 2d0b92a342..a5f903e8b7 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -50,6 +50,8 @@ use sp_consensus_slots::Slot; use sp_consensus_subspace::consensus::verify_solution; use sp_consensus_subspace::digests::CompatibleDigestItem; use sp_consensus_subspace::offence::{OffenceDetails, OffenceError, OnOffenceHandler}; +#[cfg(feature = "pot")] +use sp_consensus_subspace::PotParameters; use sp_consensus_subspace::{ ChainConstants, EquivocationProof, FarmerPublicKey, FarmerSignature, SignedVote, Vote, }; @@ -65,15 +67,14 @@ use sp_runtime::transaction_validity::{ }; use sp_runtime::DispatchError; use sp_std::collections::btree_map::BTreeMap; -use sp_std::num::NonZeroU32; 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; @@ -153,7 +154,7 @@ mod pallet { use sp_std::prelude::*; use subspace_core_primitives::crypto::Scalar; use subspace_core_primitives::{ - HistorySize, Randomness, SectorIndex, SegmentHeader, SegmentIndex, SolutionRange, + HistorySize, PotSeed, Randomness, SectorIndex, SegmentHeader, SegmentIndex, SolutionRange, }; pub(super) struct InitialSolutionRanges { @@ -333,7 +334,7 @@ mod pallet { RootPlotPublicKey::::put(root_farmer.clone()); } } - PotSlotIterations::::put(self.pot_slot_iterations.get()); + PotSlotIterations::::put(self.pot_slot_iterations); } } @@ -381,19 +382,19 @@ mod pallet { pub(super) struct DefaultPotSlotIterations {} - // TODO: Replace with `NonZeroU32` once we can use it: - // https://github.com/paritytech/parity-scale-codec/pull/505 - impl Get for DefaultPotSlotIterations { - fn get() -> u32 { - unreachable!("Always instantiated during genesis; qed"); + impl Get for DefaultPotSlotIterations { + fn get() -> NonZeroU32 { + // TODO: Replace with below panic if/when https://github.com/paritytech/polkadot-sdk/issues/1282 + // is resolved upstream + NonZeroU32::MIN + // unreachable!("Always instantiated during genesis; qed"); } } /// Number of iterations for proof of time per slot #[pallet::storage] - // #[pallet::getter(fn pot_slot_iterations)] pub(super) type PotSlotIterations = - StorageValue<_, u32, ValueQuery, DefaultPotSlotIterations>; + StorageValue<_, NonZeroU32, ValueQuery, DefaultPotSlotIterations>; /// Solution ranges used for challenges. #[pallet::storage] @@ -501,6 +502,10 @@ 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 { @@ -795,6 +800,22 @@ 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 { + FuturePotSeed::::get().expect( + "Is set at the end of `do_initialize` of every block after genesis; qed", + ) + }, + )); + let pre_digest = >::digest() .logs .iter() @@ -930,6 +951,15 @@ impl Pallet { next_global_randomness, )); } + + #[cfg(feature = "pot")] + frame_system::Pallet::::deposit_log(DigestItem::pot_slot_iterations( + PotSlotIterations::::get(), + )); + // 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) { @@ -1083,12 +1113,15 @@ impl Pallet { Some(()) } - /// Number of iterations for proof of time per slot - // TODO: Remove once we can use `NonZeroU32` directly: - // https://github.com/paritytech/parity-scale-codec/pull/505 - pub fn pot_slot_iterations() -> NonZeroU32 { - NonZeroU32::new(PotSlotIterations::::get()) - .expect("Always initialized to non-zero value; qed") + /// Proof of time parameters + #[cfg(feature = "pot")] + pub fn pot_parameters() -> PotParameters { + PotParameters::V0 { + iterations: PotSlotIterations::::get(), + // TODO: This is where adjustment for number of iterations and entropy injection will + // happen for runtime API calls + next_change: None, + } } /// Check if `farmer_public_key` is in block list (due to equivocation) diff --git a/crates/pallet-subspace/src/mock.rs b/crates/pallet-subspace/src/mock.rs index facd4a57fb..e2768017e9 100644 --- a/crates/pallet-subspace/src/mock.rs +++ b/crates/pallet-subspace/src/mock.rs @@ -266,7 +266,7 @@ pub fn make_pre_digest( slot, solution, #[cfg(feature = "pot")] - pot_info: PreDigestPotInfo::Regular { + pot_info: PreDigestPotInfo::V0 { iterations: NonZeroU32::new(100_000).unwrap(), proof_of_time: Default::default(), future_proof_of_time: Default::default(), diff --git a/crates/sc-consensus-subspace/src/slot_worker.rs b/crates/sc-consensus-subspace/src/slot_worker.rs index 46bcb1d483..e71912bda1 100644 --- a/crates/sc-consensus-subspace/src/slot_worker.rs +++ b/crates/sc-consensus-subspace/src/slot_worker.rs @@ -297,8 +297,6 @@ where let (solution_range, voting_solution_range) = extract_solution_ranges_for_block(self.client.as_ref(), parent_hash).ok()?; - #[cfg(feature = "pot")] - let pot_slot_iterations = runtime_api.pot_slot_iterations(parent_hash).ok()?; let maybe_root_plot_public_key = runtime_api.root_plot_public_key(parent_hash).ok()?; #[cfg(feature = "pot")] @@ -471,8 +469,7 @@ where slot, solution, #[cfg(feature = "pot")] - pot_info: PreDigestPotInfo::Regular { - iterations: pot_slot_iterations, + pot_info: PreDigestPotInfo::V0 { proof_of_time, future_proof_of_time, }, diff --git a/crates/sc-proof-of-time/src/source.rs b/crates/sc-proof-of-time/src/source.rs index 5b0b66b30c..005aa03cbd 100644 --- a/crates/sc-proof-of-time/src/source.rs +++ b/crates/sc-proof-of-time/src/source.rs @@ -74,7 +74,9 @@ where #[cfg(feature = "pot")] let runtime_api = client.runtime_api(); #[cfg(feature = "pot")] - let iterations = runtime_api.pot_slot_iterations(best_hash)?; + let iterations = runtime_api + .pot_parameters(best_hash)? + .iterations(Slot::from(start_slot)); #[cfg(not(feature = "pot"))] let iterations = NonZeroU32::new(100_000_000).expect("Not zero; qed"); diff --git a/crates/sp-consensus-subspace/src/digests.rs b/crates/sp-consensus-subspace/src/digests.rs index 27200799bd..41545e2ba6 100644 --- a/crates/sp-consensus-subspace/src/digests.rs +++ b/crates/sp-consensus-subspace/src/digests.rs @@ -16,6 +16,8 @@ //! Private implementation details of Subspace consensus digests. +#[cfg(feature = "pot")] +use crate::PotParametersChange; use crate::{ConsensusLog, FarmerPublicKey, FarmerSignature, SUBSPACE_ENGINE_ID}; use codec::{Decode, Encode}; use log::trace; @@ -28,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; @@ -81,11 +83,9 @@ impl PreDigest { #[cfg(feature = "pot")] #[derive(Debug, Clone, Encode, Decode)] pub enum PreDigestPotInfo { - /// Regular information + /// Initial version of proof of time information #[codec(index = 0)] - Regular { - /// Number of iterations per slot in proof of time - iterations: NonZeroU32, + V0 { /// Proof of time for this slot proof_of_time: PotProof, /// Future proof of time @@ -99,9 +99,20 @@ impl PreDigestPotInfo { #[cfg(feature = "pot")] #[inline] pub fn proof_of_time(&self) -> PotProof { - let Self::Regular { proof_of_time, .. } = self; + let Self::V0 { proof_of_time, .. } = self; *proof_of_time } + + /// Future proof of time + #[cfg(feature = "pot")] + #[inline] + pub fn future_proof_of_time(&self) -> PotProof { + let Self::V0 { + future_proof_of_time, + .. + } = self; + *future_proof_of_time + } } /// A digest item which is usable with Subspace consensus. @@ -122,6 +133,14 @@ 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 + #[cfg(feature = "pot")] + fn pot_slot_iterations(pot_slot_iterations: NonZeroU32) -> Self; + + /// If this item is a Subspace proof of time slot iterations, return it. + #[cfg(feature = "pot")] + fn as_pot_slot_iterations(&self) -> Option; + /// Construct a digest item which contains a global randomness. #[cfg(not(feature = "pot"))] fn global_randomness(global_randomness: Randomness) -> Self; @@ -136,6 +155,14 @@ pub trait CompatibleDigestItem: Sized { /// If this item is a Subspace solution range, return it. fn as_solution_range(&self) -> Option; + /// Change of parameters to apply to PoT chain + #[cfg(feature = "pot")] + fn pot_parameters_change(pot_parameters_change: PotParametersChange) -> Self; + + /// If this item is a Subspace proof of time change of parameters, return it. + #[cfg(feature = "pot")] + fn as_pot_parameters_change(&self) -> Option; + /// Construct a digest item which contains next global randomness. #[cfg(not(feature = "pot"))] fn next_global_randomness(global_randomness: Randomness) -> Self; @@ -169,11 +196,20 @@ pub trait CompatibleDigestItem: Sized { /// range, return it. fn as_enable_solution_range_adjustment_and_override(&self) -> Option>; - /// Construct digest item than indicates update of root plot public key. + /// Construct digest item that indicates update of root plot public key. fn root_plot_public_key_update(root_plot_public_key: Option) -> Self; /// 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 { @@ -197,6 +233,25 @@ impl CompatibleDigestItem for DigestItem { self.seal_try_to(&SUBSPACE_ENGINE_ID) } + #[cfg(feature = "pot")] + fn pot_slot_iterations(pot_slot_iterations: NonZeroU32) -> Self { + Self::Consensus( + SUBSPACE_ENGINE_ID, + ConsensusLog::PotSlotIterations(pot_slot_iterations).encode(), + ) + } + + #[cfg(feature = "pot")] + fn as_pot_slot_iterations(&self) -> Option { + self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| { + if let ConsensusLog::PotSlotIterations(pot_slot_iterations) = c { + Some(pot_slot_iterations) + } else { + None + } + }) + } + #[cfg(not(feature = "pot"))] fn global_randomness(global_randomness: Randomness) -> Self { Self::Consensus( @@ -233,6 +288,25 @@ impl CompatibleDigestItem for DigestItem { }) } + #[cfg(feature = "pot")] + fn pot_parameters_change(pot_parameters_change: PotParametersChange) -> Self { + Self::Consensus( + SUBSPACE_ENGINE_ID, + ConsensusLog::PotParametersChange(pot_parameters_change).encode(), + ) + } + + #[cfg(feature = "pot")] + fn as_pot_parameters_change(&self) -> Option { + self.consensus_try_to(&SUBSPACE_ENGINE_ID).and_then(|c| { + if let ConsensusLog::PotParametersChange(pot_parameters_change) = c { + Some(pot_parameters_change) + } else { + None + } + }) + } + #[cfg(not(feature = "pot"))] fn next_global_randomness(global_randomness: Randomness) -> Self { Self::Consensus( @@ -328,6 +402,25 @@ 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 @@ -337,11 +430,17 @@ pub enum ErrorDigestType { PreDigest, /// Seal (signature) Seal, + /// Number of iterations for proof of time per slot + #[cfg(feature = "pot")] + PotSlotIterations, /// Global randomness #[cfg(not(feature = "pot"))] GlobalRandomness, /// Solution range SolutionRange, + /// Change of parameters to apply to PoT chain + #[cfg(feature = "pot")] + PotParametersChange, /// Next global randomness #[cfg(not(feature = "pot"))] NextGlobalRandomness, @@ -355,6 +454,9 @@ 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 { @@ -366,6 +468,10 @@ impl fmt::Display for ErrorDigestType { ErrorDigestType::Seal => { write!(f, "Seal") } + #[cfg(feature = "pot")] + ErrorDigestType::PotSlotIterations => { + write!(f, "PotSlotIterations") + } #[cfg(not(feature = "pot"))] ErrorDigestType::GlobalRandomness => { write!(f, "GlobalRandomness") @@ -373,6 +479,10 @@ impl fmt::Display for ErrorDigestType { ErrorDigestType::SolutionRange => { write!(f, "SolutionRange") } + #[cfg(feature = "pot")] + ErrorDigestType::PotParametersChange => { + write!(f, "PotParametersChange") + } #[cfg(not(feature = "pot"))] ErrorDigestType::NextGlobalRandomness => { write!(f, "NextGlobalRandomness") @@ -392,6 +502,10 @@ impl fmt::Display for ErrorDigestType { ErrorDigestType::RootPlotPublicKeyUpdate => { write!(f, "RootPlotPublicKeyUpdate") } + #[cfg(feature = "pot")] + ErrorDigestType::FuturePotSeed => { + write!(f, "FuturePotSeed") + } } } } @@ -446,11 +560,17 @@ pub struct SubspaceDigestItems { pub pre_digest: PreDigest, /// Signature (seal) if present pub signature: Option, + /// Number of iterations for proof of time per slot + #[cfg(feature = "pot")] + pub pot_slot_iterations: NonZeroU32, /// Global randomness #[cfg(not(feature = "pot"))] pub global_randomness: Randomness, /// Solution range pub solution_range: SolutionRange, + /// Change of parameters to apply to PoT chain + #[cfg(feature = "pot")] + pub pot_parameters_change: Option, /// Next global randomness #[cfg(not(feature = "pot"))] pub next_global_randomness: Option, @@ -462,6 +582,9 @@ 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. + #[cfg(feature = "pot")] + pub future_pot_seed: PotSeed, } /// Extract the Subspace global randomness from the given header. @@ -476,15 +599,21 @@ where { let mut maybe_pre_digest = None; let mut maybe_seal = None; + #[cfg(feature = "pot")] + let mut maybe_pot_slot_iterations = None; #[cfg(not(feature = "pot"))] let mut maybe_global_randomness = None; let mut maybe_solution_range = None; + #[cfg(feature = "pot")] + let mut maybe_pot_parameters_change = None; #[cfg(not(feature = "pot"))] let mut maybe_next_global_randomness = None; let mut maybe_next_solution_range = None; 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 { @@ -516,6 +645,17 @@ where .map_err(|error| Error::FailedToDecode(ErrorDigestType::Consensus, error))?; match consensus { + #[cfg(feature = "pot")] + ConsensusLog::PotSlotIterations(pot_slot_iterations) => { + match maybe_pot_slot_iterations { + Some(_) => { + return Err(Error::Duplicate(ErrorDigestType::PotSlotIterations)); + } + None => { + maybe_pot_slot_iterations.replace(pot_slot_iterations); + } + } + } #[cfg(not(feature = "pot"))] ConsensusLog::GlobalRandomness(global_randomness) => { match maybe_global_randomness { @@ -535,6 +675,17 @@ where maybe_solution_range.replace(solution_range); } }, + #[cfg(feature = "pot")] + ConsensusLog::PotParametersChange(pot_parameters_change) => { + match maybe_pot_parameters_change { + Some(_) => { + return Err(Error::Duplicate(ErrorDigestType::PotParametersChange)); + } + None => { + maybe_pot_parameters_change.replace(pot_parameters_change); + } + } + } #[cfg(not(feature = "pot"))] ConsensusLog::NextGlobalRandomness(global_randomness) => { match maybe_next_global_randomness { @@ -568,29 +719,38 @@ where ConsensusLog::EnableSolutionRangeAdjustmentAndOverride( override_solution_range, ) => match maybe_enable_and_override_solution_range { - None => { - maybe_enable_and_override_solution_range - .replace(override_solution_range); - } Some(_) => { return Err(Error::Duplicate( ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride, )); } + None => { + maybe_enable_and_override_solution_range + .replace(override_solution_range); + } }, ConsensusLog::RootPlotPublicKeyUpdate(root_plot_public_key_update) => { - match maybe_enable_and_override_solution_range { - None => { - maybe_root_plot_public_key_update - .replace(root_plot_public_key_update); - } + match maybe_root_plot_public_key_update { Some(_) => { return Err(Error::Duplicate( ErrorDigestType::EnableSolutionRangeAdjustmentAndOverride, )); } + None => { + maybe_root_plot_public_key_update + .replace(root_plot_public_key_update); + } } } + #[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) => { @@ -622,17 +782,25 @@ where Ok(SubspaceDigestItems { pre_digest: maybe_pre_digest.ok_or(Error::Missing(ErrorDigestType::PreDigest))?, signature: maybe_seal, + #[cfg(feature = "pot")] + pot_slot_iterations: maybe_pot_slot_iterations + .ok_or(Error::Missing(ErrorDigestType::PotSlotIterations))?, #[cfg(not(feature = "pot"))] global_randomness: maybe_global_randomness .ok_or(Error::Missing(ErrorDigestType::GlobalRandomness))?, solution_range: maybe_solution_range .ok_or(Error::Missing(ErrorDigestType::SolutionRange))?, + #[cfg(feature = "pot")] + pot_parameters_change: maybe_pot_parameters_change, #[cfg(not(feature = "pot"))] next_global_randomness: maybe_next_global_randomness, next_solution_range: maybe_next_solution_range, 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 + .ok_or(Error::Missing(ErrorDigestType::FuturePotSeed))?, }) } @@ -654,8 +822,7 @@ where FarmerPublicKey::unchecked_from([0u8; 32]), ), #[cfg(feature = "pot")] - pot_info: PreDigestPotInfo::Regular { - iterations: NonZeroU32::MIN, + pot_info: PreDigestPotInfo::V0 { proof_of_time: Default::default(), future_proof_of_time: Default::default(), }, diff --git a/crates/sp-consensus-subspace/src/lib.rs b/crates/sp-consensus-subspace/src/lib.rs index 2c09cbbaf0..cbd1d85f75 100644 --- a/crates/sp-consensus-subspace/src/lib.rs +++ b/crates/sp-consensus-subspace/src/lib.rs @@ -47,6 +47,8 @@ use sp_std::vec::Vec; use subspace_core_primitives::crypto::kzg::Kzg; #[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, @@ -133,9 +135,26 @@ impl SubspaceJustification { /// An equivocation proof for multiple block authorships on the same slot (i.e. double vote). pub type EquivocationProof
= sp_consensus_slots::EquivocationProof; +/// Change of parameters to apply to PoT chain +#[cfg(feature = "pot")] +#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)] +pub struct PotParametersChange { + /// At which slot change of parameters takes effect + pub slot: Slot, + /// New number of iterations + pub iterations: NonZeroU32, + /// Entropy that should be injected at this time + // TODO: Reconsider if the type is correct here + pub entropy: Blake2b256Hash, +} + /// An consensus log item for Subspace. #[derive(Debug, Decode, Encode, Clone, PartialEq, Eq)] enum ConsensusLog { + /// Number of iterations for proof of time per slot. + #[codec(index = 0)] + #[cfg(feature = "pot")] + PotSlotIterations(NonZeroU32), /// Global randomness for this block/interval. #[codec(index = 0)] #[cfg(not(feature = "pot"))] @@ -143,6 +162,10 @@ enum ConsensusLog { /// Solution range for this block/era. #[codec(index = 1)] SolutionRange(SolutionRange), + /// Change of parameters to apply to PoT chain. + #[codec(index = 2)] + #[cfg(feature = "pot")] + PotParametersChange(PotParametersChange), /// Global randomness for next block/interval. #[codec(index = 2)] #[cfg(not(feature = "pot"))] @@ -159,6 +182,10 @@ 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. @@ -616,6 +643,38 @@ sp_api::decl_runtime_apis! { } } +/// Proof of time parameters +#[cfg(feature = "pot")] +#[derive(Debug, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum PotParameters { + /// Initial version of the parameters + V0 { + /// Base number of iterations + iterations: NonZeroU32, + /// Optional next scheduled change of parameters + next_change: Option, + }, +} + +#[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 { + let Self::V0 { + iterations, + next_change, + } = self; + + if let Some(next_change) = next_change { + if next_change.slot >= slot { + return next_change.iterations; + } + } + + *iterations + } +} + #[cfg(feature = "pot")] sp_api::decl_runtime_apis! { /// API necessary for block authorship with Subspace. @@ -623,8 +682,8 @@ sp_api::decl_runtime_apis! { /// The slot duration in milliseconds for Subspace. fn slot_duration() -> SlotDuration; - /// Number of iterations for proof of time per slot - fn pot_slot_iterations() -> NonZeroU32; + /// Proof of time parameters + fn pot_parameters() -> PotParameters; /// Solution ranges. fn solution_ranges() -> SolutionRanges; diff --git a/crates/sp-consensus-subspace/src/tests.rs b/crates/sp-consensus-subspace/src/tests.rs index b97829a9e9..7f11a88ebf 100644 --- a/crates/sp-consensus-subspace/src/tests.rs +++ b/crates/sp-consensus-subspace/src/tests.rs @@ -9,8 +9,6 @@ use sp_consensus_slots::Slot; use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::BlakeTwo256; use sp_runtime::{Digest, DigestItem}; -#[cfg(feature = "pot")] -use std::num::NonZeroU32; use std::num::NonZeroU64; use subspace_core_primitives::{HistorySize, PieceOffset, Solution}; use subspace_solving::REWARD_SIGNING_CONTEXT; @@ -47,8 +45,7 @@ fn test_is_equivocation_proof_valid() { slot, solution: solution.clone(), #[cfg(feature = "pot")] - pot_info: PreDigestPotInfo::Regular { - iterations: NonZeroU32::MIN, + pot_info: PreDigestPotInfo::V0 { proof_of_time: Default::default(), future_proof_of_time: Default::default(), }, @@ -77,8 +74,7 @@ fn test_is_equivocation_proof_valid() { slot, solution, #[cfg(feature = "pot")] - pot_info: PreDigestPotInfo::Regular { - iterations: NonZeroU32::MIN, + pot_info: PreDigestPotInfo::V0 { proof_of_time: Default::default(), future_proof_of_time: Default::default(), }, diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 7da4245100..d879fd917f 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -58,6 +58,8 @@ use sp_api::{impl_runtime_apis, BlockT}; use sp_consensus_slots::SlotDuration; #[cfg(not(feature = "pot"))] use sp_consensus_subspace::GlobalRandomnesses; +#[cfg(feature = "pot")] +use sp_consensus_subspace::PotParameters; use sp_consensus_subspace::{ ChainConstants, EquivocationProof, FarmerPublicKey, SignedVote, SolutionRanges, Vote, }; @@ -78,8 +80,6 @@ use sp_runtime::transaction_validity::{TransactionSource, TransactionValidity}; use sp_runtime::{ create_runtime_str, generic, AccountId32, ApplyExtrinsicResult, Perbill, SaturatedConversion, }; -#[cfg(feature = "pot")] -use sp_std::num::NonZeroU32; use sp_std::marker::PhantomData; use sp_std::prelude::*; #[cfg(feature = "std")] @@ -1269,8 +1269,8 @@ impl_runtime_apis! { SlotDuration::from_millis(SLOT_DURATION) } - fn pot_slot_iterations() -> NonZeroU32 { - Subspace::pot_slot_iterations() + fn pot_parameters() -> PotParameters { + Subspace::pot_parameters() } fn solution_ranges() -> SolutionRanges { diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index c2a7b04a15..b4ca069686 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -48,6 +48,8 @@ use sp_consensus_slots::SlotDuration; use sp_consensus_subspace::digests::CompatibleDigestItem; #[cfg(not(feature = "pot"))] use sp_consensus_subspace::GlobalRandomnesses; +#[cfg(feature = "pot")] +use sp_consensus_subspace::PotParameters; use sp_consensus_subspace::{ ChainConstants, EquivocationProof, FarmerPublicKey, SignedVote, SolutionRanges, Vote, }; @@ -77,8 +79,6 @@ use sp_runtime::{ }; use sp_std::iter::Peekable; use sp_std::marker::PhantomData; -#[cfg(feature = "pot")] -use sp_std::num::NonZeroU32; use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; @@ -1568,8 +1568,8 @@ impl_runtime_apis! { SlotDuration::from_millis(SLOT_DURATION) } - fn pot_slot_iterations() -> NonZeroU32 { - Subspace::pot_slot_iterations() + fn pot_parameters() -> PotParameters { + Subspace::pot_parameters() } fn solution_ranges() -> SolutionRanges { diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index f89090d2ff..8636971cdb 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -68,8 +68,6 @@ use sp_runtime::{DigestItem, OpaqueExtrinsic}; use sp_timestamp::Timestamp; use std::error::Error; use std::marker::PhantomData; -#[cfg(feature = "pot")] -use std::num::NonZeroU32; use std::sync::Arc; use std::time; use subspace_core_primitives::{Randomness, Solution}; @@ -604,8 +602,7 @@ impl MockConsensusNode { slot, solution: self.mock_solution.clone(), #[cfg(feature = "pot")] - pot_info: PreDigestPotInfo::Regular { - iterations: NonZeroU32::MIN, + pot_info: PreDigestPotInfo::V0 { proof_of_time: Default::default(), future_proof_of_time: Default::default(), }, From fa12b7708bf102d4b05a1d316dc033bf922669aa Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 29 Aug 2023 23:31:54 +0300 Subject: [PATCH 09/10] Remove strange default implementation --- crates/pallet-subspace/src/lib.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/crates/pallet-subspace/src/lib.rs b/crates/pallet-subspace/src/lib.rs index a5f903e8b7..146816d2e8 100644 --- a/crates/pallet-subspace/src/lib.rs +++ b/crates/pallet-subspace/src/lib.rs @@ -380,21 +380,9 @@ mod pallet { pub(super) type GlobalRandomnesses = StorageValue<_, sp_consensus_subspace::GlobalRandomnesses, ValueQuery>; - pub(super) struct DefaultPotSlotIterations {} - - impl Get for DefaultPotSlotIterations { - fn get() -> NonZeroU32 { - // TODO: Replace with below panic if/when https://github.com/paritytech/polkadot-sdk/issues/1282 - // is resolved upstream - NonZeroU32::MIN - // unreachable!("Always instantiated during genesis; qed"); - } - } - /// Number of iterations for proof of time per slot #[pallet::storage] - pub(super) type PotSlotIterations = - StorageValue<_, NonZeroU32, ValueQuery, DefaultPotSlotIterations>; + pub(super) type PotSlotIterations = StorageValue<_, NonZeroU32>; /// Solution ranges used for challenges. #[pallet::storage] @@ -954,7 +942,7 @@ impl Pallet { #[cfg(feature = "pot")] frame_system::Pallet::::deposit_log(DigestItem::pot_slot_iterations( - PotSlotIterations::::get(), + 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 @@ -1117,7 +1105,8 @@ impl Pallet { #[cfg(feature = "pot")] pub fn pot_parameters() -> PotParameters { PotParameters::V0 { - iterations: PotSlotIterations::::get(), + 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 next_change: None, From 90f2c6e268f4311a82f4bd9f2b740f315708afef Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Wed, 30 Aug 2023 00:32:11 +0300 Subject: [PATCH 10/10] Swap number of PoT iterations between devnet and dev network --- crates/subspace-node/src/chain_spec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/subspace-node/src/chain_spec.rs b/crates/subspace-node/src/chain_spec.rs index 944c53c6e3..b9eeb045a3 100644 --- a/crates/subspace-node/src/chain_spec.rs +++ b/crates/subspace-node/src/chain_spec.rs @@ -250,7 +250,7 @@ pub fn devnet_config_compiled() -> Result, Str enable_rewards: false, enable_storage_access: false, allow_authoring_by: AllowAuthoringBy::FirstFarmer, - pot_slot_iterations: NonZeroU32::new(100_000_000).expect("Not zero; qed"), + pot_slot_iterations: NonZeroU32::new(150_000_000).expect("Not zero; qed"), enable_domains: true, enable_transfer: true, confirmation_depth_k: 100, // TODO: Proper value here @@ -308,7 +308,7 @@ pub fn dev_config() -> Result, String> { enable_rewards: false, enable_storage_access: false, allow_authoring_by: AllowAuthoringBy::Anyone, - pot_slot_iterations: NonZeroU32::new(150_000_000).expect("Not zero; qed"), + pot_slot_iterations: NonZeroU32::new(100_000_000).expect("Not zero; qed"), enable_domains: true, enable_transfer: true, confirmation_depth_k: 5,