diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 5dddea40cb..7194b5820f 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -1814,7 +1814,7 @@ dependencies = [ [[package]] name = "frame-support-test" -version = "2.0.1" +version = "3.0.0" dependencies = [ "frame-metadata", "frame-support", @@ -4983,6 +4983,7 @@ version = "3.0.0" dependencies = [ "frame-benchmarking", "frame-support", + "frame-support-test", "frame-system", "pallet-balances", "parity-scale-codec", @@ -5278,6 +5279,7 @@ name = "pallet-society" version = "3.0.0" dependencies = [ "frame-support", + "frame-support-test", "frame-system", "pallet-balances", "parity-scale-codec", diff --git a/substrate/bin/node-template/runtime/src/lib.rs b/substrate/bin/node-template/runtime/src/lib.rs index a7372d5d02..0f026db573 100644 --- a/substrate/bin/node-template/runtime/src/lib.rs +++ b/substrate/bin/node-template/runtime/src/lib.rs @@ -358,7 +358,7 @@ impl_runtime_apis! { } fn random_seed() -> ::Hash { - RandomnessCollectiveFlip::random_seed() + RandomnessCollectiveFlip::random_seed().0 } } diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 8bb5cf0858..2043f59eb9 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1220,7 +1220,7 @@ impl_runtime_apis! { } fn random_seed() -> ::Hash { - RandomnessCollectiveFlip::random_seed() + pallet_babe::RandomnessFromOneEpochAgo::::random_seed().0 } } diff --git a/substrate/frame/babe/src/lib.rs b/substrate/frame/babe/src/lib.rs index 29c815444a..6a4bacd88d 100644 --- a/substrate/frame/babe/src/lib.rs +++ b/substrate/frame/babe/src/lib.rs @@ -25,7 +25,7 @@ use codec::{Decode, Encode}; use frame_support::{ decl_error, decl_module, decl_storage, dispatch::DispatchResultWithPostInfo, - traits::{FindAuthor, Get, KeyOwnerProofSystem, OneSessionHandler, Randomness as RandomnessT}, + traits::{FindAuthor, Get, KeyOwnerProofSystem, OneSessionHandler}, weights::{Pays, Weight}, Parameter, }; @@ -33,7 +33,7 @@ use frame_system::{ensure_none, ensure_root, ensure_signed}; use sp_application_crypto::Public; use sp_runtime::{ generic::DigestItem, - traits::{Hash, IsMember, One, SaturatedConversion, Saturating, Zero}, + traits::{IsMember, One, SaturatedConversion, Saturating, Zero}, ConsensusEngineId, KeyTypeId, }; use sp_session::{GetSessionNumber, GetValidatorCount}; @@ -49,8 +49,9 @@ use sp_consensus_vrf::schnorrkel; pub use sp_consensus_babe::{AuthorityId, PUBLIC_KEY_LENGTH, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH}; -mod equivocation; mod default_weights; +mod equivocation; +mod randomness; #[cfg(any(feature = "runtime-benchmarks", test))] mod benchmarking; @@ -60,6 +61,9 @@ mod mock; mod tests; pub use equivocation::{BabeEquivocationOffence, EquivocationHandler, HandleEquivocation}; +pub use randomness::{ + CurrentBlockRandomness, RandomnessFromOneEpochAgo, RandomnessFromTwoEpochsAgo, +}; pub trait Config: pallet_timestamp::Config { /// The amount of time, in slots, that each epoch should last. @@ -220,6 +224,13 @@ decl_storage! { /// secondary plain slots are enabled (which don't contain a VRF output). AuthorVrfRandomness get(fn author_vrf_randomness): MaybeRandomness; + /// The block numbers when the last and current epoch have started, respectively `N-1` and + /// `N`. + /// NOTE: We track this is in order to annotate the block number when a given pool of + /// entropy was fixed (i.e. it was known to chain observers). Since epochs are defined in + /// slots, which may be skipped, the block numbers may not line up with the slot numbers. + EpochStart: (T::BlockNumber, T::BlockNumber); + /// How late the current block is compared to its parent. /// /// This entry is populated as part of block execution and is cleaned up @@ -343,31 +354,6 @@ decl_module! { } } -impl RandomnessT<::Hash> for Module { - /// Some BABE blocks have VRF outputs where the block producer has exactly one bit of influence, - /// either they make the block or they do not make the block and thus someone else makes the - /// next block. Yet, this randomness is not fresh in all BABE blocks. - /// - /// If that is an insufficient security guarantee then two things can be used to improve this - /// randomness: - /// - /// - Name, in advance, the block number whose random value will be used; ensure your module - /// retains a buffer of previous random values for its subject and then index into these in - /// order to obviate the ability of your user to look up the parent hash and choose when to - /// transact based upon it. - /// - Require your user to first commit to an additional value by first posting its hash. - /// Require them to reveal the value to determine the final result, hashing it with the - /// output of this random function. This reduces the ability of a cabal of block producers - /// from conspiring against individuals. - fn random(subject: &[u8]) -> T::Hash { - let mut subject = subject.to_vec(); - subject.reserve(VRF_OUTPUT_LENGTH); - subject.extend_from_slice(&Self::randomness()[..]); - - ::Hashing::hash(&subject[..]) - } -} - /// A BABE public key pub type BabeKey = [u8; PUBLIC_KEY_LENGTH]; @@ -492,6 +478,12 @@ impl Module { // Update the next epoch authorities. NextAuthorities::put(&next_authorities); + // Update the start blocks of the previous and new current epoch. + >::mutate(|(previous_epoch_start_block, current_epoch_start_block)| { + *previous_epoch_start_block = sp_std::mem::take(current_epoch_start_block); + *current_epoch_start_block = >::block_number(); + }); + // After we update the current epoch, we signal the *next* epoch change // so that nodes can track changes. let next_randomness = NextRandomness::get(); diff --git a/substrate/frame/babe/src/randomness.rs b/substrate/frame/babe/src/randomness.rs new file mode 100644 index 0000000000..71412a962b --- /dev/null +++ b/substrate/frame/babe/src/randomness.rs @@ -0,0 +1,148 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides multiple implementations of the randomness trait based on the on-chain epoch +//! randomness collected from VRF outputs. + +use super::{ + AuthorVrfRandomness, Config, EpochStart, NextRandomness, Randomness, VRF_OUTPUT_LENGTH, +}; +use frame_support::{traits::Randomness as RandomnessT, StorageValue}; +use sp_runtime::traits::Hash; + +/// Randomness usable by consensus protocols that **depend** upon finality and take action +/// based upon on-chain commitments made during the epoch before the previous epoch. +/// +/// An off-chain consensus protocol requires randomness be finalized before usage, but one +/// extra epoch delay beyond `RandomnessFromOneEpochAgo` suffices, under the assumption +/// that finality never stalls for longer than one epoch. +/// +/// All randomness is relative to commitments to any other inputs to the computation: If +/// Alice samples randomness near perfectly using radioactive decay, but then afterwards +/// Eve selects an arbitrary value with which to xor Alice's randomness, then Eve always +/// wins whatever game they play. +/// +/// All input commitments used with `RandomnessFromTwoEpochsAgo` should come from at least +/// three epochs ago. We require BABE session keys be registered at least three epochs +/// before being used to derive `CurrentBlockRandomness` for example. +/// +/// All users learn `RandomnessFromTwoEpochsAgo` when epoch `current_epoch - 1` starts, +/// although some learn it a few block earlier inside epoch `current_epoch - 2`. +/// +/// Adversaries with enough block producers could bias this randomness by choosing upon +/// what their block producers build at the end of epoch `current_epoch - 2` or the +/// beginning epoch `current_epoch - 1`, or skipping slots at the end of epoch +/// `current_epoch - 2`. +/// +/// Adversaries should not possess many block production slots towards the beginning or +/// end of every epoch, but they possess some influence over when they possess more slots. +pub struct RandomnessFromTwoEpochsAgo(sp_std::marker::PhantomData); + +/// Randomness usable by on-chain code that **does not depend** upon finality and takes +/// action based upon on-chain commitments made during the previous epoch. +/// +/// All randomness is relative to commitments to any other inputs to the computation: If +/// Alice samples randomness near perfectly using radioactive decay, but then afterwards +/// Eve selects an arbitrary value with which to xor Alice's randomness, then Eve always +/// wins whatever game they play. +/// +/// All input commitments used with `RandomnessFromOneEpochAgo` should come from at least +/// two epochs ago, although the previous epoch might work in special cases under +/// additional assumption. +/// +/// All users learn `RandomnessFromOneEpochAgo` at the end of the previous epoch, although +/// some block producers learn it several block earlier. +/// +/// Adversaries with enough block producers could bias this randomness by choosing upon +/// what their block producers build at either the end of the previous epoch or the +/// beginning of the current epoch, or electing to skipping some of their own block +/// production slots towards the end of the previous epoch. +/// +/// Adversaries should not possess many block production slots towards the beginning or +/// end of every epoch, but they possess some influence over when they possess more slots. +/// +/// As an example usage, we determine parachain auctions ending times in Polkadot using +/// `RandomnessFromOneEpochAgo` because it reduces bias from `CurrentBlockRandomness` and +/// does not require the extra finality delay of `RandomnessFromTwoEpochsAgo`. +pub struct RandomnessFromOneEpochAgo(sp_std::marker::PhantomData); + +/// Randomness produced semi-freshly with each block, but inherits limitations of +/// `RandomnessFromTwoEpochsAgo` from which it derives. +/// +/// All randomness is relative to commitments to any other inputs to the computation: If +/// Alice samples randomness near perfectly using radioactive decay, but then afterwards +/// Eve selects an arbitrary value with which to xor Alice's randomness, then Eve always +/// wins whatever game they play. +/// +/// As with `RandomnessFromTwoEpochsAgo`, all input commitments combined with +/// `CurrentBlockRandomness` should come from at least two epoch ago, except preferably +/// not near epoch ending, and thus ideally three epochs ago. +/// +/// Almost all users learn this randomness for a block when the block producer announces +/// the block, which makes this randomness appear quite fresh. Yet, the block producer +/// themselves learned this randomness at the beginning of epoch `current_epoch - 2`, at +/// the same time as they learn `RandomnessFromTwoEpochsAgo`. +/// +/// Aside from just biasing `RandomnessFromTwoEpochsAgo`, adversaries could also bias +/// `CurrentBlockRandomness` by never announcing their block if doing so yields an +/// unfavorable randomness. As such, `CurrentBlockRandomness` should be considered weaker +/// than both other randomness sources provided by BABE, but `CurrentBlockRandomness` +/// remains constrained by declared staking, while a randomness source like block hash is +/// only constrained by adversaries' unknowable computational power. +/// +/// As an example use, parachains could assign block production slots based upon the +/// `CurrentBlockRandomness` of their relay parent or relay parent's parent, provided the +/// parachain registers collators but avoids censorship sensitive functionality like +/// slashing. Any parachain with slashing could operate BABE itself or perhaps better yet +/// a BABE-like approach that derives its `CurrentBlockRandomness`, and authorizes block +/// production, based upon the relay parent's `CurrentBlockRandomness` or more likely the +/// relay parent's `RandomnessFromTwoEpochsAgo`. +pub struct CurrentBlockRandomness(sp_std::marker::PhantomData); + +impl RandomnessT for RandomnessFromTwoEpochsAgo { + fn random(subject: &[u8]) -> (T::Hash, T::BlockNumber) { + let mut subject = subject.to_vec(); + subject.reserve(VRF_OUTPUT_LENGTH); + subject.extend_from_slice(&Randomness::get()[..]); + + (T::Hashing::hash(&subject[..]), EpochStart::::get().0) + } +} + +impl RandomnessT for RandomnessFromOneEpochAgo { + fn random(subject: &[u8]) -> (T::Hash, T::BlockNumber) { + let mut subject = subject.to_vec(); + subject.reserve(VRF_OUTPUT_LENGTH); + subject.extend_from_slice(&NextRandomness::get()[..]); + + (T::Hashing::hash(&subject[..]), EpochStart::::get().1) + } +} + +impl RandomnessT, T::BlockNumber> for CurrentBlockRandomness { + fn random(subject: &[u8]) -> (Option, T::BlockNumber) { + let random = AuthorVrfRandomness::get().map(|random| { + let mut subject = subject.to_vec(); + subject.reserve(VRF_OUTPUT_LENGTH); + subject.extend_from_slice(&random); + + T::Hashing::hash(&subject[..]) + }); + + (random, >::block_number()) + } +} diff --git a/substrate/frame/babe/src/tests.rs b/substrate/frame/babe/src/tests.rs index 6515e5fdaa..82a7782448 100644 --- a/substrate/frame/babe/src/tests.rs +++ b/substrate/frame/babe/src/tests.rs @@ -349,6 +349,31 @@ fn can_fetch_current_and_next_epoch_data() { }); } +#[test] +fn tracks_block_numbers_when_current_and_previous_epoch_started() { + new_test_ext(5).execute_with(|| { + // an epoch is 3 slots therefore at block 8 we should be in epoch #3 + // with the previous epochs having the following blocks: + // epoch 1 - [1, 2, 3] + // epoch 2 - [4, 5, 6] + // epoch 3 - [7, 8, 9] + progress_to_block(8); + + let (last_epoch, current_epoch) = EpochStart::::get(); + + assert_eq!(last_epoch, 4); + assert_eq!(current_epoch, 7); + + // once we reach block 10 we switch to epoch #4 + progress_to_block(10); + + let (last_epoch, current_epoch) = EpochStart::::get(); + + assert_eq!(last_epoch, 7); + assert_eq!(current_epoch, 10); + }); +} + #[test] fn report_equivocation_current_session_works() { let (pairs, mut ext) = new_test_ext_with_pairs(3); diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 745384a867..d0e1127db8 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -740,7 +740,9 @@ where } fn random(&self, subject: &[u8]) -> SeedOf { - T::Randomness::random(subject) + // TODO: change API to expose randomness freshness + // https://github.com/paritytech/substrate/issues/8297 + T::Randomness::random(subject).0 } fn now(&self) -> &MomentOf { diff --git a/substrate/frame/contracts/src/lib.rs b/substrate/frame/contracts/src/lib.rs index 2ce2014075..7068c4e99e 100644 --- a/substrate/frame/contracts/src/lib.rs +++ b/substrate/frame/contracts/src/lib.rs @@ -151,7 +151,7 @@ pub mod pallet { type Time: Time; /// The generator used to supply randomness to contracts through `seal_random`. - type Randomness: Randomness; + type Randomness: Randomness; /// The currency in which fees are paid and contract balances are held. type Currency: Currency; diff --git a/substrate/frame/lottery/Cargo.toml b/substrate/frame/lottery/Cargo.toml index 0d60b0aaca..73de239a4d 100644 --- a/substrate/frame/lottery/Cargo.toml +++ b/substrate/frame/lottery/Cargo.toml @@ -22,6 +22,7 @@ frame-system = { version = "3.0.0", default-features = false, path = "../system" frame-benchmarking = { version = "3.1.0", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] +frame-support-test = { version = "3.0.0", path = "../support/test" } pallet-balances = { version = "3.0.0", path = "../balances" } sp-core = { version = "3.0.0", path = "../../primitives/core" } sp-io = { version = "3.0.0", path = "../../primitives/io" } diff --git a/substrate/frame/lottery/src/lib.rs b/substrate/frame/lottery/src/lib.rs index 11543d67b3..8248caa067 100644 --- a/substrate/frame/lottery/src/lib.rs +++ b/substrate/frame/lottery/src/lib.rs @@ -85,7 +85,7 @@ pub trait Config: frame_system::Config { type Currency: ReservableCurrency; /// Something that provides randomness in the runtime. - type Randomness: Randomness; + type Randomness: Randomness; /// The overarching event type. type Event: From> + Into<::Event>; @@ -443,8 +443,10 @@ impl Module { // Note that there is potential bias introduced by using modulus operator. // You should call this function with different seed values until the random // number lies within `u32::MAX - u32::MAX % n`. + // TODO: deal with randomness freshness + // https://github.com/paritytech/substrate/issues/8311 fn generate_random_number(seed: u32) -> u32 { - let random_seed = T::Randomness::random(&(T::ModuleId::get(), seed).encode()); + let (random_seed, _) = T::Randomness::random(&(T::ModuleId::get(), seed).encode()); let random_number = ::decode(&mut random_seed.as_ref()) .expect("secure hashes should always be bigger than u32; qed"); random_number diff --git a/substrate/frame/lottery/src/mock.rs b/substrate/frame/lottery/src/mock.rs index ea73ee190e..44691427c8 100644 --- a/substrate/frame/lottery/src/mock.rs +++ b/substrate/frame/lottery/src/mock.rs @@ -22,8 +22,9 @@ use crate as pallet_lottery; use frame_support::{ parameter_types, - traits::{OnInitialize, OnFinalize, TestRandomness}, + traits::{OnFinalize, OnInitialize}, }; +use frame_support_test::TestRandomness; use sp_core::H256; use sp_runtime::{ Perbill, @@ -103,7 +104,7 @@ impl Config for Test { type ModuleId = LotteryModuleId; type Call = Call; type Currency = Balances; - type Randomness = TestRandomness; + type Randomness = TestRandomness; type Event = Event; type ManagerOrigin = EnsureRoot; type MaxCalls = MaxCalls; diff --git a/substrate/frame/randomness-collective-flip/src/lib.rs b/substrate/frame/randomness-collective-flip/src/lib.rs index 0dba6727da..57e95ccb14 100644 --- a/substrate/frame/randomness-collective-flip/src/lib.rs +++ b/substrate/frame/randomness-collective-flip/src/lib.rs @@ -56,7 +56,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_std::{prelude::*, convert::TryInto}; -use sp_runtime::traits::Hash; +use sp_runtime::traits::{Hash, Saturating}; use frame_support::{ decl_module, decl_storage, traits::Randomness, weights::Weight @@ -99,7 +99,7 @@ decl_storage! { } } -impl Randomness for Module { +impl Randomness for Module { /// This randomness uses a low-influence function, drawing upon the block hashes from the /// previous 81 blocks. Its result for any given subject will be known far in advance by anyone /// observing the chain. Any block producer has significant influence over their block hashes @@ -110,14 +110,15 @@ impl Randomness for Module { /// WARNING: Hashing the result of this function will remove any low-influence properties it has /// and mean that all bits of the resulting value are entirely manipulatable by the author of /// the parent block, who can determine the value of `parent_hash`. - fn random(subject: &[u8]) -> T::Hash { + fn random(subject: &[u8]) -> (T::Hash, T::BlockNumber) { let block_number = >::block_number(); let index = block_number_to_index::(block_number); let hash_series = >::get(); - if !hash_series.is_empty() { + let seed = if !hash_series.is_empty() { // Always the case after block 1 is initialized. - hash_series.iter() + hash_series + .iter() .cycle() .skip(index) .take(RANDOM_MATERIAL_LEN as usize) @@ -126,7 +127,12 @@ impl Randomness for Module { .triplet_mix() } else { T::Hash::default() - } + }; + + ( + seed, + block_number.saturating_sub(RANDOM_MATERIAL_LEN.into()), + ) } } @@ -272,8 +278,9 @@ mod tests { assert_eq!(CollectiveFlip::random_seed(), CollectiveFlip::random_seed()); assert_ne!(CollectiveFlip::random(b"random_1"), CollectiveFlip::random(b"random_2")); - let random = CollectiveFlip::random_seed(); + let (random, known_since) = CollectiveFlip::random_seed(); + assert_eq!(known_since, 162 - RANDOM_MATERIAL_LEN as u64); assert_ne!(random, H256::zero()); assert!(!CollectiveFlip::random_material().contains(&random)); }); diff --git a/substrate/frame/society/Cargo.toml b/substrate/frame/society/Cargo.toml index 913e40e030..a3c6dcadab 100644 --- a/substrate/frame/society/Cargo.toml +++ b/substrate/frame/society/Cargo.toml @@ -24,6 +24,7 @@ rand_chacha = { version = "0.2", default-features = false } [dev-dependencies] sp-core = { version = "3.0.0", path = "../../primitives/core" } sp-io ={ version = "3.0.0", path = "../../primitives/io" } +frame-support-test = { version = "3.0.0", path = "../support/test" } pallet-balances = { version = "3.0.0", path = "../balances" } [features] diff --git a/substrate/frame/society/src/lib.rs b/substrate/frame/society/src/lib.rs index f8f8fa61a0..66d89d67dd 100644 --- a/substrate/frame/society/src/lib.rs +++ b/substrate/frame/society/src/lib.rs @@ -283,7 +283,7 @@ pub trait Config: system::Config { type Currency: ReservableCurrency; /// Something that provides randomness in the runtime. - type Randomness: Randomness; + type Randomness: Randomness; /// The minimum amount of a deposit required for a bid to be made. type CandidateDeposit: Get>; @@ -1309,7 +1309,9 @@ impl, I: Instance> Module { let mut pot = >::get(); // we'll need a random seed here. - let seed = T::Randomness::random(phrase); + // TODO: deal with randomness freshness + // https://github.com/paritytech/substrate/issues/8312 + let (seed, _) = T::Randomness::random(phrase); // seed needs to be guaranteed to be 32 bytes. let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref())) .expect("input is padded with zeroes; qed"); @@ -1565,7 +1567,9 @@ impl, I: Instance> Module { // Start a new defender rotation let phrase = b"society_challenge"; // we'll need a random seed here. - let seed = T::Randomness::random(phrase); + // TODO: deal with randomness freshness + // https://github.com/paritytech/substrate/issues/8312 + let (seed, _) = T::Randomness::random(phrase); // seed needs to be guaranteed to be 32 bytes. let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref())) .expect("input is padded with zeroes; qed"); diff --git a/substrate/frame/society/src/mock.rs b/substrate/frame/society/src/mock.rs index 4b1bb21dd1..0a684b2a8d 100644 --- a/substrate/frame/society/src/mock.rs +++ b/substrate/frame/society/src/mock.rs @@ -22,8 +22,9 @@ use crate as pallet_society; use frame_support::{ parameter_types, ord_parameter_types, - traits::{OnInitialize, OnFinalize, TestRandomness}, + traits::{OnInitialize, OnFinalize}, }; +use frame_support_test::TestRandomness; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -104,7 +105,7 @@ impl pallet_balances::Config for Test { impl Config for Test { type Event = Event; type Currency = pallet_balances::Module; - type Randomness = TestRandomness; + type Randomness = TestRandomness; type CandidateDeposit = CandidateDeposit; type WrongSideDeduction = WrongSideDeduction; type MaxStrikes = MaxStrikes; diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index c22f694d38..1427a727a1 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -23,13 +23,13 @@ use sp_std::{prelude::*, result, marker::PhantomData, ops::Div, fmt::Debug}; use codec::{FullCodec, Codec, Encode, Decode, EncodeLike}; use sp_core::u32_trait::Value as U32; use sp_runtime::{ - RuntimeAppPublic, RuntimeDebug, BoundToRuntimeAppPublic, - ConsensusEngineId, DispatchResult, DispatchError, traits::{ - MaybeSerializeDeserialize, AtLeast32Bit, Saturating, TrailingZeroInput, Bounded, Zero, - BadOrigin, AtLeast32BitUnsigned, Convert, UniqueSaturatedFrom, UniqueSaturatedInto, - SaturatedConversion, StoredMapError, Block as BlockT, + AtLeast32Bit, AtLeast32BitUnsigned, BadOrigin, Block as BlockT, Bounded, Convert, + MaybeSerializeDeserialize, SaturatedConversion, Saturating, StoredMapError, + UniqueSaturatedFrom, UniqueSaturatedInto, Zero, }, + BoundToRuntimeAppPublic, ConsensusEngineId, DispatchError, DispatchResult, RuntimeAppPublic, + RuntimeDebug, }; use sp_staking::SessionIndex; use crate::dispatch::Parameter; @@ -1413,38 +1413,42 @@ impl InitializeMembers for () { fn initialize_members(_: &[T]) {} } -// A trait that is able to provide randomness. -pub trait Randomness { - /// Get a "random" value +/// A trait that is able to provide randomness. +/// +/// Being a deterministic blockchain, real randomness is difficult to come by, different +/// implementations of this trait will provide different security guarantees. At best, +/// this will be randomness which was hard to predict a long time ago, but that has become +/// easy to predict recently. +pub trait Randomness { + /// Get the most recently determined random seed, along with the time in the past + /// since when it was determinable by chain observers. /// - /// Being a deterministic blockchain, real randomness is difficult to come by. This gives you - /// something that approximates it. At best, this will be randomness which was - /// hard to predict a long time ago, but that has become easy to predict recently. + /// `subject` is a context identifier and allows you to get a different result to + /// other callers of this function; use it like `random(&b"my context"[..])`. /// - /// `subject` is a context identifier and allows you to get a - /// different result to other callers of this function; use it like - /// `random(&b"my context"[..])`. - fn random(subject: &[u8]) -> Output; + /// NOTE: The returned seed should only be used to distinguish commitments made before + /// the returned block number. If the block number is too early (i.e. commitments were + /// made afterwards), then ensure no further commitments may be made and repeatedly + /// call this on later blocks until the block number returned is later than the latest + /// commitment. + fn random(subject: &[u8]) -> (Output, BlockNumber); /// Get the basic random seed. /// - /// In general you won't want to use this, but rather `Self::random` which allows you to give a - /// subject for the random result and whose value will be independently low-influence random - /// from any other such seeds. - fn random_seed() -> Output { + /// In general you won't want to use this, but rather `Self::random` which allows + /// you to give a subject for the random result and whose value will be + /// independently low-influence random from any other such seeds. + /// + /// NOTE: The returned seed should only be used to distinguish commitments made before + /// the returned block number. If the block number is too early (i.e. commitments were + /// made afterwards), then ensure no further commitments may be made and repeatedly + /// call this on later blocks until the block number returned is later than the latest + /// commitment. + fn random_seed() -> (Output, BlockNumber) { Self::random(&[][..]) } } -/// Provides an implementation of [`Randomness`] that should only be used in tests! -pub struct TestRandomness; - -impl Randomness for TestRandomness { - fn random(subject: &[u8]) -> Output { - Output::decode(&mut TrailingZeroInput::new(subject)).unwrap_or_default() - } -} - /// Trait to be used by block producing consensus engine modules to determine /// how late the current block is (e.g. in a slot-based proposal mechanism how /// many slots were skipped since the previous block). diff --git a/substrate/frame/support/test/Cargo.toml b/substrate/frame/support/test/Cargo.toml index 2ec59b1013..17aeea970c 100644 --- a/substrate/frame/support/test/Cargo.toml +++ b/substrate/frame/support/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frame-support-test" -version = "2.0.1" +version = "3.0.0" authors = ["Parity Technologies "] edition = "2018" license = "Apache-2.0" diff --git a/substrate/frame/support/test/src/lib.rs b/substrate/frame/support/test/src/lib.rs index 4b1510bf81..fe1d1eb9d3 100644 --- a/substrate/frame/support/test/src/lib.rs +++ b/substrate/frame/support/test/src/lib.rs @@ -53,3 +53,21 @@ impl frame_support::traits::PalletInfo for PanicPalletInfo { unimplemented!("PanicPalletInfo mustn't be triggered by tests"); } } + +/// Provides an implementation of [`Randomness`] that should only be used in tests! +pub struct TestRandomness(sp_std::marker::PhantomData); + +impl frame_support::traits::Randomness + for TestRandomness +where + T: frame_system::Config, +{ + fn random(subject: &[u8]) -> (Output, T::BlockNumber) { + use sp_runtime::traits::TrailingZeroInput; + + ( + Output::decode(&mut TrailingZeroInput::new(subject)).unwrap_or_default(), + frame_system::Module::::block_number(), + ) + } +}