From 9fdd0e3a0a279b8b8574192c80374c2dcdb09619 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 23 Mar 2017 12:19:28 +0000 Subject: [PATCH] Switching ValidatorSet (#4961) * add multi validator set * nicer comment * validate in constructor * reporting --- ethcore/res/validator_multi.json | 42 ++++++ ethcore/src/engines/authority_round.rs | 2 +- ethcore/src/engines/basic_authority.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 2 +- ethcore/src/engines/validator_set/mod.rs | 15 +- ethcore/src/engines/validator_set/multi.rs | 158 +++++++++++++++++++++ ethcore/src/spec/spec.rs | 4 + json/src/spec/validator_set.rs | 17 ++- 8 files changed, 231 insertions(+), 11 deletions(-) create mode 100644 ethcore/res/validator_multi.json create mode 100644 ethcore/src/engines/validator_set/multi.rs diff --git a/ethcore/res/validator_multi.json b/ethcore/res/validator_multi.json new file mode 100644 index 000000000..5d51b73da --- /dev/null +++ b/ethcore/res/validator_multi.json @@ -0,0 +1,42 @@ +{ + "name": "TestMutiValidator", + "engine": { + "basicAuthority": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators": { + "multi": { + "0": { "list": ["0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"] }, + "2": { "list": ["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"] } + } + } + } + } + }, + "params": { + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x69" + }, + "genesis": { + "seal": { + "generic": "0xc180" + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2fefd8" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1": { "balance": "99999999999999999999999" }, + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "99999999999999999999999" } + } +} diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 2a18c748d..4f823fa8d 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -82,7 +82,7 @@ pub struct AuthorityRound { proposed: AtomicBool, client: RwLock>>, signer: EngineSigner, - validators: Box, + validators: Box, /// Is this Engine just for testing (prevents step calibration). calibrate_step: bool, } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index d8a1df947..e5a53d4e9 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -58,7 +58,7 @@ pub struct BasicAuthority { gas_limit_bound_divisor: U256, builtins: BTreeMap, signer: EngineSigner, - validators: Box, + validators: Box, } impl BasicAuthority { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 46e67a2a8..464e102de 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -98,7 +98,7 @@ pub struct Tendermint { /// Hash of the proposal parent block. proposal_parent: RwLock, /// Set used to determine the current validators. - validators: Box, + validators: Box, } impl Tendermint { diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs index 3e86c357f..cbbedfb33 100644 --- a/ethcore/src/engines/validator_set/mod.rs +++ b/ethcore/src/engines/validator_set/mod.rs @@ -19,6 +19,7 @@ mod simple_list; mod safe_contract; mod contract; +mod multi; use std::sync::Weak; use util::{Address, H256}; @@ -27,23 +28,27 @@ use client::Client; use self::simple_list::SimpleList; use self::contract::ValidatorContract; use self::safe_contract::ValidatorSafeContract; +use self::multi::Multi; /// Creates a validator set from spec. -pub fn new_validator_set(spec: ValidatorSpec) -> Box { +pub fn new_validator_set(spec: ValidatorSpec) -> Box { match spec { ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())), ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())), ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())), + ValidatorSpec::Multi(sequence) => Box::new( + Multi::new(sequence.into_iter().map(|(block, set)| (block.into(), new_validator_set(set))).collect()) + ), } } -pub trait ValidatorSet { +pub trait ValidatorSet: Send + Sync { /// Checks if a given address is a validator. - fn contains(&self, bh: &H256, address: &Address) -> bool; + fn contains(&self, parent_block_hash: &H256, address: &Address) -> bool; /// Draws an validator nonce modulo number of validators. - fn get(&self, bh: &H256, nonce: usize) -> Address; + fn get(&self, parent_block_hash: &H256, nonce: usize) -> Address; /// Returns the current number of validators. - fn count(&self, bh: &H256) -> usize; + fn count(&self, parent_block_hash: &H256) -> usize; /// Notifies about malicious behaviour. fn report_malicious(&self, _validator: &Address) {} /// Notifies about benign misbehaviour. diff --git a/ethcore/src/engines/validator_set/multi.rs b/ethcore/src/engines/validator_set/multi.rs new file mode 100644 index 000000000..5027f23cd --- /dev/null +++ b/ethcore/src/engines/validator_set/multi.rs @@ -0,0 +1,158 @@ +// Copyright 2015-2017 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 . + +/// Validator set changing at fork blocks. + +use std::collections::BTreeMap; +use std::sync::Weak; +use util::{H256, Address, RwLock}; +use ids::BlockId; +use header::BlockNumber; +use client::{Client, BlockChainClient}; +use super::ValidatorSet; + +type BlockNumberLookup = Box Result + Send + Sync + 'static>; + +pub struct Multi { + sets: BTreeMap>, + block_number: RwLock, +} + +impl Multi { + pub fn new(set_map: BTreeMap>) -> Self { + assert!(set_map.get(&0u64).is_some(), "ValidatorSet has to be specified from block 0."); + Multi { + sets: set_map, + block_number: RwLock::new(Box::new(move |_| Err("No client!".into()))), + } + } + + fn correct_set(&self, bh: &H256) -> Option<&Box> { + match self + .block_number + .read()(bh) + .map(|parent_block| self + .sets + .iter() + .rev() + .find(|&(block, _)| *block <= parent_block + 1) + .expect("constructor validation ensures that there is at least one validator set for block 0; + block 0 is less than any uint; + qed") + ) { + Ok((block, set)) => { + trace!(target: "engine", "Multi ValidatorSet retrieved for block {}.", block); + Some(set) + }, + Err(e) => { + debug!(target: "engine", "ValidatorSet could not be recovered: {}", e); + None + }, + } + } +} + +impl ValidatorSet for Multi { + fn contains(&self, bh: &H256, address: &Address) -> bool { + self.correct_set(bh).map_or(false, |set| set.contains(bh, address)) + } + + fn get(&self, bh: &H256, nonce: usize) -> Address { + self.correct_set(bh).map_or_else(Default::default, |set| set.get(bh, nonce)) + } + + fn count(&self, bh: &H256) -> usize { + self.correct_set(bh).map_or_else(usize::max_value, |set| set.count(bh)) + } + + fn report_malicious(&self, validator: &Address) { + for set in self.sets.values() { + set.report_malicious(validator); + } + } + + fn report_benign(&self, validator: &Address) { + for set in self.sets.values() { + set.report_benign(validator); + } + } + + fn register_contract(&self, client: Weak) { + for set in self.sets.values() { + set.register_contract(client.clone()); + } + *self.block_number.write() = Box::new(move |hash| client + .upgrade() + .ok_or("No client!".into()) + .and_then(|c| c.block_number(BlockId::Hash(*hash)).ok_or("Unknown block".into()))); + } +} + +#[cfg(test)] +mod tests { + use util::*; + use types::ids::BlockId; + use spec::Spec; + use account_provider::AccountProvider; + use client::{BlockChainClient, EngineClient}; + use ethkey::Secret; + use miner::MinerService; + use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; + + #[test] + fn uses_current_set() { + ::env_logger::init().unwrap(); + let tap = Arc::new(AccountProvider::transient_provider()); + let s0 = Secret::from_slice(&"0".sha3()).unwrap(); + let v0 = tap.insert_account(s0.clone(), "").unwrap(); + let v1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "").unwrap(); + let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_multi, Some(tap)); + client.engine().register_client(Arc::downgrade(&client)); + + // Make sure txs go through. + client.miner().set_gas_floor_target(1_000_000.into()); + + // Wrong signer for the first block. + client.miner().set_engine_signer(v1, "".into()).unwrap(); + client.transact_contract(Default::default(), Default::default()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 0); + // Right signer for the first block. + client.miner().set_engine_signer(v0, "".into()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 1); + // This time v0 is wrong. + client.transact_contract(Default::default(), Default::default()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 1); + client.miner().set_engine_signer(v1, "".into()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 2); + // v1 is still good. + client.transact_contract(Default::default(), Default::default()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 3); + + // Check syncing. + let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_multi, 0, 0, &[]); + sync_client.engine().register_client(Arc::downgrade(&sync_client)); + for i in 1..4 { + sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap(); + } + sync_client.flush_queue(); + assert_eq!(sync_client.chain_info().best_block_number, 3); + } +} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 21c07c9a3..455d0745f 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -360,6 +360,10 @@ impl Spec { /// Account is marked with `reportBenign` it can be checked as disliked with "0xd8f2e0bf". /// Validator can be removed with `reportMalicious`. pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") } + + /// Create a new Spec with BasicAuthority which uses multiple validator sets changing with height. + /// Account with secrets "0".sha3() is the validator for block 1 and with "1".sha3() onwards. + pub fn new_validator_multi() -> Self { load_bundled!("validator_multi") } } #[cfg(test)] diff --git a/json/src/spec/validator_set.rs b/json/src/spec/validator_set.rs index 080a36c50..f433caa03 100644 --- a/json/src/spec/validator_set.rs +++ b/json/src/spec/validator_set.rs @@ -16,6 +16,8 @@ //! Validator set deserialization. +use std::collections::BTreeMap; +use uint::Uint; use hash::Address; /// Different ways of specifying validators. @@ -30,6 +32,9 @@ pub enum ValidatorSet { /// Address of a contract that indicates the list of authorities and enables reporting of theor misbehaviour using transactions. #[serde(rename="contract")] Contract(Address), + /// A map of starting blocks for each validator set. + #[serde(rename="multi")] + Multi(BTreeMap), } #[cfg(test)] @@ -40,11 +45,17 @@ mod tests { #[test] fn validator_set_deserialization() { let s = r#"[{ - "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] }, { - "safeContract" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "safeContract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" }, { - "contract" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "contract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + }, { + "multi": { + "0": { "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] }, + "10": { "list": ["0xd6d9d2cd449a754c494264e1809c50e34d64562b"] }, + "20": { "contract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" } + } }]"#; let _deserialized: Vec = serde_json::from_str(s).unwrap();