Split off System random functions into a new Randomness module (#3699)

* split off system randomness functions into a new module

* bump spec and impl version

* Move randomness to bottom of construct_runtime calls, move initialization into on_initialize

* Update srml/randomness/Cargo.toml

Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update srml/randomness/src/lib.rs

Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update srml/randomness/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Update srml/randomness/Cargo.toml

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Improve system example

* Update Cargo.lock

* Fix randomness example

* Get rid of the stored index

* Add tests

* Add a random test

* Improve docs

* Fix executive test :^)

* Add a utility function to tests

* Update srml/randomness/Cargo.toml

Co-Authored-By: Gavin Wood <github@gavwood.com>

* Update srml/randomness/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Update srml/randomness/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Change interpretation of block numbers

* rename crate

* refactor randomess module usage

* change random material len to a const

* Update srml/randomness-collective-flip/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Update srml/randomness-collective-flip/src/lib.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Ashley
2019-10-09 04:31:39 +13:00
committed by Robert Habermeier
parent 520009973f
commit 968a30685f
12 changed files with 352 additions and 81 deletions
+17
View File
@@ -2478,6 +2478,7 @@ dependencies = [
"srml-indices 2.0.0",
"srml-membership 2.0.0",
"srml-offences 1.0.0",
"srml-randomness-collective-flip 2.0.0",
"srml-session 2.0.0",
"srml-staking 2.0.0",
"srml-staking-reward-curve 2.0.0",
@@ -2544,6 +2545,7 @@ dependencies = [
"srml-executive 2.0.0",
"srml-grandpa 2.0.0",
"srml-indices 2.0.0",
"srml-randomness-collective-flip 2.0.0",
"srml-sudo 2.0.0",
"srml-support 2.0.0",
"srml-system 2.0.0",
@@ -4011,6 +4013,7 @@ dependencies = [
"sr-sandbox 2.0.0",
"sr-std 2.0.0",
"srml-balances 2.0.0",
"srml-randomness-collective-flip 2.0.0",
"srml-support 2.0.0",
"srml-system 2.0.0",
"srml-timestamp 2.0.0",
@@ -4224,6 +4227,20 @@ dependencies = [
"substrate-primitives 2.0.0",
]
[[package]]
name = "srml-randomness-collective-flip"
version = "2.0.0"
dependencies = [
"parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sr-io 2.0.0",
"sr-primitives 2.0.0",
"sr-std 2.0.0",
"srml-support 2.0.0",
"srml-system 2.0.0",
"substrate-primitives 2.0.0",
]
[[package]]
name = "srml-scored-pool"
version = "1.0.0"
+1
View File
@@ -95,6 +95,7 @@ members = [
"srml/membership",
"srml/metadata",
"srml/offences",
"srml/randomness-collective-flip",
"srml/scored-pool",
"srml/session",
"srml/staking",
@@ -20,6 +20,7 @@ babe-primitives = { package = "substrate-consensus-babe-primitives", path = "../
executive = { package = "srml-executive", path = "../../srml/executive", default_features = false }
indices = { package = "srml-indices", path = "../../srml/indices", default_features = false }
grandpa = { package = "srml-grandpa", path = "../../srml/grandpa", default-features = false }
randomness-collective-flip = { package = "srml-randomness-collective-flip", path = "../../srml/randomness-collective-flip", default_features = false }
system = { package = "srml-system", path = "../../srml/system", default_features = false }
timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default_features = false }
sudo = { package = "srml-sudo", path = "../../srml/sudo", default_features = false }
@@ -46,6 +47,7 @@ std = [
"grandpa/std",
"primitives/std",
"sr-primitives/std",
"randomness-collective-flip/std",
"system/std",
"timestamp/std",
"sudo/std",
+2 -1
View File
@@ -272,6 +272,7 @@ construct_runtime!(
Sudo: sudo,
// Used for the module template in `./template.rs`
TemplateModule: template::{Module, Call, Storage, Event<T>},
RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage},
}
);
@@ -340,7 +341,7 @@ impl_runtime_apis! {
}
fn random_seed() -> <Block as BlockT>::Hash {
System::random_seed()
RandomnessCollectiveFlip::random_seed()
}
}
+2
View File
@@ -40,6 +40,7 @@ im-online = { package = "srml-im-online", path = "../../srml/im-online", default
indices = { package = "srml-indices", path = "../../srml/indices", default-features = false }
membership = { package = "srml-membership", path = "../../srml/membership", default-features = false }
offences = { package = "srml-offences", path = "../../srml/offences", default-features = false }
randomness-collective-flip = { package = "srml-randomness-collective-flip", path = "../../srml/randomness-collective-flip", default-features = false }
session = { package = "srml-session", path = "../../srml/session", default-features = false, features = ["historical"] }
staking = { package = "srml-staking", path = "../../srml/staking", default-features = false }
srml-staking-reward-curve = { path = "../../srml/staking/reward-curve"}
@@ -78,6 +79,7 @@ std = [
"offchain-primitives/std",
"offences/std",
"primitives/std",
"randomness-collective-flip/std",
"rstd/std",
"rustc-hex",
"safe-mix/std",
+4 -3
View File
@@ -84,8 +84,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to equal spec_version. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 172,
impl_version: 172,
spec_version: 173,
impl_version: 173,
apis: RUNTIME_API_VERSIONS,
};
@@ -518,6 +518,7 @@ construct_runtime!(
ImOnline: im_online::{Module, Call, Storage, Event<T>, ValidateUnsigned, Config<T>},
AuthorityDiscovery: authority_discovery::{Module, Call, Config<T>},
Offences: offences::{Module, Call, Storage, Event},
RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage},
}
);
@@ -589,7 +590,7 @@ impl_runtime_apis! {
}
fn random_seed() -> <Block as BlockT>::Hash {
System::random_seed()
RandomnessCollectiveFlip::random_seed()
}
}
+3
View File
@@ -17,6 +17,8 @@ rstd = { package = "sr-std", path = "../../core/sr-std", default-features = fals
sandbox = { package = "sr-sandbox", path = "../../core/sr-sandbox", default-features = false }
support = { package = "srml-support", path = "../support", default-features = false }
system = { package = "srml-system", path = "../system", default-features = false }
randomness-collective-flip = { package = "srml-randomness-collective-flip", path = "../randomness-collective-flip", default-features = false }
timestamp = { package = "srml-timestamp", path = "../timestamp", default-features = false }
[dev-dependencies]
wabt = "0.9.2"
@@ -33,6 +35,7 @@ std = [
"codec/std",
"primitives/std",
"sr-primitives/std",
"randomness-collective-flip/std",
"runtime-io/std",
"rstd/std",
"sandbox/std",
+1 -1
View File
@@ -753,7 +753,7 @@ where
}
fn random(&self, subject: &[u8]) -> SeedOf<T> {
system::Module::<T>::random(subject)
randomness_collective_flip::Module::<T>::random(subject)
}
fn now(&self) -> &MomentOf<T> {
+1 -1
View File
@@ -451,7 +451,7 @@ mod tests {
header: Header {
parent_hash: [69u8; 32].into(),
number: 1,
state_root: hex!("3e51b47b6cc8449eece93eee4b01f03b00a0ca7981c0b6c0447b6e0d50ca886d").into(),
state_root: hex!("a6378d7fdd31029d13718d54bdff10a370e75cc624aaf94a90e7e7d4a24e0bcc").into(),
extrinsics_root: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
digest: Digest { logs: vec![], },
},
@@ -0,0 +1,28 @@
[package]
name = "srml-randomness-collective-flip"
version = "2.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
safe-mix = { version = "1.0", default-features = false }
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] }
sr-primitives = { path = "../../core/sr-primitives", default-features = false }
support = { package = "srml-support", path = "../support", default-features = false }
system = { package = "srml-system", path = "../system", default-features = false }
rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false }
[dev-dependencies]
primitives = { package = "substrate-primitives", path = "../../core/primitives" }
runtime-io = { package = "sr-io", path = "../../core/sr-io" }
[features]
default = ["std"]
std = [
"safe-mix/std",
"system/std",
"codec/std",
"support/std",
"sr-primitives/std",
"rstd/std",
]
@@ -0,0 +1,289 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! # Randomness Module
//!
//! The Randomness Collective Flip module provides a [`random`](./struct.Module.html#method.random)
//! function that generates low-influence random values based on the block hashes from the previous
//! `81` blocks. Low-influence randomness can be useful when defending against relatively weak
//! adversaries.
//!
//! ## Public Functions
//!
//! See the [`Module`](./struct.Module.html) struct for details of publicly available functions.
//!
//! ## Usage
//!
//! ### Prerequisites
//!
//! Import the Randomness Collective Flip module and derive your module's configuration trait from
//! the system trait.
//!
//! ### Example - Get random seed for the current block
//!
//! ```
//! use support::{decl_module, dispatch::Result};
//!
//! pub trait Trait: system::Trait {}
//!
//! decl_module! {
//! pub struct Module<T: Trait> for enum Call where origin: T::Origin {
//! pub fn random_module_example(origin) -> Result {
//! let _random_seed = <srml_randomness_collective_flip::Module<T>>::random_seed();
//! Ok(())
//! }
//! }
//! }
//! # fn main() { }
//! ```
#![cfg_attr(not(feature = "std"), no_std)]
use rstd::{prelude::*, convert::TryInto};
use sr_primitives::traits::Hash;
use support::{decl_module, decl_storage};
use safe_mix::TripletMix;
use codec::Encode;
use system::Trait;
const RANDOM_MATERIAL_LEN: u32 = 81;
fn block_number_to_index<T: Trait>(block_number: T::BlockNumber) -> usize {
// on_initialize is called on the first block after genesis
let index = (block_number - 1.into()) % RANDOM_MATERIAL_LEN.into();
index.try_into().ok().expect("Something % 81 is always smaller than usize; qed")
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
fn on_initialize(block_number: T::BlockNumber) {
let parent_hash = <system::Module<T>>::parent_hash();
<RandomMaterial<T>>::mutate(|ref mut values| if values.len() < RANDOM_MATERIAL_LEN as usize {
values.push(parent_hash)
} else {
let index = block_number_to_index::<T>(block_number);
values[index] = parent_hash;
});
}
}
}
decl_storage! {
trait Store for Module<T: Trait> as RandomnessCollectiveFlip {
/// Series of block headers from the last 81 blocks that acts as random seed material. This
/// is arranged as a ring buffer with `block_number % 81` being the index into the `Vec` of
/// the oldest hash.
RandomMaterial get(random_material): Vec<T::Hash>;
}
}
impl<T: Trait> Module<T> {
/// 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.
pub fn random_seed() -> T::Hash {
Self::random(&[][..])
}
/// Get a low-influence "random" value.
///
/// Being a deterministic block chain, real randomness is difficult to come by. This gives you
/// something that approximates it. `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"[..])`. This is initially implemented through a low-influence
/// "triplet mix" convolution of previous block hash values. In the future it will be generated
/// from a secure verifiable random function (VRF).
///
/// ### Security Notes
///
/// 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
/// bounded only by their computational resources. Our low-influence function reduces the actual
/// block producer's influence over the randomness, but increases the influence of small
/// colluding groups of recent block producers.
///
/// 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.
///
/// 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`.
pub fn random(subject: &[u8]) -> T::Hash {
let block_number = <system::Module<T>>::block_number();
let index = block_number_to_index::<T>(block_number);
let hash_series = <RandomMaterial<T>>::get();
if !hash_series.is_empty() {
// Always the case after block 1 is initialised.
hash_series.iter()
.cycle()
.skip(index)
.take(RANDOM_MATERIAL_LEN as usize)
.enumerate()
.map(|(i, h)| (i as i8, subject, h).using_encoded(T::Hashing::hash))
.triplet_mix()
} else {
T::Hash::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use primitives::{H256, Blake2Hasher};
use sr_primitives::{Perbill, traits::{BlakeTwo256, OnInitialize, Header as _, IdentityLookup}, testing::Header};
use support::{impl_outer_origin, parameter_types};
use runtime_io::with_externalities;
#[derive(Clone, PartialEq, Eq)]
pub struct Test;
impl_outer_origin! {
pub enum Origin for Test {}
}
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: u32 = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl system::Trait for Test {
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = ();
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type WeightMultiplierUpdate = ();
type Event = ();
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type AvailableBlockRatio = AvailableBlockRatio;
type MaximumBlockLength = MaximumBlockLength;
type Version = ();
}
type System = system::Module<Test>;
type Randomness = Module<Test>;
fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
let t = system::GenesisConfig::default().build_storage::<Test>().unwrap();
t.into()
}
#[test]
fn test_block_number_to_index() {
for i in 1 .. 1000 {
assert_eq!((i - 1) as usize % 81, block_number_to_index::<Test>(i));
}
}
fn setup_blocks(blocks: u64) {
let mut parent_hash = System::parent_hash();
for i in 1 .. (blocks + 1) {
System::initialize(&i, &parent_hash, &Default::default(), &Default::default());
Randomness::on_initialize(i);
let header = System::finalize();
parent_hash = header.hash();
System::set_block_number(*header.number());
}
}
#[test]
fn test_random_material_parital() {
with_externalities(&mut new_test_ext(), || {
let genesis_hash = System::parent_hash();
setup_blocks(38);
let random_material = Randomness::random_material();
assert_eq!(random_material.len(), 38);
assert_eq!(random_material[0], genesis_hash);
});
}
#[test]
fn test_random_material_filled() {
with_externalities(&mut new_test_ext(), || {
let genesis_hash = System::parent_hash();
setup_blocks(81);
let random_material = Randomness::random_material();
assert_eq!(random_material.len(), 81);
assert_ne!(random_material[0], random_material[1]);
assert_eq!(random_material[0], genesis_hash);
});
}
#[test]
fn test_random_material_filled_twice() {
with_externalities(&mut new_test_ext(), || {
let genesis_hash = System::parent_hash();
setup_blocks(162);
let random_material = Randomness::random_material();
assert_eq!(random_material.len(), 81);
assert_ne!(random_material[0], random_material[1]);
assert_ne!(random_material[0], genesis_hash);
});
}
#[test]
fn test_random() {
with_externalities(&mut new_test_ext(), || {
setup_blocks(162);
assert_eq!(System::block_number(), 162);
assert_eq!(Randomness::random_seed(), Randomness::random_seed());
assert_ne!(Randomness::random(b"random_1"), Randomness::random(b"random_2"));
let random = Randomness::random_seed();
assert_ne!(random, H256::zero());
assert!(!Randomness::random_material().contains(&random));
});
}
}
+2 -75
View File
@@ -65,7 +65,7 @@
//!
//! Import the System module and derive your module's configuration trait from the system trait.
//!
//! ### Example - Get random seed and extrinsic count for the current block
//! ### Example - Get extrinsic count and parent hash for the current block
//!
//! ```
//! use support::{decl_module, dispatch::Result};
@@ -77,8 +77,8 @@
//! pub struct Module<T: Trait> for enum Call where origin: T::Origin {
//! pub fn system_module_example(origin) -> Result {
//! let _sender = ensure_signed(origin)?;
//! let _random_seed = <system::Module<T>>::random_seed();
//! let _extrinsic_count = <system::Module<T>>::extrinsic_count();
//! let _parent_hash = <system::Module<T>>::parent_hash();
//! Ok(())
//! }
//! }
@@ -114,7 +114,6 @@ use support::{
decl_module, decl_event, decl_storage, decl_error, storage, Parameter,
traits::{Contains, Get},
};
use safe_mix::TripletMix;
use codec::{Encode, Decode};
#[cfg(any(feature = "std", test))]
@@ -389,9 +388,6 @@ decl_storage! {
pub BlockHash get(block_hash) build(|_| vec![(T::BlockNumber::zero(), hash69())]): map T::BlockNumber => T::Hash;
/// Extrinsics data for the current block (maps an extrinsic's index to its data).
ExtrinsicData get(extrinsic_data): map u32 => Vec<u8>;
/// Series of block headers from the last 81 blocks that acts as random seed material. This is arranged as a
/// ring buffer with the `i8` prefix being the index into the `Vec` of the oldest hash.
RandomMaterial get(random_material): (i8, Vec<T::Hash>);
/// The current block number being processed. Set by `execute_block`.
Number get(block_number) build(|_| 1.into()): T::BlockNumber;
/// Hash of the previous block.
@@ -641,12 +637,6 @@ impl<T: Trait> Module<T> {
<ParentHash<T>>::put(parent_hash);
<BlockHash<T>>::insert(*number - One::one(), parent_hash);
<ExtrinsicsRoot<T>>::put(txs_root);
<RandomMaterial<T>>::mutate(|&mut(ref mut index, ref mut values)| if values.len() < 81 {
values.push(parent_hash.clone())
} else {
values[*index as usize] = parent_hash.clone();
*index = (*index + 1) % 81;
});
<Events<T>>::kill();
EventCount::kill();
<EventTopics<T>>::remove_prefix(&());
@@ -736,69 +726,6 @@ impl<T: Trait> Module<T> {
/// Return the chain's current runtime version.
pub fn runtime_version() -> RuntimeVersion { T::Version::get() }
/// 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.
pub fn random_seed() -> T::Hash {
Self::random(&[][..])
}
/// Get a low-influence "random" value.
///
/// Being a deterministic block chain, real randomness is difficult to come
/// by. This gives you something that approximates it. `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"[..])`.
///
/// This is initially implemented through a low-influence "triplet mix"
/// convolution of previous block hash values. In the future it will be
/// generated from a secure verifiable random function (VRF).
///
/// ### Security Notes
///
/// 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 in advance by the block producer of this block (and,
/// indeed, anyone who knows the block's `parent_hash`). However, it is
/// mostly impossible for the producer of this block *alone* to influence
/// the value of this hash. A sizable minority of dishonest and coordinating
/// block producers would be required in order to affect this value. 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.
///
/// WARNING: Hashing the result of this function will remove any
/// low-influnce 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`.
pub fn random(subject: &[u8]) -> T::Hash {
let (index, hash_series) = <RandomMaterial<T>>::get();
if hash_series.len() > 0 {
// Always the case after block 1 is initialised.
hash_series.iter()
.cycle()
.skip(index as usize)
.take(81)
.enumerate()
.map(|(i, h)| (i as i8, subject, h).using_encoded(T::Hashing::hash))
.triplet_mix()
} else {
T::Hash::default()
}
}
/// Increment a particular account's nonce by 1.
pub fn inc_account_nonce(who: &T::AccountId) {
<AccountNonce<T>>::insert(who, Self::account_nonce(who) + T::Index::one());