diff --git a/Cargo.lock b/Cargo.lock index c0077d29..3c1625f5 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" @@ -11083,15 +11070,16 @@ 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)", "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", "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", ] @@ -11724,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", @@ -12921,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", @@ -14177,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 bbfc695e..43d909a9 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/docs/src/architecture/pallets/randomness.md b/docs/src/architecture/pallets/randomness.md index 68c46151..c098bf74 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/market/src/mock.rs b/pallets/market/src/mock.rs index d93b11e9..f87eaf34 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 db96c558..31a6c53e 100644 --- a/pallets/randomness/Cargo.toml +++ b/pallets/randomness/Cargo.toml @@ -17,16 +17,19 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] 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 } @@ -38,5 +41,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 00000000..29e045f6 --- /dev/null +++ b/pallets/randomness/src/inherent.rs @@ -0,0 +1,53 @@ +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, codec::Decode))] +pub enum InherentError { + Other(RuntimeString), +} + +impl IsFatalError for InherentError { + fn is_fatal_error(&self) -> bool { + 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 + } + } +} + +#[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 sp_inherents::InherentData, + ) -> Result<(), sp_inherents::Error> { + inherent_data.put_data(INHERENT_IDENTIFIER, &()) + } + + async fn try_handle_error( + &self, + _identifier: &InherentIdentifier, + _error: &[u8], + ) -> Option> { + // Most substrate inherents return None + None + } +} diff --git a/pallets/randomness/src/lib.rs b/pallets/randomness/src/lib.rs index b81a2613..d718cae2 100644 --- a/pallets/randomness/src/lib.rs +++ b/pallets/randomness/src/lib.rs @@ -6,107 +6,128 @@ pub use pallet::*; #[cfg(test)] 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::*}; + 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"; + /// 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] 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. 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; - } + /// The latest author VRF randomness from BABE. + #[pallet::storage] + #[pallet::getter(fn author_vrf)] + pub type AuthorVrf = StorageValue<_, T::Hash, ValueQuery>; - // Save the seed - SeedsMap::::insert(block_number, seed); - log::info!(target: LOG_TARGET, "on_initialize: height: {block_number:?}, seed: {seed:?}"); + /// 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 { + 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); + 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. + // 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"); + } - 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); - } - } + 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) } } } diff --git a/pallets/randomness/src/mock.rs b/pallets/randomness/src/mock.rs index 8553d828..32b14b9b 100644 --- a/pallets/randomness/src/mock.rs +++ b/pallets/randomness/src/mock.rs @@ -1,13 +1,11 @@ -use frame_support::{ - derive_impl, - traits::{OnFinalize, OnInitialize}, +use frame_support::{derive_impl, traits::OnFinalize}; +use frame_system::{mocking::MockBlock, RawOrigin}; +use sp_runtime::{ + traits::{Hash, Header}, + BuildStorage, }; -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}; -pub type BlockNumber = u64; +use crate::GetAuthorVrf; // Configure a mock runtime to test the pallet. #[frame_support::runtime] @@ -29,8 +27,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; } @@ -40,22 +36,26 @@ 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 Generator = SubstrateRandomness; - type CleanupInterval = CleanupInterval; - type SeedAgeLimit = SeedAgeLimit; + type AuthorVrfGetter = DummyVrf; } -impl substrate_randomness::Config for Test {} +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(&[])) + } +} // 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(); @@ -74,12 +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()); - SubstrateRandomness::on_initialize(block_number); - 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 dbb6d4eb..eb210740 100644 --- a/pallets/randomness/src/tests.rs +++ b/pallets/randomness/src/tests.rs @@ -1,64 +1,34 @@ -use frame_support::{assert_err, assert_ok}; -use primitives::pallets::Randomness; +use sp_runtime::traits::Hash; -use crate::{ - mock::{new_test_ext, run_to_block, BlockNumber, RandomnessModule, SeedAgeLimit, Test}, - Error, -}; +use crate::mock::{new_test_ext, run_to_block, RandomnessModule, Test}; #[test] -fn test_randomness_availability() { +fn test_no_randomness() { 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); - } - } - } - }); + assert_eq!(::author_vrf(), Default::default()); + }) } #[test] -fn test_randomness_uniqueness() { +fn test_randomness() { 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); - } + run_to_block(1); + assert_eq!( + ::author_vrf(), + ::Hashing::hash(&[]) + ); + }) +} - previous_seed = Some(current_seed); - } +#[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/pallets/storage-provider/Cargo.toml b/pallets/storage-provider/Cargo.toml index 56dea3bf..558fa82c 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/pallets/storage-provider/src/lib.rs b/pallets/storage-provider/src/lib.rs index 06c203fe..24cc7d34 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,7 +58,7 @@ pub mod pallet { use primitives::{ commitment::{CommD, CommR, Commitment}, pallets::{ - DeadlineInfo as ExternalDeadlineInfo, Market, ProofVerification, Randomness, + DeadlineInfo as ExternalDeadlineInfo, Market, ProofVerification, StorageProviderValidation, }, proofs::{derive_prover_id, PublicReplicaInfo, RegisteredPoStProof}, @@ -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 9c93104a..7ae6a005 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 00dcdc75..aa5eeefc 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/Cargo.toml b/runtime/Cargo.toml index 364c34cb..209e89d1 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 e881700a..ab86f25a 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; @@ -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; @@ -430,44 +423,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; -} - -#[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(), - ) - } - } + type AuthorVrfGetter = BabeDataGetter; } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1d05722b..797271ad 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::{ @@ -269,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)] @@ -341,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)] @@ -661,3 +656,50 @@ impl_runtime_apis! { } } } + +// The following code cannot be placed out of this crate because of WASM constraints + +/// 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"); + +/// 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(&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/artifacts/metadata.scale b/storagext/lib/artifacts/metadata.scale index 4bc9e216..be80c82e 100644 Binary files a/storagext/lib/artifacts/metadata.scale and b/storagext/lib/artifacts/metadata.scale differ diff --git a/storagext/lib/src/clients/randomness.rs b/storagext/lib/src/clients/randomness.rs index 40d41f2e..40f140ad 100644 --- a/storagext/lib/src/clients/randomness.rs +++ b/storagext/lib/src/clients/randomness.rs @@ -16,13 +16,16 @@ impl RandomnessClientExt for crate::runtime::client::Client { &self, block_number: BlockNumber, ) -> Result, subxt::Error> { - let seed_query = runtime::storage().randomness().seeds_map(block_number); + 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())) } }