From ba0209678be7eb3f85d98591da2e055ebd1f8770 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 24 Jan 2017 10:03:58 +0100 Subject: [PATCH] ValidatorSet reporting (#4208) * remove register_account_provider * build rpc module * new dummy client * common EngineSigner struct * from -> into * initial report via call * separate safe from regular contract * transact_contract * fix build * return Signature, docs * add sign method to some engines * add safeContract spec * update specs to new contracts * use AuthorityRound for contract spec * add more reporting * add reporting test * use gas floor * shorter --- ethcore/res/validator_contract.json | 10 +- ethcore/res/validator_safe_contract.json | 41 ++++ ethcore/src/client/client.rs | 17 +- ethcore/src/client/test_client.rs | 17 +- ethcore/src/client/traits.rs | 6 +- ethcore/src/engines/authority_round.rs | 16 +- ethcore/src/engines/basic_authority.rs | 8 +- ethcore/src/engines/mod.rs | 4 + ethcore/src/engines/tendermint/mod.rs | 17 +- ethcore/src/engines/tendermint/transition.rs | 8 +- ethcore/src/engines/validator_set/contract.rs | 203 ++++++++-------- ethcore/src/engines/validator_set/mod.rs | 9 +- .../engines/validator_set/safe_contract.rs | 216 ++++++++++++++++++ ethcore/src/error.rs | 10 + ethcore/src/spec/spec.rs | 16 +- json/src/spec/validator_set.rs | 5 + 16 files changed, 466 insertions(+), 137 deletions(-) create mode 100644 ethcore/res/validator_safe_contract.json create mode 100644 ethcore/src/engines/validator_set/safe_contract.rs diff --git a/ethcore/res/validator_contract.json b/ethcore/res/validator_contract.json index 4bf120b54..33fdf4c4f 100644 --- a/ethcore/res/validator_contract.json +++ b/ethcore/res/validator_contract.json @@ -1,10 +1,11 @@ { "name": "TestValidatorContract", "engine": { - "basicAuthority": { + "authorityRound": { "params": { "gasLimitBoundDivisor": "0x0400", - "durationLimit": "0x0d", + "stepDuration": 1, + "startStep": 2, "validators": { "contract": "0x0000000000000000000000000000000000000005" } @@ -35,8 +36,9 @@ "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, "0000000000000000000000000000000000000005": { "balance": "1", - "constructor": "0x60a06040819052737d577a597b2742b498cb5cf0c26cdcd726d39e6e60609081527382a978b3f5962a5b0957d9ee9eef472ee55b42f1608052600080546002825581805290927f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639182019291905b828111156100a25782518254600160a060020a031916600160a060020a0390911617825560209092019160019091019061006d565b5b506100cd9291505b808211156100c9578054600160a060020a03191681556001016100ab565b5090565b505034610000575b610332806100e46000396000f300606060405263ffffffff60e060020a60003504166335aa2e4481146100455780634d238c8e14610071578063b7ab4db51461008c578063f94e1867146100f4575b610000565b3461000057610055600435610106565b60408051600160a060020a039092168252519081900360200190f35b346100005761008a600160a060020a0360043516610136565b005b34610000576100996101ad565b60408051602080825283518183015283519192839290830191858101910280838382156100e1575b8051825260208311156100e157601f1990920191602091820191016100c1565b5050509050019250505060405180910390f35b346100005761008a600435610217565b005b600081815481101561000057906000526020600020900160005b915054906101000a9004600160a060020a031681565b60008054806001018281815481835581811511610178576000838152602090206101789181019083015b808211156101745760008155600101610160565b5090565b5b505050916000526020600020900160005b8154600160a060020a038086166101009390930a92830292021916179055505b50565b604080516020818101835260008083528054845181840281018401909552808552929392909183018282801561020c57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116101ee575b505050505090505b90565b6000805460001981019081101561000057906000526020600020900160005b9054906101000a9004600160a060020a0316600082815481101561000057906000526020600020900160005b6101000a815481600160a060020a030219169083600160a060020a031602179055506000600160008054905003815481101561000057906000526020600020900160005b6101000a815490600160a060020a03021916905560008054809190600190038154818355818115116102fd576000838152602090206102fd9181019083015b808211156101745760008155600101610160565b5090565b5b505050505b505600a165627a7a72305820d742dd391941c1c255f0e1187ffa5b1e783219264fb10196018aefa379f5638b0029" + "constructor": "6060604052604060405190810160405280737d577a597b2742b498cb5cf0c26cdcd726d39e6e73ffffffffffffffffffffffffffffffffffffffff1681526020017382a978b3f5962a5b0957d9ee9eef472ee55b42f173ffffffffffffffffffffffffffffffffffffffff1681525060009060028280548282559060005260206000209081019282156100ec579160200282015b828111156100eb5782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555091602001919060010190610093565b5b50905061012f91905b8082111561012b57600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016100f5565b5090565b505034610000575b6000600090505b6000805490508110156101d5578060016000600084815481101561000057906000526020600020900160005b9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b808060010191505061013e565b5b505b6105f2806101e76000396000f30060606040523615610076576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806335aa2e441461007b5780634d238c8e146100d8578063b7ab4db51461010b578063bfc708a01461017d578063d8f2e0bf146101b0578063fd6e1b50146101ff575b610000565b34610000576100966004808035906020019091905050610232565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610109600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061026f565b005b346100005761011861030f565b604051808060200182810382528381815181526020019150805190602001906020028083836000831461016a575b80518252602083111561016a57602082019150602081019050602083039250610146565b5050509050019250505060405180910390f35b34610000576101ae600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506103ad565b005b34610000576101bd61055b565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610230600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610581565b005b600081815481101561000057906000526020600020900160005b915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600080548060010182818154818355818115116102b8578183600052602060002091820191016102b791905b808211156102b357600081600090555060010161029b565b5090565b5b505050916000526020600020900160005b83909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505b50565b602060405190810160405280600081525060008054806020026020016040519081016040528092919081815260200182805480156103a257602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610358575b505050505090505b90565b6000600160008054905003815481101561000057906000526020600020900160005b9054906101000a900473ffffffffffffffffffffffffffffffffffffffff166000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054815481101561000057906000526020600020900160005b6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600090556000600160008054905003815481101561000057906000526020600020900160005b6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560008054809190600190038154818355818115116105535781836000526020600020918201910161055291905b8082111561054e576000816000905550600101610536565b5090565b5b505050505b50565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b505600a165627a7a7230582063a0123d8e8f5dde980af6b47e20acc5b7a1acac3e3101fa1c933471ef4b405c0029" }, - "9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }, + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } } } diff --git a/ethcore/res/validator_safe_contract.json b/ethcore/res/validator_safe_contract.json new file mode 100644 index 000000000..3f5241c96 --- /dev/null +++ b/ethcore/res/validator_safe_contract.json @@ -0,0 +1,41 @@ +{ + "name": "TestValidatorContract", + "engine": { + "basicAuthority": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators": { + "safeContract": "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": "6060604052604060405190810160405280737d577a597b2742b498cb5cf0c26cdcd726d39e6e73ffffffffffffffffffffffffffffffffffffffff1681526020017382a978b3f5962a5b0957d9ee9eef472ee55b42f173ffffffffffffffffffffffffffffffffffffffff1681525060009060028280548282559060005260206000209081019282156100ec579160200282015b828111156100eb5782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555091602001919060010190610093565b5b50905061012f91905b8082111561012b57600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016100f5565b5090565b505034610000575b6000600090505b6000805490508110156101d5578060016000600084815481101561000057906000526020600020900160005b9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b808060010191505061013e565b5b505b6105f2806101e76000396000f30060606040523615610076576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806335aa2e441461007b5780634d238c8e146100d8578063b7ab4db51461010b578063bfc708a01461017d578063d8f2e0bf146101b0578063fd6e1b50146101ff575b610000565b34610000576100966004808035906020019091905050610232565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610109600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061026f565b005b346100005761011861030f565b604051808060200182810382528381815181526020019150805190602001906020028083836000831461016a575b80518252602083111561016a57602082019150602081019050602083039250610146565b5050509050019250505060405180910390f35b34610000576101ae600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506103ad565b005b34610000576101bd61055b565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610230600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610581565b005b600081815481101561000057906000526020600020900160005b915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600080548060010182818154818355818115116102b8578183600052602060002091820191016102b791905b808211156102b357600081600090555060010161029b565b5090565b5b505050916000526020600020900160005b83909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505b50565b602060405190810160405280600081525060008054806020026020016040519081016040528092919081815260200182805480156103a257602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610358575b505050505090505b90565b6000600160008054905003815481101561000057906000526020600020900160005b9054906101000a900473ffffffffffffffffffffffffffffffffffffffff166000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054815481101561000057906000526020600020900160005b6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600090556000600160008054905003815481101561000057906000526020600020900160005b6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560008054809190600190038154818355818115116105535781836000526020600020918201910161055291905b8082111561054e576000816000905550600101610536565b5090565b5b505050505b50565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b505600a165627a7a7230582063a0123d8e8f5dde980af6b47e20acc5b7a1acac3e3101fa1c933471ef4b405c0029" + } + } +} diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e52dd38d0..ea580c1a4 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -64,7 +64,7 @@ use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Databa use trace; use trace::FlatTransactionTraces; use evm::{Factory as EvmFactory, Schedule}; -use miner::{Miner, MinerService}; +use miner::{Miner, MinerService, TransactionImportResult}; use snapshot::{self, io as snapshot_io}; use factory::Factories; use rlp::{View, UntrustedRlp}; @@ -1455,6 +1455,21 @@ impl BlockChainClient for Client { }) } + fn transact_contract(&self, address: Address, data: Bytes) -> Result { + let transaction = Transaction { + nonce: self.latest_nonce(&self.miner.author()), + action: Action::Call(address), + gas: self.miner.gas_floor_target(), + gas_price: self.miner.sensible_gas_price(), + value: U256::zero(), + data: data, + }; + let network_id = self.engine.signing_network_id(&self.latest_env_info()); + let signature = self.engine.sign(transaction.hash(network_id))?; + let signed = SignedTransaction::new(transaction.with_signature(signature, network_id))?; + self.miner.import_own_transaction(self, signed.into()) + } + fn registrar_address(&self) -> Option
{ self.registrar.lock().as_ref().map(|r| r.address.clone()) } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index d654b0620..ce0bd5259 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -34,7 +34,7 @@ use filter::Filter; use log_entry::LocalizedLogEntry; use receipt::{Receipt, LocalizedReceipt}; use blockchain::extras::BlockReceipts; -use error::{ImportResult}; +use error::{ImportResult, Error as EthcoreError}; use evm::{Factory as EvmFactory, VMType, Schedule}; use miner::{Miner, MinerService, TransactionImportResult}; use spec::Spec; @@ -730,6 +730,21 @@ impl BlockChainClient for TestBlockChainClient { fn call_contract(&self, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } + fn transact_contract(&self, address: Address, data: Bytes) -> Result { + let transaction = Transaction { + nonce: self.latest_nonce(&self.miner.author()), + action: Action::Call(address), + gas: self.spec.gas_limit, + gas_price: U256::zero(), + value: U256::default(), + data: data, + }; + let network_id = Some(self.spec.params.network_id); + let sig = self.spec.engine.sign(transaction.hash(network_id)).unwrap(); + let signed = SignedTransaction::new(transaction.with_signature(sig, network_id)).unwrap(); + self.miner.import_own_transaction(self, signed.into()) + } + fn registrar_address(&self) -> Option
{ None } fn registry_address(&self, _name: String) -> Option
{ None } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 0c9bd9d73..d2851f15a 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -22,9 +22,10 @@ use verification::queue::QueueInfo as BlockQueueInfo; use block::{OpenBlock, SealedBlock}; use header::{BlockNumber}; use transaction::{LocalizedTransaction, PendingTransaction, SignedTransaction}; +use transaction_import::TransactionImportResult; use log_entry::LocalizedLogEntry; use filter::Filter; -use error::{ImportResult, CallError}; +use error::{ImportResult, CallError, Error as EthcoreError}; use receipt::LocalizedReceipt; use trace::LocalizedTrace; use evm::{Factory as EvmFactory, Schedule}; @@ -273,6 +274,9 @@ pub trait BlockChainClient : Sync + Send { /// Like `call`, but with various defaults. Designed to be used for calling contracts. fn call_contract(&self, address: Address, data: Bytes) -> Result; + /// Import a transaction: used for misbehaviour reporting. + fn transact_contract(&self, address: Address, data: Bytes) -> Result; + /// Get the address of the registry itself. fn registrar_address(&self) -> Option
; diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 3f371cb24..68d49b21d 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -275,15 +275,16 @@ 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())?; - if ok_sig { + let correct_proposer = self.step_proposer(header_step); + if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? { Ok(()) } else { - trace!(target: "poa", "verify_block_unordered: invalid seal signature"); - Err(BlockError::InvalidSeal)? + trace!(target: "poa", "verify_block_unordered: bad proposer for step: {}", header_step); + Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? } } else { trace!(target: "poa", "verify_block_unordered: block from the future"); + self.validators.report_benign(header.author()); Err(BlockError::InvalidSeal)? } } @@ -297,6 +298,7 @@ impl Engine for AuthorityRound { // Check if parent is from a previous step. if step == header_step(parent)? { trace!(target: "poa", "Multiple blocks proposed for step {}.", step); + self.validators.report_malicious(header.author()); Err(EngineError::DoubleVote(header.author().clone()))?; } @@ -311,12 +313,16 @@ impl Engine for AuthorityRound { fn register_client(&self, client: Weak) { *self.client.write() = Some(client.clone()); - self.validators.register_call_contract(client); + self.validators.register_contract(client); } fn set_signer(&self, ap: Arc, address: Address, password: String) { self.signer.set(ap, address, password); } + + fn sign(&self, hash: H256) -> Result { + self.signer.sign(hash).map_err(Into::into) + } } #[cfg(test)] diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index fdd2deb0b..8311f2834 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -18,7 +18,7 @@ use std::sync::Weak; use util::*; -use ethkey::{recover, public_to_address}; +use ethkey::{recover, public_to_address, Signature}; use account_provider::AccountProvider; use block::*; use builtin::Builtin; @@ -165,12 +165,16 @@ impl Engine for BasicAuthority { } fn register_client(&self, client: Weak) { - self.validators.register_call_contract(client); + self.validators.register_contract(client); } fn set_signer(&self, ap: Arc, address: Address, password: String) { self.signer.set(ap, address, password); } + + fn sign(&self, hash: H256) -> Result { + self.signer.sign(hash).map_err(Into::into) + } } #[cfg(test)] diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 6beb0f304..23d382cac 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -32,6 +32,7 @@ pub use self::tendermint::Tendermint; use std::sync::Weak; use util::*; +use ethkey::Signature; use account_provider::AccountProvider; use block::ExecutedBlock; use builtin::Builtin; @@ -207,6 +208,9 @@ pub trait Engine : Sync + Send { /// Register an account which signs consensus messages. fn set_signer(&self, _account_provider: Arc, _address: Address, _password: String) {} + /// Sign using the EngineSigner, to be used for consensus tx signing. + fn sign(&self, _hash: H256) -> Result { unimplemented!() } + /// Add Client which can be used for sealing, querying the state and sending messages. fn register_client(&self, _client: Weak) {} diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index fb744eb9f..7dae44aaa 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -36,7 +36,7 @@ use header::Header; use builtin::Builtin; use env_info::EnvInfo; use rlp::{UntrustedRlp, View}; -use ethkey::{recover, public_to_address}; +use ethkey::{recover, public_to_address, Signature}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; @@ -452,11 +452,12 @@ impl Engine for Tendermint { if !self.is_authority(&sender) { Err(EngineError::NotAuthorized(sender))?; } + self.broadcast_message(rlp.as_raw().to_vec()); if self.votes.vote(message.clone(), &sender).is_some() { + self.validators.report_malicious(&sender); Err(EngineError::DoubleVote(sender))? } trace!(target: "poa", "Handling a valid {:?} from {}.", message, sender); - self.broadcast_message(rlp.as_raw().to_vec()); self.handle_valid_message(&message); } Ok(()) @@ -548,6 +549,7 @@ impl Engine for Tendermint { 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 { + self.validators.report_malicious(header.author()); Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))?; } @@ -561,6 +563,10 @@ impl Engine for Tendermint { self.to_step(Step::Propose); } + fn sign(&self, hash: H256) -> Result { + self.signer.sign(hash).map_err(Into::into) + } + fn stop(&self) { self.step_service.stop() } @@ -589,6 +595,11 @@ impl Engine for Tendermint { let next_step = match *self.step.read() { Step::Propose => { trace!(target: "poa", "Propose timeout."); + if self.proposal.read().is_none() { + // Report the proposer if no proposal was received. + let current_proposer = self.round_proposer(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst)); + self.validators.report_benign(¤t_proposer); + } Step::Prevote }, Step::Prevote if self.has_enough_any_votes() => { @@ -620,7 +631,7 @@ impl Engine for Tendermint { fn register_client(&self, client: Weak) { *self.client.write() = Some(client.clone()); - self.validators.register_call_contract(client); + self.validators.register_contract(client); } } diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 62ae7b03f..36fc98174 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -59,10 +59,10 @@ impl TendermintTimeouts { impl Default for TendermintTimeouts { fn default() -> Self { TendermintTimeouts { - propose: Duration::milliseconds(10000), - prevote: Duration::milliseconds(10000), - precommit: Duration::milliseconds(10000), - commit: Duration::milliseconds(10000), + propose: Duration::milliseconds(1000), + prevote: Duration::milliseconds(1000), + precommit: Duration::milliseconds(1000), + commit: Duration::milliseconds(1000), } } } diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index fa7dbea41..8e6b507a0 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -14,84 +14,74 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -/// Validator set maintained in a contract. +/// Validator set maintained in a contract, updated using `getValidators` method. +/// It can also report validators for misbehaviour with two levels: `reportMalicious` and `reportBenign`. use std::sync::Weak; use util::*; use client::{Client, BlockChainClient}; -use client::chain_notify::ChainNotify; use super::ValidatorSet; -use super::simple_list::SimpleList; +use super::safe_contract::ValidatorSafeContract; /// 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, + validators: Arc, provider: RwLock>, } impl ValidatorContract { pub fn new(contract_address: Address) -> Self { ValidatorContract { - address: contract_address, - validators: Default::default(), + validators: Arc::new(ValidatorSafeContract::new(contract_address)), 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) + self.validators.contains(address) } fn get(&self, nonce: usize) -> Address { - self.validators.read().get(nonce).clone() + self.validators.get(nonce) } fn count(&self) -> usize { - self.validators.read().count() + self.validators.count() } - fn register_call_contract(&self, client: Weak) { - if let Some(c) = client.upgrade() { - c.add_notify(self.clone()); + fn report_malicious(&self, address: &Address) { + if let Some(ref provider) = *self.provider.read() { + match provider.report_malicious(address) { + Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address), + Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), + } + } else { + warn!(target: "engine", "Malicious behaviour could not be reported: no provider contract.") } - { - *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)))); + } + + fn report_benign(&self, address: &Address) { + if let Some(ref provider) = *self.provider.read() { + match provider.report_benign(address) { + Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address), + Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), + } + } else { + warn!(target: "engine", "Benign misbehaviour could not be reported: no provider contract.") } - self.update(); + } + + fn register_contract(&self, client: Weak) { + self.validators.register_contract(client.clone()); + let transact = move |a, d| client + .upgrade() + .ok_or("No client!".into()) + .and_then(|c| c.transact_contract(a, d).map_err(|e| format!("Transaction import error: {}", e))) + .map(|_| Default::default()); + *self.provider.write() = Some(provider::Contract::new(self.validators.address, transact)); } } @@ -112,23 +102,35 @@ mod provider { 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")), + contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":false,\"inputs\":[{\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"reportMalicious\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"reportBenign\",\"outputs\":[],\"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"}` + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportMalicious","outputs":[],"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)?; + pub fn report_malicious(&self, validator: &util::Address) -> Result<(), String> { + let call = self.contract.function("reportMalicious".into()).map_err(Self::as_string)?; let data = call.encode_call( - vec![] + vec![ethabi::Token::Address(validator.clone().0)] ).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::>() })) + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) + } + + /// Auto-generated from: `{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}` + #[allow(dead_code)] + pub fn report_benign(&self, validator: &util::Address) -> Result<(), String> { + let call = self.contract.function("reportBenign".into()).map_err(Self::as_string)?; + let data = call.encode_call( + vec![ethabi::Token::Address(validator.clone().0)] + ).map_err(Self::as_string)?; + call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; + + Ok(()) } } } @@ -136,12 +138,13 @@ mod provider { #[cfg(test)] mod tests { use util::*; - use spec::Spec; - use account_provider::AccountProvider; - use transaction::{Transaction, Action}; - use client::{BlockChainClient, EngineClient}; + use rlp::encode; use ethkey::Secret; + use spec::Spec; + use header::Header; + use account_provider::AccountProvider; use miner::MinerService; + use client::BlockChainClient; use tests::helpers::generate_dummy_client_with_spec_and_accounts; use super::super::ValidatorSet; use super::ValidatorContract; @@ -150,66 +153,46 @@ mod tests { fn fetches_validators() { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None); let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); - vc.register_call_contract(Arc::downgrade(&client)); - vc.update(); + vc.register_contract(Arc::downgrade(&client)); assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); } - + #[test] - fn changes_validators() { + fn reports_validators() { let tap = Arc::new(AccountProvider::transient_provider()); - let s0 = Secret::from_slice(&"1".sha3()).unwrap(); - let v0 = tap.insert_account(s0.clone(), "").unwrap(); - let v1 = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "").unwrap(); - let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, Some(tap)); + let v1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "").unwrap(); + let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, Some(tap.clone())); 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(&s0, 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(&s0, 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); + // Make sure reporting can be done. + client.miner().set_gas_floor_target(1_000_000.into()); - // 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(&s0, 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); + let mut header = Header::default(); + let seal = encode(&vec!(5u8)).to_vec(); + header.set_seal(vec!(seal)); + header.set_author(v1); + header.set_number(1); + // `reportBenign` when the designated proposer releases block from the future (bad clock). + assert!(client.engine().verify_block_unordered(&header, None).is_err()); + // Seal a block. + client.engine().step(); + assert_eq!(client.chain_info().best_block_number, 1); + // Check if the unresponsive validator is `disliked`. + assert_eq!(client.call_contract(validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"); + // Simulate a misbehaving validator by handling a double proposal. + assert!(client.engine().verify_block_family(&header, &header, None).is_err()); + // Seal a block. + client.engine().step(); + client.engine().step(); + assert_eq!(client.chain_info().best_block_number, 2); + + // Check if misbehaving validator was removed. + client.transact_contract(Default::default(), Default::default()).unwrap(); + client.engine().step(); + client.engine().step(); + assert_eq!(client.chain_info().best_block_number, 2); } } diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs index 566dccabb..a3c0ab2f7 100644 --- a/ethcore/src/engines/validator_set/mod.rs +++ b/ethcore/src/engines/validator_set/mod.rs @@ -17,6 +17,7 @@ /// Validator lists. mod simple_list; +mod safe_contract; mod contract; use std::sync::Weak; @@ -25,11 +26,13 @@ use ethjson::spec::ValidatorSet as ValidatorSpec; use client::Client; use self::simple_list::SimpleList; use self::contract::ValidatorContract; +use self::safe_contract::ValidatorSafeContract; /// 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::SafeContract(address) => Box::new(Arc::new(ValidatorSafeContract::new(address.into()))), ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))), } } @@ -41,6 +44,10 @@ pub trait ValidatorSet { fn get(&self, nonce: usize) -> Address; /// Returns the current number of validators. fn count(&self) -> usize; + /// Notifies about malicious behaviour. + fn report_malicious(&self, _validator: &Address) {} + /// Notifies about benign misbehaviour. + fn report_benign(&self, _validator: &Address) {} /// Allows blockchain state access. - fn register_call_contract(&self, _client: Weak) {} + fn register_contract(&self, _client: Weak) {} } diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs new file mode 100644 index 000000000..c02f310a1 --- /dev/null +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -0,0 +1,216 @@ +// 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, updated using `getValidators` method. + +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 ValidatorSafeContract { + pub address: Address, + validators: RwLock, + provider: RwLock>, +} + +impl ValidatorSafeContract { + pub fn new(contract_address: Address) -> Self { + ValidatorSafeContract { + 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 ValidatorSafeContract { + fn new_blocks( + &self, + _: Vec, + _: Vec, + enacted: Vec, + _: Vec, + _: Vec, + _: Vec, + _duration: u64) { + if !enacted.is_empty() { + 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) + } + + fn count(&self) -> usize { + self.validators.read().count() + } + + fn register_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 ethkey::Secret; + use miner::MinerService; + use tests::helpers::generate_dummy_client_with_spec_and_accounts; + use super::super::ValidatorSet; + use super::ValidatorSafeContract; + + #[test] + fn fetches_validators() { + let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None); + let vc = Arc::new(ValidatorSafeContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); + vc.register_contract(Arc::downgrade(&client)); + assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); + assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); + } + + #[test] + fn updates_validators() { + let tap = Arc::new(AccountProvider::transient_provider()); + let s0 = Secret::from_slice(&"1".sha3()).unwrap(); + let v0 = tap.insert_account(s0.clone(), "").unwrap(); + let v1 = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "").unwrap(); + let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, Some(tap)); + 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: "bfc708a000000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".from_hex().unwrap(), + }.sign(&s0, 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(&s0, 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(&s0, 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/error.rs b/ethcore/src/error.rs index 846972c02..05e12b526 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -26,6 +26,7 @@ use types::block_import_error::BlockImportError; use snapshot::Error as SnapshotError; use engines::EngineError; use ethkey::Error as EthkeyError; +use account_provider::Error as AccountsError; pub use types::executed::{ExecutionError, CallError}; @@ -265,6 +266,8 @@ pub enum Error { Engine(EngineError), /// Ethkey error. Ethkey(EthkeyError), + /// Account Provider error. + AccountProvider(AccountsError), } impl fmt::Display for Error { @@ -287,6 +290,7 @@ impl fmt::Display for Error { Error::Snapshot(ref err) => err.fmt(f), Error::Engine(ref err) => err.fmt(f), Error::Ethkey(ref err) => err.fmt(f), + Error::AccountProvider(ref err) => err.fmt(f), } } } @@ -396,6 +400,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: AccountsError) -> Error { + Error::AccountProvider(err) + } +} + impl From> for Error where Error: From { fn from(err: Box) -> Error { Error::from(*err) diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 2f957098d..0f5353a90 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -340,14 +340,20 @@ impl Spec { /// 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") } + + /// TestList.sol used in both specs: https://github.com/ethcore/contracts/pull/30/files + /// Accounts with secrets "0".sha3() and "1".sha3() are initially the validators. + /// Create a new Spec with BasicAuthority which uses a contract at address 5 to determine the current validators using `getValidators`. + /// Second validator can be removed with "0xbfc708a000000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1" and added back in using "0x4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1". + pub fn new_validator_safe_contract() -> Self { load_bundled!("validator_safe_contract") } + + /// The same as the `safeContract`, but allows reporting and uses AuthorityRound. + /// Account is marked with `reportBenign` it can be checked as disliked with "0xd8f2e0bf". + /// Validator can be removed with `reportMalicious`. + pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") } } #[cfg(test)] diff --git a/json/src/spec/validator_set.rs b/json/src/spec/validator_set.rs index 47acd7c36..ef3224e94 100644 --- a/json/src/spec/validator_set.rs +++ b/json/src/spec/validator_set.rs @@ -25,6 +25,9 @@ pub enum ValidatorSet { #[serde(rename="list")] List(Vec
), /// Address of a contract that indicates the list of authorities. + #[serde(rename="safeContract")] + SafeContract(Address), + /// Address of a contract that indicates the list of authorities and enables reporting of theor misbehaviour using transactions. #[serde(rename="contract")] Contract(Address), } @@ -38,6 +41,8 @@ mod tests { fn validator_set_deserialization() { let s = r#"[{ "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + }, { + "safeContract" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" }, { "contract" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" }]"#;