diff --git a/Cargo.lock b/Cargo.lock index d1ac7dcb6da..a0a1ab81ad9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -480,7 +480,6 @@ dependencies = [ "ethabi 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethereum-types 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", "keccak-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index e1c823ee2da..bfe6258071c 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -593,6 +593,12 @@ impl ::ethcore::client::ChainInfo for Client { } } +impl ::ethcore::client::Nonce for Client { + fn nonce(&self, _address: ðereum_types::H160, _blockid: ethcore::client::BlockId) -> std::option::Option { + panic!("we never call this function on a light client, so this is unreachable; qed") + } +} + impl ::ethcore::client::EngineClient for Client { fn update_sealing(&self) { } fn submit_seal(&self, _block_hash: H256, _seal: Vec>) { } diff --git a/ethcore/res/contracts/validator_set_aura.json b/ethcore/res/contracts/validator_set_aura.json index a42eee20a6c..3b678bff0e0 100644 --- a/ethcore/res/contracts/validator_set_aura.json +++ b/ethcore/res/contracts/validator_set_aura.json @@ -61,5 +61,28 @@ ], "name": "InitiateChange", "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "name": "_maliciousMiningAddress", + "type": "address" + }, + { + "name": "_blockNumber", + "type": "uint256" + } + ], + "name": "maliceReportedForBlock", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" } -] +] \ No newline at end of file diff --git a/ethcore/res/contracts/validator_set_aura.json.txt b/ethcore/res/contracts/validator_set_aura.json.txt new file mode 100644 index 00000000000..f912cf10e39 --- /dev/null +++ b/ethcore/res/contracts/validator_set_aura.json.txt @@ -0,0 +1,7 @@ +getValidators +initiateChange +emitInitiateChangeCallable +emitInitiateChange +maliceReportedForBlock +finalizeChange +InitiateChange diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 977ea01b369..2fc3f90cec0 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -2133,10 +2133,10 @@ impl BlockChainClient for Client { Ok(SignedTransaction::new(transaction.with_signature(signature, chain_id))?) } - fn transact(&self, action: Action, data: Bytes, gas: Option, gas_price: Option) + fn transact(&self, action: Action, data: Bytes, gas: Option, gas_price: Option, nonce: Option) -> Result<(), transaction::Error> { - let signed = self.create_transaction(action, data, gas, gas_price, None)?; + let signed = self.create_transaction(action, data, gas, gas_price, nonce)?; self.importer.miner.import_own_transaction(self, signed.into()) } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 131a5c77692..afd02d1662b 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -880,8 +880,14 @@ impl BlockChainClient for TestBlockChainClient { Ok(SignedTransaction::new(transaction.with_signature(sig, chain_id)).unwrap()) } - fn transact(&self, action: Action, data: Bytes, gas: Option, gas_price: Option) - -> Result<(), transaction::Error> + fn transact( + &self, + action: Action, + data: Bytes, + gas: Option, + gas_price: Option, + _nonce: Option, + ) -> Result<(), transaction::Error> { let signed = self.create_transaction(action, data, gas, gas_price, None)?; self.miner.import_own_transaction(self, signed.into()) diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index ea74aada2cc..12a5441c5c6 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -388,11 +388,12 @@ pub trait BlockChainClient : Sync + Send + AccountData + BlockChain + CallContra fn pruning_info(&self) -> PruningInfo; /// Schedule state-altering transaction to be executed on the next pending block. - fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { - self.transact(Action::Call(address), data, None, None) + fn transact_contract(&self, address: Address, data: Bytes, nonce: Option) -> Result<(), transaction::Error> { + self.transact(Action::Call(address), data, None, None, nonce) } - /// Returns a signed transaction. If gas limit, gas price, or nonce are not specified, the defaults are used. + /// Returns a signed transaction. If gas limit, gas price, or nonce are not + /// specified, the defaults are used. fn create_transaction( &self, action: Action, @@ -402,10 +403,11 @@ pub trait BlockChainClient : Sync + Send + AccountData + BlockChain + CallContra nonce: Option ) -> Result; - /// Schedule state-altering transaction to be executed on the next pending block with the given gas parameters. + /// Schedule state-altering transaction to be executed on the next pending + /// block with the given gas and nonce parameters. /// /// If they are `None`, sensible values are selected automatically. - fn transact(&self, action: Action, data: Bytes, gas: Option, gas_price: Option) + fn transact(&self, action: Action, data: Bytes, gas: Option, gas_price: Option, nonce: Option) -> Result<(), transaction::Error>; /// Get the address of the registry itself. @@ -453,7 +455,7 @@ pub trait BroadcastProposalBlock { pub trait SealedBlockImporter: ImportSealedBlock + BroadcastProposalBlock {} /// Client facilities used by internally sealing Engines. -pub trait EngineClient: Sync + Send + ChainInfo { +pub trait EngineClient: Sync + Send + ChainInfo + Nonce { /// Make a new block and seal it. fn update_sealing(&self); diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 9f0aac9988b..d467a18e449 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -1215,7 +1215,12 @@ impl Engine for AuthorityRound { }, }; - block_reward::apply_block_rewards(&rewards, block, &self.machine) + block_reward::apply_block_rewards(&rewards, block, &self.machine)?; + if let Some(ref address) = self.signer.read().address() { + self.validators.on_close_block(block.header(), address) + } else { + Ok(()) + } } /// Make calls to the randomness and validator set contracts. diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 9fb5cf1dae9..44df6fd5cee 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -37,7 +37,7 @@ use_contract!(validator_report, "res/contracts/validator_report.json"); pub struct ValidatorContract { contract_address: Address, validators: ValidatorSafeContract, - client: RwLock>>, // TODO [keorn]: remove + client: RwLock>>, // TODO [keorn]: remove } impl ValidatorContract { @@ -51,16 +51,17 @@ impl ValidatorContract { } impl ValidatorContract { - fn transact(&self, data: Bytes) -> Result<(), String> { + fn transact(&self, data: Bytes) -> Result<(), ::error::Error> { let client = self.client.read().as_ref() .and_then(Weak::upgrade) .ok_or_else(|| "No client!")?; match client.as_full_client() { Some(c) => { - c.transact(Action::Call(self.contract_address), data, None, Some(0.into())) - .map_err(|e| format!("Transaction import error: {}", e))?; - Ok(()) + match c.transact(Action::Call(self.contract_address), data, None, Some(0.into()), None) { + Ok(()) | Err(::transaction::Error::AlreadyImported) => Ok(()), + Err(e) => Err(e)?, + } }, None => Err("No full client!".into()), } @@ -83,6 +84,10 @@ impl ValidatorSet for ValidatorContract { self.validators.on_epoch_begin(first, header, call) } + fn on_close_block(&self, header: &Header, address: &Address) -> Result<(), ::error::Error> { + self.validators.on_close_block(header, address) + } + fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result, String> { self.validators.genesis_epoch_data(header, call) } @@ -116,18 +121,21 @@ impl ValidatorSet for ValidatorContract { self.validators.count_with_caller(bh, caller) } - fn report_malicious(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber, proof: Bytes) { + fn report_malicious(&self, address: &Address, set_block: BlockNumber, block: BlockNumber, proof: Bytes) { let data = validator_report::functions::report_malicious::encode_input(*address, block, proof); - match self.transact(data) { - Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address), - Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), + match self.transact(data.clone()) { + Ok(()) => warn!(target: "engine", "Reported malicious validator {} at block {}", address, set_block), + Err(s) => { + warn!(target: "engine", "Validator {} could not be reported ({}) on block {}", address, s, set_block); + self.validators.queue_report((*address, set_block, data)) + } } } fn report_benign(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber) { let data = validator_report::functions::report_benign::encode_input(*address, block); match self.transact(data) { - Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address), + Ok(()) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address), Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), } } @@ -143,7 +151,7 @@ mod tests { use std::sync::Arc; use rustc_hex::FromHex; use hash::keccak; - use ethereum_types::{U256, H520, Address}; + use ethereum_types::{H520, Address}; use bytes::ToPretty; use rlp::encode; use spec::Spec; @@ -220,7 +228,7 @@ mod tests { assert_eq!(client.chain_info().best_block_number, 2); // Check if misbehaving validator was removed. - client.transact_contract(Default::default(), Default::default()).unwrap(); + client.transact_contract(Default::default(), 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 3ce0e7061f6..d716c37d0cb 100644 --- a/ethcore/src/engines/validator_set/mod.rs +++ b/ethcore/src/engines/validator_set/mod.rs @@ -89,7 +89,11 @@ pub trait ValidatorSet: Send + Sync + 'static { Ok(()) } - #[cfg(all())] + /// Called on the close of every block. + fn on_close_block(&self, _header: &Header, _address: &Address) -> Result<(), ::error::Error> { + Ok(()) + } + /// Called for each new block this node is creating. If this block is /// the first block of an epoch, this is called *after* on_epoch_begin(), /// but with the same parameters. diff --git a/ethcore/src/engines/validator_set/multi.rs b/ethcore/src/engines/validator_set/multi.rs index c72bf4bcc87..89ed77600fc 100644 --- a/ethcore/src/engines/validator_set/multi.rs +++ b/ethcore/src/engines/validator_set/multi.rs @@ -88,6 +88,9 @@ impl ValidatorSet for Multi { self.map_children(header, &mut |set: &dyn ValidatorSet, first| set.on_prepare_block(first, header, call)) } + fn on_close_block(&self, header: &Header, address: &Address) -> Result<(), ::error::Error> { + self.map_children(header, &mut |set: &dyn ValidatorSet, _first| set.on_close_block(header, address)) + } fn on_epoch_begin(&self, _first: bool, header: &Header, call: &mut SystemCall) -> Result<(), ::error::Error> { self.map_children(header, &mut |set: &dyn ValidatorSet, first| set.on_epoch_begin(first, header, call)) @@ -188,7 +191,7 @@ mod tests { // Wrong signer for the first block. client.miner().set_author(v1, Some("".into())).unwrap(); - client.transact_contract(Default::default(), Default::default()).unwrap(); + client.transact_contract(Default::default(), Default::default(), Default::default()).unwrap(); ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 0); // Right signer for the first block. @@ -196,14 +199,14 @@ mod tests { ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 1); // This time v0 is wrong. - client.transact_contract(Default::default(), Default::default()).unwrap(); + client.transact_contract(Default::default(), Default::default(), Default::default()).unwrap(); ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 1); client.miner().set_author(v1, Some("".into())).unwrap(); ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 2); // v1 is still good. - client.transact_contract(Default::default(), Default::default()).unwrap(); + client.transact_contract(Default::default(), Default::default(), Default::default()).unwrap(); ::client::EngineClient::update_sealing(&*client); assert_eq!(client.chain_info().best_block_number, 3); diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 2c0b15f0144..b50b9fc4aa2 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -26,7 +26,8 @@ use kvdb::DBValue; use log_entry::LogEntry; use machine::{AuxiliaryData, Call, EthereumMachine, AuxiliaryRequest}; use memory_cache::MemoryLruCache; -use parking_lot::RwLock; +use parking_lot::{RwLock, Mutex}; +use transaction::Action; use receipt::Receipt; use rlp::{Rlp, RlpStream}; use std::sync::{Weak, Arc}; @@ -34,9 +35,13 @@ use super::{SystemCall, ValidatorSet}; use super::simple_list::SimpleList; use unexpected::Mismatch; use ethabi::FunctionOutputDecoder; +use std::collections::VecDeque; use_contract!(validator_set, "res/contracts/validator_set_aura.json"); +/// The maximum number of reports to keep queued. +const MAX_QUEUED_REPORTS: usize = 10; + const MEMOIZE_CAPACITY: usize = 500; // TODO: ethabi should be able to generate this. @@ -74,6 +79,7 @@ pub struct ValidatorSafeContract { contract_address: Address, validators: RwLock>, client: RwLock>>, // TODO [keorn]: remove + queued_reports: Mutex)>>, } // first proof is just a state proof call of `getValidators` at header's state. @@ -189,11 +195,34 @@ fn prove_initial(contract_address: Address, header: &Header, caller: &Call) -> R } impl ValidatorSafeContract { + fn transact(&self, data: Bytes, nonce: U256) -> Result<(), ::error::Error> { + let client = self.client.read().as_ref() + .and_then(Weak::upgrade) + .ok_or_else(|| "No client!")?; + + match client.as_full_client() { + Some(c) => { + match c.transact(Action::Call(self.contract_address), data, None, Some(0.into()), Some(nonce)) { + Ok(()) | Err(::transaction::Error::AlreadyImported) => Ok(()), + Err(e) => Err(e)?, + } + }, + None => Err("No full client!".into()), + } + } + + pub(crate) fn queue_report(&self, data: (Address, u64, Vec)) { + self.queued_reports + .lock() + .push_back(data) + } + pub fn new(contract_address: Address) -> Self { ValidatorSafeContract { contract_address, validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)), client: RwLock::new(None), + queued_reports: Mutex::new(VecDeque::new()), } } @@ -293,17 +322,87 @@ impl ValidatorSet for ValidatorSafeContract { -> Result, ::error::Error> { let (data, decoder) = validator_set::functions::emit_initiate_change_callable::call(); - if !caller(self.contract_address, data) + let mut returned_transactions = if !caller(self.contract_address, data) .and_then(|x| decoder.decode(&x) .map_err(|x| format!("chain spec bug: could not decode: {:?}", x))) .map_err(::engines::EngineError::FailedSystemCall)? { trace!(target: "engine", "New block #{} issued ― no need to call emitInitiateChange()", header.number()); - return Ok(Vec::new()); + Vec::new() + } else { + trace!(target: "engine", "New block issued #{} ― calling emitInitiateChange()", header.number()); + let (data, _decoder) = validator_set::functions::emit_initiate_change::call(); + vec![(self.contract_address, data)] + }; + let queued_reports = self.queued_reports.lock(); + for (_address, _block, data) in queued_reports.iter().take(10) { + returned_transactions.push((self.contract_address, data.clone())) } + Ok(returned_transactions) + } - trace!(target: "engine", "New block issued #{} ― calling emitInitiateChange()", header.number()); - let (data, _decoder) = validator_set::functions::emit_initiate_change::call(); - Ok(vec![(self.contract_address, data)]) + fn on_close_block(&self, header: &Header, address: &Address) -> Result<(), ::error::Error> { + let client = self.client.read() + .as_ref() + .and_then(Weak::upgrade) + .ok_or_else(|| ::error::Error::from("No client!"))?; + let client = client + .as_full_client() + .ok_or_else(|| ::error::Error::from("No full client!"))?; + let nonce = client + .nonce(&self.contract_address, BlockId::Latest) + .ok_or_else(||"No nonce!")?; + let mut i = 0u64; + debug!(target: "engine", "Checking for cached reports"); + for (address, block, data) in self.queued_reports.lock().iter() { + debug!(target: "engine", "Trying to report"); + loop { + match self.transact(data.clone(), nonce + U256::from(i)) { + Ok(()) => break, + Err(err) => { + let msg = format!("{}", err); + if msg != "Transaction import error: Transaction error (No longer valid)" { + warn!(target: "engine", "Cannot report validator {} for misbehavior on block {}: {}", address, block, err); + break + } + } + } + i += 1 + }; + i += 1 + } + let mut queue = self.queued_reports.lock(); + queue.retain(|&(malicious_validator_address, block, ref _data)| { + debug!(target: "engine", "Checking if report can be removed from cache"); + let result = { + let current_block_number = header.number(); + if block > current_block_number { + return false // Report cannot be used, as it is for a block that isn’t in the current chain + } + if current_block_number > 100 && current_block_number - 100 > block { + return false // Report is too old and cannot be used + } + let (data, decoder) = validator_set::functions::malice_reported_for_block::call( + malicious_validator_address, block); + let result = client.call_contract(BlockId::Latest, self.contract_address, data) + .expect("this is a bug in the genesis block; either the validator set contract is missing, or it is invalid; this is a fatal error in the configuration of Parity, so we cannot recover; qed"); + decoder.decode(&result[..]) + .expect("this is a bug in the genesis block; either the validator set contract is missing, or it is invalid; this is a fatal error in the configuration of Parity, so we cannot recover; qed") + }; + debug!(target: "engine", "Got data from contract: {:?}", result); + if result.contains(&address) { + debug!(target: "engine", "Successfully removed report from report cache"); + return false + } + return true + }); + + while queue.len() > MAX_QUEUED_REPORTS { + warn!(target: "engine", "Removing report from report cache, even though it has not \ + been finalized"); + drop(queue.pop_front()) + } + + Ok(()) } fn on_epoch_begin(&self, _first: bool, _header: &Header, caller: &mut SystemCall) -> Result<(), ::error::Error> { diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index fc99b4658d0..af5db826671 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . //! General error types for use in ethcore. - +#![allow(deprecated)] use std::{fmt, error}; use std::time::SystemTime; use ethereum_types::{H256, U256, Address, Bloom}; diff --git a/ethcore/src/views/view_rlp.rs b/ethcore/src/views/view_rlp.rs index b85f30cd372..e9077a86f71 100644 --- a/ethcore/src/views/view_rlp.rs +++ b/ethcore/src/views/view_rlp.rs @@ -127,6 +127,7 @@ impl<'a, 'view> Iterator for ViewRlpIterator<'a, 'view> { } } +/// Create a `ViewRlp` #[macro_export] /// Generate a view of an RLP type macro_rules! view { diff --git a/scripts/get-abi b/scripts/get-abi new file mode 100755 index 00000000000..16894e2f680 --- /dev/null +++ b/scripts/get-abi @@ -0,0 +1,35 @@ +#!/usr/bin/python3 -- +""" +Get the ABI of a contract JSON file (specified in ``sys.argv[1]``) and write it +to ``sys.argv[2]``. Both ``sys.argv[1]`` and ``sys.argv[2]`` must end in +.json. Additionally, a file named ``sys.argv[2] + '.txt'`` must exist. It +must have one line for each symbol in the ABI that will be placed in the output. + +The input file must be encoded in UTF-8, and the output file will be encoded in +UTF-8. +""" + +__version__ = '0.0.1' +__all__ = ['main'] +import re +_re = re.compile(r'\A[A-Za-z_][A-Za-z0-9_]*\n\Z') +def main(input_file, output_file, used_file): + import os, json + _type, _dict, _open, _set = type, dict, open, set + with _open(used_file, 'r', encoding='UTF-8') as used_stream: + used_functions = _set(i.strip() for i in used_stream if _re.match(i)) + with _open(input_file, 'r', encoding='UTF-8') as input_stream, \ + _open(output_file, 'w', encoding='UTF-8') as output_stream: + my_list = [i for i in json.load(input_stream)['abi'] + if _type(i) is _dict and i.get('name') in used_functions] + json.dump(my_list, output_stream, sort_keys=True, indent = '\t') +if __name__ == '__main__': + import sys, json + if len(sys.argv) != 3: + print('must have 2 arguments, not {}'.format(len(sys.argv) - 1), + file=sys.stderr) + sys.exit(1) + try: + main(sys.argv[1], sys.argv[2], sys.argv[2] + '.txt') + except (OSError, UnicodeError, json.decoder.JSONDecodeError) as e: + print('Failed to generate ABI file: {}'.format(e), file=sys.stderr) diff --git a/util/EIP-712/Cargo.toml b/util/EIP-712/Cargo.toml index 308df98d8ad..aaee48bccd5 100644 --- a/util/EIP-712/Cargo.toml +++ b/util/EIP-712/Cargo.toml @@ -12,7 +12,6 @@ keccak-hash = "0.1" ethereum-types = "0.4" failure = "0.1" itertools = "0.7" -failure_derive = "0.1" lazy_static = "1.1" toolshed = "0.4" regex = "1.0" diff --git a/util/EIP-712/src/lib.rs b/util/EIP-712/src/lib.rs index 206b685104c..b881a45e390 100644 --- a/util/EIP-712/src/lib.rs +++ b/util/EIP-712/src/lib.rs @@ -172,8 +172,6 @@ extern crate validator; #[macro_use] extern crate validator_derive; #[macro_use] -extern crate failure_derive; -#[macro_use] extern crate serde_derive; #[macro_use] extern crate lazy_static;