diff --git a/Cargo.lock b/Cargo.lock index 170ed9e2f1efb..d63806fee41c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 71ba1c17f1695..62993397fdfdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,6 +95,7 @@ members = [ "srml/membership", "srml/metadata", "srml/offences", + "srml/randomness-collective-flip", "srml/scored-pool", "srml/session", "srml/staking", diff --git a/node-template/runtime/Cargo.toml b/node-template/runtime/Cargo.toml index 18948d503cdbe..76821b6dfd8a2 100644 --- a/node-template/runtime/Cargo.toml +++ b/node-template/runtime/Cargo.toml @@ -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", diff --git a/node-template/runtime/src/lib.rs b/node-template/runtime/src/lib.rs index 216cb0edc266a..ba328c6e37442 100644 --- a/node-template/runtime/src/lib.rs +++ b/node-template/runtime/src/lib.rs @@ -272,6 +272,7 @@ construct_runtime!( Sudo: sudo, // Used for the module template in `./template.rs` TemplateModule: template::{Module, Call, Storage, Event}, + RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage}, } ); @@ -340,7 +341,7 @@ impl_runtime_apis! { } fn random_seed() -> ::Hash { - System::random_seed() + RandomnessCollectiveFlip::random_seed() } } diff --git a/node/runtime/Cargo.toml b/node/runtime/Cargo.toml index f2565750863e9..a47d65210096f 100644 --- a/node/runtime/Cargo.toml +++ b/node/runtime/Cargo.toml @@ -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", diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 7495190464693..ebe7b134e0641 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -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, ValidateUnsigned, Config}, AuthorityDiscovery: authority_discovery::{Module, Call, Config}, Offences: offences::{Module, Call, Storage, Event}, + RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage}, } ); @@ -589,7 +590,7 @@ impl_runtime_apis! { } fn random_seed() -> ::Hash { - System::random_seed() + RandomnessCollectiveFlip::random_seed() } } diff --git a/srml/contracts/Cargo.toml b/srml/contracts/Cargo.toml index f076f5fedae1d..aad45df860bde 100644 --- a/srml/contracts/Cargo.toml +++ b/srml/contracts/Cargo.toml @@ -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", diff --git a/srml/contracts/src/exec.rs b/srml/contracts/src/exec.rs index 94343c5fb93c2..e4c5539cec942 100644 --- a/srml/contracts/src/exec.rs +++ b/srml/contracts/src/exec.rs @@ -753,7 +753,7 @@ where } fn random(&self, subject: &[u8]) -> SeedOf { - system::Module::::random(subject) + randomness_collective_flip::Module::::random(subject) } fn now(&self) -> &MomentOf { diff --git a/srml/executive/src/lib.rs b/srml/executive/src/lib.rs index ad9cb7bf80742..ee11ffadf4f39 100644 --- a/srml/executive/src/lib.rs +++ b/srml/executive/src/lib.rs @@ -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![], }, }, diff --git a/srml/randomness-collective-flip/Cargo.toml b/srml/randomness-collective-flip/Cargo.toml new file mode 100644 index 0000000000000..5be9aad50f0b3 --- /dev/null +++ b/srml/randomness-collective-flip/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "srml-randomness-collective-flip" +version = "2.0.0" +authors = ["Parity Technologies "] +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", +] diff --git a/srml/randomness-collective-flip/src/lib.rs b/srml/randomness-collective-flip/src/lib.rs new file mode 100644 index 0000000000000..39dcf15ab49ed --- /dev/null +++ b/srml/randomness-collective-flip/src/lib.rs @@ -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 . + +//! # 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 for enum Call where origin: T::Origin { +//! pub fn random_module_example(origin) -> Result { +//! let _random_seed = >::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(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 for enum Call where origin: T::Origin { + fn on_initialize(block_number: T::BlockNumber) { + let parent_hash = >::parent_hash(); + + >::mutate(|ref mut values| if values.len() < RANDOM_MATERIAL_LEN as usize { + values.push(parent_hash) + } else { + let index = block_number_to_index::(block_number); + values[index] = parent_hash; + }); + } + } +} + +decl_storage! { + trait Store for Module 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; + } +} + +impl Module { + /// 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 = >::block_number(); + let index = block_number_to_index::(block_number); + + let hash_series = >::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; + 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; + type Randomness = Module; + + fn new_test_ext() -> runtime_io::TestExternalities { + let t = system::GenesisConfig::default().build_storage::().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::(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)); + }); + } +} diff --git a/srml/system/src/lib.rs b/srml/system/src/lib.rs index 5a1115b90d71c..79782d5af4be3 100644 --- a/srml/system/src/lib.rs +++ b/srml/system/src/lib.rs @@ -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 for enum Call where origin: T::Origin { //! pub fn system_module_example(origin) -> Result { //! let _sender = ensure_signed(origin)?; -//! let _random_seed = >::random_seed(); //! let _extrinsic_count = >::extrinsic_count(); +//! let _parent_hash = >::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; - /// 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); /// 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 Module { >::put(parent_hash); >::insert(*number - One::one(), parent_hash); >::put(txs_root); - >::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; - }); >::kill(); EventCount::kill(); >::remove_prefix(&()); @@ -736,69 +726,6 @@ impl Module { /// 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) = >::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) { >::insert(who, Self::account_nonce(who) + T::Index::one());