Switching ValidatorSet (#4961)
* add multi validator set * nicer comment * validate in constructor * reporting
This commit is contained in:
parent
64cec5ff7d
commit
9fdd0e3a0a
42
ethcore/res/validator_multi.json
Normal file
42
ethcore/res/validator_multi.json
Normal file
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
@ -82,7 +82,7 @@ pub struct AuthorityRound {
|
|||||||
proposed: AtomicBool,
|
proposed: AtomicBool,
|
||||||
client: RwLock<Option<Weak<EngineClient>>>,
|
client: RwLock<Option<Weak<EngineClient>>>,
|
||||||
signer: EngineSigner,
|
signer: EngineSigner,
|
||||||
validators: Box<ValidatorSet + Send + Sync>,
|
validators: Box<ValidatorSet>,
|
||||||
/// Is this Engine just for testing (prevents step calibration).
|
/// Is this Engine just for testing (prevents step calibration).
|
||||||
calibrate_step: bool,
|
calibrate_step: bool,
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ pub struct BasicAuthority {
|
|||||||
gas_limit_bound_divisor: U256,
|
gas_limit_bound_divisor: U256,
|
||||||
builtins: BTreeMap<Address, Builtin>,
|
builtins: BTreeMap<Address, Builtin>,
|
||||||
signer: EngineSigner,
|
signer: EngineSigner,
|
||||||
validators: Box<ValidatorSet + Send + Sync>,
|
validators: Box<ValidatorSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BasicAuthority {
|
impl BasicAuthority {
|
||||||
|
@ -98,7 +98,7 @@ pub struct Tendermint {
|
|||||||
/// Hash of the proposal parent block.
|
/// Hash of the proposal parent block.
|
||||||
proposal_parent: RwLock<H256>,
|
proposal_parent: RwLock<H256>,
|
||||||
/// Set used to determine the current validators.
|
/// Set used to determine the current validators.
|
||||||
validators: Box<ValidatorSet + Send + Sync>,
|
validators: Box<ValidatorSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tendermint {
|
impl Tendermint {
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
mod simple_list;
|
mod simple_list;
|
||||||
mod safe_contract;
|
mod safe_contract;
|
||||||
mod contract;
|
mod contract;
|
||||||
|
mod multi;
|
||||||
|
|
||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
use util::{Address, H256};
|
use util::{Address, H256};
|
||||||
@ -27,23 +28,27 @@ use client::Client;
|
|||||||
use self::simple_list::SimpleList;
|
use self::simple_list::SimpleList;
|
||||||
use self::contract::ValidatorContract;
|
use self::contract::ValidatorContract;
|
||||||
use self::safe_contract::ValidatorSafeContract;
|
use self::safe_contract::ValidatorSafeContract;
|
||||||
|
use self::multi::Multi;
|
||||||
|
|
||||||
/// Creates a validator set from spec.
|
/// Creates a validator set from spec.
|
||||||
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> {
|
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
|
||||||
match spec {
|
match spec {
|
||||||
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
|
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
|
||||||
ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())),
|
ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())),
|
||||||
ValidatorSpec::Contract(address) => Box::new(ValidatorContract::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.
|
/// 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.
|
/// 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.
|
/// Returns the current number of validators.
|
||||||
fn count(&self, bh: &H256) -> usize;
|
fn count(&self, parent_block_hash: &H256) -> usize;
|
||||||
/// Notifies about malicious behaviour.
|
/// Notifies about malicious behaviour.
|
||||||
fn report_malicious(&self, _validator: &Address) {}
|
fn report_malicious(&self, _validator: &Address) {}
|
||||||
/// Notifies about benign misbehaviour.
|
/// Notifies about benign misbehaviour.
|
||||||
|
158
ethcore/src/engines/validator_set/multi.rs
Normal file
158
ethcore/src/engines/validator_set/multi.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/// 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<Fn(&H256) -> Result<BlockNumber, String> + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
pub struct Multi {
|
||||||
|
sets: BTreeMap<BlockNumber, Box<ValidatorSet>>,
|
||||||
|
block_number: RwLock<BlockNumberLookup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Multi {
|
||||||
|
pub fn new(set_map: BTreeMap<BlockNumber, Box<ValidatorSet>>) -> 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<ValidatorSet>> {
|
||||||
|
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<Client>) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -360,6 +360,10 @@ impl Spec {
|
|||||||
/// Account is marked with `reportBenign` it can be checked as disliked with "0xd8f2e0bf".
|
/// Account is marked with `reportBenign` it can be checked as disliked with "0xd8f2e0bf".
|
||||||
/// Validator can be removed with `reportMalicious`.
|
/// Validator can be removed with `reportMalicious`.
|
||||||
pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") }
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
//! Validator set deserialization.
|
//! Validator set deserialization.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use uint::Uint;
|
||||||
use hash::Address;
|
use hash::Address;
|
||||||
|
|
||||||
/// Different ways of specifying validators.
|
/// 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.
|
/// Address of a contract that indicates the list of authorities and enables reporting of theor misbehaviour using transactions.
|
||||||
#[serde(rename="contract")]
|
#[serde(rename="contract")]
|
||||||
Contract(Address),
|
Contract(Address),
|
||||||
|
/// A map of starting blocks for each validator set.
|
||||||
|
#[serde(rename="multi")]
|
||||||
|
Multi(BTreeMap<Uint, ValidatorSet>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -45,6 +50,12 @@ mod tests {
|
|||||||
"safeContract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
|
"safeContract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
|
||||||
}, {
|
}, {
|
||||||
"contract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
|
"contract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
|
||||||
|
}, {
|
||||||
|
"multi": {
|
||||||
|
"0": { "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] },
|
||||||
|
"10": { "list": ["0xd6d9d2cd449a754c494264e1809c50e34d64562b"] },
|
||||||
|
"20": { "contract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" }
|
||||||
|
}
|
||||||
}]"#;
|
}]"#;
|
||||||
|
|
||||||
let _deserialized: Vec<ValidatorSet> = serde_json::from_str(s).unwrap();
|
let _deserialized: Vec<ValidatorSet> = serde_json::from_str(s).unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user