From 0f80c57dcaac179b29b077ece375ee91d0886ba2 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 12 Apr 2017 16:15:35 +0200 Subject: [PATCH] use native contracts in `ValidatorSet` --- ethcore/native_contracts/generator/src/lib.rs | 8 +- .../native_contracts/src/validator_report.rs | 22 ++++ ethcore/native_contracts/src/validator_set.rs | 22 ++++ ethcore/src/engines/validator_set/contract.rs | 104 +++++------------- .../engines/validator_set/safe_contract.rs | 96 +++++----------- 5 files changed, 107 insertions(+), 145 deletions(-) create mode 100644 ethcore/native_contracts/src/validator_report.rs create mode 100644 ethcore/native_contracts/src/validator_set.rs diff --git a/ethcore/native_contracts/generator/src/lib.rs b/ethcore/native_contracts/generator/src/lib.rs index 363e76ea1..28d658cb3 100644 --- a/ethcore/native_contracts/generator/src/lib.rs +++ b/ethcore/native_contracts/generator/src/lib.rs @@ -46,7 +46,7 @@ pub fn generate_module(struct_name: &str, abi: &str) -> Result { Ok(format!(r##" use byteorder::{{BigEndian, ByteOrder}}; -use futures::{{future, Future, BoxFuture}}; +use futures::{{future, Future, IntoFuture, BoxFuture}}; use ethabi::{{Contract, Interface, Token}}; use util::{{self, Uint}}; @@ -99,7 +99,10 @@ fn generate_functions(contract: &Contract) -> Result { /// Inputs: {abi_inputs:?} /// Outputs: {abi_outputs:?} pub fn {snake_name}(&self, call: F, {params}) -> BoxFuture<{output_type}, String> - where F: Fn(util::Address, Vec) -> U, U: Future, Error=String> + Send + 'static + where + F: Fn(util::Address, Vec) -> U, + U: IntoFuture, Error=String>, + U::Future: Send + 'static {{ let function = self.contract.function(r#"{abi_name}"#.to_string()) .expect("function existence checked at compile-time; qed"); @@ -111,6 +114,7 @@ pub fn {snake_name}(&self, call: F, {params}) -> BoxFuture<{output_type}, }}; call_future + .into_future() .and_then(move |out| function.decode_output(out).map_err(|e| format!("{{:?}}", e))) .map(::std::collections::VecDeque::from) .and_then(|mut outputs| {decode_outputs}) diff --git a/ethcore/native_contracts/src/validator_report.rs b/ethcore/native_contracts/src/validator_report.rs new file mode 100644 index 000000000..d77b07c71 --- /dev/null +++ b/ethcore/native_contracts/src/validator_report.rs @@ -0,0 +1,22 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +#![allow(unused_mut, unused_variables, unused_imports)] + +//! Validator reporting. +// TODO: testing. + +include!(concat!(env!("OUT_DIR"), "/validator_report.rs")); diff --git a/ethcore/native_contracts/src/validator_set.rs b/ethcore/native_contracts/src/validator_set.rs new file mode 100644 index 000000000..0a69913e0 --- /dev/null +++ b/ethcore/native_contracts/src/validator_set.rs @@ -0,0 +1,22 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +#![allow(unused_mut, unused_variables, unused_imports)] + +//! Validator set contract. +// TODO: testing. + +include!(concat!(env!("OUT_DIR"), "/validator_set.rs")); diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index adfdd1225..c45e8c479 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -18,27 +18,46 @@ /// It can also report validators for misbehaviour with two levels: `reportMalicious` and `reportBenign`. use std::sync::Weak; +use futures::Future; use util::*; use client::{Client, BlockChainClient}; + +use native_contracts::ValidatorReport as Provider; + use super::ValidatorSet; 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"}] +/// A validator contract with reporting. pub struct ValidatorContract { validators: ValidatorSafeContract, - provider: RwLock>, + provider: Provider, + client: RwLock>>, // TODO [keorn]: remove } impl ValidatorContract { pub fn new(contract_address: Address) -> Self { ValidatorContract { validators: ValidatorSafeContract::new(contract_address), - provider: RwLock::new(None), + provider: Provider::new(contract_address), + client: RwLock::new(None), } } } +impl ValidatorContract { + // could be `impl Trait`. + // note: dispatches transactions to network as well as execute. + // TODO [keorn]: Make more general. + fn transact(&self) -> Box) -> Result, String>> { + let client = self.client.read().clone(); + Box::new(move |a, d| client.as_ref() + .and_then(Weak::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())) + } +} + impl ValidatorSet for ValidatorContract { fn contains(&self, bh: &H256, address: &Address) -> bool { self.validators.contains(bh, address) @@ -53,85 +72,22 @@ impl ValidatorSet for ValidatorContract { } 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.") + match self.provider.report_malicious(&*self.transact(), *address).wait() { + Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address), + Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), } } 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.") + match self.provider.report_benign(&*self.transact(), *address).wait() { + Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address), + Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), } } 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)); - } -} - -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::{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\":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":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"}` - #[allow(dead_code)] - 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![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(()) - } - - /// 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(()) - } + *self.client.write() = Some(client); } } diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 0a0eaecfd..958f3e0a0 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -17,24 +17,26 @@ /// Validator set maintained in a contract, updated using `getValidators` method. use std::sync::Weak; -use ethabi; +use futures::Future; +use native_contracts::ValidatorSet as Provider; + use util::*; use util::cache::MemoryLruCache; + use types::ids::BlockId; use client::{Client, BlockChainClient}; + use super::ValidatorSet; use super::simple_list::SimpleList; const MEMOIZE_CAPACITY: usize = 500; -const CONTRACT_INTERFACE: &'static [u8] = b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]"; -const GET_VALIDATORS: &'static str = "getValidators"; /// 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>, + provider: Provider, + client: RwLock>>, // TODO [keorn]: remove } impl ValidatorSafeContract { @@ -42,26 +44,30 @@ impl ValidatorSafeContract { ValidatorSafeContract { address: contract_address, validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)), - provider: RwLock::new(None), + provider: Provider::new(contract_address), + client: RwLock::new(None), } } + fn do_call(&self, id: BlockId) -> Box) -> Result, String>> { + let client = self.client.read().clone(); + Box::new(move |addr, data| client.as_ref() + .and_then(Weak::upgrade) + .ok_or("No client!".into()) + .and_then(|c| c.call_contract(id, addr, data))) + } + /// Queries the state and gets the set of validators. fn get_list(&self, block_hash: H256) -> Option { - if let Some(ref provider) = *self.provider.read() { - match provider.get_validators(BlockId::Hash(block_hash)) { - Ok(new) => { - debug!(target: "engine", "Set of validators obtained: {:?}", new); - Some(SimpleList::new(new)) - }, - Err(s) => { - debug!(target: "engine", "Set of validators could not be updated: {}", s); - None - }, - } - } else { - warn!(target: "engine", "Set of validators could not be updated: no provider contract."); - None + match self.provider.get_validators(&*self.do_call(BlockId::Hash(block_hash))).wait() { + Ok(new) => { + debug!(target: "engine", "Set of validators obtained: {:?}", new); + Some(SimpleList::new(new)) + }, + Err(s) => { + debug!(target: "engine", "Set of validators could not be updated: {}", s); + None + }, } } } @@ -114,55 +120,7 @@ impl ValidatorSet for ValidatorSafeContract { fn register_contract(&self, client: Weak) { trace!(target: "engine", "Setting up contract caller."); - let contract = ethabi::Contract::new(ethabi::Interface::load(CONTRACT_INTERFACE).expect("JSON interface is valid; qed")); - let call = contract.function(GET_VALIDATORS.into()).expect("Method name is valid; qed"); - let data = call.encode_call(vec![]).expect("get_validators does not take any arguments; qed"); - let contract_address = self.address.clone(); - let do_call = move |id| client - .upgrade() - .ok_or("No client!".into()) - .and_then(|c| c.call_contract(id, contract_address.clone(), data.clone())) - .map(|raw_output| call.decode_output(raw_output).expect("ethabi is correct; qed")); - *self.provider.write() = Some(provider::Contract::new(do_call)); - } -} - -mod provider { - use std::string::String; - use std::result::Result; - use {util, ethabi}; - use types::ids::BlockId; - - pub struct Contract { - do_call: Box Result, String> + Send + Sync + 'static>, - } - - impl Contract { - pub fn new(do_call: F) -> Self where F: Fn(BlockId) -> Result, String> + Send + Sync + 'static { - Contract { - do_call: Box::new(do_call), - } - } - - /// Gets validators from contract with interface: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}` - pub fn get_validators(&self, id: BlockId) -> Result, String> { - Ok((self.do_call)(id)? - .into_iter() - .rev() - .collect::>() - .pop() - .expect("get_validators returns one argument; qed") - .to_array() - .and_then(|v| v - .into_iter() - .map(|a| a.to_address()) - .collect::>>()) - .expect("get_validators returns a list of addresses; qed") - .into_iter() - .map(util::Address::from) - .collect::>() - ) - } + *self.client.write() = Some(client); } }