Validator/authority contract (#3937)

* dir

* simple validator list

* stub validator contract

* make the engine hold Weak<Client> instead of IoChannel

* validator set factory

* register weak client with ValidatorContract

* check chain security

* add address array to generator

* register provider contract

* update validator set on notify

* add validator contract spec

* simple list test

* split update and contract test

* contract change

* use client in tendermint

* fix deadlock

* step duration in params

* adapt tendermint tests

* add storage fields to test spec

* constructor spec

* execute under wrong address

* create under correct address

* revert

* validator contract constructor

* move genesis block lookup

* add removal ability to contract

* validator contract adding validators

* fix basic authority

* validator changing test

* more docs

* update sync tests

* remove env_logger

* another env_logger

* cameltoe

* hold EngineClient instead of Client

* add a comment about lock scope
This commit is contained in:
keorn 2017-01-10 12:23:59 +01:00 committed by Arkadiy Paronyan
parent 5c5244911e
commit be30c44179
29 changed files with 749 additions and 314 deletions

View File

@ -6,10 +6,12 @@
"gasLimitBoundDivisor": "0x0400",
"stepDuration": 1,
"startStep": 2,
"authorities" : [
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e",
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"
]
"validators": {
"list": [
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e",
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"
]
}
}
}
},

View File

@ -5,7 +5,9 @@
"params": {
"gasLimitBoundDivisor": "0x0400",
"durationLimit": "0x0d",
"authorities" : ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"]
"validators": {
"list": ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"]
}
}
}
},
@ -17,7 +19,7 @@
},
"genesis": {
"seal": {
"generic": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"
"generic": "0xc180"
},
"difficulty": "0x20000",
"author": "0x0000000000000000000000000000000000000000",

View File

@ -4,10 +4,12 @@
"tendermint": {
"params": {
"gasLimitBoundDivisor": "0x0400",
"authorities" : [
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
]
"validators" : {
"list": [
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
]
}
}
}
},

View File

@ -0,0 +1,42 @@
{
"name": "TestValidatorContract",
"engine": {
"basicAuthority": {
"params": {
"gasLimitBoundDivisor": "0x0400",
"durationLimit": "0x0d",
"validators": {
"contract": "0x0000000000000000000000000000000000000005"
}
}
}
},
"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 } } } },
"0000000000000000000000000000000000000005": {
"balance": "1",
"constructor": "0x60a06040819052737d577a597b2742b498cb5cf0c26cdcd726d39e6e60609081527382a978b3f5962a5b0957d9ee9eef472ee55b42f1608052600080546002825581805290927f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639182019291905b828111156100a25782518254600160a060020a031916600160a060020a0390911617825560209092019160019091019061006d565b5b506100cd9291505b808211156100c9578054600160a060020a03191681556001016100ab565b5090565b505034610000575b610332806100e46000396000f300606060405263ffffffff60e060020a60003504166335aa2e4481146100455780634d238c8e14610071578063b7ab4db51461008c578063f94e1867146100f4575b610000565b3461000057610055600435610106565b60408051600160a060020a039092168252519081900360200190f35b346100005761008a600160a060020a0360043516610136565b005b34610000576100996101ad565b60408051602080825283518183015283519192839290830191858101910280838382156100e1575b8051825260208311156100e157601f1990920191602091820191016100c1565b5050509050019250505060405180910390f35b346100005761008a600435610217565b005b600081815481101561000057906000526020600020900160005b915054906101000a9004600160a060020a031681565b60008054806001018281815481835581811511610178576000838152602090206101789181019083015b808211156101745760008155600101610160565b5090565b5b505050916000526020600020900160005b8154600160a060020a038086166101009390930a92830292021916179055505b50565b604080516020818101835260008083528054845181840281018401909552808552929392909183018282801561020c57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116101ee575b505050505090505b90565b6000805460001981019081101561000057906000526020600020900160005b9054906101000a9004600160a060020a0316600082815481101561000057906000526020600020900160005b6101000a815481600160a060020a030219169083600160a060020a031602179055506000600160008054905003815481101561000057906000526020600020900160005b6101000a815490600160a060020a03021916905560008054809190600190038154818355818115116102fd576000838152602090206102fd9181019083015b808211156101745760008155600101610160565b5090565b5b505050505b505600a165627a7a72305820d742dd391941c1c255f0e1187ffa5b1e783219264fb10196018aefa379f5638b0029"
},
"9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }
}
}

View File

