From d5726d8e8a410f908a76a8a13568c1c206a778d7 Mon Sep 17 00:00:00 2001 From: Robert Habermeier <rphmeier@gmail.com> Date: Tue, 6 Aug 2019 02:50:37 +0200 Subject: [PATCH] fix BABE randomness calculation (#3305) --- substrate/node/runtime/src/lib.rs | 4 +-- substrate/srml/babe/src/lib.rs | 56 +++++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 9ab7b84853e..06285571ba8 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -79,8 +79,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: 127, - impl_version: 128, + spec_version: 129, + impl_version: 129, apis: RUNTIME_API_VERSIONS, }; diff --git a/substrate/srml/babe/src/lib.rs b/substrate/srml/babe/src/lib.rs index a47fb04c2f4..8e72fdffd4f 100644 --- a/substrate/srml/babe/src/lib.rs +++ b/substrate/srml/babe/src/lib.rs @@ -22,7 +22,7 @@ pub use timestamp; use rstd::{result, prelude::*}; -use srml_support::{decl_storage, decl_module, StorageValue, traits::FindAuthor, traits::Get}; +use srml_support::{decl_storage, decl_module, StorageValue, StorageMap, traits::FindAuthor, traits::Get}; use timestamp::{OnTimestampSet}; use sr_primitives::{generic::DigestItem, ConsensusEngineId}; use sr_primitives::traits::{IsMember, SaturatedConversion, Saturating, RandomnessBeacon, Convert}; @@ -115,6 +115,8 @@ pub trait Trait: timestamp::Trait { /// The length of the BABE randomness pub const RANDOMNESS_LENGTH: usize = 32; +const UNDER_CONSTRUCTION_SEGMENT_LENGTH: usize = 256; + decl_storage! { trait Store for Module<T: Trait> as Babe { /// Current epoch index. @@ -150,7 +152,16 @@ decl_storage! { NextRandomness: [u8; 32 /* RANDOMNESS_LENGTH */]; /// Randomness under construction. - UnderConstruction: [u8; 32 /* VRF_OUTPUT_LENGTH */]; + /// + /// We make a tradeoff between storage accesses and list length. + /// We store the under-construction randomness in segments of up to + /// `UNDER_CONSTRUCTION_SEGMENT_LENGTH`. + /// + /// Once a segment reaches this length, we begin the next one. + /// We reset all segments and return to `0` at the beginning of every + /// epoch. + SegmentIndex build(|_| 0): u32; + UnderConstruction: map u32 => Vec<[u8; 32 /* VRF_OUTPUT_LENGTH */]>; } } @@ -248,19 +259,35 @@ impl<T: Trait> Module<T> { } fn deposit_vrf_output(vrf_output: &[u8; VRF_OUTPUT_LENGTH]) { - UnderConstruction::mutate(|z| z.iter_mut().zip(vrf_output).for_each(|(x, y)| *x^=y)) + let segment_idx = <SegmentIndex>::get(); + let mut segment = <UnderConstruction>::get(&segment_idx); + if segment.len() < UNDER_CONSTRUCTION_SEGMENT_LENGTH { + // push onto current segment: not full. + segment.push(*vrf_output); + <UnderConstruction>::insert(&segment_idx, &segment); + } else { + // move onto the next segment and update the index. + let segment_idx = segment_idx + 1; + <UnderConstruction>::insert(&segment_idx, vec![*vrf_output].as_ref()); + <SegmentIndex>::put(&segment_idx); + } } /// Call this function exactly once when an epoch changes, to update the /// randomness. Returns the new randomness. fn randomness_change_epoch(next_epoch_index: u64) -> [u8; RANDOMNESS_LENGTH] { let this_randomness = NextRandomness::get(); + let segment_idx: u32 = <SegmentIndex>::mutate(|s| rstd::mem::replace(s, 0)); + + // overestimate to the segment being full. + let rho_size = segment_idx.saturating_add(1) as usize * UNDER_CONSTRUCTION_SEGMENT_LENGTH; + let next_randomness = compute_randomness( this_randomness, next_epoch_index, - UnderConstruction::get(), + (0..segment_idx).flat_map(|i| <UnderConstruction>::take(&i)), + Some(rho_size), ); - UnderConstruction::put(&[0; RANDOMNESS_LENGTH]); NextRandomness::put(&next_randomness); this_randomness } @@ -345,15 +372,24 @@ impl<T: Trait + staking::Trait> session::OneSessionHandler<T::AccountId> for Mod } } +// compute randomness for a new epoch. rho is the concatenation of all +// VRF outputs in the prior epoch. +// +// an optional size hint as to how many VRF outputs there were may be provided. fn compute_randomness( last_epoch_randomness: [u8; RANDOMNESS_LENGTH], epoch_index: u64, - rho: [u8; VRF_OUTPUT_LENGTH], + rho: impl Iterator<Item=[u8; VRF_OUTPUT_LENGTH]>, + rho_size_hint: Option<usize>, ) -> [u8; RANDOMNESS_LENGTH] { - let mut s = [0; 40 + VRF_OUTPUT_LENGTH]; - s[..32].copy_from_slice(&last_epoch_randomness); - s[32..40].copy_from_slice(&epoch_index.to_le_bytes()); - s[40..].copy_from_slice(&rho); + let mut s = Vec::with_capacity(40 + rho_size_hint.unwrap_or(0) * VRF_OUTPUT_LENGTH); + s.extend_from_slice(&last_epoch_randomness); + s.extend_from_slice(&epoch_index.to_le_bytes()); + + for vrf_output in rho { + s.extend_from_slice(&vrf_output[..]); + } + runtime_io::blake2_256(&s) } -- GitLab