diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index ee3c75045fa..0254121a932 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -18,11 +18,12 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::{cmp, fmt}; -use std::iter::FromIterator; +use std::iter::{self, FromIterator}; use std::ops::Deref; -use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering}; +use std::sync::atomic::{AtomicU16, AtomicU64, AtomicBool, Ordering as AtomicOrdering}; use std::sync::{Weak, Arc}; use std::time::{UNIX_EPOCH, SystemTime, Duration}; +use std::u64; use block::*; use bytes::Bytes; @@ -31,7 +32,7 @@ use engines::{Engine, Seal, EngineError, ConstructedVerifier}; use engines::block_reward; use engines::block_reward::{BlockRewardContract, RewardKind}; use error::{Error, ErrorKind, BlockError}; -use ethjson; +use ethjson::{spec::StepDuration}; use machine::{AuxiliaryData, Call, EthereumMachine}; use hash::keccak; use super::signer::EngineSigner; @@ -61,12 +62,13 @@ pub type RandomnessPhaseError = randomness::PhaseError; /// `AuthorityRound` params. pub struct AuthorityRoundParams { - /// Time to wait before next block or authority switching, - /// in seconds. + /// A map defining intervals of blocks with the given times (in seconds) to wait before next + /// block or authority switching. The keys in the map are numbers of starting blocks of those + /// periods. The entry at `0` should be defined. /// - /// Deliberately typed as u16 as too high of a value leads - /// to slow block issuance. - pub step_duration: u16, + /// Wait times (durations) are deliberately typed as `u16` since larger values lead to slow + /// block issuance. + pub step_durations: BTreeMap, /// Starting step, pub start_step: Option, /// Valid validators. @@ -101,13 +103,25 @@ const U16_MAX: usize = ::std::u16::MAX as usize; impl From for AuthorityRoundParams { fn from(p: ethjson::spec::AuthorityRoundParams) -> Self { - let mut step_duration_usize: usize = p.step_duration.into(); - if step_duration_usize > U16_MAX { - step_duration_usize = U16_MAX; - warn!(target: "engine", "step_duration is too high ({}), setting it to {}", step_duration_usize, U16_MAX); - } + let map_step_duration = |u: ethjson::uint::Uint| { + let mut step_duration_usize: usize = u.into(); + if step_duration_usize == 0 { + panic!("AuthorityRoundParams: step duration cannot be 0"); + } + if step_duration_usize > U16_MAX { + step_duration_usize = U16_MAX; + warn!(target: "engine", "step duration is too high ({}), setting it to {}", step_duration_usize, U16_MAX); + } + step_duration_usize as u16 + }; + let step_durations: BTreeMap = match p.step_duration { + StepDuration::Single(u) => iter::once((0, map_step_duration(u))).collect(), + StepDuration::Transitions(tr) => { + tr.into_iter().map(|(blknum, u)| (blknum.into(), map_step_duration(u))).collect() + } + }; AuthorityRoundParams { - step_duration: step_duration_usize as u16, + step_durations, validators: new_validator_set(p.validators), start_step: p.start_step.map(Into::into), validate_score_transition: p.validate_score_transition.map_or(0, Into::into), @@ -130,51 +144,80 @@ impl From for AuthorityRoundParams { } } -// Helper for managing the step. +/// Helper for managing the step. #[derive(Debug)] struct Step { calibrate: bool, // whether calibration is enabled. - inner: AtomicUsize, - duration: u16, + inner: AtomicU64, + /// Duration of the current step. + current_duration: AtomicU16, + /// Planned durations of steps. + durations: BTreeMap, + /// The time of the start of the first step after the last change of step duration, in seconds. + starting_sec: AtomicU64, + /// The number of the first step after the last change of step duration. + starting_step: AtomicU64, } impl Step { - fn load(&self) -> u64 { self.inner.load(AtomicOrdering::SeqCst) as u64 } + fn load(&self) -> u64 { self.inner.load(AtomicOrdering::SeqCst) } fn duration_remaining(&self) -> Duration { let now = unix_now(); let expected_seconds = self.load() - .checked_add(1) - .and_then(|ctr| ctr.checked_mul(self.duration as u64)) + .checked_sub(self.starting_step.load(AtomicOrdering::SeqCst) as u64) + .and_then(|x| x.checked_add(1)) + .and_then(|x| x.checked_mul(self.current_duration.load(AtomicOrdering::SeqCst) as u64)) + .and_then(|x| x.checked_add(self.starting_sec.load(AtomicOrdering::SeqCst) as u64)) .map(Duration::from_secs); - match expected_seconds { Some(step_end) if step_end > now => step_end - now, Some(_) => Duration::from_secs(0), None => { let ctr = self.load(); - error!(target: "engine", "Step counter is too high: {}, aborting", ctr); - panic!("step counter is too high: {}", ctr) + error!(target: "engine", "Step counter under- or overflow: {}, aborting", ctr); + panic!("step counter under- or overflow: {}", ctr) }, } - } + /// Increments the step number. + /// + /// Panics if the new step number is `usize::MAX`. fn increment(&self) { use std::usize; // fetch_add won't panic on overflow but will rather wrap // around, leading to zero as the step counter, which might // lead to unexpected situations, so it's better to shut down. - if self.inner.fetch_add(1, AtomicOrdering::SeqCst) == usize::MAX { + let prev_step = self.inner.fetch_add(1, AtomicOrdering::SeqCst); + if prev_step == u64::MAX { error!(target: "engine", "Step counter is too high: {}, aborting", usize::MAX); panic!("step counter is too high: {}", usize::MAX); } - + let next_step = prev_step + 1; + if let Some(&next_dur) = self.durations.get(&next_step) { + let prev_dur = *self.durations.range(0 .. next_step).last().expect("step duration map is empty").1; + let prev_starting_sec = self.starting_sec.load(AtomicOrdering::SeqCst); + let prev_starting_step = self.starting_step.load(AtomicOrdering::SeqCst); + let steps_elapsed = prev_step - prev_starting_step; + let starting_sec = prev_starting_sec + (steps_elapsed * prev_dur as u64); + self.current_duration.store(next_dur, AtomicOrdering::SeqCst); + self.starting_sec.store(starting_sec, AtomicOrdering::SeqCst); + self.starting_step.store(next_step, AtomicOrdering::SeqCst); + self.inner.store(next_step, AtomicOrdering::SeqCst); + trace!(target: "engine", "Step duration updated to {} at step {}", next_dur, next_step); + } } fn calibrate(&self) { if self.calibrate { - let new_step = unix_now().as_secs() / (self.duration as u64); - self.inner.store(new_step as usize, AtomicOrdering::SeqCst); + let starting_sec = self.starting_sec.load(AtomicOrdering::SeqCst); + let starting_step = self.starting_step.load(AtomicOrdering::SeqCst); + let step = ( + (unix_now().as_secs() - starting_sec) / + (self.current_duration.load(AtomicOrdering::SeqCst) as u64) + ) + starting_step + 1; + trace!(target: "engine", "calibrating step {}", step); + self.inner.store(step, AtomicOrdering::SeqCst); } } @@ -195,7 +238,7 @@ impl Step { Err(None) // wait a bit for blocks in near future } else if given > current { - let d = self.duration as u64; + let d = self.current_duration.load(AtomicOrdering::SeqCst) as u64; Err(Some(OutOfBounds { min: None, max: Some(d * current), @@ -669,20 +712,25 @@ impl<'a, A: ?Sized, B> Deref for CowLike<'a, A, B> where B: AsRef { impl AuthorityRound { /// Create a new instance of AuthorityRound engine. pub fn new(our_params: AuthorityRoundParams, machine: EthereumMachine) -> Result, Error> { - if our_params.step_duration == 0 { - error!(target: "engine", "Authority Round step duration can't be zero, aborting"); - panic!("authority_round: step duration can't be zero") + let duration = *our_params.step_durations.get(&0).unwrap_or_else(|| { + error!(target: "engine", "Authority Round step 0 duration is undefined, aborting"); + panic!("authority_round: step 0 duration is undefined") + }); + if our_params.step_durations.values().any(|v| *v == 0) { + panic!("authority_round: step duration cannot be 0"); } let should_timeout = our_params.start_step.is_none(); - let initial_step = our_params.start_step.unwrap_or_else(|| (unix_now().as_secs() / (our_params.step_duration as u64))); let engine = Arc::new( AuthorityRound { transition_service: IoService::<()>::start()?, step: Arc::new(PermissionedStep { inner: Step { - inner: AtomicUsize::new(initial_step as usize), + inner: AtomicU64::new(0), calibrate: our_params.start_step.is_none(), - duration: our_params.step_duration, + current_duration: AtomicU16::new(duration), + durations: our_params.step_durations.clone(), + starting_sec: AtomicU64::new(unix_now().as_secs()), + starting_step: AtomicU64::new(0), }, can_propose: AtomicBool::new(true), }), @@ -924,8 +972,10 @@ impl IoHandler<()> for TransitionHandler { } } - let next_run_at = AsMillis::as_millis(&self.step.inner.duration_remaining()) >> 2; - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, Duration::from_millis(next_run_at)) + let next_run_at = Duration::from_millis( + AsMillis::as_millis(&self.step.inner.duration_remaining()) >> 2 + ); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, next_run_at) .unwrap_or_else(|e| warn!(target: "engine", "Failed to restart consensus step timer: {}.", e)) } } @@ -1179,7 +1229,7 @@ impl Engine for AuthorityRound { self.validators.on_epoch_begin(first, &header, &mut call) } - /// Apply the block reward on finalisation of the block. + /// Applies the block reward on finalisation of the block. fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> { let mut beneficiaries = Vec::new(); if block.header().number() >= self.empty_steps_transition { @@ -1234,7 +1284,12 @@ impl Engine for AuthorityRound { // Genesis is never a new block, but might as well check. let header = block.header().clone(); let first = header.number() == 0; - + let opt_signer = self.signer.read(); + let signer = match opt_signer.as_ref() { + Some(signer) => signer, + None => return Ok(Vec::new()), // We are not a validator, so we shouldn't call the contracts. + }; + let our_addr = signer.address(); let client = self.client.read().as_ref().and_then(|weak| weak.upgrade()).ok_or_else(|| { debug!(target: "engine", "Unable to prepare block: missing client ref."); EngineError::RequiresClient @@ -1247,14 +1302,8 @@ impl Engine for AuthorityRound { full_client.call_contract(BlockId::Latest, to, data).map_err(|e| format!("{}", e)) }; - let opt_signer = self.signer.read(); - let signer = match opt_signer.as_ref() { - Some(signer) => signer, - None => return Ok(Vec::new()), // We are not a validator, so we shouldn't call the contracts. - }; - // Our current account nonce. The transactions must have consecutive nonces, starting with this one. - let mut tx_nonce = block.state.nonce(&signer.address())?; + let mut tx_nonce = block.state.nonce(&our_addr)?; let mut transactions = Vec::new(); // Creates and signs a transaction with the given contract call. @@ -1268,7 +1317,7 @@ impl Engine for AuthorityRound { if let Some(contract_addr) = self.randomness_contract_address { let mut contract = util::BoundContract::bind(&*client, BlockId::Latest, contract_addr); // TODO: How should these errors be handled? - let phase = randomness::RandomnessPhase::load(&contract, signer.address()) + let phase = randomness::RandomnessPhase::load(&contract, our_addr) .map_err(EngineError::RandomnessLoadError)?; let mut rng = ::rand::OsRng::new()?; if let Some(data) = phase.advance(&contract, &mut rng, signer.as_ref()) @@ -1618,7 +1667,7 @@ impl Engine for AuthorityRound { mod tests { use std::collections::BTreeMap; use std::sync::Arc; - use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; + use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering as AtomicOrdering}; use hash::keccak; use accounts::AccountProvider; use ethereum_types::{Address, H520, H256, U256}; @@ -1641,7 +1690,7 @@ mod tests { F: FnOnce(&mut AuthorityRoundParams), { let mut params = AuthorityRoundParams { - step_duration: 1, + step_durations: [(0, 1)].to_vec().into_iter().collect(), start_step: Some(1), validators: Box::new(TestSet::default()), validate_score_transition: 0, @@ -1941,31 +1990,41 @@ mod tests { #[should_panic(expected="counter is too high")] fn test_counter_increment_too_high() { use super::Step; + use std::sync::atomic::AtomicU16; + let step = Step { calibrate: false, - inner: AtomicUsize::new(::std::usize::MAX), - duration: 1, + inner: AtomicU64::new(::std::u64::MAX), + current_duration: AtomicU16::new(1), + durations: [(0, 1)].to_vec().into_iter().collect(), + starting_sec: AtomicU64::new(::std::u64::MAX), + starting_step: AtomicU64::new(::std::u64::MAX), }; step.increment(); } #[test] - #[should_panic(expected="counter is too high")] + #[should_panic(expected="step counter under- or overflow")] fn test_counter_duration_remaining_too_high() { use super::Step; + use std::sync::atomic::AtomicU16; + let step = Step { calibrate: false, - inner: AtomicUsize::new(::std::usize::MAX), - duration: 1, + inner: AtomicU64::new(::std::u64::MAX), + current_duration: AtomicU16::new(1), + durations: [(0, 1)].to_vec().into_iter().collect(), + starting_sec: AtomicU64::new(::std::u64::MAX), + starting_step: AtomicU64::new(::std::u64::MAX), }; step.duration_remaining(); } #[test] - #[should_panic(expected="authority_round: step duration can't be zero")] + #[should_panic(expected="authority_round: step duration cannot be 0")] fn test_step_duration_zero() { aura(|params| { - params.step_duration = 0; + params.step_durations = [(0, 0)].to_vec().into_iter().collect();; }); } @@ -2357,7 +2416,7 @@ mod tests { #[test] fn test_empty_steps() { let engine = aura(|p| { - p.step_duration = 4; + p.step_durations = [(0, 4)].to_vec().into_iter().collect(); p.empty_steps_transition = 0; p.maximum_empty_steps = 0; }); @@ -2391,7 +2450,7 @@ mod tests { let (_spec, tap, accounts) = setup_empty_steps(); let engine = aura(|p| { p.validators = Box::new(SimpleList::new(accounts.clone())); - p.step_duration = 4; + p.step_durations = [(0, 4)].to_vec().into_iter().collect(); p.empty_steps_transition = 0; p.maximum_empty_steps = 0; }); @@ -2428,7 +2487,7 @@ mod tests { let (_spec, tap, accounts) = setup_empty_steps(); let engine = aura(|p| { p.validators = Box::new(SimpleList::new(accounts.clone())); - p.step_duration = 4; + p.step_durations = [(0, 4)].to_vec().into_iter().collect(); p.empty_steps_transition = 0; p.maximum_empty_steps = 0; }); diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index b061e30bae9..23dfeadbbe0 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -19,7 +19,7 @@ use hash::Address; use uint::Uint; use bytes::Bytes; -use super::ValidatorSet; +use super::{StepDuration, ValidatorSet}; #[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] @@ -34,7 +34,7 @@ pub enum ConsensusKind { #[serde(rename_all = "camelCase")] pub struct AuthorityRoundParams { /// Block duration, in seconds. - pub step_duration: Uint, + pub step_duration: StepDuration, /// Valid authorities pub validators: ValidatorSet, /// Starting step. Determined automatically if not specified. @@ -87,6 +87,7 @@ mod tests { use hash::Address; use spec::validator_set::ValidatorSet; use spec::authority_round::AuthorityRound; + use spec::step_duration::StepDuration; #[test] fn authority_round_deserialization() { @@ -105,12 +106,11 @@ mod tests { }"#; let deserialized: AuthorityRound = serde_json::from_str(s).unwrap(); - assert_eq!(deserialized.params.step_duration, Uint(U256::from(0x02))); + assert_eq!(deserialized.params.step_duration, StepDuration::Single(Uint(U256::from(2)))); assert_eq!(deserialized.params.validators, ValidatorSet::List(vec![Address(H160::from("0xc6d9d2cd449a754c494264e1809c50e34d64562b"))])); assert_eq!(deserialized.params.start_step, Some(Uint(U256::from(24)))); assert_eq!(deserialized.params.immediate_transitions, None); assert_eq!(deserialized.params.maximum_uncle_count_transition, Some(Uint(10_000_000.into()))); assert_eq!(deserialized.params.maximum_uncle_count, Some(Uint(5.into()))); - } } diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index 1d6815d37c1..486b4ab60d3 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -31,6 +31,7 @@ pub mod authority_round; pub mod null_engine; pub mod instant_seal; pub mod hardcoded_sync; +pub mod step_duration; pub use self::account::Account; pub use self::builtin::{Builtin, Pricing, Linear}; @@ -47,3 +48,4 @@ pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; pub use self::null_engine::{NullEngine, NullEngineParams}; pub use self::instant_seal::{InstantSeal, InstantSealParams}; pub use self::hardcoded_sync::HardcodedSync; +pub use self::step_duration::StepDuration; diff --git a/json/src/spec/step_duration.rs b/json/src/spec/step_duration.rs new file mode 100644 index 00000000000..6a060b0e3c3 --- /dev/null +++ b/json/src/spec/step_duration.rs @@ -0,0 +1,33 @@ +// Copyright 2015-2019 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Step duration configuration parameter + +use std::collections::BTreeMap; +use uint::Uint; + +/// Step duration can be specified either as a `Uint` (in seconds), in which case it will be +/// constant, or as a list of pairs consisting of a block number and a duration, in which case the +/// duration of a step will be determined by a mapping arising from that list. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum StepDuration { + /// Duration of all steps. + Single(Uint), + /// Step duration transitions: a mapping of starting block numbers to step durations. + Transitions(BTreeMap), +}