@ -53,7 +53,7 @@ use verification::queue::BlockQueue;
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
use client::{
BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient,
MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode,
MiningBlockChainClient, EngineClient, TraceFilter, CallAnalytics, BlockImportError, Mode,
ChainNotify, PruningInfo,
};
use client::Error as ClientError;
@ -1315,11 +1315,6 @@ impl BlockChainClient for Client {
}
}
fn broadcast_consensus_message(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(message.clone()));
}
fn signing_network_id(&self) -> Option<u64> {
self.engine.signing_network_id(&self.latest_env_info())
}
@ -1414,16 +1409,6 @@ impl MiningBlockChainClient for Client {
&self.factories.vm
}
fn update_sealing(&self) {
self.miner.update_sealing(self)
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if self.miner.submit_seal(self, block_hash, seal).is_err() {
warn!(target: "poa", "Wrong internal seal submission!")
}
}
fn broadcast_proposal_block(&self, block: SealedBlock) {
self.notify(|notify| {
notify.new_blocks(
@ -1471,6 +1456,22 @@ impl MiningBlockChainClient for Client {
}
}
impl EngineClient for Client {
fn update_sealing(&self) {
self.miner.update_sealing(self)
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if self.miner.submit_seal(self, block_hash, seal).is_err() {
warn!(target: "poa", "Wrong internal seal submission!")
}
}
fn broadcast_consensus_message(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(message.clone()));
}
}
impl MayPanic for Client {
fn on_panic<F>(&self, closure: F) where F: OnPanicListener {
self.panic_handler.on_panic(closure);

View File

@ -28,7 +28,7 @@ pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChain
pub use self::error::Error;
pub use self::test_client::{TestBlockChainClient, EachBlockWith};
pub use self::chain_notify::ChainNotify;
pub use self::traits::{BlockChainClient, MiningBlockChainClient};
pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient};
pub use self::traits::ProvingBlockChainClient;

View File

@ -24,7 +24,7 @@ use devtools::*;
use transaction::{Transaction, LocalizedTransaction, SignedTransaction, PendingTransaction, Action};
use blockchain::TreeRoute;
use client::{
BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId,
BlockChainClient, MiningBlockChainClient, EngineClient, BlockChainInfo, BlockStatus, BlockId,
TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError,
};
use db::{NUM_COLUMNS, COL_STATE};
@ -372,16 +372,6 @@ impl MiningBlockChainClient for TestBlockChainClient {
}
fn broadcast_proposal_block(&self, _block: SealedBlock) {}
fn update_sealing(&self) {
self.miner.update_sealing(self)
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if self.miner.submit_seal(self, block_hash, seal).is_err() {
warn!(target: "poa", "Wrong internal seal submission!")
}
}
}
impl BlockChainClient for TestBlockChainClient {
@ -699,8 +689,6 @@ impl BlockChainClient for TestBlockChainClient {
self.spec.engine.handle_message(&message).unwrap();
}
fn broadcast_consensus_message(&self, _message: Bytes) {}
fn ready_transactions(&self) -> Vec<PendingTransaction> {
self.miner.ready_transactions(self.chain_info().best_block_number)
}
@ -727,3 +715,17 @@ impl BlockChainClient for TestBlockChainClient {
fn registry_address(&self, _name: String) -> Option<Address> { None }
}
impl EngineClient for TestBlockChainClient {
fn update_sealing(&self) {
self.miner.update_sealing(self)
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if self.miner.submit_seal(self, block_hash, seal).is_err() {
warn!(target: "poa", "Wrong internal seal submission!")
}
}
fn broadcast_consensus_message(&self, _message: Bytes) {}
}

View File

@ -208,9 +208,6 @@ pub trait BlockChainClient : Sync + Send {
/// Queue conensus engine message.
fn queue_consensus_message(&self, message: Bytes);
/// Used by PoA to communicate with peers.
fn broadcast_consensus_message(&self, message: Bytes);
/// List all transactions that are allowed into the next block.
fn ready_transactions(&self) -> Vec<PendingTransaction>;
@ -294,12 +291,6 @@ pub trait MiningBlockChainClient: BlockChainClient {
/// Returns EvmFactory.
fn vm_factory(&self) -> &EvmFactory;
/// Used by PoA to try sealing on period change.
fn update_sealing(&self);
/// Used by PoA to submit gathered signatures.
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>);
/// Broadcast a block proposal.
fn broadcast_proposal_block(&self, block: SealedBlock);
@ -310,6 +301,18 @@ pub trait MiningBlockChainClient: BlockChainClient {
fn latest_schedule(&self) -> Schedule;
}
/// Client facilities used by internally sealing Engines.
pub trait EngineClient: MiningBlockChainClient {
/// Make a new block and seal it.
fn update_sealing(&self);
/// Submit a seal for a block in the mining queue.
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>);
/// Broadcast a consensus message to the network.
fn broadcast_consensus_message(&self, message: Bytes);
}
/// Extended client interface for providing proofs of the state.
pub trait ProvingBlockChainClient: BlockChainClient {
/// Prove account storage at a specific block id.

View File

@ -32,11 +32,12 @@ use blockchain::extras::BlockDetails;
use views::HeaderView;
use evm::Schedule;
use ethjson;
use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel};
use service::ClientIoMessage;
use io::{IoContext, IoHandler, TimerToken, IoService};
use transaction::SignedTransaction;
use env_info::EnvInfo;
use builtin::Builtin;
use client::{Client, EngineClient};
use super::validator_set::{ValidatorSet, new_validator_set};
use state::CleanupMode;
/// `AuthorityRound` params.
@ -46,14 +47,12 @@ pub struct AuthorityRoundParams {
pub gas_limit_bound_divisor: U256,
/// Time to wait before next block or authority switching.
pub step_duration: Duration,
/// Valid authorities.
pub authorities: Vec<Address>,
/// Number of authorities.
pub authority_n: usize,
/// Block reward.
pub block_reward: U256,
/// Starting step,
pub start_step: Option<u64>,
/// Valid validators.
pub validators: ethjson::spec::ValidatorSet,
}
impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
@ -61,8 +60,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
AuthorityRoundParams {
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
step_duration: Duration::from_secs(p.step_duration.into()),
authority_n: p.authorities.len(),
authorities: p.authorities.into_iter().map(Into::into).collect::<Vec<_>>(),
validators: p.validators,
block_reward: p.block_reward.map_or_else(U256::zero, Into::into),
start_step: p.start_step.map(Into::into),
}
@ -73,14 +71,17 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
/// mainnet chains in the Olympic, Frontier and Homestead eras.
pub struct AuthorityRound {
params: CommonParams,
our_params: AuthorityRoundParams,
gas_limit_bound_divisor: U256,
block_reward: U256,
step_duration: Duration,
builtins: BTreeMap<Address, Builtin>,
transition_service: IoService<()>,
message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>,
step: AtomicUsize,
proposed: AtomicBool,
account_provider: Mutex<Option<Arc<AccountProvider>>>,
client: RwLock<Option<Weak<EngineClient>>>,
account_provider: Mutex<Arc<AccountProvider>>,
password: RwLock<Option<String>>,
validators: Box<ValidatorSet + Send + Sync>,
}
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
@ -109,14 +110,17 @@ impl AuthorityRound {
let engine = Arc::new(
AuthorityRound {
params: params,
our_params: our_params,
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
block_reward: our_params.block_reward,
step_duration: our_params.step_duration,
builtins: builtins,
transition_service: IoService::<()>::start()?,
message_channel: Mutex::new(None),
step: AtomicUsize::new(initial_step),
proposed: AtomicBool::new(false),
account_provider: Mutex::new(None),
client: RwLock::new(None),
account_provider: Mutex::new(Arc::new(AccountProvider::transient_provider())),
password: RwLock::new(None),
validators: new_validator_set(our_params.validators),
});
// Do not initialize timeouts for tests.
if should_timeout {
@ -128,7 +132,7 @@ impl AuthorityRound {
fn remaining_step_duration(&self) -> Duration {
let now = unix_now();
let step_end = self.our_params.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
if step_end > now {
step_end - now
} else {
@ -136,13 +140,12 @@ impl AuthorityRound {
}
}
fn step_proposer(&self, step: usize) -> &Address {
let p = &self.our_params;
p.authorities.get(step % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed")
fn step_proposer(&self, step: usize) -> Address {
self.validators.get(step)
}
fn is_step_proposer(&self, step: usize, address: &Address) -> bool {
self.step_proposer(step) == address
self.step_proposer(step) == *address
}
}
@ -187,10 +190,9 @@ impl Engine for AuthorityRound {
fn step(&self) {
self.step.fetch_add(1, AtomicOrdering::SeqCst);
self.proposed.store(false, AtomicOrdering::SeqCst);
if let Some(ref channel) = *self.message_channel.lock() {
match channel.send(ClientIoMessage::UpdateSealing) {
Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", self.step.load(AtomicOrdering::Relaxed)),
Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, self.step.load(AtomicOrdering::Relaxed)),
if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() {
c.update_sealing();
}
}
}
@ -211,7 +213,7 @@ impl Engine for AuthorityRound {
header.set_difficulty(parent.difficulty().clone());
header.set_gas_limit({
let gas_limit = parent.gas_limit().clone();
let bound_divisor = self.our_params.gas_limit_bound_divisor;
let bound_divisor = self.gas_limit_bound_divisor;
if gas_limit < gas_floor_target {
min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into())
} else {
@ -221,8 +223,7 @@ impl Engine for AuthorityRound {
}
fn is_sealer(&self, author: &Address) -> Option<bool> {
let p = &self.our_params;
Some(p.authorities.contains(author))
Some(self.validators.contains(author))
}
/// Attempt to seal the block internally.
@ -234,18 +235,13 @@ impl Engine for AuthorityRound {
let header = block.header();
let step = self.step.load(AtomicOrdering::SeqCst);
if self.is_step_proposer(step, header.author()) {
if let Some(ref ap) = *self.account_provider.lock() {
// Account should be permanently unlocked, otherwise sealing will fail.
if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) {
trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step);
self.proposed.store(true, AtomicOrdering::SeqCst);
let rlps = vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()];
return Seal::Regular(rlps);
} else {
warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable.");
}
let ref ap = *self.account_provider.lock();
if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) {
trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step);
self.proposed.store(true, AtomicOrdering::SeqCst);
return Seal::Regular(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
} else {
warn!(target: "poa", "generate_seal: FAIL: Accounts not provided.");
warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable.");
}
} else {
trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step);
@ -255,12 +251,9 @@ impl Engine for AuthorityRound {
/// Apply the block reward on finalisation of the block.
fn on_close_block(&self, block: &mut ExecutedBlock) {
let reward = self.our_params.block_reward;
let fields = block.fields_mut();
// Bestow block reward
fields.state.add_balance(fields.header.author(), &reward, CleanupMode::NoEmpty);
fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty);
// Commit state so that we can actually figure out the state root.
if let Err(e) = fields.state.commit() {
warn!("Encountered error on state commit: {}", e);
@ -285,7 +278,7 @@ impl Engine for AuthorityRound {
// Give one step slack if step is lagging, double vote is still not possible.
if header_step <= self.step.load(AtomicOrdering::SeqCst) + 1 {
let proposer_signature = header_signature(header)?;
let ok_sig = verify_address(self.step_proposer(header_step), &proposer_signature, &header.bare_hash())?;
let ok_sig = verify_address(&self.step_proposer(header_step), &proposer_signature, &header.bare_hash())?;
if ok_sig {
Ok(())
} else {
@ -310,7 +303,7 @@ impl Engine for AuthorityRound {
Err(EngineError::DoubleVote(header.author().clone()))?;
}
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
let gas_limit_divisor = self.gas_limit_bound_divisor;
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
@ -341,8 +334,9 @@ impl Engine for AuthorityRound {
}
}
fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) {
*self.message_channel.lock() = Some(message_channel);
fn register_client(&self, client: Weak<Client>) {
*self.client.write() = Some(client.clone());
self.validators.register_call_contract(client);
}
fn set_signer(&self, _address: Address, password: String) {
@ -350,7 +344,7 @@ impl Engine for AuthorityRound {
}
fn register_account_provider(&self, account_provider: Arc<AccountProvider>) {
*self.account_provider.lock() = Some(account_provider);
*self.account_provider.lock() = account_provider;
}
}
@ -458,7 +452,7 @@ mod tests {
let engine = Spec::new_test_round().engine;
let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap();
// Two authorities.
// Two validators.
// Spec starts with step 2.
header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_err());
@ -477,7 +471,7 @@ mod tests {
let engine = Spec::new_test_round().engine;
let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap();
// Two authorities.
// Two validators.
// Spec starts with step 2.
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_seal(&header).is_ok());

View File

@ -16,6 +16,8 @@
//! A blockchain engine that supports a basic, non-BFT proof-of-authority.
use std::sync::Weak;
use util::*;
use ethkey::{recover, public_to_address};
use account_provider::AccountProvider;
use block::*;
@ -28,26 +30,23 @@ use evm::Schedule;
use ethjson;
use header::Header;
use transaction::SignedTransaction;
use util::*;
use client::Client;
use super::validator_set::{ValidatorSet, new_validator_set};
/// `BasicAuthority` params.
#[derive(Debug, PartialEq)]
pub struct BasicAuthorityParams {
/// Gas limit divisor.
pub gas_limit_bound_divisor: U256,
/// Block duration.
pub duration_limit: u64,
/// Valid signatories.
pub authorities: HashSet<Address>,
pub validators: ethjson::spec::ValidatorSet,
}
impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams {
fn from(p: ethjson::spec::BasicAuthorityParams) -> Self {
BasicAuthorityParams {
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
duration_limit: p.duration_limit.into(),
authorities: p.authorities.into_iter().map(Into::into).collect::<HashSet<_>>(),
validators: p.validators,
}
}
}
@ -56,10 +55,11 @@ impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams {
/// mainnet chains in the Olympic, Frontier and Homestead eras.
pub struct BasicAuthority {
params: CommonParams,
our_params: BasicAuthorityParams,
gas_limit_bound_divisor: U256,
builtins: BTreeMap<Address, Builtin>,
account_provider: Mutex<Option<Arc<AccountProvider>>>,
password: RwLock<Option<String>>,
validators: Box<ValidatorSet + Send + Sync>,
}
impl BasicAuthority {
@ -67,8 +67,9 @@ impl BasicAuthority {
pub fn new(params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap<Address, Builtin>) -> Self {
BasicAuthority {
params: params,
our_params: our_params,
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
builtins: builtins,
validators: new_validator_set(our_params.validators),
account_provider: Mutex::new(None),
password: RwLock::new(None),
}
@ -95,7 +96,7 @@ impl Engine for BasicAuthority {
header.set_difficulty(parent.difficulty().clone());
header.set_gas_limit({
let gas_limit = parent.gas_limit().clone();
let bound_divisor = self.our_params.gas_limit_bound_divisor;
let bound_divisor = self.gas_limit_bound_divisor;
if gas_limit < gas_floor_target {
min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into())
} else {
@ -105,22 +106,22 @@ impl Engine for BasicAuthority {
}
fn is_sealer(&self, author: &Address) -> Option<bool> {
Some(self.our_params.authorities.contains(author))
Some(self.validators.contains(author))
}
/// Attempt to seal the block internally.
///
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
/// be returned.
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
if let Some(ref ap) = *self.account_provider.lock() {
let header = block.header();
let message = header.bare_hash();
// account should be pernamently unlocked, otherwise sealing will fail
if let Ok(signature) = ap.sign(*block.header().author(), self.password.read().clone(), message) {
return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]);
} else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
let author = header.author();
if self.validators.contains(author) {
let message = header.bare_hash();
// account should be pernamently unlocked, otherwise sealing will fail
if let Ok(signature) = ap.sign(*author, self.password.read().clone(), message) {
return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]);
} else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
}
}
} else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided");
@ -145,7 +146,7 @@ impl Engine for BasicAuthority {
// check the signature is legit.
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
if !self.our_params.authorities.contains(&signer) {
if !self.validators.contains(&signer) {
return Err(BlockError::InvalidSeal)?;
}
Ok(())
@ -161,7 +162,7 @@ impl Engine for BasicAuthority {
if header.difficulty() != parent.difficulty() {
return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: *parent.difficulty(), found: *header.difficulty() })))
}
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
let gas_limit_divisor = self.gas_limit_bound_divisor;
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
@ -179,6 +180,10 @@ impl Engine for BasicAuthority {
t.sender().map(|_|()) // Perform EC recovery and cache sender
}
fn register_client(&self, client: Weak<Client>) {
self.validators.register_call_contract(client);
}
fn set_signer(&self, _address: Address, password: String) {
*self.password.write() = Some(password);
}

View File

@ -21,6 +21,7 @@ mod instant_seal;
mod basic_authority;
mod authority_round;
mod tendermint;
mod validator_set;
pub use self::null_engine::NullEngine;
pub use self::instant_seal::InstantSeal;
@ -28,6 +29,7 @@ pub use self::basic_authority::BasicAuthority;
pub use self::authority_round::AuthorityRound;
pub use self::tendermint::Tendermint;
use std::sync::Weak;
use util::*;
use account_provider::AccountProvider;
use block::ExecutedBlock;
@ -36,13 +38,12 @@ use env_info::EnvInfo;
use error::Error;
use spec::CommonParams;
use evm::Schedule;
use io::IoChannel;
use service::ClientIoMessage;
use header::Header;
use transaction::SignedTransaction;
use ethereum::ethash;
use blockchain::extras::BlockDetails;
use views::HeaderView;
use client::Client;
/// Voting errors.
#[derive(Debug)]
@ -207,14 +208,15 @@ pub trait Engine : Sync + Send {
/// Register an account which signs consensus messages.
fn set_signer(&self, _address: Address, _password: String) {}
/// Stops any services that the may hold the Engine and makes it safe to drop.
fn stop(&self) {}
/// Add a channel for communication with Client which can be used for sealing.
fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {}
/// Add Client which can be used for sealing, querying the state and sending messages.
fn register_client(&self, _client: Weak<Client>) {}
/// Add an account provider useful for Engines that sign stuff.
fn register_account_provider(&self, _account_provider: Arc<AccountProvider>) {}
/// Trigger next step of the consensus engine.
fn step(&self) {}
/// Stops any services that the may hold the Engine and makes it safe to drop.
fn stop(&self) {}
}

View File

@ -27,8 +27,10 @@ mod transition;
mod params;
mod vote_collector;
use std::sync::Weak;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use util::*;
use client::{Client, EngineClient};
use error::{Error, BlockError};
use header::Header;
use builtin::Builtin;
@ -43,9 +45,9 @@ use engines::{Engine, Seal, EngineError};
use blockchain::extras::BlockDetails;
use views::HeaderView;
use evm::Schedule;
use io::{IoService, IoChannel};
use service::ClientIoMessage;
use state::CleanupMode;
use io::IoService;
use super::validator_set::{ValidatorSet, new_validator_set};
use self::message::*;
use self::transition::TransitionHandler;
use self::params::TendermintParams;
@ -75,9 +77,11 @@ pub type BlockHash = H256;
/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain.
pub struct Tendermint {
params: CommonParams,
our_params: TendermintParams,
gas_limit_bound_divisor: U256,
builtins: BTreeMap<Address, Builtin>,
step_service: IoService<Step>,
client: RwLock<Option<Weak<EngineClient>>>,
block_reward: U256,
/// Address to be used as authority.
authority: RwLock<Address>,
/// Password used for signing messages.
@ -90,8 +94,6 @@ pub struct Tendermint {
step: RwLock<Step>,
/// Vote accumulator.
votes: VoteCollector,
/// Channel for updating the sealing.
message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>,
/// Used to sign messages and proposals.
account_provider: Mutex<Option<Arc<AccountProvider>>>,
/// Message for the last PoLC.
@ -100,6 +102,8 @@ pub struct Tendermint {
last_lock: AtomicUsize,
/// Bare hash of the proposed block, used for seal submission.
proposal: RwLock<Option<H256>>,
/// Set used to determine the current validators.
validators: Box<ValidatorSet + Send + Sync>,
}
impl Tendermint {
@ -108,53 +112,49 @@ impl Tendermint {
let engine = Arc::new(
Tendermint {
params: params,
our_params: our_params,
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
builtins: builtins,
client: RwLock::new(None),
step_service: IoService::<Step>::start()?,
block_reward: our_params.block_reward,
authority: RwLock::new(Address::default()),
password: RwLock::new(None),
height: AtomicUsize::new(1),
round: AtomicUsize::new(0),
step: RwLock::new(Step::Propose),
votes: VoteCollector::new(),
message_channel: Mutex::new(None),
account_provider: Mutex::new(None),
lock_change: RwLock::new(None),
last_lock: AtomicUsize::new(0),
proposal: RwLock::new(None),
validators: new_validator_set(our_params.validators),
});
let handler = TransitionHandler { engine: Arc::downgrade(&engine) };
let handler = TransitionHandler::new(Arc::downgrade(&engine), our_params.timeouts);
engine.step_service.register_handler(Arc::new(handler))?;
Ok(engine)
}
fn update_sealing(&self) {
if let Some(ref channel) = *self.message_channel.lock() {
match channel.send(ClientIoMessage::UpdateSealing) {
Ok(_) => trace!(target: "poa", "UpdateSealing message sent."),
Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err),
if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() {
c.update_sealing();
}
}
}
fn submit_seal(&self, block_hash: H256, seal: Vec<Bytes>) {
if let Some(ref channel) = *self.message_channel.lock() {
match channel.send(ClientIoMessage::SubmitSeal(block_hash, seal)) {
Ok(_) => trace!(target: "poa", "SubmitSeal message sent."),
Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err),
if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() {
c.submit_seal(block_hash, seal);
}
}
}
fn broadcast_message(&self, message: Bytes) {
let channel = self.message_channel.lock().clone();
if let Some(ref channel) = channel {
match channel.send(ClientIoMessage::BroadcastMessage(message)) {
Ok(_) => trace!(target: "poa", "BroadcastMessage message sent."),
Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err),
if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() {
c.broadcast_consensus_message(message);
}
} else {
warn!(target: "poa", "broadcast_message: No IoChannel available.");
}
}
@ -265,23 +265,22 @@ impl Tendermint {
}
fn is_authority(&self, address: &Address) -> bool {
self.our_params.authorities.contains(address)
self.validators.contains(address)
}
fn is_above_threshold(&self, n: usize) -> bool {
n > self.our_params.authority_n * 2/3
n > self.validators.count() * 2/3
}
/// Check if address is a proposer for given round.
fn is_round_proposer(&self, height: Height, round: Round, address: &Address) -> Result<(), EngineError> {
let ref p = self.our_params;
let proposer_nonce = height + round;
trace!(target: "poa", "is_proposer: Proposer nonce: {}", proposer_nonce);
let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed");
if proposer == address {
let proposer = self.validators.get(proposer_nonce);
if proposer == *address {
Ok(())
} else {
Err(EngineError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() }))
Err(EngineError::NotProposer(Mismatch { expected: proposer, found: address.clone() }))
}
}
@ -405,7 +404,7 @@ impl Engine for Tendermint {
header.set_difficulty(parent.difficulty().clone());
header.set_gas_limit({
let gas_limit = parent.gas_limit().clone();
let bound_divisor = self.our_params.gas_limit_bound_divisor;
let bound_divisor = self.gas_limit_bound_divisor;
if gas_limit < gas_floor_target {
min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into())
} else {
@ -472,12 +471,9 @@ impl Engine for Tendermint {
/// Apply the block reward on finalisation of the block.
fn on_close_block(&self, block: &mut ExecutedBlock) {
let reward = self.our_params.block_reward;
let fields = block.fields_mut();
// Bestow block reward
fields.state.add_balance(fields.header.author(), &reward, CleanupMode::NoEmpty);
fields.state.add_balance(fields.header.author(), &self.block_reward, CleanupMode::NoEmpty);
// Commit state so that we can actually figure out the state root.
if let Err(e) = fields.state.commit() {
warn!("Encountered error on state commit: {}", e);
@ -522,7 +518,7 @@ impl Engine for Tendermint {
Some(a) => a,
None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?),
};
if !self.our_params.authorities.contains(&address) {
if !self.validators.contains(&address) {
Err(EngineError::NotAuthorized(address.to_owned()))?
}
@ -555,7 +551,7 @@ impl Engine for Tendermint {
Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?;
}
let gas_limit_divisor = self.our_params.gas_limit_bound_divisor;
let gas_limit_divisor = self.gas_limit_bound_divisor;
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
@ -658,9 +654,9 @@ impl Engine for Tendermint {
self.to_step(next_step);
}
fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) {
trace!(target: "poa", "Register the IoChannel.");
*self.message_channel.lock() = Some(message_channel);
fn register_client(&self, client: Weak<Client>) {
*self.client.write() = Some(client.clone());
self.validators.register_call_contract(client);
}
fn register_account_provider(&self, account_provider: Arc<AccountProvider>) {
@ -671,21 +667,20 @@ impl Engine for Tendermint {
#[cfg(test)]
mod tests {
use util::*;
use io::{IoContext, IoHandler};
use block::*;
use error::{Error, BlockError};
use header::Header;
use io::IoChannel;
use env_info::EnvInfo;
use client::chain_notify::ChainNotify;
use miner::MinerService;
use tests::helpers::*;
use account_provider::AccountProvider;
use service::ClientIoMessage;
use spec::Spec;
use engines::{Engine, EngineError, Seal};
use super::*;
use super::message::*;
/// Accounts inserted with "0" and "1" are authorities. First proposer is "0".
/// Accounts inserted with "0" and "1" are validators. First proposer is "0".
fn setup() -> (Spec, Arc<AccountProvider>) {
let tap = Arc::new(AccountProvider::transient_provider());
let spec = Spec::new_test_tendermint();
@ -693,13 +688,13 @@ mod tests {
(spec, tap)
}
fn propose_default(spec: &Spec, proposer: Address) -> (LockedBlock, Vec<Bytes>) {
fn propose_default(spec: &Spec, proposer: Address) -> (ClosedBlock, Vec<Bytes>) {
let mut db_result = get_temp_state_db();
let db = spec.ensure_db_good(db_result.take(), &Default::default()).unwrap();
let genesis_header = spec.genesis_header();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = b.close_and_lock();
let b = b.close();
if let Seal::Proposal(seal) = spec.engine.generate_seal(b.block()) {
(b, seal)
} else {
@ -707,7 +702,7 @@ mod tests {
}
}
fn vote<F>(engine: &Arc<Engine>, signer: F, height: usize, round: usize, step: Step, block_hash: Option<H256>) -> Bytes where F: FnOnce(H256) -> Result<H520, ::account_provider::Error> {
fn vote<F>(engine: &Engine, signer: F, height: usize, round: usize, step: Step, block_hash: Option<H256>) -> Bytes where F: FnOnce(H256) -> Result<H520, ::account_provider::Error> {
let mi = message_info_rlp(height, round, step, block_hash);
let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi);
engine.handle_message(&m).unwrap();
@ -725,37 +720,26 @@ mod tests {
]
}
fn precommit_signatures(tap: &Arc<AccountProvider>, height: Height, round: Round, bare_hash: Option<H256>, v1: H160, v2: H160) -> Bytes {
let vote_info = message_info_rlp(height, round, Step::Precommit, bare_hash);
::rlp::encode(&vec![
H520::from(tap.sign(v1, None, vote_info.sha3()).unwrap()),
H520::from(tap.sign(v2, None, vote_info.sha3()).unwrap())
]).to_vec()
}
fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address {
let addr = tap.insert_account(acc.sha3(), acc).unwrap();
tap.unlock_account_permanently(addr, acc.into()).unwrap();
addr
}
fn insert_and_register(tap: &Arc<AccountProvider>, engine: &Arc<Engine>, acc: &str) -> Address {
fn insert_and_register(tap: &Arc<AccountProvider>, engine: &Engine, acc: &str) -> Address {
let addr = insert_and_unlock(tap, acc);
engine.set_signer(addr.clone(), acc.into());
addr
}
struct TestIo {
received: RwLock<Vec<ClientIoMessage>>
#[derive(Default)]
struct TestNotify {
messages: RwLock<Vec<Bytes>>,
}
impl TestIo {
fn new() -> Arc<Self> { Arc::new(TestIo { received: RwLock::new(Vec::new()) }) }
}
impl IoHandler<ClientIoMessage> for TestIo {
fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) {
self.received.write().push(net_message.clone());
impl ChainNotify for TestNotify {
fn broadcast(&self, data: Vec<u8>) {
self.messages.write().push(data);
}
}
@ -879,10 +863,10 @@ mod tests {
fn can_generate_seal() {
let (spec, tap) = setup();
let proposer = insert_and_register(&tap, &spec.engine, "1");
let proposer = insert_and_register(&tap, spec.engine.as_ref(), "1");
let (b, seal) = propose_default(&spec, proposer);
assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok());
assert!(b.lock().try_seal(spec.engine.as_ref(), seal).is_ok());
spec.engine.stop();
}
@ -890,10 +874,10 @@ mod tests {
fn can_recognize_proposal() {
let (spec, tap) = setup();
let proposer = insert_and_register(&tap, &spec.engine, "1");
let proposer = insert_and_register(&tap, spec.engine.as_ref(), "1");
let (b, seal) = propose_default(&spec, proposer);
let sealed = b.seal(spec.engine.as_ref(), seal).unwrap();
let sealed = b.lock().seal(spec.engine.as_ref(), seal).unwrap();
assert!(spec.engine.is_proposal(sealed.header()));
spec.engine.stop();
}
@ -903,8 +887,8 @@ mod tests {
let (spec, tap) = setup();
let engine = spec.engine.clone();
let v0 = insert_and_register(&tap, &engine, "0");
let v1 = insert_and_register(&tap, &engine, "1");
let v0 = insert_and_register(&tap, engine.as_ref(), "0");
let v1 = insert_and_register(&tap, engine.as_ref(), "1");
let h = 0;
let r = 0;
@ -913,57 +897,74 @@ mod tests {
let (b, _) = propose_default(&spec, v1.clone());
let proposal = Some(b.header().bare_hash());
// Register IoHandler remembers messages.
let test_io = TestIo::new();
let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>)));
engine.register_message_channel(channel);
let client = generate_dummy_client(0);
let notify = Arc::new(TestNotify::default());
client.add_notify(notify.clone());
engine.register_client(Arc::downgrade(&client));
let prevote_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
let prevote_current = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
let precommit_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
let precommit_current = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
let prevote_future = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal);
let prevote_future = vote(engine.as_ref(), |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal);
// Relays all valid present and future messages.
assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_current)));
assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(precommit_current)));
assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_future)));
assert!(notify.messages.read().contains(&prevote_current));
assert!(notify.messages.read().contains(&precommit_current));
assert!(notify.messages.read().contains(&prevote_future));
engine.stop();
}
#[test]
fn seal_submission() {
let (spec, tap) = setup();
let engine = spec.engine.clone();
let v0 = insert_and_register(&tap, &engine, "0");
let v1 = insert_and_register(&tap, &engine, "1");
use ethkey::{Generator, Random};
use types::transaction::{Transaction, Action};
use client::BlockChainClient;
let client = generate_dummy_client_with_spec_and_data(Spec::new_test_tendermint, 0, 0, &[]);
let engine = client.engine();
let tap = Arc::new(AccountProvider::transient_provider());
// Accounts for signing votes.
let v0 = insert_and_unlock(&tap, "0");
let v1 = insert_and_unlock(&tap, "1");
let notify = Arc::new(TestNotify::default());
engine.register_account_provider(tap.clone());
client.add_notify(notify.clone());
engine.register_client(Arc::downgrade(&client));
let keypair = Random.generate().unwrap();
let transaction = Transaction {
action: Action::Create,
value: U256::zero(),
data: "3331600055".from_hex().unwrap(),
gas: U256::from(100_000),
gas_price: U256::zero(),
nonce: U256::zero(),
}.sign(keypair.secret(), None);
client.miner().import_own_transaction(client.as_ref(), transaction.into()).unwrap();
client.miner().set_engine_signer(v1.clone(), "1".into()).unwrap();
// Propose
let proposal = Some(client.miner().pending_block().unwrap().header.bare_hash());
// Propose timeout
engine.step();
let h = 1;
let r = 0;
// Register IoHandler remembers messages.
let test_io = TestIo::new();
let channel = IoChannel::to_handler(Arc::downgrade(&(test_io.clone() as Arc<IoHandler<ClientIoMessage>>)));
engine.register_message_channel(channel);
// Propose
let (b, mut seal) = propose_default(&spec, v1.clone());
let proposal = Some(b.header().bare_hash());
engine.step();
// Prevote.
vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
vote(engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal);
vote(engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0);
let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone()));
seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1);
let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal));
assert_eq!(client.chain_info().best_block_number, 0);
// Last precommit.
vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal);
assert_eq!(client.chain_info().best_block_number, 1);
assert!(first ^ second);
engine.stop();
}
}

View File

@ -18,38 +18,22 @@
use ethjson;
use super::transition::TendermintTimeouts;
use util::{Address, Uint, U256};
use util::{U256, Uint};
use time::Duration;
/// `Tendermint` params.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct TendermintParams {
/// Gas limit divisor.
pub gas_limit_bound_divisor: U256,
/// List of authorities.
pub authorities: Vec<Address>,
/// Number of authorities.
pub authority_n: usize,
/// List of validators.
pub validators: ethjson::spec::ValidatorSet,
/// Timeout durations for different steps.
pub timeouts: TendermintTimeouts,
/// Block reward.
pub block_reward: U256,
}
impl Default for TendermintParams {
fn default() -> Self {
let authorities = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()];
let val_n = authorities.len();
TendermintParams {
gas_limit_bound_divisor: 0x0400.into(),
authorities: authorities,
authority_n: val_n,
block_reward: U256::zero(),
timeouts: TendermintTimeouts::default(),
}
}
}
fn to_duration(ms: ethjson::uint::Uint) -> Duration {
let ms: usize = ms.into();
Duration::milliseconds(ms as i64)
@ -57,13 +41,10 @@ fn to_duration(ms: ethjson::uint::Uint) -> Duration {
impl From<ethjson::spec::TendermintParams> for TendermintParams {
fn from(p: ethjson::spec::TendermintParams) -> Self {
let val: Vec<_> = p.authorities.into_iter().map(Into::into).collect();
let val_n = val.len();
let dt = TendermintTimeouts::default();
TendermintParams {
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
authorities: val,
authority_n: val_n,
validators: p.validators,
timeouts: TendermintTimeouts {
propose: p.timeout_propose.map_or(dt.propose, to_duration),
prevote: p.timeout_prevote.map_or(dt.prevote, to_duration),

View File

@ -23,7 +23,17 @@ use super::{Tendermint, Step};
use engines::Engine;
pub struct TransitionHandler {
pub engine: Weak<Tendermint>,
engine: Weak<Tendermint>,
timeouts: TendermintTimeouts,
}
impl TransitionHandler {
pub fn new(engine: Weak<Tendermint>, timeouts: TendermintTimeouts) -> Self {
TransitionHandler {
engine: engine,
timeouts: timeouts,
}
}
}
/// Base timeout of each step in ms.
@ -67,9 +77,7 @@ fn set_timeout(io: &IoContext<Step>, timeout: Duration) {
impl IoHandler<Step> for TransitionHandler {
fn initialize(&self, io: &IoContext<Step>) {
if let Some(engine) = self.engine.upgrade() {
set_timeout(io, engine.our_params.timeouts.propose)
}
set_timeout(io, self.timeouts.propose)
}
fn timeout(&self, _io: &IoContext<Step>, timer: TimerToken) {
@ -81,16 +89,14 @@ impl IoHandler<Step> for TransitionHandler {
}
fn message(&self, io: &IoContext<Step>, next_step: &Step) {
if let Some(engine) = self.engine.upgrade() {
if let Err(io_err) = io.clear_timer(ENGINE_TIMEOUT_TOKEN) {
warn!(target: "poa", "Could not remove consensus timer {}.", io_err)
}
match *next_step {
Step::Propose => set_timeout(io, engine.our_params.timeouts.propose),
Step::Prevote => set_timeout(io, engine.our_params.timeouts.prevote),
Step::Precommit => set_timeout(io, engine.our_params.timeouts.precommit),
Step::Commit => set_timeout(io, engine.our_params.timeouts.commit),
};
if let Err(io_err) = io.clear_timer(ENGINE_TIMEOUT_TOKEN) {
warn!(target: "poa", "Could not remove consensus timer {}.", io_err)
}
match *next_step {
Step::Propose => set_timeout(io, self.timeouts.propose),
Step::Prevote => set_timeout(io, self.timeouts.prevote),
Step::Precommit => set_timeout(io, self.timeouts.precommit),
Step::Commit => set_timeout(io, self.timeouts.commit),
};
}
}

View File

@ -0,0 +1,218 @@
// Copyright 2015, 2016 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 maintained in a contract.
use std::sync::Weak;
use util::*;
use client::{Client, BlockChainClient};
use client::chain_notify::ChainNotify;
use super::ValidatorSet;
use super::simple_list::SimpleList;
/// The validator contract should have the following interface:
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
pub struct ValidatorContract {
address: Address,
validators: RwLock<SimpleList>,
provider: RwLock<Option<provider::Contract>>,
}
impl ValidatorContract {
pub fn new(contract_address: Address) -> Self {
ValidatorContract {
address: contract_address,
validators: Default::default(),
provider: RwLock::new(None),
}
}
/// Queries the state and updates the set of validators.
pub fn update(&self) {
if let Some(ref provider) = *self.provider.read() {
match provider.get_validators() {
Ok(new) => {
debug!(target: "engine", "Set of validators obtained: {:?}", new);
*self.validators.write() = SimpleList::new(new);
},
Err(s) => warn!(target: "engine", "Set of validators could not be updated: {}", s),
}
} else {
warn!(target: "engine", "Set of validators could not be updated: no provider contract.")
}
}
}
/// Checks validators on every block.
impl ChainNotify for ValidatorContract {
fn new_blocks(
&self,
_imported: Vec<H256>,
_: Vec<H256>,
_: Vec<H256>,
_: Vec<H256>,
_: Vec<H256>,
_: Vec<Bytes>,
_duration: u64) {
self.update();
}
}
impl ValidatorSet for Arc<ValidatorContract> {
fn contains(&self, address: &Address) -> bool {
self.validators.read().contains(address)
}
fn get(&self, nonce: usize) -> Address {
self.validators.read().get(nonce).clone()
}
fn count(&self) -> usize {
self.validators.read().count()
}
fn register_call_contract(&self, client: Weak<Client>) {
if let Some(c) = client.upgrade() {
c.add_notify(self.clone());
}
{
*self.provider.write() = Some(provider::Contract::new(self.address, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))));
}
self.update();
}
}
mod provider {
// Autogenerated from JSON contract definition using Rust contract convertor.
#![allow(unused_imports)]
use std::string::String;
use std::result::Result;
use std::fmt;
use {util, ethabi};
use util::{FixedHash, Uint};
pub struct Contract {
contract: ethabi::Contract,
address: util::Address,
do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static>,
}
impl Contract {
pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static {
Contract {
contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")),
address: address,
do_call: Box::new(do_call),
}
}
fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
/// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
#[allow(dead_code)]
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> {
let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?;
let data = call.encode_call(
vec![]
).map_err(Self::as_string)?;
let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
let mut result = output.into_iter().rev().collect::<Vec<_>>();
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() }))
}
}
}
#[cfg(test)]
mod tests {
use util::*;
use spec::Spec;
use account_provider::AccountProvider;
use transaction::{Transaction, Action};
use client::{BlockChainClient, EngineClient};
use miner::MinerService;
use tests::helpers::generate_dummy_client_with_spec_and_data;
use super::super::ValidatorSet;
use super::ValidatorContract;
#[test]
fn fetches_validators() {
let client = generate_dummy_client_with_spec_and_data(Spec::new_validator_contract, 0, 0, &[]);
let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap()));
vc.register_call_contract(Arc::downgrade(&client));
vc.update();
assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
}
#[test]
fn changes_validators() {
let tap = Arc::new(AccountProvider::transient_provider());
let v0 = tap.insert_account("1".sha3(), "").unwrap();
let v1 = tap.insert_account("0".sha3(), "").unwrap();
let spec_factory = || {
let spec = Spec::new_validator_contract();
spec.engine.register_account_provider(tap.clone());
spec
};
let client = generate_dummy_client_with_spec_and_data(spec_factory, 0, 0, &[]);
client.engine().register_client(Arc::downgrade(&client));
let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap();
client.miner().set_engine_signer(v1, "".into()).unwrap();
// Remove "1" validator.
let tx = Transaction {
nonce: 0.into(),
gas_price: 0.into(),
gas: 500_000.into(),
action: Action::Call(validator_contract),
value: 0.into(),
data: "f94e18670000000000000000000000000000000000000000000000000000000000000001".from_hex().unwrap(),
}.sign(&"1".sha3(), None);
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
client.update_sealing();
assert_eq!(client.chain_info().best_block_number, 1);
// Add "1" validator back in.
let tx = Transaction {
nonce: 1.into(),
gas_price: 0.into(),
gas: 500_000.into(),
action: Action::Call(validator_contract),
value: 0.into(),
data: "4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".from_hex().unwrap(),
}.sign(&"1".sha3(), None);
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
client.update_sealing();
// The transaction is not yet included so still unable to seal.
assert_eq!(client.chain_info().best_block_number, 1);
// Switch to the validator that is still there.
client.miner().set_engine_signer(v0, "".into()).unwrap();
client.update_sealing();
assert_eq!(client.chain_info().best_block_number, 2);
// Switch back to the added validator, since the state is updated.
client.miner().set_engine_signer(v1, "".into()).unwrap();
let tx = Transaction {
nonce: 2.into(),
gas_price: 0.into(),
gas: 21000.into(),
action: Action::Call(Address::default()),
value: 0.into(),
data: Vec::new(),
}.sign(&"1".sha3(), None);
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
client.update_sealing();
// Able to seal again.
assert_eq!(client.chain_info().best_block_number, 3);
}
}

View File

@ -0,0 +1,46 @@
// Copyright 2015, 2016 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 lists.
mod simple_list;
mod contract;
use std::sync::Weak;
use util::{Address, Arc};
use ethjson::spec::ValidatorSet as ValidatorSpec;
use client::Client;
use self::simple_list::SimpleList;
use self::contract::ValidatorContract;
/// Creates a validator set from spec.
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> {
match spec {
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))),
}
}
pub trait ValidatorSet {
/// Checks if a given address is a validator.
fn contains(&self, address: &Address) -> bool;
/// Draws an validator nonce modulo number of validators.
fn get(&self, nonce: usize) -> Address;
/// Returns the current number of validators.
fn count(&self) -> usize;
/// Allows blockchain state access.
fn register_call_contract(&self, _client: Weak<Client>) {}
}

View File

@ -0,0 +1,68 @@
// Copyright 2015, 2016 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/>.
/// Preconfigured validator list.
use util::Address;
use super::ValidatorSet;
#[derive(Debug, PartialEq, Eq, Default)]
pub struct SimpleList {
validators: Vec<Address>,
validator_n: usize,
}
impl SimpleList {
pub fn new(validators: Vec<Address>) -> Self {
SimpleList {
validator_n: validators.len(),
validators: validators,
}
}
}
impl ValidatorSet for SimpleList {
fn contains(&self, address: &Address) -> bool {
self.validators.contains(address)
}
fn get(&self, nonce: usize) -> Address {
self.validators.get(nonce % self.validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone()
}
fn count(&self) -> usize {
self.validator_n
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use util::Address;
use super::super::ValidatorSet;
use super::SimpleList;
#[test]
fn simple_list() {
let a1 = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
let a2 = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
let list = SimpleList::new(vec![a1.clone(), a2.clone()]);
assert!(list.contains(&a1));
assert_eq!(list.get(0), a1);
assert_eq!(list.get(1), a2);
assert_eq!(list.get(2), a1);
}
}

View File

@ -764,9 +764,17 @@ impl MinerService for Miner {
if let Some(ref ap) = self.accounts {
ap.sign(address.clone(), Some(password.clone()), Default::default())?;
}
let mut sealing_work = self.sealing_work.lock();
sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false);
*self.author.write() = address;
// Limit the scope of the locks.
{
let mut sealing_work = self.sealing_work.lock();
sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false);
*self.author.write() = address;
}
// --------------------------------------------------------------------------
// | NOTE Code below may require author and sealing_work locks |
// | (some `Engine`s call `EngineClient.update_sealing()`) |.
// | Make sure to release the locks before calling that method. |
// --------------------------------------------------------------------------
self.engine.set_signer(address, password);
}
Ok(())

View File

@ -20,7 +20,7 @@ use util::*;
use io::*;
use spec::Spec;
use error::*;
use client::{Client, BlockChainClient, MiningBlockChainClient, ClientConfig, ChainNotify};
use client::{Client, ClientConfig, ChainNotify};
use miner::Miner;
use snapshot::ManifestData;
use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams};
@ -46,12 +46,6 @@ pub enum ClientIoMessage {
FeedBlockChunk(H256, Bytes),
/// Take a snapshot for the block with given number.
TakeSnapshot(u64),
/// Trigger sealing update (useful for internal sealing).
UpdateSealing,
/// Submit seal (useful for internal sealing).
SubmitSeal(H256, Vec<Bytes>),
/// Broadcast a message to the network.
BroadcastMessage(Bytes),
/// New consensus message received.
NewMessage(Bytes)
}
@ -114,7 +108,7 @@ impl ClientService {
});
io_service.register_handler(client_io)?;
spec.engine.register_message_channel(io_service.channel());
spec.engine.register_client(Arc::downgrade(&client));
let stop_guard = ::devtools::StopGuard::new();
run_ipc(ipc_path, client.clone(), snapshot.clone(), stop_guard.share());
@ -221,9 +215,6 @@ impl IoHandler<ClientIoMessage> for ClientIoHandler {
debug!(target: "snapshot", "Failed to initialize periodic snapshot thread: {:?}", e);
}
},
ClientIoMessage::UpdateSealing => self.client.update_sealing(),
ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()),
ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_consensus_message(message.clone()),
ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) {
trace!(target: "poa", "Invalid message received: {}", e);
},

View File

@ -337,9 +337,14 @@ impl Spec {
pub fn new_instant() -> Spec { load_bundled!("instant_seal") }
/// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work).
/// Accounts with secrets "0".sha3() and "1".sha3() are the authorities.
/// Accounts with secrets "0".sha3() and "1".sha3() are the validators.
pub fn new_test_round() -> Self { load_bundled!("authority_round") }
/// Create a new Spec with BasicAuthority which uses a contract at address 5 to determine the current validators.
/// Accounts with secrets "0".sha3() and "1".sha3() are initially the validators.
/// Second validator can be removed with "0xf94e18670000000000000000000000000000000000000000000000000000000000000001" and added back in using "0x4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".
pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") }
/// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work).
/// Account "0".sha3() and "1".sha3() are a authorities.
pub fn new_test_tendermint() -> Self { load_bundled!("tendermint") }

View File

@ -17,7 +17,7 @@
//! Authority params deserialization.
use uint::Uint;
use hash::Address;
use super::ValidatorSet;
/// Authority params deserialization.
#[derive(Debug, PartialEq, Deserialize)]
@ -29,7 +29,7 @@ pub struct AuthorityRoundParams {
#[serde(rename="stepDuration")]
pub step_duration: Uint,
/// Valid authorities
pub authorities: Vec<Address>,
pub validators: ValidatorSet,
/// Block reward.
#[serde(rename="blockReward")]
pub block_reward: Option<Uint>,
@ -57,7 +57,9 @@ mod tests {
"params": {
"gasLimitBoundDivisor": "0x0400",
"stepDuration": "0x02",
"authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"],
"validators": {
"list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"]
},
"blockReward": "0x50",
"startStep" : 24
}

View File

@ -17,7 +17,7 @@
//! Authority params deserialization.
use uint::Uint;
use hash::Address;
use super::ValidatorSet;
/// Authority params deserialization.
#[derive(Debug, PartialEq, Deserialize)]
@ -29,7 +29,7 @@ pub struct BasicAuthorityParams {
#[serde(rename="durationLimit")]
pub duration_limit: Uint,
/// Valid authorities
pub authorities: Vec<Address>,
pub validators: ValidatorSet,
}
/// Authority engine deserialization.
@ -50,7 +50,9 @@ mod tests {
"params": {
"gasLimitBoundDivisor": "0x0400",
"durationLimit": "0x0d",
"authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"]
"validators" : {
"list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"]
}
}
}"#;

View File

@ -16,10 +16,7 @@
//! Engine deserialization.
use spec::Ethash;
use spec::BasicAuthority;
use spec::AuthorityRound;
use spec::Tendermint;
use super::{Ethash, BasicAuthority, AuthorityRound, Tendermint};
/// Engine deserialization.
#[derive(Debug, PartialEq, Deserialize)]

View File

@ -25,6 +25,7 @@ pub mod seal;
pub mod engine;
pub mod state;
pub mod ethash;
pub mod validator_set;
pub mod basic_authority;
pub mod authority_round;
pub mod tendermint;
@ -38,6 +39,7 @@ pub use self::seal::{Seal, Ethereum, AuthorityRoundSeal, TendermintSeal};
pub use self::engine::Engine;
pub use self::state::State;
pub use self::ethash::{Ethash, EthashParams};
pub use self::validator_set::ValidatorSet;
pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams};
pub use self::authority_round::{AuthorityRound, AuthorityRoundParams};
pub use self::tendermint::{Tendermint, TendermintParams};

View File

@ -17,7 +17,7 @@
//! Tendermint params deserialization.
use uint::Uint;
use hash::Address;
use super::ValidatorSet;
/// Tendermint params deserialization.
#[derive(Debug, PartialEq, Deserialize)]
@ -25,8 +25,8 @@ pub struct TendermintParams {
/// Gas limit divisor.
#[serde(rename="gasLimitBoundDivisor")]
pub gas_limit_bound_divisor: Uint,
/// Valid authorities
pub authorities: Vec<Address>,
/// Valid validators.
pub validators: ValidatorSet,
/// Propose step timeout in milliseconds.
#[serde(rename="timeoutPropose")]
pub timeout_propose: Option<Uint>,
@ -60,8 +60,10 @@ mod tests {
fn tendermint_deserialization() {
let s = r#"{
"params": {
"gasLimitBoundDivisor": "0x400",
"authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"],
"gasLimitBoundDivisor": "0x0400",
"validators": {
"list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"]
},
"blockReward": "0x50"
}
}"#;

View File

@ -0,0 +1,47 @@
// Copyright 2015, 2016 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 deserialization.
use hash::Address;
/// Different ways of specifying validators.
#[derive(Debug, PartialEq, Deserialize)]
pub enum ValidatorSet {
/// A simple list of authorities.
#[serde(rename="list")]
List(Vec<Address>),
/// Address of a contract that indicates the list of authorities.
#[serde(rename="contract")]
Contract(Address),
}
#[cfg(test)]
mod tests {
use serde_json;
use spec::validator_set::ValidatorSet;
#[test]
fn validator_set_deserialization() {
let s = r#"[{
"list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"]
}, {
"contract" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
}]"#;
let _deserialized: Vec<ValidatorSet> = serde_json::from_str(s).unwrap();
}
}

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@
use util::*;
use io::{IoHandler, IoContext, IoChannel};
use ethcore::client::{BlockChainClient, Client, MiningBlockChainClient};
use ethcore::client::{BlockChainClient, Client};
use ethcore::service::ClientIoMessage;
use ethcore::spec::Spec;
use ethcore::miner::MinerService;
@ -33,9 +33,6 @@ struct TestIoHandler {
impl IoHandler<ClientIoMessage> for TestIoHandler {
fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) {
match *net_message {
ClientIoMessage::UpdateSealing => self.client.update_sealing(),
ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()),
ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_consensus_message(message.clone()),
ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) {
panic!("Invalid message received: {}", e);
},
@ -75,8 +72,8 @@ fn authority_round() {
// Push transaction to both clients. Only one of them gets lucky to produce a block.
net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap();
net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap();
net.peer(0).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0)));
net.peer(1).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1)));
net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain));
net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain));
net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1)));
net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0)));
// exchange statuses
@ -140,8 +137,8 @@ fn tendermint() {
trace!(target: "poa", "Peer 0 is {}.", s0.address());
net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap();
trace!(target: "poa", "Peer 1 is {}.", s1.address());
net.peer(0).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0)));
net.peer(1).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1)));
net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain));
net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain));
net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0)));
net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1)));
// Exhange statuses

View File

@ -391,7 +391,7 @@ impl<Message> IoChannel<Message> where Message: Send + Clone + Sync + 'static {
}
Ok(())
}
/// Create a new channel to connected to event loop.
/// Create a new channel disconnected from an event loop.
pub fn disconnected() -> IoChannel<Message> {
IoChannel {
channel: None,