From c376142a62f0e3dcde0d2cfcad7002b79c14c603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 12 Dec 2024 14:02:22 +0000 Subject: [PATCH 1/6] feat: implement AuthorVRF-based randomness --- Cargo.lock | 2 + pallets/market/src/mock.rs | 22 ++- pallets/randomness/Cargo.toml | 13 +- pallets/randomness/src/inherent.rs | 61 ++++++++ pallets/randomness/src/lib.rs | 174 +++++++++++++++------- pallets/randomness/src/mock.rs | 24 ++- pallets/randomness/src/tests.rs | 63 -------- pallets/storage-provider/src/lib.rs | 16 +- pallets/storage-provider/src/tests/mod.rs | 22 ++- primitives/src/pallets.rs | 5 - runtime/src/configs/mod.rs | 4 +- runtime/src/lib.rs | 41 ++++- 12 files changed, 301 insertions(+), 146 deletions(-) create mode 100644 pallets/randomness/src/inherent.rs diff --git a/Cargo.lock b/Cargo.lock index c0077d29a..d70c8ac30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11083,6 +11083,7 @@ dependencies = [ name = "pallet-randomness" version = "0.0.0" dependencies = [ + "async-trait", "frame-benchmarking 38.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "frame-support 38.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "frame-system 38.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", @@ -11092,6 +11093,7 @@ dependencies = [ "primitives", "scale-info", "sp-core 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", + "sp-inherents 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "sp-io 38.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "sp-runtime 39.0.1", ] diff --git a/pallets/market/src/mock.rs b/pallets/market/src/mock.rs index d93b11e9d..f87eaf346 100644 --- a/pallets/market/src/mock.rs +++ b/pallets/market/src/mock.rs @@ -6,7 +6,7 @@ use frame_support::{ PalletId, }; use frame_system::{self as system, pallet_prelude::BlockNumberFor}; -use primitives::{pallets::Randomness, proofs::RegisteredPoStProof}; +use primitives::proofs::RegisteredPoStProof; use sp_core::Pair; use sp_runtime::{ traits::{ConstU32, ConstU64, IdentifyAccount, IdentityLookup, Verify}, @@ -84,16 +84,26 @@ impl crate::Config for Test { } /// Randomness generator used by tests. -pub struct DummyRandomnessGenerator; -impl Randomness for DummyRandomnessGenerator { - fn get_randomness(_: BlockNumber) -> Result<[u8; 32], sp_runtime::DispatchError> { - Ok([0; 32]) +pub struct DummyRandomnessGenerator(core::marker::PhantomData) +where + C: frame_system::Config; + +impl frame_support::traits::Randomness> + for DummyRandomnessGenerator +where + C: frame_system::Config, +{ + fn random(_subject: &[u8]) -> (C::Hash, BlockNumberFor) { + ( + Default::default(), + >::block_number(), + ) } } impl pallet_storage_provider::Config for Test { type RuntimeEvent = RuntimeEvent; - type Randomness = DummyRandomnessGenerator; + type Randomness = DummyRandomnessGenerator; type PeerId = BoundedVec>; // Max length of SHA256 hash type Currency = Balances; type Market = Market; diff --git a/pallets/randomness/Cargo.toml b/pallets/randomness/Cargo.toml index db96c5585..1e05e0326 100644 --- a/pallets/randomness/Cargo.toml +++ b/pallets/randomness/Cargo.toml @@ -16,6 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = { workspace = true, optional = true } codec = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support.workspace = true @@ -25,6 +26,7 @@ pallet-insecure-randomness-collective-flip = { workspace = true, default-feature primitives = { workspace = true } scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true, default-features = false } +sp-inherents.workspace = true sp-runtime.workspace = true [dev-dependencies] @@ -38,5 +40,14 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] -std = ["codec/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "primitives/std", "scale-info/std", "sp-runtime/std"] +std = [ + "async-trait", + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "primitives/std", + "scale-info/std", + "sp-runtime/std", +] try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime", "sp-runtime/try-runtime"] diff --git a/pallets/randomness/src/inherent.rs b/pallets/randomness/src/inherent.rs new file mode 100644 index 000000000..3f889b6a4 --- /dev/null +++ b/pallets/randomness/src/inherent.rs @@ -0,0 +1,61 @@ +use codec::{Decode, Encode}; +use sp_inherents::{Error, InherentData, InherentIdentifier, IsFatalError}; +use sp_runtime::RuntimeString; + +#[derive(Encode)] +#[cfg_attr(feature = "std", derive(Debug, Decode))] +pub enum InherentError { + Other(RuntimeString), +} + +impl IsFatalError for InherentError { + fn is_fatal_error(&self) -> bool { + match *self { + InherentError::Other(_) => true, + } + } +} + +impl InherentError { + /// Try to create an instance ouf of the given identifier and data. + #[cfg(feature = "std")] + pub fn try_from(id: &InherentIdentifier, data: &[u8]) -> Option { + if id == &INHERENT_IDENTIFIER { + ::decode(&mut &*data).ok() + } else { + None + } + } +} + +/// The InherentIdentifier to set the babe randomness results +pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babe_vrf"; + +/// A bare minimum inherent data provider that provides no real data. +/// The inherent is simply used as a way to kick off some computation +/// until https://github.com/paritytech/substrate/pull/10128 lands. +pub struct InherentDataProvider; + +#[cfg(feature = "std")] +#[async_trait::async_trait] +impl sp_inherents::InherentDataProvider for InherentDataProvider { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + inherent_data.put_data(INHERENT_IDENTIFIER, &()) + } + + async fn try_handle_error( + &self, + identifier: &InherentIdentifier, + _error: &[u8], + ) -> Option> { + // Don't process modules from other inherents + if *identifier != INHERENT_IDENTIFIER { + return None; + } + + // All errors with the randomness inherent are fatal + Some(Err(Error::Application(Box::from(String::from( + "Error processing dummy randomness inherent", + ))))) + } +} diff --git a/pallets/randomness/src/lib.rs b/pallets/randomness/src/lib.rs index b81a26133..8bdfd6067 100644 --- a/pallets/randomness/src/lib.rs +++ b/pallets/randomness/src/lib.rs @@ -10,16 +10,33 @@ mod mock; #[cfg(test)] mod tests; +mod inherent; + +pub trait GetAuthorVrf +where + H: core::hash::Hash, +{ + fn get_author_vrf() -> Option; +} + #[frame_support::pallet(dev_mode)] pub mod pallet { + extern crate alloc; use alloc::vec::Vec; - use frame_support::{pallet_prelude::*, traits::Randomness as SubstrateRandomness}; - use frame_system::pallet_prelude::*; - use primitives::pallets::Randomness; - use sp_runtime::{traits::Zero, Saturating}; + use frame_support::{ + inherent::ProvideInherent, + pallet_prelude::{ValueQuery, *}, + traits::Randomness as SubstrateRandomness, + }; + use frame_system::pallet_prelude::{BlockNumberFor, *}; + use sp_inherents::{InherentData, InherentIdentifier}; + use sp_runtime::traits::Hash; + + use super::GetAuthorVrf; + use crate::inherent::{InherentError, INHERENT_IDENTIFIER}; pub const LOG_TARGET: &'static str = "runtime::randomness"; @@ -27,12 +44,16 @@ pub mod pallet { pub trait Config: frame_system::Config { /// Underlying randomness generator type Generator: SubstrateRandomness>; + /// Clean-up interval specified in number of blocks between cleanups. #[pallet::constant] type CleanupInterval: Get>; + /// The number of blocks after which the seed is cleaned up. #[pallet::constant] type SeedAgeLimit: Get>; + + type AuthorVrfGetter: GetAuthorVrf; } #[pallet::pallet] @@ -48,65 +69,110 @@ pub mod pallet { SeedNotAvailable, } - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(block_number: BlockNumberFor) -> Weight { - // TODO(no-ref,@cernicc,22/10/2024): Set proper weights - let weight = T::DbWeight::get().reads(1); - - // The determinable_after is a block number in the past since which - // the current seed is determinable by chain observers. - let (seed, determinable_after) = T::Generator::random_seed(); - let seed: [u8; 32] = seed.as_ref().try_into().expect("seed should be 32 bytes"); - - // We are not saving the seed for the zeroth block. This is an edge - // case when trying to use randomness at the network genesis. - if determinable_after == Zero::zero() { - return weight; + #[pallet::storage] + #[pallet::getter(fn author_vrf)] + pub type AuthorVrf = StorageValue<_, T::Hash, ValueQuery>; + + #[pallet::call] + impl Pallet { + pub fn set_author_vrf(origin: OriginFor) -> DispatchResult { + ensure_none(origin)?; + + if let Some(author_vrf) = T::AuthorVrfGetter::get_author_vrf() { + AuthorVrf::::put(author_vrf); + } else { + log::warn!("AuthorVrf is empty, keeping previous value"); } - // Save the seed - SeedsMap::::insert(block_number, seed); - log::info!(target: LOG_TARGET, "on_initialize: height: {block_number:?}, seed: {seed:?}"); - - weight + Ok(()) } + } - fn on_finalize(current_block_number: BlockNumberFor) { - // Check if we should clean the seeds - if current_block_number % T::CleanupInterval::get() != Zero::zero() { - return; - } - - // Mark which seeds to remove - let mut blocks_to_remove = Vec::new(); - for creation_height in SeedsMap::::iter_keys() { - let age_limit = T::SeedAgeLimit::get(); - let current_age = current_block_number.saturating_sub(creation_height); + #[pallet::inherent] + impl ProvideInherent for Pallet { + type Call = Call; + type Error = InherentError; + + const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; + + fn is_inherent_required(_: &InherentData) -> Result, Self::Error> { + // Return Ok(Some(_)) unconditionally because this inherent is required in every block + // If it is not found, throw a VrfInherentRequired error. + Ok(Some(InherentError::Other( + sp_runtime::RuntimeString::Borrowed( + "Inherent required to set babe randomness results", + ), + ))) + } - // Seed is old enough to be removed - if current_age >= age_limit { - blocks_to_remove.push(creation_height); - } - } + // The empty-payload inherent extrinsic. + fn create_inherent(_data: &InherentData) -> Option { + Some(Call::set_author_vrf {}) + } - // Remove old seeds - blocks_to_remove.iter().for_each(|number| { - SeedsMap::::remove(number); - }); + fn is_inherent(call: &Self::Call) -> bool { + matches!(call, Call::set_author_vrf { .. }) } } - impl Randomness> for Pallet { - fn get_randomness(block_number: BlockNumberFor) -> Result<[u8; 32], DispatchError> { - // Get the seed for the given block number - let current_block_number = frame_system::Pallet::::block_number(); - let seed = SeedsMap::::get(block_number).ok_or_else(|| { - log::error!(target: LOG_TARGET, "get_randomness: No seed available for {block_number:?} at {current_block_number:?}"); - Error::::SeedNotAvailable - })?; - - Ok(seed) + impl frame_support::traits::Randomness> for Pallet { + fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor) { + let author_vrf = AuthorVrf::::get(); + let block_number = frame_system::Pallet::::block_number(); + let mut digest = Vec::new(); + digest.extend_from_slice(author_vrf.as_ref()); + digest.extend_from_slice(subject); + let randomness = T::Hashing::hash(digest.as_slice()); + (randomness, block_number) } } + + // #[pallet::hooks] + // impl Hooks> for Pallet { + // fn on_initialize(block_number: BlockNumberFor) -> Weight { + // // TODO(no-ref,@cernicc,22/10/2024): Set proper weights + // let weight = T::DbWeight::get().reads(1); + + // // The determinable_after is a block number in the past since which + // // the current seed is determinable by chain observers. + // let (seed, determinable_after) = T::Generator::random_seed(); + // let seed: [u8; 32] = seed.as_ref().try_into().expect("seed should be 32 bytes"); + + // // We are not saving the seed for the zeroth block. This is an edge + // // case when trying to use randomness at the network genesis. + // if determinable_after == Zero::zero() { + // return weight; + // } + + // // Save the seed + // SeedsMap::::insert(block_number, seed); + // log::info!(target: LOG_TARGET, "on_initialize: height: {block_number:?}, seed: {seed:?}"); + + // weight + // } + + // fn on_finalize(current_block_number: BlockNumberFor) { + // // Check if we should clean the seeds + // if current_block_number % T::CleanupInterval::get() != Zero::zero() { + // return; + // } + + // // Mark which seeds to remove + // let mut blocks_to_remove = Vec::new(); + // for creation_height in SeedsMap::::iter_keys() { + // let age_limit = T::SeedAgeLimit::get(); + // let current_age = current_block_number.saturating_sub(creation_height); + + // // Seed is old enough to be removed + // if current_age >= age_limit { + // blocks_to_remove.push(creation_height); + // } + // } + + // // Remove old seeds + // blocks_to_remove.iter().for_each(|number| { + // SeedsMap::::remove(number); + // }); + // } + // } } diff --git a/pallets/randomness/src/mock.rs b/pallets/randomness/src/mock.rs index 8553d8287..7e7f03c27 100644 --- a/pallets/randomness/src/mock.rs +++ b/pallets/randomness/src/mock.rs @@ -5,7 +5,12 @@ use frame_support::{ use frame_system::{self as system, mocking::MockBlock}; use pallet_insecure_randomness_collective_flip as substrate_randomness; use sp_core::parameter_types; -use sp_runtime::{traits::Header, BuildStorage}; +use sp_runtime::{ + traits::{Hash, HashingFor, Header}, + BuildStorage, +}; + +use crate::GetAuthorVrf; pub type BlockNumber = u64; @@ -42,13 +47,28 @@ impl frame_system::Config for Test { parameter_types! { pub const CleanupInterval: BlockNumber = 1; - pub const SeedAgeLimit: BlockNumber = 200; + pub const SeedAgeLimit: BlockNumber = 200; } impl crate::Config for Test { type Generator = SubstrateRandomness; type CleanupInterval = CleanupInterval; type SeedAgeLimit = SeedAgeLimit; + + type AuthorVrfGetter = DummyVrf; +} + +pub struct DummyVrf(core::marker::PhantomData) +where + C: frame_system::Config; + +impl GetAuthorVrf for DummyVrf +where + C: frame_system::Config, +{ + fn get_author_vrf() -> Option { + Some(C::Hashing::hash(&[])) + } } impl substrate_randomness::Config for Test {} diff --git a/pallets/randomness/src/tests.rs b/pallets/randomness/src/tests.rs index dbb6d4eb5..8b1378917 100644 --- a/pallets/randomness/src/tests.rs +++ b/pallets/randomness/src/tests.rs @@ -1,64 +1 @@ -use frame_support::{assert_err, assert_ok}; -use primitives::pallets::Randomness; -use crate::{ - mock::{new_test_ext, run_to_block, BlockNumber, RandomnessModule, SeedAgeLimit, Test}, - Error, -}; - -#[test] -fn test_randomness_availability() { - new_test_ext().execute_with(|| { - let n_blocks = 500; - run_to_block(n_blocks); - - // Iterate all blocks and check if the random seed is available - for block_number in 0..=n_blocks { - let seed = >::get_randomness(block_number); - - // Seed on zero block should never be available - if block_number == 0 { - assert_err!(seed, Error::::SeedNotAvailable); - continue; - } - - // Check availability - match block_number { - // Seeds older than SeedAgeLimit should not be available. They - // were cleaned up. - block_number if block_number < n_blocks - SeedAgeLimit::get() => { - assert_err!(seed, Error::::SeedNotAvailable); - } - // Other seeds should be available - _else => { - assert_ok!(seed); - } - } - } - }); -} - -#[test] -fn test_randomness_uniqueness() { - new_test_ext().execute_with(|| { - let n_blocks = 200; - run_to_block(n_blocks); - - // Iterate all blocks and check if the seed is different from the one - // before - let mut previous_seed = None; - for block_number in 0..=n_blocks { - let Ok(current_seed) = - >::get_randomness(block_number) - else { - continue; - }; - - if let Some(previous_seed) = previous_seed { - assert_ne!(previous_seed, current_seed); - } - - previous_seed = Some(current_seed); - } - }) -} diff --git a/pallets/storage-provider/src/lib.rs b/pallets/storage-provider/src/lib.rs index 06c203fee..24e8c8d86 100644 --- a/pallets/storage-provider/src/lib.rs +++ b/pallets/storage-provider/src/lib.rs @@ -36,7 +36,7 @@ pub mod pallet { extern crate alloc; use alloc::{collections::BTreeMap, vec, vec::Vec}; - use core::fmt::Debug; + use core::{convert::AsRef, fmt::Debug}; use cid::Cid; use codec::{Decode, Encode}; @@ -46,7 +46,7 @@ pub mod pallet { pallet_prelude::*, sp_runtime::traits::{CheckedAdd, CheckedSub, One}, traits::{ - Currency, ExistenceRequirement::KeepAlive, Imbalance, ReservableCurrency, + Currency, ExistenceRequirement::KeepAlive, Imbalance, Randomness, ReservableCurrency, WithdrawReasons, }, }; @@ -58,8 +58,8 @@ pub mod pallet { use primitives::{ commitment::{CommD, CommR, Commitment}, pallets::{ - DeadlineInfo as ExternalDeadlineInfo, Market, ProofVerification, Randomness, - StorageProviderValidation, + CurrentDeadline, DeadlineInfo as ExternalDeadlineInfo, Market, ProofVerification, + Randomness, StorageProviderValidation, }, proofs::{derive_prover_id, PublicReplicaInfo, RegisteredPoStProof}, randomness::{draw_randomness, DomainSeparationTag}, @@ -102,7 +102,7 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Randomness generator - type Randomness: Randomness>; + type Randomness: Randomness>; /// Peer ID is derived by hashing an encoded public key. /// Usually represented in bytes. @@ -1691,7 +1691,7 @@ pub mod pallet { entropy: &[u8], ) -> Result<[u8; 32], DispatchError> { // Get randomness from chain - let digest = T::Randomness::get_randomness(block_number)?; + let (digest, _) = T::Randomness::random(&personalization.as_bytes()); // Converting block_height to the type accepted by draw_randomness let block_number = block_number @@ -1701,8 +1701,10 @@ pub mod pallet { Error::::ConversionError })?; + let mut sized_digest = [0; 32]; + sized_digest.copy_from_slice(&digest.as_ref()); // Randomness with the bias - let randomness = draw_randomness(&digest, personalization, block_number, entropy); + let randomness = draw_randomness(&sized_digest, personalization, block_number, entropy); Ok(randomness) } diff --git a/pallets/storage-provider/src/tests/mod.rs b/pallets/storage-provider/src/tests/mod.rs index 9c93104a2..7ae6a005b 100644 --- a/pallets/storage-provider/src/tests/mod.rs +++ b/pallets/storage-provider/src/tests/mod.rs @@ -16,7 +16,7 @@ use frame_system::pallet_prelude::BlockNumberFor; use pallet_market::{BalanceOf, ClientDealProposal, DealProposal, DealState}; use primitives::{ commitment::{CommP, Commitment, RawCommitment}, - pallets::{ProofVerification, Randomness}, + pallets::ProofVerification, proofs::{ProverId, PublicReplicaInfo, RegisteredPoStProof, RegisteredSealProof, Ticket}, sector::SectorNumber, DealId, PartitionNumber, CID_SIZE_IN_BYTES, MAX_DEALS_PER_SECTOR, MAX_PARTITIONS_PER_DEADLINE, @@ -118,10 +118,20 @@ impl ProofVerification for DummyProofsVerification { } /// Randomness generator used by tests. -pub struct DummyRandomnessGenerator; -impl Randomness for DummyRandomnessGenerator { - fn get_randomness(_: BlockNumber) -> Result<[u8; 32], sp_runtime::DispatchError> { - Ok([0; 32]) +pub struct DummyRandomnessGenerator(core::marker::PhantomData) +where + C: frame_system::Config; + +impl frame_support::traits::Randomness> + for DummyRandomnessGenerator +where + C: frame_system::Config, +{ + fn random(_subject: &[u8]) -> (C::Hash, BlockNumberFor) { + ( + Default::default(), + >::block_number(), + ) } } @@ -165,7 +175,7 @@ parameter_types! { impl pallet_storage_provider::Config for Test { type RuntimeEvent = RuntimeEvent; - type Randomness = DummyRandomnessGenerator; + type Randomness = DummyRandomnessGenerator; type PeerId = BoundedVec>; // Max length of SHA256 hash type Currency = Balances; type Market = Market; diff --git a/primitives/src/pallets.rs b/primitives/src/pallets.rs index 00dcdc75c..aa5eeefca 100644 --- a/primitives/src/pallets.rs +++ b/primitives/src/pallets.rs @@ -13,11 +13,6 @@ use crate::{ MAX_SECTORS_PER_PROOF, }; -/// Represents functions that are provided by the Randomness Pallet -pub trait Randomness { - fn get_randomness(block_number: BlockNumber) -> Result<[u8; 32], DispatchError>; -} - pub trait StorageProviderValidation { /// Checks that the storage provider is registered. fn is_registered_storage_provider(storage_provider: &AccountId) -> bool; diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index e881700ab..b589f799e 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -61,7 +61,7 @@ use super::{ MAXIMUM_BLOCK_WEIGHT, MICROUNIT, NORMAL_DISPATCH_RATIO, RELAY_CHAIN_SLOT_DURATION_MILLIS, SLOT_DURATION, UNINCLUDED_SEGMENT_CAPACITY, VERSION, }; -use crate::{DAYS, MINUTES}; +use crate::{BabeDataGetter, DAYS, MINUTES}; parameter_types! { pub const Version: RuntimeVersion = VERSION; @@ -442,6 +442,8 @@ impl pallet_randomness::Config for Runtime { type CleanupInterval = CleanupInterval; type SeedAgeLimit = SeedAgeLimit; + + type AuthorVrfGetter = BabeDataGetter; } #[cfg(feature = "testnet")] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1d05722be..ebc04d6da 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -14,6 +14,7 @@ mod weights; extern crate alloc; use alloc::vec::Vec; +use cumulus_pallet_parachain_system::{RelayChainStateProof, RelayStateProof, ValidationData}; use frame_support::{ genesis_builder_helper::{build_state, get_preset}, weights::{ @@ -25,7 +26,7 @@ use pallet_aura::Authorities; use smallvec::smallvec; use sp_api::impl_runtime_apis; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_core::{crypto::KeyTypeId, hex2array, Get, OpaqueMetadata}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; use sp_runtime::{ @@ -661,3 +662,41 @@ impl_runtime_apis! { } } } + +/// Only callable after `set_validation_data` is called which forms this proof the same way +fn relay_chain_state_proof() -> RelayChainStateProof +where + Runtime: cumulus_pallet_parachain_system::Config, +{ + let relay_storage_root = ValidationData::::get() + .expect("set in `set_validation_data`") + .relay_parent_storage_root; + let relay_chain_state = + RelayStateProof::::get().expect("set in `set_validation_data`"); + RelayChainStateProof::new(ParachainInfo::get(), relay_storage_root, relay_chain_state) + .expect("Invalid relay chain state proof, already constructed in `set_validation_data`") +} + +pub struct BabeDataGetter(sp_std::marker::PhantomData); +impl pallet_randomness::GetAuthorVrf for BabeDataGetter +where + Runtime: cumulus_pallet_parachain_system::Config, +{ + // Tolerate panic here because only ever called in inherent (so can be omitted) + fn get_author_vrf() -> Option { + if cfg!(feature = "runtime-benchmarks") { + // storage reads as per actual reads + let _relay_storage_root = ValidationData::::get(); + let _relay_chain_state = RelayStateProof::::get(); + return None; + } + relay_chain_state_proof::() + .read_optional_entry(&hex2array!( + // Encoded Storage Key for AuthorVrfRandomness + "1cb6f36e027abb2091cfb5110ab5087fd077dfdb8adb10f78f10a5df8742c545" + )) + .ok() + .flatten() + .expect("expected to be able to read epoch index from relay chain state proof") + } +} From f86a8676d5690ed98da6de3d8ff556a32bbfa8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 12 Dec 2024 19:48:00 +0000 Subject: [PATCH 2/6] feat: author vrf --- Cargo.lock | 20 +------ Cargo.toml | 2 - pallets/randomness/Cargo.toml | 7 ++- pallets/randomness/src/inherent.rs | 38 +++++-------- pallets/randomness/src/lib.rs | 76 ++++--------------------- pallets/randomness/src/mock.rs | 10 ---- pallets/storage-provider/Cargo.toml | 1 - runtime/Cargo.toml | 1 - runtime/src/configs/mod.rs | 39 ------------- runtime/src/lib.rs | 17 +++--- storagext/lib/src/clients/randomness.rs | 5 +- 11 files changed, 42 insertions(+), 174 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d70c8ac30..3c1625f5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9901,7 +9901,7 @@ dependencies = [ "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", - "pallet-insecure-randomness-collective-flip 26.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pallet-insecure-randomness-collective-flip", "pallet-message-queue 41.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "pallet-proxy 38.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "pallet-timestamp 37.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -10427,19 +10427,6 @@ dependencies = [ "sp-runtime 39.0.2", ] -[[package]] -name = "pallet-insecure-randomness-collective-flip" -version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2#dba2dd59101617aad64d167e400b19e2c35052b1" -dependencies = [ - "frame-support 38.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", - "frame-system 38.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", - "parity-scale-codec", - "safe-mix", - "scale-info", - "sp-runtime 39.0.1", -] - [[package]] name = "pallet-lottery" version = "38.0.0" @@ -11088,7 +11075,6 @@ dependencies = [ "frame-support 38.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "frame-system 38.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "log", - "pallet-insecure-randomness-collective-flip 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "parity-scale-codec", "primitives", "scale-info", @@ -11726,7 +11712,6 @@ dependencies = [ "log", "multihash-codetable", "pallet-balances 39.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", - "pallet-insecure-randomness-collective-flip 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "pallet-market", "pallet-proofs", "pallet-randomness", @@ -12923,7 +12908,6 @@ dependencies = [ "pallet-balances 39.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "pallet-collator-selection 19.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "pallet-faucet", - "pallet-insecure-randomness-collective-flip 26.0.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "pallet-market", "pallet-message-queue 41.0.2 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409-2)", "pallet-proofs", @@ -14179,7 +14163,7 @@ dependencies = [ "pallet-identity 38.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "pallet-im-online 37.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "pallet-indices 38.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pallet-insecure-randomness-collective-flip 26.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership 38.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "pallet-message-queue 41.0.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index bbfc695ed..43d909a97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -215,8 +215,6 @@ frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk", tag = pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409-2", default-features = false } pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409-2", default-features = false } pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409-2", default-features = false } -# TODO(#458,@cernicc,17/10/2024): Use secure randomness -pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409-2", default-features = false } pallet-message-queue = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409-2", default-features = false } pallet-session = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409-2", default-features = false } pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2409-2", default-features = false } diff --git a/pallets/randomness/Cargo.toml b/pallets/randomness/Cargo.toml index 1e05e0326..31a6c53ee 100644 --- a/pallets/randomness/Cargo.toml +++ b/pallets/randomness/Cargo.toml @@ -16,19 +16,20 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = { workspace = true, optional = true } codec = { features = ["derive"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } frame-support.workspace = true frame-system.workspace = true log = { workspace = true } -pallet-insecure-randomness-collective-flip = { workspace = true, default-features = false } primitives = { workspace = true } scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true, default-features = false } sp-inherents.workspace = true sp-runtime.workspace = true +# Optional +async-trait = { workspace = true, optional = true } +frame-benchmarking = { workspace = true, optional = true } + [dev-dependencies] sp-io = { default-features = true, workspace = true } diff --git a/pallets/randomness/src/inherent.rs b/pallets/randomness/src/inherent.rs index 3f889b6a4..29e045f64 100644 --- a/pallets/randomness/src/inherent.rs +++ b/pallets/randomness/src/inherent.rs @@ -1,18 +1,19 @@ -use codec::{Decode, Encode}; -use sp_inherents::{Error, InherentData, InherentIdentifier, IsFatalError}; +use codec::Encode; +use sp_inherents::{InherentIdentifier, IsFatalError}; use sp_runtime::RuntimeString; +/// BABE VRF Inherent Identifier +pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babe_vrf"; + #[derive(Encode)] -#[cfg_attr(feature = "std", derive(Debug, Decode))] +#[cfg_attr(feature = "std", derive(Debug, codec::Decode))] pub enum InherentError { Other(RuntimeString), } impl IsFatalError for InherentError { fn is_fatal_error(&self) -> bool { - match *self { - InherentError::Other(_) => true, - } + true } } @@ -28,34 +29,25 @@ impl InherentError { } } -/// The InherentIdentifier to set the babe randomness results -pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babe_vrf"; - -/// A bare minimum inherent data provider that provides no real data. -/// The inherent is simply used as a way to kick off some computation -/// until https://github.com/paritytech/substrate/pull/10128 lands. +#[cfg(feature = "std")] pub struct InherentDataProvider; #[cfg(feature = "std")] #[async_trait::async_trait] impl sp_inherents::InherentDataProvider for InherentDataProvider { - async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + async fn provide_inherent_data( + &self, + inherent_data: &mut sp_inherents::InherentData, + ) -> Result<(), sp_inherents::Error> { inherent_data.put_data(INHERENT_IDENTIFIER, &()) } async fn try_handle_error( &self, - identifier: &InherentIdentifier, + _identifier: &InherentIdentifier, _error: &[u8], ) -> Option> { - // Don't process modules from other inherents - if *identifier != INHERENT_IDENTIFIER { - return None; - } - - // All errors with the randomness inherent are fatal - Some(Err(Error::Application(Box::from(String::from( - "Error processing dummy randomness inherent", - ))))) + // Most substrate inherents return None + None } } diff --git a/pallets/randomness/src/lib.rs b/pallets/randomness/src/lib.rs index 8bdfd6067..01461cdba 100644 --- a/pallets/randomness/src/lib.rs +++ b/pallets/randomness/src/lib.rs @@ -29,7 +29,6 @@ pub mod pallet { use frame_support::{ inherent::ProvideInherent, pallet_prelude::{ValueQuery, *}, - traits::Randomness as SubstrateRandomness, }; use frame_system::pallet_prelude::{BlockNumberFor, *}; use sp_inherents::{InherentData, InherentIdentifier}; @@ -42,27 +41,13 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - /// Underlying randomness generator - type Generator: SubstrateRandomness>; - - /// Clean-up interval specified in number of blocks between cleanups. - #[pallet::constant] - type CleanupInterval: Get>; - - /// The number of blocks after which the seed is cleaned up. - #[pallet::constant] - type SeedAgeLimit: Get>; - + /// The Author VRF getter. type AuthorVrfGetter: GetAuthorVrf; } #[pallet::pallet] pub struct Pallet(_); - #[pallet::storage] - #[pallet::getter(fn seeds)] - pub type SeedsMap = StorageMap<_, _, BlockNumberFor, [u8; 32]>; - #[pallet::error] pub enum Error { /// The seed for the given block number is not available. @@ -78,9 +63,19 @@ pub mod pallet { pub fn set_author_vrf(origin: OriginFor) -> DispatchResult { ensure_none(origin)?; + // `get_author_vrf` should only return `None` iff the BABE leader election fails + // and falls back to the secondary slots + // + // References: + // * https://github.com/paritytech/polkadot-sdk/blob/5788ae8609e1e6947c588a5745d22d8777e47f4e/substrate/frame/babe/src/lib.rs#L268-L273 + // * https://spec.polkadot.network/sect-block-production#defn-babe-secondary-slots if let Some(author_vrf) = T::AuthorVrfGetter::get_author_vrf() { AuthorVrf::::put(author_vrf); } else { + // We don't change the value here, this isn't great but we're not expecting + // leader election to fail often enough that it truly affects security. + // We're aware this is somewhat wishful thinking but time/output constraints + // dictate that this is good enough for now! log::warn!("AuthorVrf is empty, keeping previous value"); } @@ -126,53 +121,4 @@ pub mod pallet { (randomness, block_number) } } - - // #[pallet::hooks] - // impl Hooks> for Pallet { - // fn on_initialize(block_number: BlockNumberFor) -> Weight { - // // TODO(no-ref,@cernicc,22/10/2024): Set proper weights - // let weight = T::DbWeight::get().reads(1); - - // // The determinable_after is a block number in the past since which - // // the current seed is determinable by chain observers. - // let (seed, determinable_after) = T::Generator::random_seed(); - // let seed: [u8; 32] = seed.as_ref().try_into().expect("seed should be 32 bytes"); - - // // We are not saving the seed for the zeroth block. This is an edge - // // case when trying to use randomness at the network genesis. - // if determinable_after == Zero::zero() { - // return weight; - // } - - // // Save the seed - // SeedsMap::::insert(block_number, seed); - // log::info!(target: LOG_TARGET, "on_initialize: height: {block_number:?}, seed: {seed:?}"); - - // weight - // } - - // fn on_finalize(current_block_number: BlockNumberFor) { - // // Check if we should clean the seeds - // if current_block_number % T::CleanupInterval::get() != Zero::zero() { - // return; - // } - - // // Mark which seeds to remove - // let mut blocks_to_remove = Vec::new(); - // for creation_height in SeedsMap::::iter_keys() { - // let age_limit = T::SeedAgeLimit::get(); - // let current_age = current_block_number.saturating_sub(creation_height); - - // // Seed is old enough to be removed - // if current_age >= age_limit { - // blocks_to_remove.push(creation_height); - // } - // } - - // // Remove old seeds - // blocks_to_remove.iter().for_each(|number| { - // SeedsMap::::remove(number); - // }); - // } - // } } diff --git a/pallets/randomness/src/mock.rs b/pallets/randomness/src/mock.rs index 7e7f03c27..5ff32b666 100644 --- a/pallets/randomness/src/mock.rs +++ b/pallets/randomness/src/mock.rs @@ -3,7 +3,6 @@ use frame_support::{ traits::{OnFinalize, OnInitialize}, }; use frame_system::{self as system, mocking::MockBlock}; -use pallet_insecure_randomness_collective_flip as substrate_randomness; use sp_core::parameter_types; use sp_runtime::{ traits::{Hash, HashingFor, Header}, @@ -34,8 +33,6 @@ mod test_runtime { #[runtime::pallet_index(0)] pub type System = frame_system; #[runtime::pallet_index(1)] - pub type SubstrateRandomness = substrate_randomness; - #[runtime::pallet_index(2)] pub type RandomnessModule = crate; } @@ -51,10 +48,6 @@ parameter_types! { } impl crate::Config for Test { - type Generator = SubstrateRandomness; - type CleanupInterval = CleanupInterval; - type SeedAgeLimit = SeedAgeLimit; - type AuthorVrfGetter = DummyVrf; } @@ -71,8 +64,6 @@ where } } -impl substrate_randomness::Config for Test {} - // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { let t = system::GenesisConfig::::default() @@ -98,7 +89,6 @@ pub fn run_to_block(n: u64) { } System::initialize(&block_number, &parent_hash, &Default::default()); - SubstrateRandomness::on_initialize(block_number); RandomnessModule::on_initialize(block_number); let header = System::finalize(); diff --git a/pallets/storage-provider/Cargo.toml b/pallets/storage-provider/Cargo.toml index 56dea3bff..558fa82c4 100644 --- a/pallets/storage-provider/Cargo.toml +++ b/pallets/storage-provider/Cargo.toml @@ -19,7 +19,6 @@ cid = { workspace = true, features = ["alloc"] } codec = { workspace = true, default-features = false, features = ["derive"] } hex = { workspace = true, default-features = false, features = ["alloc"] } log = { workspace = true, features = ["kv"] } -pallet-insecure-randomness-collective-flip = { workspace = true, default-features = false } pallet-proofs = { workspace = true, default-features = false } pallet-randomness = { workspace = true, default-features = false } primitives = { workspace = true, default-features = false } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 364c34cb9..209e89d18 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -47,7 +47,6 @@ frame-try-runtime = { workspace = true, default-features = false, optional = tru pallet-aura = { workspace = true, default-features = false } pallet-authorship = { workspace = true, default-features = false } pallet-balances = { workspace = true, default-features = false } -pallet-insecure-randomness-collective-flip = { workspace = true, default-features = false } pallet-message-queue = { workspace = true, default-features = false } pallet-session = { workspace = true, default-features = false } pallet-sudo = { workspace = true, default-features = false } diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index b589f799e..52bbf9adb 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -430,46 +430,7 @@ impl pallet_faucet::Config for Runtime { type FaucetDripDelay = FaucetDripDelay; } -/// Config for insecure randomness -impl pallet_insecure_randomness_collective_flip::Config for Runtime {} - /// Config for our randomness pallet impl pallet_randomness::Config for Runtime { - #[cfg(not(feature = "testnet"))] - type Generator = super::RandomnessSource; - #[cfg(feature = "testnet")] - type Generator = randomness_source_testnet::PredictableRandomnessSource; - - type CleanupInterval = CleanupInterval; - type SeedAgeLimit = SeedAgeLimit; - type AuthorVrfGetter = BabeDataGetter; } - -#[cfg(feature = "testnet")] -mod randomness_source_testnet { - use codec::Decode; - use frame_support::traits::Randomness; - use frame_system::{pallet_prelude::BlockNumberFor, Config, Pallet}; - use sp_runtime::traits::TrailingZeroInput; - use sp_std::marker::PhantomData; - - /// Randomness source that always returns same random value based on the - /// subject used. - /// - /// ! USE THIS ONLY IN TESTNET ! - pub struct PredictableRandomnessSource(PhantomData); - impl Randomness> for PredictableRandomnessSource - where - Output: Decode + Default, - T: Config, - { - fn random(subject: &[u8]) -> (Output, BlockNumberFor) { - ( - Output::decode(&mut TrailingZeroInput::new(subject)).unwrap_or_default(), - // This means the the randomness can be used immediately. - Pallet::::block_number(), - ) - } - } -} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ebc04d6da..9887cd86b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -270,9 +270,6 @@ mod runtime { pub type Timestamp = pallet_timestamp; #[runtime::pallet_index(3)] pub type ParachainInfo = parachain_info; - // Temporary. Will be removed after we switch to babe - #[runtime::pallet_index(4)] - pub type RandomnessSource = pallet_insecure_randomness_collective_flip; // Monetary stuff. #[runtime::pallet_index(10)] @@ -342,9 +339,6 @@ mod runtime { pub type Timestamp = pallet_timestamp; #[runtime::pallet_index(3)] pub type ParachainInfo = parachain_info; - // Temporary. Will be removed after we switch to babe - #[runtime::pallet_index(4)] - pub type RandomnessSource = pallet_insecure_randomness_collective_flip; // Monetary stuff. #[runtime::pallet_index(10)] @@ -663,6 +657,12 @@ impl_runtime_apis! { } } +// The following code cannot be placed out of this crate because of WASM constraints + +/// Storage Key for AuthorVrfRandomness +const AUTHOR_VRF_STORAGE_KEY: [u8; 32] = + hex2array!("1cb6f36e027abb2091cfb5110ab5087fd077dfdb8adb10f78f10a5df8742c545"); + /// Only callable after `set_validation_data` is called which forms this proof the same way fn relay_chain_state_proof() -> RelayChainStateProof where @@ -691,10 +691,7 @@ where return None; } relay_chain_state_proof::() - .read_optional_entry(&hex2array!( - // Encoded Storage Key for AuthorVrfRandomness - "1cb6f36e027abb2091cfb5110ab5087fd077dfdb8adb10f78f10a5df8742c545" - )) + .read_optional_entry(&AUTHOR_VRF_STORAGE_KEY) .ok() .flatten() .expect("expected to be able to read epoch index from relay chain state proof") diff --git a/storagext/lib/src/clients/randomness.rs b/storagext/lib/src/clients/randomness.rs index 40d41f2e8..ab9b3a948 100644 --- a/storagext/lib/src/clients/randomness.rs +++ b/storagext/lib/src/clients/randomness.rs @@ -14,9 +14,9 @@ pub trait RandomnessClientExt { impl RandomnessClientExt for crate::runtime::client::Client { async fn get_randomness( &self, - block_number: BlockNumber, + _block_number: BlockNumber, ) -> Result, subxt::Error> { - let seed_query = runtime::storage().randomness().seeds_map(block_number); + let seed_query = runtime::storage().randomness().author_vrf(); self.client .storage() @@ -24,5 +24,6 @@ impl RandomnessClientExt for crate::runtime::client::Client { .await? .fetch(&seed_query) .await + .map(|opt_hash| opt_hash.map(|hash| *hash.as_fixed_bytes())) } } From a1bec74292c2bbd944edfaaa9c5425b4ec722ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 13 Dec 2024 09:26:03 +0000 Subject: [PATCH 3/6] rebase --- pallets/storage-provider/src/lib.rs | 4 ++-- storagext/lib/artifacts/metadata.scale | Bin 165185 -> 164964 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/storage-provider/src/lib.rs b/pallets/storage-provider/src/lib.rs index 24e8c8d86..24cc7d34c 100644 --- a/pallets/storage-provider/src/lib.rs +++ b/pallets/storage-provider/src/lib.rs @@ -58,8 +58,8 @@ pub mod pallet { use primitives::{ commitment::{CommD, CommR, Commitment}, pallets::{ - CurrentDeadline, DeadlineInfo as ExternalDeadlineInfo, Market, ProofVerification, - Randomness, StorageProviderValidation, + DeadlineInfo as ExternalDeadlineInfo, Market, ProofVerification, + StorageProviderValidation, }, proofs::{derive_prover_id, PublicReplicaInfo, RegisteredPoStProof}, randomness::{draw_randomness, DomainSeparationTag}, diff --git a/storagext/lib/artifacts/metadata.scale b/storagext/lib/artifacts/metadata.scale index 4bc9e2168dae670042befb57c5f11164c254cf2d..e9495b0f450c47c1981b998cda644ceab6c2b623 100644 GIT binary patch delta 3135 zcmcgueNa@_6~Djx?#jX}x?5dX1ms&li@JbSJ_CXpFbV?2D5ki96<2pzWfufRn>ZSy z)#>1ibK|rmC5>&;lvG^Ji>*d6#RL-+Izv+HFoqeeAuUdrshzvkUvy8Cj_nT#%Ze^-1+kHpFeVWdxjrP5xLU6D&jkJL75k^8&W%<7d8 zQD(P!-1QzpdIdRD&OaL))F%-_dn7U-$uTWULIx*w+?u9i(68f@vsFR2RN+C14Ecr4 z!;``z(}aiR_2;aXds4&-o6}ZbV~2(4p?B2Ylb|oEl}$b~y(h`JFNMl~_k{$Fs0xQA zGO8&Yn^gF4n!+)maC6W@Rd7rqeISj^Y)ex3A~CtJ+EWf#v)@_f+wIzDKlDmT4*3m~8D-V_K%$c$Hsj;X-S zy1E2`zPziOBm~Pp>&c-e#=qW+Ir4*^&;T<->k4=CiGN1ON!Q%dw!YtAi}BkS`smQ4 zX@jxG=5$V-@V)u571Yf906plPi9+8*Zw$q_9QEm5W@0!k|MdE1`O&8d2$Q4x4zoyx zEWzWfrlc}keXZM7Z})f@$)`2Faz#(J;AWzQuE^0|TiG>pk%^J0CK ze>xG2NO|N=8e)8rU;I^~G1@gE?wXMxr{4X6_&V;cMzXK#?q`gpFhdgWtU|c_s}U=# zzCVx5G2oPM^kG&MdU&!pgI-=@MvB<&Gb0&R<=19hG{DMV&p{HF^5e0Hf}MB9B1`N> zVlfXHTpx$nzzh*Anj_rj^|`o?eEv-w&Iy?ZPa&SJXYy!ag7Qr~ehK`LcPGNkBNH*3 zU!RYhX@te%z}iH_Y9tGPIT6dXb-LhK3y>&wZAF;NmnR`qkV=zKDo7nksDX{=6+)HE zOh&x;RVSlg__1UGis?!w-zf@}9VOA}WF=#klAT_MyPmNM6fx+H>)iErLWr^4?Xojc zB(4JD|1rIBEhB4{`wK9iVm+Ukf?{EBV+x)L-N>+fgTq<7w$bTzkT<3q)e7-NeDW zN)aCDP$6|O;^Ln#!iU(ULTZtt3TNXG|7bDZ6@d`wM*Hp;=7E1^#R44RNEOjL?nhWY zy2`*yQ(+Z*ftgi^3v9#r%~S+vq!2!uil;U4NM4>MAhl~KYbU6k$+uUlYRA$LsmaaZ zap~A5wPRrdnW2g zv5y0fLFfrLTT3urQKgB!m50xEGMkCScbb$Jcw4Dxf|vY&1YJ@A>1L#x`PLMC4(-wmzQk2-iVK=Q z7Zgz>`^7xqmp9>6(PE#iLJ|7-8&z;&KrwE{2NDLAo7MQSgj>9%1`0-aKrI^S1XK3c zVm$z*-;Q?xozhx|HqcvIF$_%>!@X(6FvNwXaPD?s1si5~U&ba2?{mOHho_3+p6d4y zBO|JC1Yfruxftcgw_}~@AwztryWCsa;C62%WYm8s+^ALE*fc|9stzN0sHrI5fdV%E z*xuP4!ru53d*UM8=tL`@=|l|gbi&1tImH~>vfs!L*I^c~aG?T$e9LZxDpy=+)gf5P z@t}z!n0K_oYBuRK2bSul%HHIc=cXnU&ob-8qbGW2gL|jjV{^vv?M+x=G3!uRvUO|C zHk+fq+)-C=^ENh!MsMbSY!cHgj6Y}+vnGt2b|ZRmqz-xZnp%%7GjmFm5)!5(M9LG> zK~EypWxjtIqnfT@2|9TF`1bF{)Bmvx;91Rh0m=MgGr~nvQZJ#lAwf|u$(PZbBn-R*yghi5YFndso4cX9sX;`O$oV6yl-j*81RzG)bpSUYoXa~1 z3lfx#2SI0q2}GdM{v~!$R49Re!&mXjxRDk!bn`+peS`u2R0w?wL;TASnj+FK zB$Os$TzM)~Jnlp?tPiKPA`RXPr~i;(Qr?N8&r1kXX2#Jgz!<+7Prnw)bR~fvM5MBE zJ~aj+S*cu1uLWCD7|sw(=%PqcL8GhD+2|1imY`2b&!IYqqRY&suj@q7{iT3*gbE~D ztHj$$c>7v49hxEDG_6}{Y*2i@AGykgtsD;!KZ77g;I7ox(J<&HBr`KB2e#2qAfmEv nJIz;4Y^Pu71uh;B{heMsapGF(9)ZMbt+ZV~!^|o!1(1IO#>cnf delta 3486 zcmcgueQ;FO6~Di`cOjc5l4v%&ONe<1SxCeUyCk#(5==-00>s1wsY28|HZRGO{RsP! z4;0aM5J+SV&6Bf*AQsihj6eZ+@7JSXAeLYZ+I$|M&9~CoE)A+j^*mf@!zB?o6CK6o$FZ zonmPx4`pe)U*B~NnW)w=g>oiZ-srGQ?>_U^v1z_`Wbl`;C0gF;r-t-zM_OUk8izL8 ztc;KuPZJx{La#Zr_lLYlPkc4B(PGJDDcpAfIa+jBf?eA;d^23y&~Sh9J2%mNRGQN5 z&|ZBvWGKyhFLz4V`kohJ*?(*vsuFJ;%cpG8g2Ng{TD0=xbC{jsg(PwKxSQoNj3$Yt zCyH1;!$6Yw-3f_$n0D&@d$biN3z?5$Nbv90-W9|M!(bA$_JeusG{bO`82MlU8)Xp-JH3yYq712H)*-mpAmmWkxV*elarXyb~I$H{NqUG!b3*5q6m)3GF z3&q;jb3Yc}7tgJNC-K?2KQZcKym}9A;FFt>s+}MAVR6FoNmd%3P2BvCWqCNpJMD1e z1m9zaM^JCuQH;g<1v}nO!(#rIN)(}-A1^>2RQ_oJmIezA9Dok@%bn)S$6)-3ZXrT zvh&6oVX&Nc)nLA04K2cK-ctmJU_Duc#%Ywd7Pmp+J8DrTBz?sw6u(~;ru~;l5tx&q!2l}A8EcvM#XseG0Vrq3Xs>F<4ck{12sB_8;6^3wSJk+6XRs(9R z&ydQzr35v?)#po4?owG=TCYEt(p{Q%r$5LtYUDt`7#Bf~mB_ILjgW3-WGl~Jh~Fb_ zgw(0!)!u+zJhL7yp6A7Wkqr@IDRHs33f#U3H{l6hwMe9}-w3l^+r2W4Ke-4#L5t}5 z1YCqVhhJBUnIuksxm;N!CwGV)60yt3;3>|Ko&ZA>s`2!uU&!+efARU!qmao zLPFwvs2u&Yk7>^|G#Ni*+-+y(yxhsA=OrQA`K6^;fQR|oWpMN5%aCTS8|cHyP5hQe zalLJ)5zFn2?BW-fiBfuk&#Hhb&e2yPGP(cXW^zCOr~-BL8K!;NT%oahGL2>3`nh<9 z|DsYP@maGe4wy|bU^GP@9b`OxE@tC^*&HvJ&GCX^{Ut_T`r+o_qB+v(D@OcAO*2g>oVS&oN|L%hkzoBu&MY8Pvl;T2t4jS`r7|34SxfLV|O z|Gk17Z2DUr z=m$MxcEi|IH;iA?4P%D79NzdGW@;bx{fHk?;l+7=Sw$&5e@#D(8~%?ma>>AN#ASZ` zKHQdhg`u!9+#G9+gv0HGTuyGt$uOI4;uE_xgeot+KJGET6LhV*kY2Eu+Hve!N-qQ;nK-x^W^@TsfNeGKqAAu9a zx??Bk4A-p;-tI+9mo_QUF7a?w2qh^SN*Rgki++wc*a=3)c-0A!s(RX1Yd^_Ow5 zEF0$40~ag&He7J&M?c0s8`AYnxqZxLw*D2>Z84or>J1M50oABpj4tor)BYqcLfT&lE=m zOO8phKNgkbs1z{_?+8j!MQM|QQ@Tr}^_^-|s8$6gA|M??D8RB5Q9~Wl=6HL%5|Mh; zSf|vQw0UnR9^5QgZegZX+N>BT!?GG`Q+mZW7EY3_;=SG~9IZ`K;Xs=r9P5;$GdoK} zX%MC#%%E$ZI+;z2FsXl$O*0wNxh;pbiOLS;(9bQf z>;62t*@9gCXaPL}Oz=kw>1X2Z>UGmckgrd=X@(6R{knzpXu4Cp8V?doTJuOzO*|M6 z#G~S=Kro_zT0yN4W909Z^f@a=^}RLpfI}2U*(NGppy~RCd+C`O;&oQmP78L3z$x8uLmI@foJ_b0THR_Z7(P!j9C_sroyU zF;AvhWHqT!7>#$8=)2pg3sx`}bLlU3&_6&NFs9OKOE@O!hgEtu1rz#H5&CM17&$BN Vr`tps58Y3nO_||h>j!6&e*%X@9jpKV From 2fd86d5e435a6b468ef025923c1a5de5ef5e26cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 13 Dec 2024 12:07:53 +0000 Subject: [PATCH 4/6] feat: storagext fix --- pallets/randomness/src/lib.rs | 19 ++++++++++++++----- storagext/lib/artifacts/metadata.scale | Bin 164964 -> 165206 bytes storagext/lib/src/clients/randomness.rs | 8 +++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/pallets/randomness/src/lib.rs b/pallets/randomness/src/lib.rs index 01461cdba..15f5923d8 100644 --- a/pallets/randomness/src/lib.rs +++ b/pallets/randomness/src/lib.rs @@ -21,15 +21,11 @@ where #[frame_support::pallet(dev_mode)] pub mod pallet { - extern crate alloc; use alloc::vec::Vec; - use frame_support::{ - inherent::ProvideInherent, - pallet_prelude::{ValueQuery, *}, - }; + use frame_support::{inherent::ProvideInherent, pallet_prelude::*}; use frame_system::pallet_prelude::{BlockNumberFor, *}; use sp_inherents::{InherentData, InherentIdentifier}; use sp_runtime::traits::Hash; @@ -39,6 +35,8 @@ pub mod pallet { pub const LOG_TARGET: &'static str = "runtime::randomness"; + const HISTORY_SIZE: u32 = 256; + #[pallet::config] pub trait Config: frame_system::Config { /// The Author VRF getter. @@ -54,10 +52,16 @@ pub mod pallet { SeedNotAvailable, } + /// The latest author VRF randomness from BABE. #[pallet::storage] #[pallet::getter(fn author_vrf)] pub type AuthorVrf = StorageValue<_, T::Hash, ValueQuery>; + /// The last 256 author VRF randomness values from BABE, organized in a ring buffer fashion. + #[pallet::storage] + #[pallet::getter(fn author_vrf_history)] + pub type AuthorVrfHistory = CountedStorageMap<_, _, BlockNumberFor, T::Hash>; + #[pallet::call] impl Pallet { pub fn set_author_vrf(origin: OriginFor) -> DispatchResult { @@ -71,6 +75,11 @@ pub mod pallet { // * https://spec.polkadot.network/sect-block-production#defn-babe-secondary-slots if let Some(author_vrf) = T::AuthorVrfGetter::get_author_vrf() { AuthorVrf::::put(author_vrf); + let current_block = >::block_number(); + if AuthorVrfHistory::::count() == HISTORY_SIZE { + AuthorVrfHistory::::remove(current_block - HISTORY_SIZE.into()); + } + AuthorVrfHistory::::insert(current_block, author_vrf); } else { // We don't change the value here, this isn't great but we're not expecting // leader election to fail often enough that it truly affects security. diff --git a/storagext/lib/artifacts/metadata.scale b/storagext/lib/artifacts/metadata.scale index e9495b0f450c47c1981b998cda644ceab6c2b623..be80c82ec8999555f2fc714c91fcf18d8cf4aefa 100644 GIT binary patch delta 282 zcmaFT!F8>RtD%K)3sYzsBhU1(G$y@zmJJFa8L0|6i6yDUB?^h9B^mie3SmKR3Pp)| zDfzj1sl~+#X+`FEUAo8bAYB8nVR7+ zsVp(4G!@A}oqC1*qV&YP%&OEBh0HvKM1`WvymW=6(zLYHB89ZX;*8AvJiQF({L;LV z)FQY1B6Ry183Y6vfPiHURE Result, subxt::Error> { - let seed_query = runtime::storage().randomness().author_vrf(); + let randomness_query = runtime::storage() + .randomness() + .author_vrf_history(block_number); self.client .storage() .at_latest() .await? - .fetch(&seed_query) + .fetch(&randomness_query) .await .map(|opt_hash| opt_hash.map(|hash| *hash.as_fixed_bytes())) } From c3707a9c3a24e0b98556ca230aec4d39d8575584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 13 Dec 2024 15:46:06 +0000 Subject: [PATCH 5/6] more fixes --- docs/src/architecture/pallets/randomness.md | 10 ------- pallets/randomness/src/lib.rs | 1 - pallets/randomness/src/mock.rs | 24 +++++---------- pallets/randomness/src/tests.rs | 33 +++++++++++++++++++++ runtime/src/configs/mod.rs | 7 ----- 5 files changed, 40 insertions(+), 35 deletions(-) diff --git a/docs/src/architecture/pallets/randomness.md b/docs/src/architecture/pallets/randomness.md index 68c461518..c098bf74d 100644 --- a/docs/src/architecture/pallets/randomness.md +++ b/docs/src/architecture/pallets/randomness.md @@ -36,13 +36,3 @@ The pallet does not emit any events. The Randomness Pallet actions can fail with the following errors: - `SeedNotAvailable` - the seed for the given block number is not available, which means the randomness pallet has not gathered randomness for this block yet. - -## Constants - -The Randomness Pallet has the following constants: - -| Name | Description | Value | -| ----------------- | ----------------------------------------------------------------- | ------- | -| `CleanupInterval` | Clean-up interval specified in number of blocks between cleanups. | 1 Day | -| `SeedAgeLimit` | The number of blocks after which the seed is cleaned up. | 30 Days | - diff --git a/pallets/randomness/src/lib.rs b/pallets/randomness/src/lib.rs index 15f5923d8..a3c27a104 100644 --- a/pallets/randomness/src/lib.rs +++ b/pallets/randomness/src/lib.rs @@ -6,7 +6,6 @@ pub use pallet::*; #[cfg(test)] mod mock; - #[cfg(test)] mod tests; diff --git a/pallets/randomness/src/mock.rs b/pallets/randomness/src/mock.rs index 5ff32b666..32b14b9bf 100644 --- a/pallets/randomness/src/mock.rs +++ b/pallets/randomness/src/mock.rs @@ -1,18 +1,12 @@ -use frame_support::{ - derive_impl, - traits::{OnFinalize, OnInitialize}, -}; -use frame_system::{self as system, mocking::MockBlock}; -use sp_core::parameter_types; +use frame_support::{derive_impl, traits::OnFinalize}; +use frame_system::{mocking::MockBlock, RawOrigin}; use sp_runtime::{ - traits::{Hash, HashingFor, Header}, + traits::{Hash, Header}, BuildStorage, }; use crate::GetAuthorVrf; -pub type BlockNumber = u64; - // Configure a mock runtime to test the pallet. #[frame_support::runtime] mod test_runtime { @@ -42,11 +36,6 @@ impl frame_system::Config for Test { type Nonce = u64; } -parameter_types! { - pub const CleanupInterval: BlockNumber = 1; - pub const SeedAgeLimit: BlockNumber = 200; -} - impl crate::Config for Test { type AuthorVrfGetter = DummyVrf; } @@ -66,7 +55,7 @@ where // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { - let t = system::GenesisConfig::::default() + let t = frame_system::GenesisConfig::::default() .build_storage() .unwrap() .into(); @@ -85,11 +74,12 @@ pub fn run_to_block(n: u64) { if System::block_number() > 1 { let finalizing_block_number = block_number - 1; System::on_finalize(finalizing_block_number); - RandomnessModule::on_finalize(finalizing_block_number); } + // It's ok under test + RandomnessModule::set_author_vrf(RawOrigin::None.into()).unwrap(); + System::initialize(&block_number, &parent_hash, &Default::default()); - RandomnessModule::on_initialize(block_number); let header = System::finalize(); parent_hash = header.hash(); diff --git a/pallets/randomness/src/tests.rs b/pallets/randomness/src/tests.rs index 8b1378917..eb2107406 100644 --- a/pallets/randomness/src/tests.rs +++ b/pallets/randomness/src/tests.rs @@ -1 +1,34 @@ +use sp_runtime::traits::Hash; +use crate::mock::{new_test_ext, run_to_block, RandomnessModule, Test}; + +#[test] +fn test_no_randomness() { + new_test_ext().execute_with(|| { + assert_eq!(::author_vrf(), Default::default()); + }) +} + +#[test] +fn test_randomness() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_eq!( + ::author_vrf(), + ::Hashing::hash(&[]) + ); + }) +} + +#[test] +fn test_history() { + new_test_ext().execute_with(|| { + run_to_block(256); + assert_eq!(::author_vrf_history(0), None); + assert_eq!( + ::author_vrf_history(1), + Some(::Hashing::hash(&[])) + ); + assert_eq!(::author_vrf_history(258), None); + }) +} diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index 52bbf9adb..ab86f25a6 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -338,9 +338,6 @@ parameter_types! { pub const MinDealDuration: u64 = 20 * DAYS; pub const MaxDealDuration: u64 = 1278 * DAYS; - // Randomness pallet - pub const CleanupInterval: BlockNumber = 6 * HOURS; - pub const SeedAgeLimit: BlockNumber = 1 * DAYS; } #[cfg(feature = "testnet")] @@ -365,10 +362,6 @@ parameter_types! { pub const MinDealDuration: u64 = 5 * MINUTES; pub const MaxDealDuration: u64 = 180 * MINUTES; - // Randomness pallet - pub const CleanupInterval: BlockNumber = DAYS; - pub const SeedAgeLimit: BlockNumber = 30 * DAYS; - // Faucet pallet pub const FaucetDripAmount: Balance = 10_000_000_000_000; pub const FaucetDripDelay: BlockNumber = DAYS; From e63f3d76c030b13869c9dd07cbf406c837b34754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 17 Dec 2024 11:57:57 +0000 Subject: [PATCH 6/6] docs: info on the consts --- pallets/randomness/src/lib.rs | 3 ++- runtime/src/lib.rs | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pallets/randomness/src/lib.rs b/pallets/randomness/src/lib.rs index a3c27a104..d718cae21 100644 --- a/pallets/randomness/src/lib.rs +++ b/pallets/randomness/src/lib.rs @@ -34,6 +34,8 @@ pub mod pallet { pub const LOG_TARGET: &'static str = "runtime::randomness"; + /// Size of previous [`AuthorVrf`] values. Used in [`AuthorVrfHistory`]. + // This value is arbitrary, originally "inspired" by the 256 blocks of history the node keeps. const HISTORY_SIZE: u32 = 256; #[pallet::config] @@ -108,7 +110,6 @@ pub mod pallet { ))) } - // The empty-payload inherent extrinsic. fn create_inherent(_data: &InherentData) -> Option { Some(Call::set_author_vrf {}) } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9887cd86b..797271ad6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -659,7 +659,13 @@ impl_runtime_apis! { // The following code cannot be placed out of this crate because of WASM constraints -/// Storage Key for AuthorVrfRandomness +/// Storage Key for BABE's [`AuthorVrfRandomness`][1] — taken from the Polkadot UI for Relay Chain's +/// version 8 and `pallet-babe` version `28.0.0`. +/// +/// For more information on fetching runtime storage values, see: +/// * +/// +/// [1]: https://github.com/paritytech/polkadot-sdk/blob/5b04b4598cc7b2c8e817a6304c7cdfaf002c1fee/substrate/frame/babe/src/lib.rs#L268-L273 const AUTHOR_VRF_STORAGE_KEY: [u8; 32] = hex2array!("1cb6f36e027abb2091cfb5110ab5087fd077dfdb8adb10f78f10a5df8742c545");