diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index 2dd38c755..ac7eb5041 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -6,10 +6,12 @@ "gasLimitBoundDivisor": "0x0400", "stepDuration": 1, "startStep": 2, - "authorities" : [ - "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", - "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" - ] + "validators": { + "list": [ + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + ] + } } } }, diff --git a/ethcore/res/basic_authority.json b/ethcore/res/basic_authority.json index 623590bfa..6b9f4c0ed 100644 --- a/ethcore/res/basic_authority.json +++ b/ethcore/res/basic_authority.json @@ -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", diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index da62448e5..a1262fa33 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -4,10 +4,12 @@ "tendermint": { "params": { "gasLimitBoundDivisor": "0x0400", - "authorities" : [ - "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", - "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" - ] + "validators" : { + "list": [ + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" + ] + } } } }, diff --git a/ethcore/res/validator_contract.json b/ethcore/res/validator_contract.json new file mode 100644 index 000000000..4bf120b54 --- /dev/null +++ b/ethcore/res/validator_contract.json @@ -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" } + } +} diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index f57b1248a..0a5bad75a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -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 { 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) { - 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) { + 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(&self, closure: F) where F: OnPanicListener { self.panic_handler.on_panic(closure); diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index d8e5f19e5..8af20f516 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -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; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index da7425a3d..551b52437 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -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) { - 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 { 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
{ None } } + +impl EngineClient for TestBlockChainClient { + fn update_sealing(&self) { + self.miner.update_sealing(self) + } + + fn submit_seal(&self, block_hash: H256, seal: Vec) { + 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) {} +} diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 7e39d5002..571515198 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -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; @@ -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); - /// 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); + + /// 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. diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index dca84e3a2..0986a5aa7 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -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
, - /// Number of authorities. - pub authority_n: usize, /// Block reward. pub block_reward: U256, /// Starting step, pub start_step: Option, + /// Valid validators. + pub validators: ethjson::spec::ValidatorSet, } impl From for AuthorityRoundParams { @@ -61,8 +60,7 @@ impl From 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::>(), + 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 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, transition_service: IoService<()>, - message_channel: Mutex>>, step: AtomicUsize, proposed: AtomicBool, - account_provider: Mutex>>, + client: RwLock>>, + account_provider: Mutex>, password: RwLock>, + validators: Box, } fn header_step(header: &Header) -> Result { @@ -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 { - 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) { - *self.message_channel.lock() = Some(message_channel); + fn register_client(&self, client: Weak) { + *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) { - *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()); diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 8112a574e..61e25e58f 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -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
, + pub validators: ethjson::spec::ValidatorSet, } impl From 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::>(), + validators: p.validators, } } } @@ -56,10 +55,11 @@ impl From 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, account_provider: Mutex>>, password: RwLock>, + validators: Box, } impl BasicAuthority { @@ -67,8 +67,9 @@ impl BasicAuthority { pub fn new(params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap) -> 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 { - 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::()?; 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) { + self.validators.register_call_contract(client); + } + fn set_signer(&self, _address: Address, password: String) { *self.password.write() = Some(password); } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index a3e57dd65..b82fce1b8 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -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) {} + /// Add Client which can be used for sealing, querying the state and sending messages. + fn register_client(&self, _client: Weak) {} /// Add an account provider useful for Engines that sign stuff. fn register_account_provider(&self, _account_provider: Arc) {} + /// 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) {} } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index becc9d9c0..5fe9a0248 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -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, step_service: IoService, + client: RwLock>>, + block_reward: U256, /// Address to be used as authority. authority: RwLock
, /// Password used for signing messages. @@ -90,8 +94,6 @@ pub struct Tendermint { step: RwLock, /// Vote accumulator. votes: VoteCollector, - /// Channel for updating the sealing. - message_channel: Mutex>>, /// Used to sign messages and proposals. account_provider: Mutex>>, /// 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>, + /// Set used to determine the current validators. + validators: Box, } 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::::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) { - 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) { - trace!(target: "poa", "Register the IoChannel."); - *self.message_channel.lock() = Some(message_channel); + fn register_client(&self, client: Weak) { + *self.client.write() = Some(client.clone()); + self.validators.register_call_contract(client); } fn register_account_provider(&self, account_provider: Arc) { @@ -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) { 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) { + fn propose_default(spec: &Spec, proposer: Address) -> (ClosedBlock, Vec) { 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(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) -> Bytes where F: FnOnce(H256) -> Result { + fn vote(engine: &Engine, signer: F, height: usize, round: usize, step: Step, block_hash: Option) -> Bytes where F: FnOnce(H256) -> Result { 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, height: Height, round: Round, bare_hash: Option, 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, 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, engine: &Arc, acc: &str) -> Address { + fn insert_and_register(tap: &Arc, engine: &Engine, acc: &str) -> Address { let addr = insert_and_unlock(tap, acc); engine.set_signer(addr.clone(), acc.into()); addr } - struct TestIo { - received: RwLock> + #[derive(Default)] + struct TestNotify { + messages: RwLock>, } - impl TestIo { - fn new() -> Arc { Arc::new(TestIo { received: RwLock::new(Vec::new()) }) } - } - - impl IoHandler for TestIo { - fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { - self.received.write().push(net_message.clone()); + impl ChainNotify for TestNotify { + fn broadcast(&self, data: Vec) { + 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>))); - 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>))); - 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(); } } diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index e31182ae9..aad3a4b7a 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -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
, - /// 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 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), diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 83b390d74..62ae7b03f 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -23,7 +23,17 @@ use super::{Tendermint, Step}; use engines::Engine; pub struct TransitionHandler { - pub engine: Weak, + engine: Weak, + timeouts: TendermintTimeouts, +} + +impl TransitionHandler { + pub fn new(engine: Weak, timeouts: TendermintTimeouts) -> Self { + TransitionHandler { + engine: engine, + timeouts: timeouts, + } + } } /// Base timeout of each step in ms. @@ -67,9 +77,7 @@ fn set_timeout(io: &IoContext, timeout: Duration) { impl IoHandler for TransitionHandler { fn initialize(&self, io: &IoContext) { - 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, timer: TimerToken) { @@ -81,16 +89,14 @@ impl IoHandler for TransitionHandler { } fn message(&self, io: &IoContext, 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), + }; } } diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs new file mode 100644 index 000000000..b8b63112c --- /dev/null +++ b/ethcore/src/engines/validator_set/contract.rs @@ -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 . + +/// 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, + provider: RwLock>, +} + +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, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _duration: u64) { + self.update(); + } +} + +impl ValidatorSet for Arc { + 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) { + 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) -> Result, String> + Send + Sync + 'static>, + } + impl Contract { + pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, 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(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, 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::>(); + 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::>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::>() })) + } + } +} + +#[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); + } +} diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs new file mode 100644 index 000000000..566dccabb --- /dev/null +++ b/ethcore/src/engines/validator_set/mod.rs @@ -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 . + +/// 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 { + 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) {} +} diff --git a/ethcore/src/engines/validator_set/simple_list.rs b/ethcore/src/engines/validator_set/simple_list.rs new file mode 100644 index 000000000..9244ce477 --- /dev/null +++ b/ethcore/src/engines/validator_set/simple_list.rs @@ -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 . + +/// Preconfigured validator list. + +use util::Address; +use super::ValidatorSet; + +#[derive(Debug, PartialEq, Eq, Default)] +pub struct SimpleList { + validators: Vec
, + validator_n: usize, +} + +impl SimpleList { + pub fn new(validators: Vec
) -> 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); + } +} diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 3ffb790b1..7a848b21d 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -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(()) diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index b0f1df420..d55a228e4 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -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), - /// 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 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); }, diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index f9a4b6f6a..2f957098d 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -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") } diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index b302cc692..330a9b665 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -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
, + pub validators: ValidatorSet, /// Block reward. #[serde(rename="blockReward")] pub block_reward: Option, @@ -57,7 +57,9 @@ mod tests { "params": { "gasLimitBoundDivisor": "0x0400", "stepDuration": "0x02", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"], + "validators": { + "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, "blockReward": "0x50", "startStep" : 24 } diff --git a/json/src/spec/basic_authority.rs b/json/src/spec/basic_authority.rs index 0e388f1eb..b6ff3f47f 100644 --- a/json/src/spec/basic_authority.rs +++ b/json/src/spec/basic_authority.rs @@ -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
, + pub validators: ValidatorSet, } /// Authority engine deserialization. @@ -50,7 +50,9 @@ mod tests { "params": { "gasLimitBoundDivisor": "0x0400", "durationLimit": "0x0d", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "validators" : { + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } } }"#; diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index b0c34b2ea..d437b06ba 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -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)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index d923be069..2f4b06f31 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -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}; diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index b821b945c..ede6f52e5 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -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
, + /// Valid validators. + pub validators: ValidatorSet, /// Propose step timeout in milliseconds. #[serde(rename="timeoutPropose")] pub timeout_propose: Option, @@ -60,8 +60,10 @@ mod tests { fn tendermint_deserialization() { let s = r#"{ "params": { - "gasLimitBoundDivisor": "0x400", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"], + "gasLimitBoundDivisor": "0x0400", + "validators": { + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, "blockReward": "0x50" } }"#; diff --git a/json/src/spec/validator_set.rs b/json/src/spec/validator_set.rs new file mode 100644 index 000000000..47acd7c36 --- /dev/null +++ b/json/src/spec/validator_set.rs @@ -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 . + +//! 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 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 = serde_json::from_str(s).unwrap(); + } +} diff --git a/scripts/contractABI.js b/scripts/contractABI.js index e6c8e923a..9a8225ccd 100644 --- a/scripts/contractABI.js +++ b/scripts/contractABI.js @@ -24,7 +24,7 @@ String.prototype.toSnake = function(){ function makeContractFile(name, json, prefs) { return `// Autogenerated from JSON contract definition using Rust contract convertor. - +#![allow(unused_imports)] use std::string::String; use std::result::Result; use std::fmt; @@ -39,10 +39,10 @@ function convertContract(name, json, prefs) { return `${prefs._pub ? "pub " : ""}struct ${name} { contract: ethabi::Contract, address: util::Address, - do_call: Box) -> Result, String> + Send + 'static>, + do_call: Box) -> Result, String> + Send ${prefs._sync ? "+ Sync " : ""}+ 'static>, } impl ${name} { - pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + 'static { + pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send ${prefs._sync ? "+ Sync " : ""}+ 'static { ${name} { contract: ethabi::Contract::new(ethabi::Interface::load(b"${JSON.stringify(json.filter(a => a.type == 'function')).replaceAll('"', '\\"')}").expect("JSON is autogenerated; qed")), address: address, @@ -112,8 +112,10 @@ function mapReturnType(name, type, _prefs) { return "String"; if (type == "bytes") return "Vec"; + if (type == "address[]") + return "Vec"; - console.log(`Unsupported argument type: ${type} (${name})`); + console.log(`Unsupported return type: ${type} (${name})`); } function convertToken(name, type, _prefs) { @@ -163,8 +165,10 @@ function tokenType(name, type, _prefs) { return `${name}.to_string()`; if (type == "bytes") return `${name}.to_bytes()`; + if (type == "address[]") + return `${name}.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::>>())`; - // ERROR - unsupported + console.log(`Unsupported return type: ${type} (${name})`); } function tokenCoerce(name, type, _prefs) { @@ -190,6 +194,8 @@ function tokenCoerce(name, type, _prefs) { return `${name}`; if (type == "bytes") return `${name}`; + if (type == "address[]") + return `${name}.into_iter().map(|a| util::Address::from(a)).collect::>()`; console.log(`Unsupported return type: ${type} (${name})`); } @@ -217,6 +223,7 @@ function convertFunction(json, _prefs) { }`; } -jsonabi = [{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"resetClientOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"isLatest","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"rejectTransaction","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_number","type":"uint32"},{"name":"_name","type":"bytes32"},{"name":"_hard","type":"bool"},{"name":"_spec","type":"bytes32"}],"name":"proposeFork","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"}],"name":"removeClient","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"release","outputs":[{"name":"o_forkBlock","type":"uint32"},{"name":"o_track","type":"uint8"},{"name":"o_semver","type":"uint24"},{"name":"o_critical","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"build","outputs":[{"name":"o_release","type":"bytes32"},{"name":"o_platform","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"rejectFork","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"client","outputs":[{"name":"owner","type":"address"},{"name":"required","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"setClientOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint32"}],"name":"fork","outputs":[{"name":"name","type":"bytes32"},{"name":"spec","type":"bytes32"},{"name":"hard","type":"bool"},{"name":"ratified","type":"bool"},{"name":"requiredCount","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"},{"name":"_checksum","type":"bytes32"}],"name":"addChecksum","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"}],"name":"confirmTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"proxy","outputs":[{"name":"requiredCount","type":"uint256"},{"name":"to","type":"address"},{"name":"data","type":"bytes"},{"name":"value","type":"uint256"},{"name":"gas","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"addClient","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"clientOwner","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_txid","type":"bytes32"},{"name":"_to","type":"address"},{"name":"_data","type":"bytes"},{"name":"_value","type":"uint256"},{"name":"_gas","type":"uint256"}],"name":"proposeTransaction","outputs":[{"name":"txSuccess","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"grandOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_release","type":"bytes32"},{"name":"_forkBlock","type":"uint32"},{"name":"_track","type":"uint8"},{"name":"_semver","type":"uint24"},{"name":"_critical","type":"bool"}],"name":"addRelease","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"acceptFork","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"clientsRequired","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"}],"name":"track","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_r","type":"bool"}],"name":"setClientRequired","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"latestFork","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_track","type":"uint8"}],"name":"latestInTrack","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_client","type":"bytes32"},{"name":"_release","type":"bytes32"},{"name":"_platform","type":"bytes32"}],"name":"checksum","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"proposedFork","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"data","type":"bytes"}],"name":"Received","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"gas","type":"uint256"}],"name":"TransactionProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"txid","type":"bytes32"}],"name":"TransactionRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"txid","type":"bytes32"},{"indexed":false,"name":"success","type":"bool"}],"name":"TransactionRelayed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"},{"indexed":true,"name":"name","type":"bytes32"},{"indexed":false,"name":"spec","type":"bytes32"},{"indexed":false,"name":"hard","type":"bool"}],"name":"ForkProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkAcceptedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"number","type":"uint32"}],"name":"ForkRejectedBy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"forkNumber","type":"uint32"}],"name":"ForkRatified","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"forkBlock","type":"uint32"},{"indexed":false,"name":"release","type":"bytes32"},{"indexed":false,"name":"track","type":"uint8"},{"indexed":false,"name":"semver","type":"uint24"},{"indexed":true,"name":"critical","type":"bool"}],"name":"ReleaseAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"release","type":"bytes32"},{"indexed":true,"name":"platform","type":"bytes32"},{"indexed":false,"name":"checksum","type":"bytes32"}],"name":"ChecksumAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"ClientAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"}],"name":"ClientRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"now","type":"address"}],"name":"ClientOwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"bytes32"},{"indexed":false,"name":"now","type":"bool"}],"name":"ClientRequiredChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"old","type":"address"},{"indexed":false,"name":"now","type":"address"}],"name":"OwnerChanged","type":"event"}]; +let jsonabi = [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]; -makeContractFile("Operations", jsonabi, {"_pub": true, "_": {"_client": {"string": true}, "_platform": {"string": true}}}); +let out = makeContractFile("Contract", jsonabi, {"_pub": true, "_": {"_client": {"string": true}, "_platform": {"string": true}}, "_sync": true}); +console.log(`${out}`); diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 855386870..ea8bd970d 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -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 for TestIoHandler { fn message(&self, _io: &IoContext, 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 diff --git a/util/io/src/service.rs b/util/io/src/service.rs index 6f5c87004..ed63600f2 100644 --- a/util/io/src/service.rs +++ b/util/io/src/service.rs @@ -391,7 +391,7 @@ impl IoChannel 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 { IoChannel { channel: None,