Private transactions integration pr (#6422)

* Private transaction message added

* Empty line removed

* Private transactions logic removed from client into the separate module

* Fixed compilation after merge with head

* Signed private transaction message added as well

* Comments after the review fixed

* Private tx execution

* Test update

* Renamed some methods

* Fixed some tests

* Reverted submodules

* Fixed build

* Private transaction message added

* Empty line removed

* Private transactions logic removed from client into the separate module

* Fixed compilation after merge with head

* Signed private transaction message added as well

* Comments after the review fixed

* Encrypted private transaction message and signed reply added

* Private tx execution

* Test update

* Main scenario completed

* Merged with the latest head

* Private transactions API

* Comments after review fixed

* Parameters for private transactions added to parity arguments

* New files added

* New API methods added

* Do not process packets from unconfirmed peers

* Merge with ptm_ss branch

* Encryption and permissioning with key server added

* Fixed compilation after merge

* Version of Parity protocol incremented in order to support private transactions

* Doc strings for constants added

* Proper format for doc string added

* fixed some encryptor.rs grumbles

* Private transactions functionality moved to the separate crate

* Refactoring in order to remove late initialisation

* Tests fixed after moving to the separate crate

* Fetch method removed

* Sync test helpers refactored

* Interaction with encryptor refactored

* Contract address retrieving via substate removed

* Sensible gas limit for private transactions implemented

* New private contract with nonces added

* Parsing of the response from key server fixed

* Build fixed after the merge, native contracts removed

* Crate renamed

* Tests moved to the separate directory

* Handling of errors reworked in order to use error chain

* Encodable macro added, new constructor replaced with default

* Native ethabi usage removed

* Couple conversions optimized

* Interactions with client reworked

* Errors omitting removed

* Fix after merge

* Fix after the merge

* private transactions improvements in progress

* private_transactions -> ethcore/private-tx

* making private transactions more idiomatic

* private-tx encryptor uses shared FetchClient and is more idiomatic

* removed redundant tests, moved integration tests to tests/ dir

* fixed failing service test

* reenable add_notify on private tx provider

* removed private_tx tests from sync module

* removed commented out code

* Use plain password instead of unlocking account manager

* remove dead code

* Link to the contract changed

* Transaction signature chain replay protection module created

* Redundant type conversion removed

* Contract address returned by private provider

* Test fixed

* Addressing grumbles in PrivateTransactions (#8249)

* Tiny fixes part 1.

* A bunch of additional comments and todos.

* Fix ethsync tests.

* resolved merge conflicts

* final private tx pr (#8318)

* added cli option that enables private transactions

* fixed failing test

* fixed failing test

* fixed failing test

* fixed failing test
This commit is contained in:
Anton Gavrilov 2018-04-09 16:14:33 +02:00 committed by Marek Kotewicz
parent c039ab79b5
commit e6f75bccfe
90 changed files with 2745 additions and 192 deletions

41
Cargo.lock generated
View File

@ -519,6 +519,7 @@ dependencies = [
"ethkey 0.3.0", "ethkey 0.3.0",
"ethstore 0.2.0", "ethstore 0.2.0",
"evm 0.1.0", "evm 0.1.0",
"fetch 0.1.0",
"futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"hardware-wallet 1.11.0", "hardware-wallet 1.11.0",
"hashdb 0.1.1", "hashdb 0.1.1",
@ -720,6 +721,40 @@ dependencies = [
"tiny-keccak 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "ethcore-private-tx"
version = "1.0.0"
dependencies = [
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi-contract 5.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi-derive 5.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.11.0",
"ethcore-bytes 0.1.0",
"ethcore-io 1.11.0",
"ethcore-logger 1.11.0",
"ethcore-miner 1.11.0",
"ethcore-transaction 0.1.0",
"ethcrypto 0.1.0",
"ethereum-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethjson 0.1.0",
"ethkey 0.3.0",
"fetch 0.1.0",
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"keccak-hash 0.1.0",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"patricia-trie 0.1.0",
"rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.2.1",
"rlp_derive 0.1.0",
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tiny-keccak 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "ethcore-secretstore" name = "ethcore-secretstore"
version = "1.0.0" version = "1.0.0"
@ -762,8 +797,11 @@ name = "ethcore-service"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.11.0", "ethcore 1.11.0",
"ethcore-io 1.11.0", "ethcore-io 1.11.0",
"ethcore-private-tx 1.0.0",
"ethsync 1.11.0",
"kvdb 0.1.0", "kvdb 0.1.0",
"kvdb-rocksdb 0.1.0", "kvdb-rocksdb 0.1.0",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
@ -948,6 +986,7 @@ dependencies = [
"plain_hasher 0.1.0", "plain_hasher 0.1.0",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.2.1", "rlp 0.2.1",
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"triehash 0.1.0", "triehash 0.1.0",
@ -1939,6 +1978,7 @@ dependencies = [
"ethcore-migrations 0.1.0", "ethcore-migrations 0.1.0",
"ethcore-miner 1.11.0", "ethcore-miner 1.11.0",
"ethcore-network 1.11.0", "ethcore-network 1.11.0",
"ethcore-private-tx 1.0.0",
"ethcore-secretstore 1.0.0", "ethcore-secretstore 1.0.0",
"ethcore-service 0.1.0", "ethcore-service 0.1.0",
"ethcore-stratum 1.11.0", "ethcore-stratum 1.11.0",
@ -2145,6 +2185,7 @@ dependencies = [
"ethcore-logger 1.11.0", "ethcore-logger 1.11.0",
"ethcore-miner 1.11.0", "ethcore-miner 1.11.0",
"ethcore-network 1.11.0", "ethcore-network 1.11.0",
"ethcore-private-tx 1.0.0",
"ethcore-transaction 0.1.0", "ethcore-transaction 0.1.0",
"ethcrypto 0.1.0", "ethcrypto 0.1.0",
"ethereum-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethereum-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -42,6 +42,7 @@ ethcore-logger = { path = "logger" }
ethcore-migrations = { path = "ethcore/migrations" } ethcore-migrations = { path = "ethcore/migrations" }
ethcore-miner = { path = "miner" } ethcore-miner = { path = "miner" }
ethcore-network = { path = "util/network" } ethcore-network = { path = "util/network" }
ethcore-private-tx = { path = "ethcore/private-tx" }
ethcore-service = { path = "ethcore/service" } ethcore-service = { path = "ethcore/service" }
ethcore-stratum = { path = "stratum" } ethcore-stratum = { path = "stratum" }
ethcore-transaction = { path = "ethcore/transaction" } ethcore-transaction = { path = "ethcore/transaction" }

View File

@ -24,7 +24,7 @@ use fetch::{self, Fetch};
use futures::sync::oneshot; use futures::sync::oneshot;
use futures::{self, Future}; use futures::{self, Future};
use futures_cpupool::CpuPool; use futures_cpupool::CpuPool;
use hyper::{self, Method, StatusCode}; use hyper::{self, StatusCode};
use parking_lot::Mutex; use parking_lot::Mutex;
use endpoint::{self, EndpointPath}; use endpoint::{self, EndpointPath};
@ -261,7 +261,7 @@ impl ContentFetcherHandler {
// Validation of method // Validation of method
let status = match *method { let status = match *method {
// Start fetching content // Start fetching content
Method::Get => { hyper::Method::Get => {
trace!(target: "dapps", "Fetching content from: {:?}", url); trace!(target: "dapps", "Fetching content from: {:?}", url);
FetchState::InProgress(Self::fetch_content( FetchState::InProgress(Self::fetch_content(
pool, pool,
@ -295,7 +295,7 @@ impl ContentFetcherHandler {
) -> Box<Future<Item=FetchState, Error=()> + Send> { ) -> Box<Future<Item=FetchState, Error=()> + Send> {
// Start fetching the content // Start fetching the content
let pool2 = pool.clone(); let pool2 = pool.clone();
let future = fetch.fetch(url, abort.into()).then(move |result| { let future = fetch.get(url, abort.into()).then(move |result| {
trace!(target: "dapps", "Fetching content finished. Starting validation: {:?}", result); trace!(target: "dapps", "Fetching content finished. Starting validation: {:?}", result);
Ok(match result { Ok(match result {
Ok(response) => match installer.validate_and_install(response) { Ok(response) => match installer.validate_and_install(response) {

View File

@ -20,7 +20,7 @@ use parking_lot::Mutex;
use hyper; use hyper;
use futures::{self, Future}; use futures::{self, Future};
use fetch::{self, Fetch, Url}; use fetch::{self, Fetch, Url, Method};
pub struct FetchControl { pub struct FetchControl {
sender: mpsc::Sender<()>, sender: mpsc::Sender<()>,
@ -97,7 +97,7 @@ impl FakeFetch {
impl Fetch for FakeFetch { impl Fetch for FakeFetch {
type Result = Box<Future<Item = fetch::Response, Error = fetch::Error> + Send>; type Result = Box<Future<Item = fetch::Response, Error = fetch::Error> + Send>;
fn fetch(&self, url: &str, abort: fetch::Abort) -> Self::Result { fn fetch(&self, url: &str, _method: Method, abort: fetch::Abort) -> Self::Result {
let u = Url::parse(url).unwrap(); let u = Url::parse(url).unwrap();
self.requested.lock().push(url.into()); self.requested.lock().push(url.into());
let manual = self.manual.clone(); let manual = self.manual.clone();

View File

@ -16,6 +16,7 @@ crossbeam = "0.3"
ethash = { path = "../ethash" } ethash = { path = "../ethash" }
ethcore-bloom-journal = { path = "../util/bloom" } ethcore-bloom-journal = { path = "../util/bloom" }
ethcore-bytes = { path = "../util/bytes" } ethcore-bytes = { path = "../util/bytes" }
fetch = { path = "../util/fetch" }
hashdb = { path = "../util/hashdb" } hashdb = { path = "../util/hashdb" }
memorydb = { path = "../util/memorydb" } memorydb = { path = "../util/memorydb" }
patricia-trie = { path = "../util/patricia_trie" } patricia-trie = { path = "../util/patricia_trie" }
@ -52,6 +53,7 @@ rlp_compress = { path = "../util/rlp_compress" }
rlp_derive = { path = "../util/rlp_derive" } rlp_derive = { path = "../util/rlp_derive" }
kvdb = { path = "../util/kvdb" } kvdb = { path = "../util/kvdb" }
kvdb-memorydb = { path = "../util/kvdb-memorydb" } kvdb-memorydb = { path = "../util/kvdb-memorydb" }
kvdb-rocksdb = { path = "../util/kvdb-rocksdb" }
util-error = { path = "../util/error" } util-error = { path = "../util/error" }
snappy = { git = "https://github.com/paritytech/rust-snappy" } snappy = { git = "https://github.com/paritytech/rust-snappy" }
stop-guard = { path = "../util/stop-guard" } stop-guard = { path = "../util/stop-guard" }
@ -72,7 +74,6 @@ journaldb = { path = "../util/journaldb" }
[dev-dependencies] [dev-dependencies]
tempdir = "0.3" tempdir = "0.3"
trie-standardmap = { path = "../util/trie-standardmap" } trie-standardmap = { path = "../util/trie-standardmap" }
kvdb-rocksdb = { path = "../util/kvdb-rocksdb" }
[features] [features]
evm-debug = ["slow-blocks"] evm-debug = ["slow-blocks"]

View File

@ -0,0 +1,36 @@
[package]
description = "Parity Private Transactions"
name = "ethcore-private-tx"
version = "1.0.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
error-chain = { version = "0.11", default-features = false }
ethabi = "5.1"
ethabi-contract = "5.0"
ethabi-derive = "5.0"
ethcore = { path = ".." }
ethcore-bytes = { path = "../../util/bytes" }
ethcore-io = { path = "../../util/io" }
ethcore-logger = { path = "../../logger" }
ethcore-miner = { path = "../../miner" }
ethcore-transaction = { path = "../transaction" }
ethcrypto = { path = "../../ethcrypto" }
ethereum-types = "0.3"
ethjson = { path = "../../json" }
ethkey = { path = "../../ethkey" }
fetch = { path = "../../util/fetch" }
futures = "0.1"
keccak-hash = { path = "../../util/hash" }
log = "0.3"
parking_lot = "0.5"
patricia-trie = { path = "../../util/patricia_trie" }
rand = "0.3"
rlp = { path = "../../util/rlp" }
rlp_derive = { path = "../../util/rlp_derive" }
rustc-hex = "1.0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
tiny-keccak = "1.3"

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
[{"constant": false,"inputs": [{"name": "newState","type": "bytes"},{"name": "v","type": "uint8[]"},{"name": "r","type": "bytes32[]"},{"name": "s","type": "bytes32[]"}],"name": "setState","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": true,"inputs": [],"name": "code","outputs": [{"name": "","type": "bytes"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": true,"inputs": [{"name": "","type": "uint256"}],"name": "validators","outputs": [{"name": "","type": "address"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": true,"inputs": [],"name": "nonce","outputs": [{"name": "","type": "uint256"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": true,"inputs": [],"name": "getValidators","outputs": [{"name": "","type": "address[]"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": true,"inputs": [],"name": "state","outputs": [{"name": "","type": "bytes"}],"payable": false,"stateMutability": "view","type": "function"},{"inputs": [{"name": "initialValidators","type": "address[]"},{"name": "initialCode","type": "bytes"},{"name": "initialState","type": "bytes"}],"payable": false,"stateMutability": "nonpayable","type": "constructor"}]

View File

@ -0,0 +1,272 @@
// 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 <http://www.gnu.org/licenses/>.
//! Encryption providers.
use std::io::Read;
use std::iter::repeat;
use std::time::{Instant, Duration};
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use parking_lot::Mutex;
use ethcore::account_provider::AccountProvider;
use ethereum_types::{H128, H256, Address};
use ethjson;
use ethkey::{Signature, Public};
use ethcrypto;
use futures::Future;
use fetch::{Fetch, Client as FetchClient, Method, BodyReader};
use bytes::{Bytes, ToPretty};
use error::{Error, ErrorKind};
use super::find_account_password;
/// Initialization vector length.
const INIT_VEC_LEN: usize = 16;
/// Duration of storing retrieved keys (in ms)
const ENCRYPTION_SESSION_DURATION: u64 = 30 * 1000;
/// Trait for encryption/decryption operations.
pub trait Encryptor: Send + Sync + 'static {
/// Generate unique contract key && encrypt passed data. Encryption can only be performed once.
fn encrypt(
&self,
contract_address: &Address,
accounts: &AccountProvider,
initialisation_vector: &H128,
plain_data: &[u8],
) -> Result<Bytes, Error>;
/// Decrypt data using previously generated contract key.
fn decrypt(
&self,
contract_address: &Address,
accounts: &AccountProvider,
cypher: &[u8],
) -> Result<Bytes, Error>;
}
/// Configurtion for key server encryptor
#[derive(Default, PartialEq, Debug, Clone)]
pub struct EncryptorConfig {
/// URL to key server
pub base_url: Option<String>,
/// Key server's threshold
pub threshold: u32,
/// Account used for signing requests to key server
pub key_server_account: Option<Address>,
/// Passwords used to unlock accounts
pub passwords: Vec<String>,
}
struct EncryptionSession {
key: Bytes,
end_time: Instant,
}
/// SecretStore-based encryption/decryption operations.
pub struct SecretStoreEncryptor {
config: EncryptorConfig,
client: FetchClient,
sessions: Mutex<HashMap<Address, EncryptionSession>>,
}
impl SecretStoreEncryptor {
/// Create new encryptor
pub fn new(config: EncryptorConfig, client: FetchClient) -> Result<Self, Error> {
Ok(SecretStoreEncryptor {
config,
client,
sessions: Mutex::default(),
})
}
/// Ask secret store for key && decrypt the key.
fn retrieve_key(
&self,
url_suffix: &str,
use_post: bool,
contract_address: &Address,
accounts: &AccountProvider,
) -> Result<Bytes, Error> {
// check if the key was already cached
if let Some(key) = self.obtained_key(contract_address) {
return Ok(key);
}
let contract_address_signature = self.sign_contract_address(contract_address, accounts)?;
let requester = self.config.key_server_account.ok_or_else(|| ErrorKind::KeyServerAccountNotSet)?;
// key id in SS is H256 && we have H160 here => expand with assitional zeros
let contract_address_extended: H256 = contract_address.into();
let base_url = self.config.base_url.clone().ok_or_else(|| ErrorKind::KeyServerNotSet)?;
// prepare request url
let url = format!("{}/{}/{}{}",
base_url,
contract_address_extended.to_hex(),
contract_address_signature,
url_suffix,
);
// send HTTP request
let method = if use_post {
Method::Post
} else {
Method::Get
};
let response = self.client.fetch(&url, method, Default::default()).wait()
.map_err(|e| ErrorKind::Encrypt(e.to_string()))?;
if response.is_not_found() {
bail!(ErrorKind::EncryptionKeyNotFound(*contract_address));
}
if !response.is_success() {
bail!(ErrorKind::Encrypt(response.status().canonical_reason().unwrap_or("unknown").into()));
}
// read HTTP response
let mut result = String::new();
BodyReader::new(response).read_to_string(&mut result)?;
// response is JSON string (which is, in turn, hex-encoded, encrypted Public)
let encrypted_bytes: ethjson::bytes::Bytes = result.trim_matches('\"').parse().map_err(|e| ErrorKind::Encrypt(e))?;
let password = find_account_password(&self.config.passwords, &*accounts, &requester);
// decrypt Public
let decrypted_bytes = accounts.decrypt(requester, password, &ethcrypto::DEFAULT_MAC, &encrypted_bytes)?;
let decrypted_key = Public::from_slice(&decrypted_bytes);
// and now take x coordinate of Public as a key
let key: Bytes = (*decrypted_key)[..INIT_VEC_LEN].into();
// cache the key in the session and clear expired sessions
self.sessions.lock().insert(*contract_address, EncryptionSession{
key: key.clone(),
end_time: Instant::now() + Duration::from_millis(ENCRYPTION_SESSION_DURATION),
});
self.clean_expired_sessions();
Ok(key)
}
fn clean_expired_sessions(&self) {
let mut sessions = self.sessions.lock();
sessions.retain(|_, session| session.end_time < Instant::now());
}
fn obtained_key(&self, contract_address: &Address) -> Option<Bytes> {
let mut sessions = self.sessions.lock();
let stored_session = sessions.entry(*contract_address);
match stored_session {
Entry::Occupied(session) => {
if Instant::now() > session.get().end_time {
session.remove_entry();
None
} else {
Some(session.get().key.clone())
}
}
Entry::Vacant(_) => None,
}
}
fn sign_contract_address(&self, contract_address: &Address, accounts: &AccountProvider) -> Result<Signature, Error> {
// key id in SS is H256 && we have H160 here => expand with assitional zeros
let contract_address_extended: H256 = contract_address.into();
let key_server_account = self.config.key_server_account.ok_or_else(|| ErrorKind::KeyServerAccountNotSet)?;
let password = find_account_password(&self.config.passwords, accounts, &key_server_account);
Ok(accounts.sign(key_server_account, password, H256::from_slice(&contract_address_extended))?)
}
}
impl Encryptor for SecretStoreEncryptor {
fn encrypt(
&self,
contract_address: &Address,
accounts: &AccountProvider,
initialisation_vector: &H128,
plain_data: &[u8],
) -> Result<Bytes, Error> {
// retrieve the key, try to generate it if it doesn't exist yet
let key = match self.retrieve_key("", false, contract_address, &*accounts) {
Ok(key) => Ok(key),
Err(Error(ErrorKind::EncryptionKeyNotFound(_), _)) => {
trace!("Key for account wasnt found in sstore. Creating. Address: {:?}", contract_address);
self.retrieve_key(&format!("/{}", self.config.threshold), true, contract_address, &*accounts)
}
Err(err) => Err(err),
}?;
// encrypt data
let mut cypher = Vec::with_capacity(plain_data.len() + initialisation_vector.len());
cypher.extend(repeat(0).take(plain_data.len()));
ethcrypto::aes::encrypt(&key, initialisation_vector, plain_data, &mut cypher);
cypher.extend_from_slice(&initialisation_vector);
Ok(cypher)
}
/// Decrypt data using previously generated contract key.
fn decrypt(
&self,
contract_address: &Address,
accounts: &AccountProvider,
cypher: &[u8],
) -> Result<Bytes, Error> {
// initialization vector takes INIT_VEC_LEN bytes
let cypher_len = cypher.len();
if cypher_len < INIT_VEC_LEN {
bail!(ErrorKind::Decrypt("Invalid cypher".into()));
}
// retrieve existing key
let key = self.retrieve_key("", false, contract_address, accounts)?;
// use symmetric decryption to decrypt document
let (cypher, iv) = cypher.split_at(cypher_len - INIT_VEC_LEN);
let mut plain_data = Vec::with_capacity(cypher_len - INIT_VEC_LEN);
plain_data.extend(repeat(0).take(cypher_len - INIT_VEC_LEN));
ethcrypto::aes::decrypt(&key, &iv, cypher, &mut plain_data);
Ok(plain_data)
}
}
/// Dummy encryptor.
#[derive(Default)]
pub struct NoopEncryptor;
impl Encryptor for NoopEncryptor {
fn encrypt(
&self,
_contract_address: &Address,
_accounts: &AccountProvider,
_initialisation_vector: &H128,
data: &[u8],
) -> Result<Bytes, Error> {
Ok(data.to_vec())
}
fn decrypt(
&self,
_contract_address: &Address,
_accounts: &AccountProvider,
data: &[u8],
) -> Result<Bytes, Error> {
Ok(data.to_vec())
}
}

View File

@ -0,0 +1,208 @@
// 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 <http://www.gnu.org/licenses/>.
use ethereum_types::Address;
use rlp::DecoderError;
use trie::TrieError;
use ethcore::account_provider::SignError;
use ethcore::error::{Error as EthcoreError, ExecutionError};
use transaction::Error as TransactionError;
use ethkey::Error as KeyError;
error_chain! {
foreign_links {
Io(::std::io::Error) #[doc = "Error concerning the Rust standard library's IO subsystem."];
Decoder(DecoderError) #[doc = "RLP decoding error."];
Trie(TrieError) #[doc = "Error concerning TrieDBs."];
}
errors {
#[doc = "Encryption error."]
Encrypt(err: String) {
description("Encryption error"),
display("Encryption error. ({})", err),
}
#[doc = "Decryption error."]
Decrypt(err: String) {
description("Decryption error"),
display("Decryption error. ({})", err),
}
#[doc = "Address not authorized."]
NotAuthorised(address: Address) {
description("Address not authorized"),
display("Private transaction execution is not authorised for {}", address),
}
#[doc = "Transaction creates more than one contract."]
TooManyContracts {
description("Transaction creates more than one contract."),
display("Private transaction created too many contracts"),
}
#[doc = "Contract call error."]
Call(err: String) {
description("Contract call error."),
display("Contract call error. ({})", err),
}
#[doc = "State is not available."]
StatePruned {
description("State is not available."),
display("State is not available"),
}
#[doc = "State is incorrect."]
StateIncorrect {
description("State is incorrect."),
display("State is incorrect"),
}
#[doc = "Wrong private transaction type."]
BadTransactonType {
description("Wrong private transaction type."),
display("Wrong private transaction type"),
}
#[doc = "Contract does not exist or was not created."]
ContractDoesNotExist {
description("Contract does not exist or was not created."),
display("Contract does not exist or was not created"),
}
#[doc = "Reference to the client is corrupted."]
ClientIsMalformed {
description("Reference to the client is corrupted."),
display("Reference to the client is corrupted"),
}
#[doc = "Queue of private transactions for verification is full."]
QueueIsFull {
description("Queue of private transactions for verification is full."),
display("Queue of private transactions for verification is full"),
}
#[doc = "The transaction already exists in queue of private transactions."]
PrivateTransactionAlreadyImported {
description("The transaction already exists in queue of private transactions."),
display("The transaction already exists in queue of private transactions."),
}
#[doc = "The information about private transaction is not found in the store."]
PrivateTransactionNotFound {
description("The information about private transaction is not found in the store."),
display("The information about private transaction is not found in the store."),
}
#[doc = "Account for signing public transactions not set."]
SignerAccountNotSet {
description("Account for signing public transactions not set."),
display("Account for signing public transactions not set."),
}
#[doc = "Account for validating private transactions not set."]
ValidatorAccountNotSet {
description("Account for validating private transactions not set."),
display("Account for validating private transactions not set."),
}
#[doc = "Account for signing requests to key server not set."]
KeyServerAccountNotSet {
description("Account for signing requests to key server not set."),
display("Account for signing requests to key server not set."),
}
#[doc = "Encryption key is not found on key server."]
EncryptionKeyNotFound(address: Address) {
description("Encryption key is not found on key server"),
display("Encryption key is not found on key server for {}", address),
}
#[doc = "Key server URL is not set."]
KeyServerNotSet {
description("Key server URL is not set."),
display("Key server URL is not set."),
}
#[doc = "VM execution error."]
Execution(err: ExecutionError) {
description("VM execution error."),
display("VM execution error {}", err),
}
#[doc = "General signing error."]
Key(err: KeyError) {
description("General signing error."),
display("General signing error {}", err),
}
#[doc = "Account provider signing error."]
Sign(err: SignError) {
description("Account provider signing error."),
display("Account provider signing error {}", err),
}
#[doc = "Error of transactions processing."]
Transaction(err: TransactionError) {
description("Error of transactions processing."),
display("Error of transactions processing {}", err),
}
#[doc = "General ethcore error."]
Ethcore(err: EthcoreError) {
description("General ethcore error."),
display("General ethcore error {}", err),
}
}
}
impl From<SignError> for Error {
fn from(err: SignError) -> Self {
ErrorKind::Sign(err).into()
}
}
impl From<KeyError> for Error {
fn from(err: KeyError) -> Self {
ErrorKind::Key(err).into()
}
}
impl From<ExecutionError> for Error {
fn from(err: ExecutionError) -> Self {
ErrorKind::Execution(err).into()
}
}
impl From<TransactionError> for Error {
fn from(err: TransactionError) -> Self {
ErrorKind::Transaction(err).into()
}
}
impl From<EthcoreError> for Error {
fn from(err: EthcoreError) -> Self {
ErrorKind::Ethcore(err).into()
}
}
impl<E> From<Box<E>> for Error where Error: From<E> {
fn from(err: Box<E>) -> Error {
Error::from(*err)
}
}

View File

@ -0,0 +1,676 @@
// 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 <http://www.gnu.org/licenses/>.
//! Private transactions module.
// Recursion limit required because of
// error_chain foreign_links.
#![recursion_limit="256"]
mod encryptor;
mod private_transactions;
mod messages;
mod error;
extern crate ethcore;
extern crate ethcore_io as io;
extern crate ethcore_bytes as bytes;
extern crate ethcore_transaction as transaction;
extern crate ethcore_miner;
extern crate ethcrypto;
extern crate ethabi;
extern crate ethereum_types;
extern crate ethkey;
extern crate ethjson;
extern crate fetch;
extern crate futures;
extern crate keccak_hash as hash;
extern crate parking_lot;
extern crate patricia_trie as trie;
extern crate rlp;
extern crate rustc_hex;
#[macro_use]
extern crate log;
#[macro_use]
extern crate ethabi_derive;
#[macro_use]
extern crate ethabi_contract;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate rlp_derive;
#[cfg(test)]
extern crate rand;
#[cfg(test)]
extern crate ethcore_logger;
pub use encryptor::{Encryptor, SecretStoreEncryptor, EncryptorConfig, NoopEncryptor};
pub use private_transactions::{PrivateTransactionDesc, VerificationStore, PrivateTransactionSigningDesc, SigningStore};
pub use messages::{PrivateTransaction, SignedPrivateTransaction};
pub use error::{Error, ErrorKind};
use std::sync::{Arc, Weak};
use std::collections::{HashMap, HashSet};
use ethereum_types::{H128, H256, U256, Address};
use hash::keccak;
use rlp::*;
use parking_lot::{Mutex, RwLock};
use bytes::Bytes;
use ethkey::{Signature, recover, public_to_address};
use io::IoChannel;
use ethcore::executive::{Executive, TransactOptions};
use ethcore::executed::{Executed};
use transaction::{SignedTransaction, Transaction, Action, UnverifiedTransaction};
use ethcore::{contract_address as ethcore_contract_address};
use ethcore::client::{
Client, ChainNotify, ChainMessageType, ClientIoMessage, BlockId,
MiningBlockChainClient, ChainInfo, Nonce, CallContract
};
use ethcore::account_provider::AccountProvider;
use ethcore_miner::transaction_queue::{TransactionDetailsProvider as TransactionQueueDetailsProvider, AccountDetails};
use ethcore::miner::MinerService;
use ethcore::trace::{Tracer, VMTracer};
use rustc_hex::FromHex;
// Source avaiable at https://github.com/parity-contracts/private-tx/blob/master/contracts/PrivateContract.sol
const DEFAULT_STUB_CONTRACT: &'static str = include_str!("../res/private.evm");
use_contract!(private, "PrivateContract", "res/private.json");
/// Initialization vector length.
const INIT_VEC_LEN: usize = 16;
struct TransactionDetailsProvider<'a> {
client: &'a MiningBlockChainClient,
}
impl<'a> TransactionDetailsProvider<'a> {
pub fn new(client: &'a MiningBlockChainClient) -> Self {
TransactionDetailsProvider {
client: client,
}
}
}
impl<'a> TransactionQueueDetailsProvider for TransactionDetailsProvider<'a> {
fn fetch_account(&self, address: &Address) -> AccountDetails {
AccountDetails {
nonce: self.client.latest_nonce(address),
balance: self.client.latest_balance(address),
}
}
fn estimate_gas_required(&self, tx: &SignedTransaction) -> U256 {
tx.gas_required(&self.client.latest_schedule()).into()
}
fn is_service_transaction_acceptable(&self, _tx: &SignedTransaction) -> Result<bool, String> {
Ok(false)
}
}
/// Configurtion for private transaction provider
#[derive(Default, PartialEq, Debug, Clone)]
pub struct ProviderConfig {
/// Accounts that can be used for validation
pub validator_accounts: Vec<Address>,
/// Account used for signing public transactions created from private transactions
pub signer_account: Option<Address>,
/// Passwords used to unlock accounts
pub passwords: Vec<String>,
}
#[derive(Debug)]
/// Private transaction execution receipt.
pub struct Receipt {
/// Private transaction hash.
pub hash: H256,
/// Created contract address if any.
pub contract_address: Option<Address>,
/// Execution status.
pub status_code: u8,
}
/// Manager of private transactions
pub struct Provider {
encryptor: Box<Encryptor>,
validator_accounts: HashSet<Address>,
signer_account: Option<Address>,
passwords: Vec<String>,
notify: RwLock<Vec<Weak<ChainNotify>>>,
transactions_for_signing: Mutex<SigningStore>,
transactions_for_verification: Mutex<VerificationStore>,
client: Arc<Client>,
accounts: Arc<AccountProvider>,
channel: IoChannel<ClientIoMessage>,
}
#[derive(Debug)]
pub struct PrivateExecutionResult<T, V> where T: Tracer, V: VMTracer {
code: Option<Bytes>,
state: Bytes,
contract_address: Option<Address>,
result: Executed<T::Output, V::Output>,
}
impl Provider where {
/// Create a new provider.
pub fn new(
client: Arc<Client>,
accounts: Arc<AccountProvider>,
encryptor: Box<Encryptor>,
config: ProviderConfig,
channel: IoChannel<ClientIoMessage>,
) -> Result<Self, Error> {
Ok(Provider {
encryptor,
validator_accounts: config.validator_accounts.into_iter().collect(),
signer_account: config.signer_account,
passwords: config.passwords,
notify: RwLock::default(),
transactions_for_signing: Mutex::default(),
transactions_for_verification: Mutex::default(),
client,
accounts,
channel,
})
}
// TODO [ToDr] Don't use `ChainNotify` here!
// Better to create a separate notification type for this.
/// Adds an actor to be notified on certain events
pub fn add_notify(&self, target: Arc<ChainNotify>) {
self.notify.write().push(Arc::downgrade(&target));
}
fn notify<F>(&self, f: F) where F: Fn(&ChainNotify) {
for np in self.notify.read().iter() {
if let Some(n) = np.upgrade() {
f(&*n);
}
}
}
/// 1. Create private transaction from the signed transaction
/// 2. Executes private transaction
/// 3. Save it with state returned on prev step to the queue for signing
/// 4. Broadcast corresponding message to the chain
pub fn create_private_transaction(&self, signed_transaction: SignedTransaction) -> Result<Receipt, Error> {
trace!("Creating private transaction from regular transaction: {:?}", signed_transaction);
if self.signer_account.is_none() {
trace!("Signing account not set");
bail!(ErrorKind::SignerAccountNotSet);
}
let tx_hash = signed_transaction.hash();
match signed_transaction.action {
Action::Create => {
bail!(ErrorKind::BadTransactonType);
}
Action::Call(contract) => {
let data = signed_transaction.rlp_bytes();
let encrypted_transaction = self.encrypt(&contract, &Self::iv_from_transaction(&signed_transaction), &data)?;
let private = PrivateTransaction {
encrypted: encrypted_transaction,
contract,
};
// TODO [ToDr] Using BlockId::Latest is bad here,
// the block may change in the middle of execution
// causing really weird stuff to happen.
// We should retrieve hash and stick to that. IMHO
// best would be to change the API and only allow H256 instead of BlockID
// in private-tx to avoid such mistakes.
let contract_nonce = self.get_contract_nonce(&contract, BlockId::Latest)?;
let private_state = self.execute_private_transaction(BlockId::Latest, &signed_transaction)?;
trace!("Private transaction created, encrypted transaction: {:?}, private state: {:?}", private, private_state);
let contract_validators = self.get_validators(BlockId::Latest, &contract)?;
trace!("Required validators: {:?}", contract_validators);
let private_state_hash = self.calculate_state_hash(&private_state, contract_nonce);
trace!("Hashed effective private state for sender: {:?}", private_state_hash);
self.transactions_for_signing.lock().add_transaction(private.hash(), signed_transaction, contract_validators, private_state, contract_nonce)?;
self.broadcast_private_transaction(private.rlp_bytes().into_vec());
Ok(Receipt {
hash: tx_hash,
contract_address: None,
status_code: 0,
})
}
}
}
/// Calculate hash from united private state and contract nonce
pub fn calculate_state_hash(&self, state: &Bytes, nonce: U256) -> H256 {
let state_hash = keccak(state);
let mut state_buf = [0u8; 64];
state_buf[..32].clone_from_slice(&state_hash);
state_buf[32..].clone_from_slice(&H256::from(nonce));
keccak(&state_buf.as_ref())
}
/// Extract signed transaction from private transaction
fn extract_original_transaction(&self, private: PrivateTransaction, contract: &Address) -> Result<UnverifiedTransaction, Error> {
let encrypted_transaction = private.encrypted;
let transaction_bytes = self.decrypt(contract, &encrypted_transaction)?;
let original_transaction: UnverifiedTransaction = UntrustedRlp::new(&transaction_bytes).as_val()?;
Ok(original_transaction)
}
/// Process received private transaction
pub fn import_private_transaction(&self, rlp: &[u8]) -> Result<(), Error> {
trace!("Private transaction received");
let private_tx: PrivateTransaction = UntrustedRlp::new(rlp).as_val()?;
let contract = private_tx.contract;
let contract_validators = self.get_validators(BlockId::Latest, &contract)?;
let validation_account = contract_validators
.iter()
.find(|address| self.validator_accounts.contains(address));
match validation_account {
None => {
// Not for verification, broadcast further to peers
self.broadcast_private_transaction(rlp.into());
return Ok(());
},
Some(&validation_account) => {
let hash = private_tx.hash();
trace!("Private transaction taken for verification");
let original_tx = self.extract_original_transaction(private_tx, &contract)?;
trace!("Validating transaction: {:?}", original_tx);
let details_provider = TransactionDetailsProvider::new(&*self.client as &MiningBlockChainClient);
let insertion_time = self.client.chain_info().best_block_number;
// Verify with the first account available
trace!("The following account will be used for verification: {:?}", validation_account);
self.transactions_for_verification.lock()
.add_transaction(original_tx, contract, validation_account, hash, &details_provider, insertion_time)?;
self.channel.send(ClientIoMessage::NewPrivateTransaction).map_err(|_| ErrorKind::ClientIsMalformed.into())
}
}
}
/// Private transaction for validation added into queue
pub fn on_private_transaction_queued(&self) -> Result<(), Error> {
self.process_queue()
}
/// Retrieve and verify the first available private transaction for every sender
fn process_queue(&self) -> Result<(), Error> {
let mut verification_queue = self.transactions_for_verification.lock();
let ready_transactions = verification_queue.ready_transactions();
let fetch_nonce = |a: &Address| self.client.latest_nonce(a);
for transaction in ready_transactions {
let transaction_hash = transaction.hash();
match verification_queue.private_transaction_descriptor(&transaction_hash) {
Ok(desc) => {
if !self.validator_accounts.contains(&desc.validator_account) {
trace!("Cannot find validator account in config");
bail!(ErrorKind::ValidatorAccountNotSet);
}
let account = desc.validator_account;
if let Action::Call(contract) = transaction.action {
let contract_nonce = self.get_contract_nonce(&contract, BlockId::Latest)?;
let private_state = self.execute_private_transaction(BlockId::Latest, &transaction)?;
let private_state_hash = self.calculate_state_hash(&private_state, contract_nonce);
trace!("Hashed effective private state for validator: {:?}", private_state_hash);
let password = find_account_password(&self.passwords, &*self.accounts, &account);
let signed_state = self.accounts.sign(account, password, private_state_hash)?;
let signed_private_transaction = SignedPrivateTransaction::new(desc.private_hash, signed_state, None);
trace!("Sending signature for private transaction: {:?}", signed_private_transaction);
self.broadcast_signed_private_transaction(signed_private_transaction.rlp_bytes().into_vec());
} else {
trace!("Incorrect type of action for the transaction");
bail!(ErrorKind::BadTransactonType);
}
},
Err(e) => {
trace!("Cannot retrieve descriptor for transaction with error {:?}", e);
bail!(e);
}
}
verification_queue.remove_private_transaction(&transaction_hash, &fetch_nonce);
}
Ok(())
}
/// Add signed private transaction into the store
/// Creates corresponding public transaction if last required singature collected and sends it to the chain
pub fn import_signed_private_transaction(&self, rlp: &[u8]) -> Result<(), Error> {
let tx: SignedPrivateTransaction = UntrustedRlp::new(rlp).as_val()?;
trace!("Signature for private transaction received: {:?}", tx);
let private_hash = tx.private_transaction_hash();
let desc = match self.transactions_for_signing.lock().get(&private_hash) {
None => {
// Not our transaction, broadcast further to peers
self.broadcast_signed_private_transaction(rlp.into());
return Ok(());
},
Some(desc) => desc,
};
let last = self.last_required_signature(&desc, tx.signature())?;
if last {
let mut signatures = desc.received_signatures.clone();
signatures.push(tx.signature());
let rsv: Vec<Signature> = signatures.into_iter().map(|sign| sign.into_electrum().into()).collect();
//Create public transaction
let public_tx = self.public_transaction(
desc.state.clone(),
&desc.original_transaction,
&rsv,
desc.original_transaction.nonce,
desc.original_transaction.gas_price
)?;
trace!("Last required signature received, public transaction created: {:?}", public_tx);
//Sign and add it to the queue
let chain_id = desc.original_transaction.chain_id();
let hash = public_tx.hash(chain_id);
let signer_account = self.signer_account.ok_or_else(|| ErrorKind::SignerAccountNotSet)?;
let password = find_account_password(&self.passwords, &*self.accounts, &signer_account);
let signature = self.accounts.sign(signer_account, password, hash)?;
let signed = SignedTransaction::new(public_tx.with_signature(signature, chain_id))?;
match self.client.miner().import_own_transaction(&*self.client, signed.into()) {
Ok(_) => trace!("Public transaction added to queue"),
Err(err) => {
trace!("Failed to add transaction to queue, error: {:?}", err);
bail!(err);
}
}
//Remove from store for signing
match self.transactions_for_signing.lock().remove(&private_hash) {
Ok(_) => {}
Err(err) => {
trace!("Failed to remove transaction from signing store, error: {:?}", err);
bail!(err);
}
}
} else {
//Add signature to the store
match self.transactions_for_signing.lock().add_signature(&private_hash, tx.signature()) {
Ok(_) => trace!("Signature stored for private transaction"),
Err(err) => {
trace!("Failed to add signature to signing store, error: {:?}", err);
bail!(err);
}
}
}
Ok(())
}
fn last_required_signature(&self, desc: &PrivateTransactionSigningDesc, sign: Signature) -> Result<bool, Error> {
if desc.received_signatures.contains(&sign) {
return Ok(false);
}
let state_hash = self.calculate_state_hash(&desc.state, desc.contract_nonce);
match recover(&sign, &state_hash) {
Ok(public) => {
let sender = public_to_address(&public);
match desc.validators.contains(&sender) {
true => {
Ok(desc.received_signatures.len() + 1 == desc.validators.len())
}
false => {
trace!("Sender's state doesn't correspond to validator's");
bail!(ErrorKind::StateIncorrect);
}
}
}
Err(err) => {
trace!("Sender's state doesn't correspond to validator's, error {:?}", err);
bail!(err);
}
}
}
/// Broadcast the private transaction message to the chain
fn broadcast_private_transaction(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(ChainMessageType::PrivateTransaction(message.clone())));
}
/// Broadcast signed private transaction message to the chain
fn broadcast_signed_private_transaction(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(ChainMessageType::SignedPrivateTransaction(message.clone())));
}
fn iv_from_transaction(transaction: &SignedTransaction) -> H128 {
let nonce = keccak(&transaction.nonce.rlp_bytes());
let (iv, _) = nonce.split_at(INIT_VEC_LEN);
H128::from_slice(iv)
}
fn iv_from_address(contract_address: &Address) -> H128 {
let address = keccak(&contract_address.rlp_bytes());
let (iv, _) = address.split_at(INIT_VEC_LEN);
H128::from_slice(iv)
}
fn encrypt(&self, contract_address: &Address, initialisation_vector: &H128, data: &[u8]) -> Result<Bytes, Error> {
trace!("Encrypt data using key(address): {:?}", contract_address);
Ok(self.encryptor.encrypt(contract_address, &*self.accounts, initialisation_vector, data)?)
}
fn decrypt(&self, contract_address: &Address, data: &[u8]) -> Result<Bytes, Error> {
trace!("Decrypt data using key(address): {:?}", contract_address);
Ok(self.encryptor.decrypt(contract_address, &*self.accounts, data)?)
}
fn get_decrypted_state(&self, address: &Address, block: BlockId) -> Result<Bytes, Error> {
let contract = private::PrivateContract::default();
let state = contract.functions()
.state()
.call(&|data| self.client.call_contract(block, *address, data))
.map_err(|e| ErrorKind::Call(format!("Contract call failed {:?}", e)))?;
self.decrypt(address, &state)
}
fn get_decrypted_code(&self, address: &Address, block: BlockId) -> Result<Bytes, Error> {
let contract = private::PrivateContract::default();
let code = contract.functions()
.code()
.call(&|data| self.client.call_contract(block, *address, data))
.map_err(|e| ErrorKind::Call(format!("Contract call failed {:?}", e)))?;
self.decrypt(address, &code)
}
pub fn get_contract_nonce(&self, address: &Address, block: BlockId) -> Result<U256, Error> {
let contract = private::PrivateContract::default();
Ok(contract.functions()
.nonce()
.call(&|data| self.client.call_contract(block, *address, data))
.map_err(|e| ErrorKind::Call(format!("Contract call failed {:?}", e)))?)
}
fn snapshot_to_storage(raw: Bytes) -> HashMap<H256, H256> {
let items = raw.len() / 64;
(0..items).map(|i| {
let offset = i * 64;
let key = H256::from_slice(&raw[offset..(offset + 32)]);
let value = H256::from_slice(&raw[(offset + 32)..(offset + 64)]);
(key, value)
}).collect()
}
fn snapshot_from_storage(storage: &HashMap<H256, H256>) -> Bytes {
let mut raw = Vec::with_capacity(storage.len() * 64);
for (key, value) in storage {
raw.extend_from_slice(key);
raw.extend_from_slice(value);
};
raw
}
pub fn execute_private<T, V>(&self, transaction: &SignedTransaction, options: TransactOptions<T, V>, block: BlockId) -> Result<PrivateExecutionResult<T, V>, Error>
where
T: Tracer,
V: VMTracer,
{
let mut env_info = self.client.env_info(block).ok_or(ErrorKind::StatePruned)?;
env_info.gas_limit = transaction.gas;
let mut state = self.client.state_at(block).ok_or(ErrorKind::StatePruned)?;
// TODO: in case of BlockId::Latest these need to operate on the same state
let contract_address = match transaction.action {
Action::Call(ref contract_address) => {
let contract_code = Arc::new(self.get_decrypted_code(contract_address, block)?);
let contract_state = self.get_decrypted_state(contract_address, block)?;
trace!("Patching contract at {:?}, code: {:?}, state: {:?}", contract_address, contract_code, contract_state);
state.patch_account(contract_address, contract_code, Self::snapshot_to_storage(contract_state))?;
Some(*contract_address)
},
Action::Create => None,
};
let engine = self.client.engine();
let contract_address = contract_address.or({
let sender = transaction.sender();
let nonce = state.nonce(&sender)?;
let (new_address, _) = ethcore_contract_address(engine.create_address_scheme(env_info.number), &sender, &nonce, &transaction.data);
Some(new_address)
});
let result = Executive::new(&mut state, &env_info, engine.machine()).transact_virtual(transaction, options)?;
let (encrypted_code, encrypted_storage) = match contract_address {
None => bail!(ErrorKind::ContractDoesNotExist),
Some(address) => {
let (code, storage) = state.into_account(&address)?;
let enc_code = match code {
Some(c) => Some(self.encrypt(&address, &Self::iv_from_address(&address), &c)?),
None => None,
};
(enc_code, self.encrypt(&address, &Self::iv_from_transaction(transaction), &Self::snapshot_from_storage(&storage))?)
},
};
trace!("Private contract executed. code: {:?}, state: {:?}, result: {:?}", encrypted_code, encrypted_storage, result.output);
Ok(PrivateExecutionResult {
code: encrypted_code,
state: encrypted_storage,
contract_address,
result,
})
}
fn generate_constructor(validators: &[Address], code: Bytes, storage: Bytes) -> Bytes {
let constructor_code = DEFAULT_STUB_CONTRACT.from_hex().expect("Default contract code is valid");
let private = private::PrivateContract::default();
private.constructor(constructor_code, validators.iter().map(|a| *a).collect::<Vec<Address>>(), code, storage)
}
fn generate_set_state_call(signatures: &[Signature], storage: Bytes) -> Bytes {
let private = private::PrivateContract::default();
private.functions().set_state().input(
storage,
signatures.iter().map(|s| {
let mut v: [u8; 32] = [0; 32];
v[31] = s.v();
v
}).collect::<Vec<[u8; 32]>>(),
signatures.iter().map(|s| s.r()).collect::<Vec<&[u8]>>(),
signatures.iter().map(|s| s.s()).collect::<Vec<&[u8]>>()
)
}
/// Returns the key from the key server associated with the contract
pub fn contract_key_id(&self, contract_address: &Address) -> Result<H256, Error> {
//current solution uses contract address extended with 0 as id
let contract_address_extended: H256 = contract_address.into();
Ok(H256::from_slice(&contract_address_extended))
}
/// Create encrypted public contract deployment transaction.
pub fn public_creation_transaction(&self, block: BlockId, source: &SignedTransaction, validators: &[Address], gas_price: U256) -> Result<(Transaction, Option<Address>), Error> {
if let Action::Call(_) = source.action {
bail!(ErrorKind::BadTransactonType);
}
let sender = source.sender();
let state = self.client.state_at(block).ok_or(ErrorKind::StatePruned)?;
let nonce = state.nonce(&sender)?;
let executed = self.execute_private(source, TransactOptions::with_no_tracing(), block)?;
let gas: u64 = 650000 +
validators.len() as u64 * 30000 +
executed.code.as_ref().map_or(0, |c| c.len() as u64) * 8000 +
executed.state.len() as u64 * 8000;
Ok((Transaction {
nonce: nonce,
action: Action::Create,
gas: gas.into(),
gas_price: gas_price,
value: source.value,
data: Self::generate_constructor(validators, executed.code.unwrap_or_default(), executed.state)
},
executed.contract_address))
}
/// Create encrypted public contract deployment transaction. Returns updated encrypted state.
pub fn execute_private_transaction(&self, block: BlockId, source: &SignedTransaction) -> Result<Bytes, Error> {
if let Action::Create = source.action {
bail!(ErrorKind::BadTransactonType);
}
let result = self.execute_private(source, TransactOptions::with_no_tracing(), block)?;
Ok(result.state)
}
/// Create encrypted public transaction from private transaction.
pub fn public_transaction(&self, state: Bytes, source: &SignedTransaction, signatures: &[Signature], nonce: U256, gas_price: U256) -> Result<Transaction, Error> {
let gas: u64 = 650000 + state.len() as u64 * 8000 + signatures.len() as u64 * 50000;
Ok(Transaction {
nonce: nonce,
action: source.action.clone(),
gas: gas.into(),
gas_price: gas_price,
value: 0.into(),
data: Self::generate_set_state_call(signatures, state)
})
}
/// Call into private contract.
pub fn private_call(&self, block: BlockId, transaction: &SignedTransaction) -> Result<Executed, Error> {
let result = self.execute_private(transaction, TransactOptions::with_no_tracing(), block)?;
Ok(result.result)
}
/// Returns private validators for a contract.
pub fn get_validators(&self, block: BlockId, address: &Address) -> Result<Vec<Address>, Error> {
let contract = private::PrivateContract::default();
Ok(contract.functions()
.get_validators()
.call(&|data| self.client.call_contract(block, *address, data))
.map_err(|e| ErrorKind::Call(format!("Contract call failed {:?}", e)))?)
}
}
/// Try to unlock account using stored password, return found password if any
fn find_account_password(passwords: &Vec<String>, account_provider: &AccountProvider, account: &Address) -> Option<String> {
for password in passwords {
if let Ok(true) = account_provider.test_password(account, password) {
return Some(password.clone());
}
}
None
}
impl ChainNotify for Provider {
fn new_blocks(&self, imported: Vec<H256>, _invalid: Vec<H256>, _enacted: Vec<H256>, _retracted: Vec<H256>, _sealed: Vec<H256>, _proposed: Vec<Bytes>, _duration: u64) {
if !imported.is_empty() {
trace!("New blocks imported, try to prune the queue");
if let Err(err) = self.process_queue() {
trace!("Cannot prune private transactions queue. error: {:?}", err);
}
}
}
}

View File

@ -0,0 +1,76 @@
// 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 <http://www.gnu.org/licenses/>.
use ethereum_types::{H256, U256, Address};
use bytes::Bytes;
use hash::keccak;
use rlp::Encodable;
use ethkey::Signature;
use transaction::signature::{add_chain_replay_protection, check_replay_protection};
/// Message with private transaction encrypted
#[derive(Default, Debug, Clone, PartialEq, RlpEncodable, RlpDecodable, Eq)]
pub struct PrivateTransaction {
/// Encrypted data
pub encrypted: Bytes,
/// Address of the contract
pub contract: Address,
}
impl PrivateTransaction {
/// Compute hash on private transaction
pub fn hash(&self) -> H256 {
keccak(&*self.rlp_bytes())
}
}
/// Message about private transaction's signing
#[derive(Default, Debug, Clone, PartialEq, RlpEncodable, RlpDecodable, Eq)]
pub struct SignedPrivateTransaction {
/// Hash of the corresponding private transaction
private_transaction_hash: H256,
/// Signature of the validator
/// The V field of the signature
v: u64,
/// The R field of the signature
r: U256,
/// The S field of the signature
s: U256,
}
impl SignedPrivateTransaction {
/// Construct a signed private transaction message
pub fn new(private_transaction_hash: H256, sig: Signature, chain_id: Option<u64>) -> Self {
SignedPrivateTransaction {
private_transaction_hash: private_transaction_hash,
r: sig.r().into(),
s: sig.s().into(),
v: add_chain_replay_protection(sig.v() as u64, chain_id),
}
}
pub fn standard_v(&self) -> u8 { check_replay_protection(self.v) }
/// Construct a signature object from the sig.
pub fn signature(&self) -> Signature {
Signature::from_rsv(&self.r.into(), &self.s.into(), self.standard_v())
}
/// Get the hash of of the original transaction.
pub fn private_transaction_hash(&self) -> H256 {
self.private_transaction_hash
}
}

View File

@ -0,0 +1,173 @@
// 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 <http://www.gnu.org/licenses/>.
use ethkey::Signature;
use bytes::Bytes;
use std::collections::HashMap;
use ethereum_types::{H256, U256, Address};
use transaction::{UnverifiedTransaction, SignedTransaction};
use ethcore_miner::transaction_queue::{TransactionQueue, RemovalReason,
TransactionDetailsProvider as TransactionQueueDetailsProvider, TransactionOrigin};
use error::{Error, ErrorKind};
use ethcore::header::BlockNumber;
/// Maximum length for private transactions queues.
const MAX_QUEUE_LEN: usize = 8312;
/// Desriptor for private transaction stored in queue for verification
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct PrivateTransactionDesc {
/// Hash of the private transaction
pub private_hash: H256,
/// Contract's address used in private transaction
pub contract: Address,
/// Address that should be used for verification
pub validator_account: Address,
}
/// Storage for private transactions for verification
#[derive(Default)]
pub struct VerificationStore {
/// Descriptors for private transactions in queue for verification with key - hash of the original transaction
descriptors: HashMap<H256, PrivateTransactionDesc>,
/// Queue with transactions for verification
transactions: TransactionQueue,
}
impl VerificationStore {
/// Adds private transaction for verification into the store
pub fn add_transaction(
&mut self,
transaction: UnverifiedTransaction,
contract: Address,
validator_account: Address,
private_hash: H256,
details_provider: &TransactionQueueDetailsProvider,
insertion_time: BlockNumber,
) -> Result<(), Error> {
if self.descriptors.len() > MAX_QUEUE_LEN {
bail!(ErrorKind::QueueIsFull);
}
if self.descriptors.get(&transaction.hash()).is_some() {
bail!(ErrorKind::PrivateTransactionAlreadyImported);
}
let transaction_hash = transaction.hash();
let signed_transaction = SignedTransaction::new(transaction)?;
self.transactions
.add(signed_transaction, TransactionOrigin::External, insertion_time, None, details_provider)
.and_then(|_| {
self.descriptors.insert(transaction_hash, PrivateTransactionDesc{
private_hash,
contract,
validator_account,
});
Ok(())
})
.map_err(Into::into)
}
/// Returns transactions ready for verification
/// Returns only one transaction per sender because several cannot be verified in a row without verification from other peers
pub fn ready_transactions(&self) -> Vec<SignedTransaction> {
// TODO [ToDr] Performance killer, re-work with new transaction queue.
let mut transactions = self.transactions.top_transactions();
// TODO [ToDr] Potential issue (create low address to have your transactions processed first)
transactions.sort_by(|a, b| a.sender().cmp(&b.sender()));
transactions.dedup_by(|a, b| a.sender().eq(&b.sender()));
transactions
}
/// Returns descriptor of the corresponding private transaction
pub fn private_transaction_descriptor(&self, transaction_hash: &H256) -> Result<&PrivateTransactionDesc, Error> {
self.descriptors.get(transaction_hash).ok_or(ErrorKind::PrivateTransactionNotFound.into())
}
/// Remove transaction from the queue for verification
pub fn remove_private_transaction<F>(&mut self, transaction_hash: &H256, fetch_nonce: &F)
where F: Fn(&Address) -> U256 {
self.descriptors.remove(transaction_hash);
self.transactions.remove(transaction_hash, fetch_nonce, RemovalReason::Invalid);
}
}
/// Desriptor for private transaction stored in queue for signing
#[derive(Debug, Clone)]
pub struct PrivateTransactionSigningDesc {
/// Original unsigned transaction
pub original_transaction: SignedTransaction,
/// Supposed validators from the contract
pub validators: Vec<Address>,
/// Already obtained signatures
pub received_signatures: Vec<Signature>,
/// State after transaction execution to compare further with received from validators
pub state: Bytes,
/// Build-in nonce of the contract
pub contract_nonce: U256,
}
/// Storage for private transactions for signing
#[derive(Default)]
pub struct SigningStore {
/// Transactions and descriptors for signing
transactions: HashMap<H256, PrivateTransactionSigningDesc>,
}
impl SigningStore {
/// Adds new private transaction into the store for signing
pub fn add_transaction(
&mut self,
private_hash: H256,
transaction: SignedTransaction,
validators: Vec<Address>,
state: Bytes,
contract_nonce: U256,
) -> Result<(), Error> {
if self.transactions.len() > MAX_QUEUE_LEN {
bail!(ErrorKind::QueueIsFull);
}
self.transactions.insert(private_hash, PrivateTransactionSigningDesc {
original_transaction: transaction.clone(),
validators: validators.clone(),
received_signatures: Vec::new(),
state,
contract_nonce,
});
Ok(())
}
/// Get copy of private transaction's description from the storage
pub fn get(&self, private_hash: &H256) -> Option<PrivateTransactionSigningDesc> {
self.transactions.get(private_hash).cloned()
}
/// Removes desc from the store (after verification is completed)
pub fn remove(&mut self, private_hash: &H256) -> Result<(), Error> {
self.transactions.remove(private_hash);
Ok(())
}
/// Adds received signature for the stored private transaction
pub fn add_signature(&mut self, private_hash: &H256, signature: Signature) -> Result<(), Error> {
let desc = self.transactions.get_mut(private_hash).ok_or_else(|| ErrorKind::PrivateTransactionNotFound)?;
if !desc.received_signatures.contains(&signature) {
desc.received_signatures.push(signature);
}
Ok(())
}
}

View File

@ -0,0 +1,137 @@
// 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 <http://www.gnu.org/licenses/>.
//! Contract for private transactions tests.
extern crate rustc_hex;
extern crate ethcore;
extern crate ethkey;
extern crate keccak_hash as hash;
extern crate ethcore_io;
extern crate ethcore_logger;
extern crate ethcore_private_tx;
extern crate ethcore_transaction;
#[macro_use]
extern crate log;
use std::sync::Arc;
use rustc_hex::FromHex;
use ethcore::CreateContractAddress;
use ethcore::account_provider::AccountProvider;
use ethcore::client::BlockChainClient;
use ethcore::client::BlockId;
use ethcore::executive::{contract_address};
use ethcore::test_helpers::{generate_dummy_client, push_block_with_transactions};
use ethcore_transaction::{Transaction, Action};
use ethkey::{Secret, KeyPair, Signature};
use hash::keccak;
use ethcore_private_tx::{NoopEncryptor, Provider, ProviderConfig};
#[test]
fn private_contract() {
// This uses a simple private contract: contract Test1 { bytes32 public x; function setX(bytes32 _x) { x = _x; } }
ethcore_logger::init_log();
let client = generate_dummy_client(0);
let chain_id = client.signing_chain_id();
let key1 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000011")).unwrap();
let _key2 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000012")).unwrap();
let key3 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000013")).unwrap();
let key4 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000014")).unwrap();
let ap = Arc::new(AccountProvider::transient_provider());
ap.insert_account(key1.secret().clone(), "").unwrap();
ap.insert_account(key3.secret().clone(), "").unwrap();
ap.insert_account(key4.secret().clone(), "").unwrap();
let config = ProviderConfig{
validator_accounts: vec![key3.address(), key4.address()],
signer_account: None,
passwords: vec!["".into()],
};
let io = ethcore_io::IoChannel::disconnected();
let pm = Arc::new(Provider::new(client.clone(), ap.clone(), Box::new(NoopEncryptor::default()), config, io).unwrap());
let (address, _) = contract_address(CreateContractAddress::FromSenderAndNonce, &key1.address(), &0.into(), &[]);
trace!("Creating private contract");
let private_contract_test = "6060604052341561000f57600080fd5b60d88061001d6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630c55699c146046578063bc64b76d14607457600080fd5b3415605057600080fd5b60566098565b60405180826000191660001916815260200191505060405180910390f35b3415607e57600080fd5b6096600480803560001916906020019091905050609e565b005b60005481565b8060008160001916905550505600a165627a7a723058206acbdf4b15ca4c2d43e1b1879b830451a34f1e9d02ff1f2f394d8d857e79d2080029".from_hex().unwrap();
let mut private_create_tx = Transaction::default();
private_create_tx.action = Action::Create;
private_create_tx.data = private_contract_test;
private_create_tx.gas = 200000.into();
let private_create_tx_signed = private_create_tx.sign(&key1.secret(), None);
let validators = vec![key3.address(), key4.address()];
let (public_tx, _) = pm.public_creation_transaction(BlockId::Latest, &private_create_tx_signed, &validators, 0.into()).unwrap();
let public_tx = public_tx.sign(&key1.secret(), chain_id);
trace!("Transaction created. Pushing block");
push_block_with_transactions(&client, &[public_tx]);
trace!("Modifying private state");
let mut private_tx = Transaction::default();
private_tx.action = Action::Call(address.clone());
private_tx.data = "bc64b76d2a00000000000000000000000000000000000000000000000000000000000000".from_hex().unwrap(); //setX(42)
private_tx.gas = 120000.into();
private_tx.nonce = 1.into();
let private_tx = private_tx.sign(&key1.secret(), None);
let private_contract_nonce = pm.get_contract_nonce(&address, BlockId::Latest).unwrap();
let private_state = pm.execute_private_transaction(BlockId::Latest, &private_tx).unwrap();
let nonced_state_hash = pm.calculate_state_hash(&private_state, private_contract_nonce);
let signatures: Vec<_> = [&key3, &key4].iter().map(|k|
Signature::from(::ethkey::sign(&k.secret(), &nonced_state_hash).unwrap().into_electrum())).collect();
let public_tx = pm.public_transaction(private_state, &private_tx, &signatures, 1.into(), 0.into()).unwrap();
let public_tx = public_tx.sign(&key1.secret(), chain_id);
push_block_with_transactions(&client, &[public_tx]);
trace!("Querying private state");
let mut query_tx = Transaction::default();
query_tx.action = Action::Call(address.clone());
query_tx.data = "0c55699c".from_hex().unwrap(); // getX
query_tx.gas = 50000.into();
query_tx.nonce = 2.into();
let query_tx = query_tx.sign(&key1.secret(), chain_id);
let result = pm.private_call(BlockId::Latest, &query_tx).unwrap();
assert_eq!(&result.output[..], &("2a00000000000000000000000000000000000000000000000000000000000000".from_hex().unwrap()[..]));
assert_eq!(pm.get_validators(BlockId::Latest, &address).unwrap(), validators);
// Now try modification with just one signature
trace!("Modifying private state");
let mut private_tx = Transaction::default();
private_tx.action = Action::Call(address.clone());
private_tx.data = "bc64b76d2b00000000000000000000000000000000000000000000000000000000000000".from_hex().unwrap(); //setX(43)
private_tx.gas = 120000.into();
private_tx.nonce = 2.into();
let private_tx = private_tx.sign(&key1.secret(), None);
let private_state = pm.execute_private_transaction(BlockId::Latest, &private_tx).unwrap();
let private_state_hash = keccak(&private_state);
let signatures: Vec<_> = [&key4].iter().map(|k|
Signature::from(::ethkey::sign(&k.secret(), &private_state_hash).unwrap().into_electrum())).collect();
let public_tx = pm.public_transaction(private_state, &private_tx, &signatures, 2.into(), 0.into()).unwrap();
let public_tx = public_tx.sign(&key1.secret(), chain_id);
push_block_with_transactions(&client, &[public_tx]);
trace!("Querying private state");
let mut query_tx = Transaction::default();
query_tx.action = Action::Call(address.clone());
query_tx.data = "0c55699c".from_hex().unwrap(); // getX
query_tx.gas = 50000.into();
query_tx.nonce = 3.into();
let query_tx = query_tx.sign(&key1.secret(), chain_id);
let result = pm.private_call(BlockId::Latest, &query_tx).unwrap();
assert_eq!(result.output, "2a00000000000000000000000000000000000000000000000000000000000000".from_hex().unwrap());
}

View File

@ -5,8 +5,11 @@ authors = ["Parity Technologies <admin@parity.io>"]
[dependencies] [dependencies]
ansi_term = "0.10" ansi_term = "0.10"
error-chain = { version = "0.11", default-features = false }
ethcore = { path = ".." } ethcore = { path = ".." }
ethcore-io = { path = "../../util/io" } ethcore-io = { path = "../../util/io" }
ethcore-private-tx = { path = "../private-tx" }
ethsync = { path = "../../sync" }
kvdb = { path = "../../util/kvdb" } kvdb = { path = "../../util/kvdb" }
log = "0.3" log = "0.3"
stop-guard = { path = "../../util/stop-guard" } stop-guard = { path = "../../util/stop-guard" }

View File

@ -0,0 +1,30 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use ethcore;
use io;
use ethcore_private_tx;
error_chain! {
links {
PrivateTransactions(ethcore_private_tx::Error, ethcore_private_tx::ErrorKind);
}
foreign_links {
Ethcore(ethcore::error::Error);
IoError(io::IoError);
}
}

View File

@ -17,18 +17,25 @@
extern crate ansi_term; extern crate ansi_term;
extern crate ethcore; extern crate ethcore;
extern crate ethcore_io as io; extern crate ethcore_io as io;
extern crate ethsync;
extern crate kvdb; extern crate kvdb;
extern crate ethcore_private_tx;
extern crate stop_guard; extern crate stop_guard;
#[macro_use]
extern crate error_chain;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[cfg(test)] #[cfg(test)]
extern crate tempdir; extern crate tempdir;
mod error;
mod service;
#[cfg(test)] #[cfg(test)]
extern crate kvdb_rocksdb; extern crate kvdb_rocksdb;
mod service; pub use error::{Error, ErrorKind};
pub use service::{ClientService, PrivateTxService};
pub use service::ClientService;

View File

@ -24,18 +24,50 @@ use io::{IoContext, TimerToken, IoHandler, IoService, IoError};
use kvdb::{KeyValueDB, KeyValueDBHandler}; use kvdb::{KeyValueDB, KeyValueDBHandler};
use stop_guard::StopGuard; use stop_guard::StopGuard;
use ethsync::PrivateTxHandler;
use ethcore::client::{Client, ClientConfig, ChainNotify, ClientIoMessage}; use ethcore::client::{Client, ClientConfig, ChainNotify, ClientIoMessage};
use ethcore::error::Error;
use ethcore::miner::Miner; use ethcore::miner::Miner;
use ethcore::snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams}; use ethcore::snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams};
use ethcore::snapshot::{RestorationStatus}; use ethcore::snapshot::{RestorationStatus};
use ethcore::spec::Spec; use ethcore::spec::Spec;
use ethcore::account_provider::AccountProvider;
use ethcore_private_tx;
use Error;
pub struct PrivateTxService {
provider: Arc<ethcore_private_tx::Provider>,
}
impl PrivateTxService {
fn new(provider: Arc<ethcore_private_tx::Provider>) -> Self {
PrivateTxService {
provider,
}
}
/// Returns underlying provider.
pub fn provider(&self) -> Arc<ethcore_private_tx::Provider> {
self.provider.clone()
}
}
impl PrivateTxHandler for PrivateTxService {
fn import_private_transaction(&self, rlp: &[u8]) -> Result<(), String> {
self.provider.import_private_transaction(rlp).map_err(|e| e.to_string())
}
fn import_signed_private_transaction(&self, rlp: &[u8]) -> Result<(), String> {
self.provider.import_signed_private_transaction(rlp).map_err(|e| e.to_string())
}
}
/// Client service setup. Creates and registers client and network services with the IO subsystem. /// Client service setup. Creates and registers client and network services with the IO subsystem.
pub struct ClientService { pub struct ClientService {
io_service: Arc<IoService<ClientIoMessage>>, io_service: Arc<IoService<ClientIoMessage>>,
client: Arc<Client>, client: Arc<Client>,
snapshot: Arc<SnapshotService>, snapshot: Arc<SnapshotService>,
private_tx: Arc<PrivateTxService>,
database: Arc<KeyValueDB>, database: Arc<KeyValueDB>,
_stop_guard: StopGuard, _stop_guard: StopGuard,
} }
@ -50,6 +82,9 @@ impl ClientService {
restoration_db_handler: Box<KeyValueDBHandler>, restoration_db_handler: Box<KeyValueDBHandler>,
_ipc_path: &Path, _ipc_path: &Path,
miner: Arc<Miner>, miner: Arc<Miner>,
account_provider: Arc<AccountProvider>,
encryptor: Box<ethcore_private_tx::Encryptor>,
private_tx_conf: ethcore_private_tx::ProviderConfig,
) -> Result<ClientService, Error> ) -> Result<ClientService, Error>
{ {
let io_service = IoService::<ClientIoMessage>::start()?; let io_service = IoService::<ClientIoMessage>::start()?;
@ -70,9 +105,13 @@ impl ClientService {
}; };
let snapshot = Arc::new(SnapshotService::new(snapshot_params)?); let snapshot = Arc::new(SnapshotService::new(snapshot_params)?);
let provider = Arc::new(ethcore_private_tx::Provider::new(client.clone(), account_provider, encryptor, private_tx_conf, io_service.channel())?);
let private_tx = Arc::new(PrivateTxService::new(provider));
let client_io = Arc::new(ClientIoHandler { let client_io = Arc::new(ClientIoHandler {
client: client.clone(), client: client.clone(),
snapshot: snapshot.clone(), snapshot: snapshot.clone(),
private_tx: private_tx.clone(),
}); });
io_service.register_handler(client_io)?; io_service.register_handler(client_io)?;
@ -84,6 +123,7 @@ impl ClientService {
io_service: Arc::new(io_service), io_service: Arc::new(io_service),
client: client, client: client,
snapshot: snapshot, snapshot: snapshot,
private_tx,
database: client_db, database: client_db,
_stop_guard: stop_guard, _stop_guard: stop_guard,
}) })
@ -104,6 +144,11 @@ impl ClientService {
self.snapshot.clone() self.snapshot.clone()
} }
/// Get private transaction service.
pub fn private_tx_service(&self) -> Arc<PrivateTxService> {
self.private_tx.clone()
}
/// Get network service component /// Get network service component
pub fn io(&self) -> Arc<IoService<ClientIoMessage>> { pub fn io(&self) -> Arc<IoService<ClientIoMessage>> {
self.io_service.clone() self.io_service.clone()
@ -122,6 +167,7 @@ impl ClientService {
struct ClientIoHandler { struct ClientIoHandler {
client: Arc<Client>, client: Arc<Client>,
snapshot: Arc<SnapshotService>, snapshot: Arc<SnapshotService>,
private_tx: Arc<PrivateTxService>,
} }
const CLIENT_TICK_TIMER: TimerToken = 0; const CLIENT_TICK_TIMER: TimerToken = 0;
@ -180,6 +226,9 @@ impl IoHandler<ClientIoMessage> for ClientIoHandler {
ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) { ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) {
trace!(target: "poa", "Invalid message received: {}", e); trace!(target: "poa", "Invalid message received: {}", e);
}, },
ClientIoMessage::NewPrivateTransaction => if let Err(e) = self.private_tx.provider.on_private_transaction_queued() {
warn!("Failed to handle private transaction {:?}", e);
},
_ => {} // ignore other messages _ => {} // ignore other messages
} }
} }
@ -192,6 +241,7 @@ mod tests {
use tempdir::TempDir; use tempdir::TempDir;
use ethcore::account_provider::AccountProvider;
use ethcore::client::ClientConfig; use ethcore::client::ClientConfig;
use ethcore::miner::Miner; use ethcore::miner::Miner;
use ethcore::spec::Spec; use ethcore::spec::Spec;
@ -200,6 +250,8 @@ mod tests {
use kvdb_rocksdb::{Database, DatabaseConfig, CompactionProfile}; use kvdb_rocksdb::{Database, DatabaseConfig, CompactionProfile};
use super::*; use super::*;
use ethcore_private_tx;
#[test] #[test]
fn it_can_be_started() { fn it_can_be_started() {
let tempdir = TempDir::new("").unwrap(); let tempdir = TempDir::new("").unwrap();
@ -241,6 +293,9 @@ mod tests {
restoration_db_handler, restoration_db_handler,
tempdir.path(), tempdir.path(),
Arc::new(Miner::with_spec(&spec)), Arc::new(Miner::with_spec(&spec)),
Arc::new(AccountProvider::transient_provider()),
Box::new(ethcore_private_tx::NoopEncryptor),
Default::default()
); );
assert!(service.is_ok()); assert!(service.is_ok());
drop(service.unwrap()); drop(service.unwrap());

View File

@ -645,7 +645,7 @@ pub fn enact_verified(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use tests::helpers::get_temp_state_db; use test_helpers::get_temp_state_db;
use super::*; use super::*;
use engines::EthEngine; use engines::EthEngine;
use vm::LastHashes; use vm::LastHashes;

View File

@ -1421,7 +1421,7 @@ mod tests {
use ethereum_types::*; use ethereum_types::*;
use receipt::{Receipt, TransactionOutcome}; use receipt::{Receipt, TransactionOutcome};
use blockchain::{BlockProvider, BlockChain, Config, ImportRoute}; use blockchain::{BlockProvider, BlockChain, Config, ImportRoute};
use tests::helpers::{ use test_helpers::{
generate_dummy_blockchain, generate_dummy_blockchain_with_extra, generate_dummy_blockchain, generate_dummy_blockchain_with_extra,
generate_dummy_empty_blockchain generate_dummy_empty_blockchain
}; };

View File

@ -17,6 +17,16 @@
use ethereum_types::H256; use ethereum_types::H256;
use bytes::Bytes; use bytes::Bytes;
/// Messages to broadcast via chain
pub enum ChainMessageType {
/// Consensus message
Consensus(Vec<u8>),
/// Message with private transaction
PrivateTransaction(Vec<u8>),
/// Message with signed private transaction
SignedPrivateTransaction(Vec<u8>),
}
/// Represents what has to be handled by actor listening to chain events /// Represents what has to be handled by actor listening to chain events
pub trait ChainNotify : Send + Sync { pub trait ChainNotify : Send + Sync {
/// fires when chain has new blocks. /// fires when chain has new blocks.
@ -45,7 +55,7 @@ pub trait ChainNotify : Send + Sync {
} }
/// fires when chain broadcasts a message /// fires when chain broadcasts a message
fn broadcast(&self, _data: Vec<u8>) {} fn broadcast(&self, _message_type: ChainMessageType) {}
/// fires when new transactions are received from a peer /// fires when new transactions are received from a peer
fn transactions_received(&self, fn transactions_received(&self,

View File

@ -45,7 +45,7 @@ use client::{
use client::{ use client::{
BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient,
MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode,
ChainNotify, PruningInfo, ProvingBlockChainClient, EngineInfo ChainNotify, PruningInfo, ProvingBlockChainClient, EngineInfo, ChainMessageType
}; };
use encoded; use encoded;
use engines::{EthEngine, EpochTransition}; use engines::{EthEngine, EpochTransition};
@ -2126,7 +2126,7 @@ impl super::traits::EngineClient for Client {
} }
fn broadcast_consensus_message(&self, message: Bytes) { fn broadcast_consensus_message(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(message.clone())); self.notify(|notify| notify.broadcast(ChainMessageType::Consensus(message.clone())));
} }
fn epoch_transition_for(&self, parent_hash: H256) -> Option<::engines::EpochTransition> { fn epoch_transition_for(&self, parent_hash: H256) -> Option<::engines::EpochTransition> {
@ -2237,7 +2237,7 @@ mod tests {
#[test] #[test]
fn should_not_cache_details_before_commit() { fn should_not_cache_details_before_commit() {
use client::{BlockChainClient, ChainInfo}; use client::{BlockChainClient, ChainInfo};
use tests::helpers::{generate_dummy_client, get_good_dummy_block_hash}; use test_helpers::{generate_dummy_client, get_good_dummy_block_hash};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;

View File

@ -36,6 +36,8 @@ pub enum ClientIoMessage {
/// Take a snapshot for the block with given number. /// Take a snapshot for the block with given number.
TakeSnapshot(u64), TakeSnapshot(u64),
/// New consensus message received. /// New consensus message received.
NewMessage(Bytes) NewMessage(Bytes),
/// New private transaction arrived
NewPrivateTransaction,
} }

View File

@ -31,11 +31,12 @@ pub use self::error::Error;
pub use self::evm_test_client::{EvmTestClient, EvmTestError, TransactResult}; pub use self::evm_test_client::{EvmTestClient, EvmTestError, TransactResult};
pub use self::io_message::ClientIoMessage; pub use self::io_message::ClientIoMessage;
pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::test_client::{TestBlockChainClient, EachBlockWith};
pub use self::chain_notify::ChainNotify; pub use self::chain_notify::{ChainNotify, ChainMessageType};
pub use self::traits::{ pub use self::traits::{
Nonce, Balance, ChainInfo, BlockInfo, ReopenBlock, PrepareOpenBlock, CallContract, TransactionInfo, RegistryInfo, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, Nonce, Balance, ChainInfo, BlockInfo, ReopenBlock, PrepareOpenBlock, CallContract, TransactionInfo, RegistryInfo, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock,
StateOrBlock, StateClient, Call, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter StateOrBlock, StateClient, Call, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter
}; };
//pub use self::private_notify::PrivateNotify;
pub use state::StateInfo; pub use state::StateInfo;
pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient, ProvingBlockChainClient}; pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient, ProvingBlockChainClient};
@ -53,3 +54,4 @@ pub use verification::VerifierType;
mod traits; mod traits;
mod chain_notify; mod chain_notify;
mod private_notify;

View File

@ -0,0 +1,23 @@
// 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 <http://www.gnu.org/licenses/>.
use error::TransactionImportError;
/// Represent private transactions handler inside the client
pub trait PrivateNotify : Send + Sync {
/// fires when private transaction message queued via client io queue
fn private_transaction_queued(&self) -> Result<(), TransactionImportError>;
}

View File

@ -1326,7 +1326,7 @@ mod tests {
use header::Header; use header::Header;
use rlp::encode; use rlp::encode;
use block::*; use block::*;
use tests::helpers::{ use test_helpers::{
generate_dummy_client_with_spec_and_accounts, get_temp_state_db, generate_dummy_client, generate_dummy_client_with_spec_and_accounts, get_temp_state_db, generate_dummy_client,
TestNotify TestNotify
}; };

View File

@ -199,7 +199,7 @@ mod tests {
use hash::keccak; use hash::keccak;
use ethereum_types::H520; use ethereum_types::H520;
use block::*; use block::*;
use tests::helpers::get_temp_state_db; use test_helpers::get_temp_state_db;
use account_provider::AccountProvider; use account_provider::AccountProvider;
use header::Header; use header::Header;
use spec::Spec; use spec::Spec;

View File

@ -67,7 +67,7 @@ impl<M: Machine> Engine<M> for InstantSeal<M>
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use ethereum_types::{H520, Address}; use ethereum_types::{H520, Address};
use tests::helpers::{get_temp_state_db}; use test_helpers::get_temp_state_db;
use spec::Spec; use spec::Spec;
use header::Header; use header::Header;
use block::*; use block::*;

View File

@ -514,7 +514,6 @@ impl Engine<EthereumMachine> for Tendermint {
fn fmt_err<T: ::std::fmt::Debug>(x: T) -> EngineError { fn fmt_err<T: ::std::fmt::Debug>(x: T) -> EngineError {
EngineError::MalformedMessage(format!("{:?}", x)) EngineError::MalformedMessage(format!("{:?}", x))
} }
let rlp = UntrustedRlp::new(rlp); let rlp = UntrustedRlp::new(rlp);
let message: ConsensusMessage = rlp.as_val().map_err(fmt_err)?; let message: ConsensusMessage = rlp.as_val().map_err(fmt_err)?;
if !self.votes.is_old_or_known(&message) { if !self.votes.is_old_or_known(&message) {
@ -783,7 +782,7 @@ mod tests {
use header::Header; use header::Header;
use client::ChainInfo; use client::ChainInfo;
use miner::MinerService; use miner::MinerService;
use tests::helpers::{ use test_helpers::{
TestNotify, get_temp_state_db, generate_dummy_client, TestNotify, get_temp_state_db, generate_dummy_client,
generate_dummy_client_with_spec_and_accounts generate_dummy_client_with_spec_and_accounts
}; };

View File

@ -145,8 +145,8 @@ mod tests {
use account_provider::AccountProvider; use account_provider::AccountProvider;
use miner::MinerService; use miner::MinerService;
use types::ids::BlockId; use types::ids::BlockId;
use test_helpers::generate_dummy_client_with_spec_and_accounts;
use client::{BlockChainClient, ChainInfo, BlockInfo, CallContract}; use client::{BlockChainClient, ChainInfo, BlockInfo, CallContract};
use tests::helpers::generate_dummy_client_with_spec_and_accounts;
use super::super::ValidatorSet; use super::super::ValidatorSet;
use super::ValidatorContract; use super::ValidatorContract;

View File

@ -155,7 +155,7 @@ mod tests {
use header::Header; use header::Header;
use miner::MinerService; use miner::MinerService;
use spec::Spec; use spec::Spec;
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; use test_helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
use types::ids::BlockId; use types::ids::BlockId;
use ethereum_types::Address; use ethereum_types::Address;

View File

@ -459,7 +459,7 @@ mod tests {
use client::{ChainInfo, BlockInfo, ImportBlock}; use client::{ChainInfo, BlockInfo, ImportBlock};
use ethkey::Secret; use ethkey::Secret;
use miner::MinerService; use miner::MinerService;
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; use test_helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
use super::super::ValidatorSet; use super::super::ValidatorSet;
use super::{ValidatorSafeContract, EVENT_NAME_HASH}; use super::{ValidatorSafeContract, EVENT_NAME_HASH};

View File

@ -16,7 +16,7 @@
//! General error types for use in ethcore. //! General error types for use in ethcore.
use std::fmt; use std::{fmt, error};
use kvdb; use kvdb;
use ethereum_types::{H256, U256, Address, Bloom}; use ethereum_types::{H256, U256, Address, Bloom};
use util_error::UtilError; use util_error::UtilError;
@ -268,6 +268,13 @@ impl fmt::Display for Error {
} }
} }
impl error::Error for Error {
fn description(&self) -> &str {
// improve description
"ethcore error"
}
}
/// Result of import block operation. /// Result of import block operation.
pub type ImportResult = Result<H256, Error>; pub type ImportResult = Result<H256, Error>;

View File

@ -487,7 +487,7 @@ mod tests {
use std::sync::Arc; use std::sync::Arc;
use ethereum_types::{H64, H256, U256, Address}; use ethereum_types::{H64, H256, U256, Address};
use block::*; use block::*;
use tests::helpers::get_temp_state_db; use test_helpers::get_temp_state_db;
use error::{BlockError, Error}; use error::{BlockError, Error};
use header::Header; use header::Header;
use spec::Spec; use spec::Spec;

View File

@ -145,7 +145,7 @@ mod tests {
use ethereum_types::U256; use ethereum_types::U256;
use state::*; use state::*;
use super::*; use super::*;
use tests::helpers::get_temp_state_db; use test_helpers::get_temp_state_db;
use views::BlockView; use views::BlockView;
#[test] #[test]

View File

@ -701,7 +701,7 @@ mod tests {
use error::ExecutionError; use error::ExecutionError;
use machine::EthereumMachine; use machine::EthereumMachine;
use state::{Substate, CleanupMode}; use state::{Substate, CleanupMode};
use tests::helpers::{get_temp_state_with_factory, get_temp_state}; use test_helpers::{get_temp_state_with_factory, get_temp_state};
use trace::trace; use trace::trace;
use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer}; use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer};
use trace::{VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, VMTracer, NoopVMTracer, ExecutiveVMTracer}; use trace::{VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, VMTracer, NoopVMTracer, ExecutiveVMTracer};
@ -746,7 +746,6 @@ mod tests {
assert_eq!(state.storage_at(&address, &H256::new()).unwrap(), H256::from(&U256::from(0xf9u64))); assert_eq!(state.storage_at(&address, &H256::new()).unwrap(), H256::from(&U256::from(0xf9u64)));
assert_eq!(state.balance(&sender).unwrap(), U256::from(0xf9)); assert_eq!(state.balance(&sender).unwrap(), U256::from(0xf9));
assert_eq!(state.balance(&address).unwrap(), U256::from(0x7)); assert_eq!(state.balance(&address).unwrap(), U256::from(0x7));
// 0 cause contract hasn't returned
assert_eq!(substate.contracts_created.len(), 0); assert_eq!(substate.contracts_created.len(), 0);
// TODO: just test state root. // TODO: just test state root.

View File

@ -414,7 +414,7 @@ mod tests {
use ethereum_types::{U256, Address}; use ethereum_types::{U256, Address};
use evm::{EnvInfo, Ext, CallType}; use evm::{EnvInfo, Ext, CallType};
use state::{State, Substate}; use state::{State, Substate};
use tests::helpers::get_temp_state; use test_helpers::get_temp_state;
use super::*; use super::*;
use trace::{NoopTracer, NoopVMTracer}; use trace::{NoopTracer, NoopVMTracer};

View File

@ -25,7 +25,7 @@ use vm::{
CreateContractAddress, ReturnData, CreateContractAddress, ReturnData,
}; };
use externalities::*; use externalities::*;
use tests::helpers::get_temp_state; use test_helpers::get_temp_state;
use ethjson; use ethjson;
use trace::{Tracer, NoopTracer}; use trace::{Tracer, NoopTracer};
use trace::{VMTracer, NoopVMTracer}; use trace::{VMTracer, NoopVMTracer};

View File

@ -94,6 +94,7 @@ extern crate ansi_term;
extern crate unexpected; extern crate unexpected;
extern crate kvdb; extern crate kvdb;
extern crate kvdb_memorydb; extern crate kvdb_memorydb;
extern crate kvdb_rocksdb;
extern crate util_error; extern crate util_error;
extern crate snappy; extern crate snappy;
@ -128,9 +129,6 @@ extern crate trace_time;
#[cfg_attr(test, macro_use)] #[cfg_attr(test, macro_use)]
extern crate evm; extern crate evm;
#[cfg(test)]
extern crate kvdb_rocksdb;
pub extern crate ethstore; pub extern crate ethstore;
pub mod account_provider; pub mod account_provider;
@ -142,6 +140,7 @@ pub mod engines;
pub mod error; pub mod error;
pub mod ethereum; pub mod ethereum;
pub mod executed; pub mod executed;
pub mod executive;
pub mod header; pub mod header;
pub mod machine; pub mod machine;
pub mod miner; pub mod miner;
@ -150,6 +149,8 @@ pub mod snapshot;
pub mod spec; pub mod spec;
pub mod state; pub mod state;
pub mod state_db; pub mod state_db;
// Test helpers made public for usage outside ethcore
pub mod test_helpers;
pub mod trace; pub mod trace;
pub mod verification; pub mod verification;
pub mod views; pub mod views;
@ -159,7 +160,6 @@ mod blooms;
mod pod_account; mod pod_account;
mod account_db; mod account_db;
mod builtin; mod builtin;
mod executive;
mod externalities; mod externalities;
mod blockchain; mod blockchain;
mod factory; mod factory;

View File

@ -24,7 +24,7 @@ use ethereum_types::{H256, U256, Address};
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use bytes::Bytes; use bytes::Bytes;
use engines::{EthEngine, Seal}; use engines::{EthEngine, Seal};
use error::*; use error::{ExecutionError, Error};
use ethcore_miner::banning_queue::{BanningTransactionQueue, Threshold}; use ethcore_miner::banning_queue::{BanningTransactionQueue, Threshold};
use ethcore_miner::local_transactions::{Status as LocalTransactionStatus}; use ethcore_miner::local_transactions::{Status as LocalTransactionStatus};
use ethcore_miner::transaction_queue::{ use ethcore_miner::transaction_queue::{
@ -1327,8 +1327,7 @@ mod tests {
use spec::Spec; use spec::Spec;
use transaction::{SignedTransaction, Transaction, PendingTransaction, Action}; use transaction::{SignedTransaction, Transaction, PendingTransaction, Action};
use miner::MinerService; use miner::MinerService;
use test_helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts};
use tests::helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts};
#[test] #[test]
fn should_prepare_block_to_seal() { fn should_prepare_block_to_seal() {

View File

@ -210,7 +210,7 @@ pub fn from_fat_rlp(
mod tests { mod tests {
use account_db::{AccountDB, AccountDBMut}; use account_db::{AccountDB, AccountDBMut};
use basic_account::BasicAccount; use basic_account::BasicAccount;
use tests::helpers::get_temp_state_db; use test_helpers::get_temp_state_db;
use snapshot::tests::helpers::fill_storage; use snapshot::tests::helpers::fill_storage;
use hash::{KECCAK_EMPTY, KECCAK_NULL_RLP, keccak}; use hash::{KECCAK_EMPTY, KECCAK_NULL_RLP, keccak};

View File

@ -635,7 +635,7 @@ mod tests {
use snapshot::{ManifestData, RestorationStatus, SnapshotService}; use snapshot::{ManifestData, RestorationStatus, SnapshotService};
use super::*; use super::*;
use tempdir::TempDir; use tempdir::TempDir;
use tests::helpers::restoration_db_handler; use test_helpers::restoration_db_handler;
struct NoopDBRestore; struct NoopDBRestore;
impl DatabaseRestore for NoopDBRestore { impl DatabaseRestore for NoopDBRestore {

View File

@ -25,7 +25,7 @@ use client::{Client, BlockChainClient, ChainInfo};
use ethkey::Secret; use ethkey::Secret;
use snapshot::tests::helpers as snapshot_helpers; use snapshot::tests::helpers as snapshot_helpers;
use spec::Spec; use spec::Spec;
use tests::helpers; use test_helpers::generate_dummy_client_with_spec_and_accounts;
use transaction::{Transaction, Action, SignedTransaction}; use transaction::{Transaction, Action, SignedTransaction};
use tempdir::TempDir; use tempdir::TempDir;
@ -89,7 +89,7 @@ enum Transition {
// create a chain with the given transitions and some blocks beyond that transition. // create a chain with the given transitions and some blocks beyond that transition.
fn make_chain(accounts: Arc<AccountProvider>, blocks_beyond: usize, transitions: Vec<Transition>) -> Arc<Client> { fn make_chain(accounts: Arc<AccountProvider>, blocks_beyond: usize, transitions: Vec<Transition>) -> Arc<Client> {
let client = helpers::generate_dummy_client_with_spec_and_accounts( let client = generate_dummy_client_with_spec_and_accounts(
spec_fixed_to_contract, Some(accounts.clone())); spec_fixed_to_contract, Some(accounts.clone()));
let mut cur_signers = vec![*RICH_ADDR]; let mut cur_signers = vec![*RICH_ADDR];

View File

@ -24,7 +24,7 @@ use ids::BlockId;
use snapshot::service::{Service, ServiceParams}; use snapshot::service::{Service, ServiceParams};
use snapshot::{self, ManifestData, SnapshotService}; use snapshot::{self, ManifestData, SnapshotService};
use spec::Spec; use spec::Spec;
use tests::helpers::{generate_dummy_client_with_spec_and_data, restoration_db_handler}; use test_helpers::{generate_dummy_client_with_spec_and_data, restoration_db_handler};
use io::IoChannel; use io::IoChannel;
use kvdb_rocksdb::{Database, DatabaseConfig}; use kvdb_rocksdb::{Database, DatabaseConfig};

View File

@ -892,7 +892,7 @@ impl Spec {
mod tests { mod tests {
use super::*; use super::*;
use state::State; use state::State;
use tests::helpers::get_temp_state_db; use test_helpers::get_temp_state_db;
use views::BlockView; use views::BlockView;
use tempdir::TempDir; use tempdir::TempDir;

View File

@ -180,6 +180,16 @@ impl Account {
self.init_code(code); self.init_code(code);
} }
/// Reset this account's code and storage to given values.
pub fn reset_code_and_storage(&mut self, code: Arc<Bytes>, storage: HashMap<H256, H256>) {
self.code_hash = keccak(&*code);
self.code_cache = code;
self.code_size = Some(self.code_cache.len());
self.code_filth = Filth::Dirty;
self.storage_cache = Self::empty_storage_cache();
self.storage_changes = storage;
}
/// Set (and cache) the contents of the trie's storage at `key` to `value`. /// Set (and cache) the contents of the trie's storage at `key` to `value`.
pub fn set_storage(&mut self, key: H256, value: H256) { pub fn set_storage(&mut self, key: H256, value: H256) {
self.storage_changes.insert(key, value); self.storage_changes.insert(key, value);

View File

@ -494,6 +494,13 @@ impl<B: Backend> State<B> {
(self.root, self.db) (self.root, self.db)
} }
/// Destroy the current object and return single account data.
pub fn into_account(self, account: &Address) -> trie::Result<(Option<Arc<Bytes>>, HashMap<H256, H256>)> {
// TODO: deconstruct without cloning.
let account = self.require(account, true)?;
Ok((account.code().clone(), account.storage_changes().clone()))
}
/// Return reference to root /// Return reference to root
pub fn root(&self) -> &H256 { pub fn root(&self) -> &H256 {
&self.root &self.root
@ -1014,6 +1021,11 @@ impl<B: Backend> State<B> {
} }
})) }))
} }
/// Replace account code and storage. Creates account if it does not exist.
pub fn patch_account(&self, a: &Address, code: Arc<Bytes>, storage: HashMap<H256, H256>) -> trie::Result<()> {
Ok(self.require(a, false)?.reset_code_and_storage(code, storage))
}
} }
// State proof implementations; useful for light client protocols. // State proof implementations; useful for light client protocols.
@ -1099,7 +1111,7 @@ mod tests {
use super::*; use super::*;
use ethkey::Secret; use ethkey::Secret;
use ethereum_types::{H256, U256, Address}; use ethereum_types::{H256, U256, Address};
use tests::helpers::{get_temp_state, get_temp_state_db}; use test_helpers::{get_temp_state, get_temp_state_db};
use machine::EthereumMachine; use machine::EthereumMachine;
use vm::EnvInfo; use vm::EnvInfo;
use spec::*; use spec::*;

View File

@ -480,7 +480,7 @@ unsafe impl Sync for SyncAccount {}
mod tests { mod tests {
use ethereum_types::{H256, U256, Address}; use ethereum_types::{H256, U256, Address};
use kvdb::DBTransaction; use kvdb::DBTransaction;
use tests::helpers::{get_temp_state_db}; use test_helpers::get_temp_state_db;
use state::{Account, Backend}; use state::{Account, Backend};
use ethcore_logger::init_log; use ethcore_logger::init_log;

View File

@ -14,12 +14,14 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Set of different helpers for client tests
use account_provider::AccountProvider; use account_provider::AccountProvider;
use ethereum_types::{H256, U256}; use ethereum_types::{H256, U256, Address};
use block::{OpenBlock, Drain}; use block::{OpenBlock, Drain};
use blockchain::{BlockChain, Config as BlockChainConfig}; use blockchain::{BlockChain, Config as BlockChainConfig};
use bytes::Bytes; use bytes::Bytes;
use client::{Client, ClientConfig, ChainInfo, ImportBlock, ChainNotify}; use client::{Client, ClientConfig, ChainInfo, ImportBlock, ChainNotify, ChainMessageType, PrepareOpenBlock};
use ethkey::KeyPair; use ethkey::KeyPair;
use evm::Factory as EvmFactory; use evm::Factory as EvmFactory;
use factory::Factories; use factory::Factories;
@ -39,6 +41,7 @@ use views::BlockView;
use kvdb::{KeyValueDB, KeyValueDBHandler}; use kvdb::{KeyValueDB, KeyValueDBHandler};
use kvdb_rocksdb::{Database, DatabaseConfig}; use kvdb_rocksdb::{Database, DatabaseConfig};
/// Creates test block with corresponding header
pub fn create_test_block(header: &Header) -> Bytes { pub fn create_test_block(header: &Header) -> Bytes {
let mut rlp = RlpStream::new_list(3); let mut rlp = RlpStream::new_list(3);
rlp.append(header); rlp.append(header);
@ -76,6 +79,7 @@ fn create_unverifiable_block(order: u32, parent_hash: H256) -> Bytes {
create_test_block(&create_unverifiable_block_header(order, parent_hash)) create_test_block(&create_unverifiable_block_header(order, parent_hash))
} }
/// Creates test block with corresponding header and data
pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransaction], uncles: &[Header]) -> Bytes { pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransaction], uncles: &[Header]) -> Bytes {
let mut rlp = RlpStream::new_list(3); let mut rlp = RlpStream::new_list(3);
rlp.append(header); rlp.append(header);
@ -87,23 +91,27 @@ pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransa
rlp.out() rlp.out()
} }
/// Generates dummy client (not test client) with corresponding amount of blocks
pub fn generate_dummy_client(block_number: u32) -> Arc<Client> { pub fn generate_dummy_client(block_number: u32) -> Arc<Client> {
generate_dummy_client_with_spec_and_data(Spec::new_test, block_number, 0, &[]) generate_dummy_client_with_spec_and_data(Spec::new_test, block_number, 0, &[])
} }
/// Generates dummy client (not test client) with corresponding amount of blocks and txs per every block
pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> { pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> {
generate_dummy_client_with_spec_and_data(Spec::new_null, block_number, txs_per_block, tx_gas_prices) generate_dummy_client_with_spec_and_data(Spec::new_null, block_number, txs_per_block, tx_gas_prices)
} }
/// Generates dummy client (not test client) with corresponding amount of blocks, txs per block and spec
pub fn generate_dummy_client_with_spec_and_data<F>(test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> where F: Fn()->Spec { pub fn generate_dummy_client_with_spec_and_data<F>(test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> where F: Fn()->Spec {
generate_dummy_client_with_spec_accounts_and_data(test_spec, None, block_number, txs_per_block, tx_gas_prices) generate_dummy_client_with_spec_accounts_and_data(test_spec, None, block_number, txs_per_block, tx_gas_prices)
} }
/// Generates dummy client (not test client) with corresponding spec and accounts
pub fn generate_dummy_client_with_spec_and_accounts<F>(test_spec: F, accounts: Option<Arc<AccountProvider>>) -> Arc<Client> where F: Fn()->Spec { pub fn generate_dummy_client_with_spec_and_accounts<F>(test_spec: F, accounts: Option<Arc<AccountProvider>>) -> Arc<Client> where F: Fn()->Spec {
generate_dummy_client_with_spec_accounts_and_data(test_spec, accounts, 0, 0, &[]) generate_dummy_client_with_spec_accounts_and_data(test_spec, accounts, 0, 0, &[])
} }
/// Generates dummy client (not test client) with corresponding blocks, accounts and spec
pub fn generate_dummy_client_with_spec_accounts_and_data<F>(test_spec: F, accounts: Option<Arc<AccountProvider>>, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> where F: Fn()->Spec { pub fn generate_dummy_client_with_spec_accounts_and_data<F>(test_spec: F, accounts: Option<Arc<AccountProvider>>, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> where F: Fn()->Spec {
let test_spec = test_spec(); let test_spec = test_spec();
let client_db = new_db(); let client_db = new_db();
@ -174,6 +182,7 @@ pub fn generate_dummy_client_with_spec_accounts_and_data<F>(test_spec: F, accoun
client client
} }
/// Adds blocks to the client
pub fn push_blocks_to_client(client: &Arc<Client>, timestamp_salt: u64, starting_number: usize, block_number: usize) { pub fn push_blocks_to_client(client: &Arc<Client>, timestamp_salt: u64, starting_number: usize, block_number: usize) {
let test_spec = Spec::new_test(); let test_spec = Spec::new_test();
let state_root = test_spec.genesis_header().state_root().clone(); let state_root = test_spec.genesis_header().state_root().clone();
@ -203,6 +212,29 @@ pub fn push_blocks_to_client(client: &Arc<Client>, timestamp_salt: u64, starting
} }
} }
/// Adds one block with transactions
pub fn push_block_with_transactions(client: &Arc<Client>, transactions: &[SignedTransaction]) {
let test_spec = Spec::new_test();
let test_engine = &*test_spec.engine;
let block_number = client.chain_info().best_block_number as u64 + 1;
let mut b = client.prepare_open_block(Address::default(), (0.into(), 5000000.into()), Bytes::new());
b.set_timestamp(block_number * 10);
for t in transactions {
b.push_transaction(t.clone(), None).unwrap();
}
let b = b.close_and_lock().seal(test_engine, vec![]).unwrap();
if let Err(e) = client.import_block(b.rlp_bytes()) {
panic!("error importing block which is valid by definition: {:?}", e);
}
client.flush_queue();
client.import_verified_blocks();
}
/// Creates dummy client (not test client) with corresponding blocks
pub fn get_test_client_with_blocks(blocks: Vec<Bytes>) -> Arc<Client> { pub fn get_test_client_with_blocks(blocks: Vec<Bytes>) -> Arc<Client> {
let test_spec = Spec::new_test(); let test_spec = Spec::new_test();
let client_db = new_db(); let client_db = new_db();
@ -229,6 +261,7 @@ fn new_db() -> Arc<::kvdb::KeyValueDB> {
Arc::new(::kvdb_memorydb::create(::db::NUM_COLUMNS.unwrap_or(0))) Arc::new(::kvdb_memorydb::create(::db::NUM_COLUMNS.unwrap_or(0)))
} }
/// Generates dummy blockchain with corresponding amount of blocks
pub fn generate_dummy_blockchain(block_number: u32) -> BlockChain { pub fn generate_dummy_blockchain(block_number: u32) -> BlockChain {
let db = new_db(); let db = new_db();
let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone());
@ -242,6 +275,7 @@ pub fn generate_dummy_blockchain(block_number: u32) -> BlockChain {
bc bc
} }
/// Generates dummy blockchain with corresponding amount of blocks (using creation with extra method for blocks creation)
pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> BlockChain { pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> BlockChain {
let db = new_db(); let db = new_db();
let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone());
@ -256,17 +290,20 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> BlockChain {
bc bc
} }
/// Returns empty dummy blockchain
pub fn generate_dummy_empty_blockchain() -> BlockChain { pub fn generate_dummy_empty_blockchain() -> BlockChain {
let db = new_db(); let db = new_db();
let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone());
bc bc
} }
/// Returns temp state
pub fn get_temp_state() -> State<::state_db::StateDB> { pub fn get_temp_state() -> State<::state_db::StateDB> {
let journal_db = get_temp_state_db(); let journal_db = get_temp_state_db();
State::new(journal_db, U256::from(0), Default::default()) State::new(journal_db, U256::from(0), Default::default())
} }
/// Returns temp state using coresponding factory
pub fn get_temp_state_with_factory(factory: EvmFactory) -> State<::state_db::StateDB> { pub fn get_temp_state_with_factory(factory: EvmFactory) -> State<::state_db::StateDB> {
let journal_db = get_temp_state_db(); let journal_db = get_temp_state_db();
let mut factories = Factories::default(); let mut factories = Factories::default();
@ -274,17 +311,20 @@ pub fn get_temp_state_with_factory(factory: EvmFactory) -> State<::state_db::Sta
State::new(journal_db, U256::from(0), factories) State::new(journal_db, U256::from(0), factories)
} }
/// Returns temp state db
pub fn get_temp_state_db() -> StateDB { pub fn get_temp_state_db() -> StateDB {
let db = new_db(); let db = new_db();
let journal_db = ::journaldb::new(db, ::journaldb::Algorithm::EarlyMerge, ::db::COL_STATE); let journal_db = ::journaldb::new(db, ::journaldb::Algorithm::EarlyMerge, ::db::COL_STATE);
StateDB::new(journal_db, 5 * 1024 * 1024) StateDB::new(journal_db, 5 * 1024 * 1024)
} }
/// Returns sequence of hashes of the dummy blocks
pub fn get_good_dummy_block_seq(count: usize) -> Vec<Bytes> { pub fn get_good_dummy_block_seq(count: usize) -> Vec<Bytes> {
let test_spec = Spec::new_test(); let test_spec = Spec::new_test();
get_good_dummy_block_fork_seq(1, count, &test_spec.genesis_header().hash()) get_good_dummy_block_fork_seq(1, count, &test_spec.genesis_header().hash())
} }
/// Returns sequence of hashes of the dummy blocks beginning from corresponding parent
pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_hash: &H256) -> Vec<Bytes> { pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_hash: &H256) -> Vec<Bytes> {
let test_spec = Spec::new_test(); let test_spec = Spec::new_test();
let genesis_gas = test_spec.genesis_header().gas_limit().clone(); let genesis_gas = test_spec.genesis_header().gas_limit().clone();
@ -308,6 +348,7 @@ pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_h
r r
} }
/// Returns hash and header of the correct dummy block
pub fn get_good_dummy_block_hash() -> (H256, Bytes) { pub fn get_good_dummy_block_hash() -> (H256, Bytes) {
let mut block_header = Header::new(); let mut block_header = Header::new();
let test_spec = Spec::new_test(); let test_spec = Spec::new_test();
@ -322,11 +363,13 @@ pub fn get_good_dummy_block_hash() -> (H256, Bytes) {
(block_header.hash(), create_test_block(&block_header)) (block_header.hash(), create_test_block(&block_header))
} }
/// Returns hash of the correct dummy block
pub fn get_good_dummy_block() -> Bytes { pub fn get_good_dummy_block() -> Bytes {
let (_, bytes) = get_good_dummy_block_hash(); let (_, bytes) = get_good_dummy_block_hash();
bytes bytes
} }
/// Returns hash of the dummy block with incorrect state root
pub fn get_bad_state_dummy_block() -> Bytes { pub fn get_bad_state_dummy_block() -> Bytes {
let mut block_header = Header::new(); let mut block_header = Header::new();
let test_spec = Spec::new_test(); let test_spec = Spec::new_test();
@ -342,17 +385,25 @@ pub fn get_bad_state_dummy_block() -> Bytes {
create_test_block(&block_header) create_test_block(&block_header)
} }
/// Test actor for chain events
#[derive(Default)] #[derive(Default)]
pub struct TestNotify { pub struct TestNotify {
/// Messages store
pub messages: RwLock<Vec<Bytes>>, pub messages: RwLock<Vec<Bytes>>,
} }
impl ChainNotify for TestNotify { impl ChainNotify for TestNotify {
fn broadcast(&self, data: Vec<u8>) { fn broadcast(&self, message: ChainMessageType) {
let data = match message {
ChainMessageType::Consensus(data) => data,
ChainMessageType::SignedPrivateTransaction(data) => data,
ChainMessageType::PrivateTransaction(data) => data,
};
self.messages.write().push(data); self.messages.write().push(data);
} }
} }
/// Creates new instance of KeyValueDBHandler
pub fn restoration_db_handler(config: DatabaseConfig) -> Box<KeyValueDBHandler> { pub fn restoration_db_handler(config: DatabaseConfig) -> Box<KeyValueDBHandler> {
use kvdb::Error; use kvdb::Error;

View File

@ -23,7 +23,7 @@ use state::{self, State, CleanupMode};
use executive::{Executive, TransactOptions}; use executive::{Executive, TransactOptions};
use ethereum; use ethereum;
use block::IsBlock; use block::IsBlock;
use tests::helpers::{ use test_helpers::{
generate_dummy_client, push_blocks_to_client, get_test_client_with_blocks, get_good_dummy_block_seq, generate_dummy_client, push_blocks_to_client, get_test_client_with_blocks, get_good_dummy_block_seq,
generate_dummy_client_with_data, get_good_dummy_block, get_bad_state_dummy_block generate_dummy_client_with_data, get_good_dummy_block, get_bad_state_dummy_block
}; };

View File

@ -22,7 +22,7 @@ use vm::{EnvInfo, ActionParams, ActionValue, CallType, ParamsType};
use evm::{Factory, VMType}; use evm::{Factory, VMType};
use executive::Executive; use executive::Executive;
use state::Substate; use state::Substate;
use tests::helpers::get_temp_state_with_factory; use test_helpers::get_temp_state_with_factory;
use trace::{NoopVMTracer, NoopTracer}; use trace::{NoopVMTracer, NoopTracer};
use transaction::SYSTEM_ADDRESS; use transaction::SYSTEM_ADDRESS;

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
pub mod helpers;
mod client; mod client;
mod evm; mod evm;
mod trace; mod trace;

View File

@ -24,7 +24,7 @@ use ethereum_types::{U256, Address};
use io::*; use io::*;
use spec::*; use spec::*;
use client::*; use client::*;
use tests::helpers::get_temp_state_db; use test_helpers::get_temp_state_db;
use client::{BlockChainClient, Client, ClientConfig}; use client::{BlockChainClient, Client, ClientConfig};
use kvdb_rocksdb::{Database, DatabaseConfig}; use kvdb_rocksdb::{Database, DatabaseConfig};
use std::sync::Arc; use std::sync::Arc;

View File

@ -731,7 +731,7 @@ mod tests {
use spec::Spec; use spec::Spec;
use super::{BlockQueue, Config, State}; use super::{BlockQueue, Config, State};
use super::kind::blocks::Unverified; use super::kind::blocks::Unverified;
use tests::helpers::{get_good_dummy_block_seq, get_good_dummy_block}; use test_helpers::{get_good_dummy_block_seq, get_good_dummy_block};
use error::*; use error::*;
use views::*; use views::*;

View File

@ -359,7 +359,7 @@ mod tests {
use error::BlockError::*; use error::BlockError::*;
use ethkey::{Random, Generator}; use ethkey::{Random, Generator};
use spec::{CommonParams, Spec}; use spec::{CommonParams, Spec};
use tests::helpers::{create_test_block_with_data, create_test_block}; use test_helpers::{create_test_block_with_data, create_test_block};
use transaction::{SignedTransaction, Transaction, UnverifiedTransaction, Action}; use transaction::{SignedTransaction, Transaction, UnverifiedTransaction, Action};
use types::log_entry::{LogEntry, LocalizedLogEntry}; use types::log_entry::{LogEntry, LocalizedLogEntry};
use rlp; use rlp;

View File

@ -77,6 +77,25 @@ pub enum Condition {
Timestamp(u64), Timestamp(u64),
} }
/// Replay protection logic for v part of transaction's signature
pub mod signature {
/// Adds chain id into v
pub fn add_chain_replay_protection(v: u64, chain_id: Option<u64>) -> u64 {
v + if let Some(n) = chain_id { 35 + n * 2 } else { 27 }
}
/// Returns refined v
/// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if invalid.
pub fn check_replay_protection(v: u64) -> u8 {
match v {
v if v == 27 => 0,
v if v == 28 => 1,
v if v > 36 => ((v - 1) % 2) as u8,
_ => 4
}
}
}
/// A set of information describing an externally-originating message call /// A set of information describing an externally-originating message call
/// or contract creation operation. /// or contract creation operation.
#[derive(Default, Debug, Clone, PartialEq, Eq)] #[derive(Default, Debug, Clone, PartialEq, Eq)]
@ -186,7 +205,7 @@ impl Transaction {
unsigned: self, unsigned: self,
r: sig.r().into(), r: sig.r().into(),
s: sig.s().into(), s: sig.s().into(),
v: sig.v() as u64 + if let Some(n) = chain_id { 35 + n * 2 } else { 27 }, v: signature::add_chain_replay_protection(sig.v() as u64, chain_id),
hash: 0.into(), hash: 0.into(),
}.compute_hash() }.compute_hash()
} }
@ -330,8 +349,7 @@ impl UnverifiedTransaction {
&self.unsigned &self.unsigned
} }
/// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if invalid. pub fn standard_v(&self) -> u8 { signature::check_replay_protection(self.v) }
pub fn standard_v(&self) -> u8 { match self.v { v if v == 27 || v == 28 || v > 36 => ((v - 1) % 2) as u8, _ => 4 } }
/// The `v` value that appears in the RLP. /// The `v` value that appears in the RLP.
pub fn original_v(&self) -> u64 { self.v } pub fn original_v(&self) -> u64 { self.v }

View File

@ -153,7 +153,7 @@ impl<F: Fetch + 'static> HashFetch for Client<F> {
.into_future() .into_future()
.and_then(move |url| { .and_then(move |url| {
debug!(target: "fetch", "Resolved {:?} to {:?}. Fetching...", hash, url); debug!(target: "fetch", "Resolved {:?} to {:?}. Fetching...", hash, url);
remote_fetch.fetch(&url, abort).from_err() remote_fetch.get(&url, abort).from_err()
}) })
.and_then(move |response| { .and_then(move |response| {
if !response.is_success() { if !response.is_success() {
@ -199,7 +199,7 @@ mod tests {
use parking_lot::Mutex; use parking_lot::Mutex;
use futures::future; use futures::future;
use futures_cpupool::CpuPool; use futures_cpupool::CpuPool;
use fetch::{self, Fetch, Url}; use fetch::{self, Fetch, Url, Method};
use parity_reactor::Remote; use parity_reactor::Remote;
use urlhint::tests::{FakeRegistrar, URLHINT}; use urlhint::tests::{FakeRegistrar, URLHINT};
use super::{Error, Client, HashFetch, random_temp_path}; use super::{Error, Client, HashFetch, random_temp_path};
@ -214,7 +214,7 @@ mod tests {
impl Fetch for FakeFetch { impl Fetch for FakeFetch {
type Result = future::Ok<fetch::Response, fetch::Error>; type Result = future::Ok<fetch::Response, fetch::Error>;
fn fetch(&self, url: &str, abort: fetch::Abort) -> Self::Result { fn fetch(&self, url: &str, _method: Method, abort: fetch::Abort) -> Self::Result {
assert_eq!(url, "https://parity.io/assets/images/ethcore-black-horizontal.png"); assert_eq!(url, "https://parity.io/assets/images/ethcore-black-horizontal.png");
let u = Url::parse(url).unwrap(); let u = Url::parse(url).unwrap();
future::ok(if self.return_success { future::ok(if self.return_success {

View File

@ -25,6 +25,7 @@ use hash::{keccak, KECCAK_NULL_RLP};
use ethereum_types::{U256, H256, Address}; use ethereum_types::{U256, H256, Address};
use bytes::ToPretty; use bytes::ToPretty;
use rlp::PayloadInfo; use rlp::PayloadInfo;
use ethcore::account_provider::AccountProvider;
use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, Nonce, Balance, BlockChainClient, BlockId, BlockInfo, ImportBlock}; use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, Nonce, Balance, BlockChainClient, BlockId, BlockInfo, ImportBlock};
use ethcore::db::NUM_COLUMNS; use ethcore::db::NUM_COLUMNS;
use ethcore::error::ImportError; use ethcore::error::ImportError;
@ -39,6 +40,7 @@ use helpers::{to_client_config, execute_upgrades, open_client_db, client_db_conf
use dir::Directories; use dir::Directories;
use user_defaults::UserDefaults; use user_defaults::UserDefaults;
use fdlimit; use fdlimit;
use ethcore_private_tx;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DataFormat { pub enum DataFormat {
@ -389,6 +391,9 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> {
restoration_db_handler, restoration_db_handler,
&cmd.dirs.ipc_path(), &cmd.dirs.ipc_path(),
Arc::new(Miner::with_spec(&spec)), Arc::new(Miner::with_spec(&spec)),
Arc::new(AccountProvider::transient_provider()),
Box::new(ethcore_private_tx::NoopEncryptor),
Default::default()
).map_err(|e| format!("Client service error: {:?}", e))?; ).map_err(|e| format!("Client service error: {:?}", e))?;
// free up the spec in memory. // free up the spec in memory.
@ -576,6 +581,9 @@ fn start_client(
restoration_db_handler, restoration_db_handler,
&dirs.ipc_path(), &dirs.ipc_path(),
Arc::new(Miner::with_spec(&spec)), Arc::new(Miner::with_spec(&spec)),
Arc::new(AccountProvider::transient_provider()),
Box::new(ethcore_private_tx::NoopEncryptor),
Default::default()
).map_err(|e| format!("Client service error: {:?}", e))?; ).map_err(|e| format!("Client service error: {:?}", e))?;
drop(spec); drop(spec);

View File

@ -350,6 +350,35 @@ usage! {
"--password=[FILE]...", "--password=[FILE]...",
"Provide a file containing a password for unlocking an account. Leading and trailing whitespace is trimmed.", "Provide a file containing a password for unlocking an account. Leading and trailing whitespace is trimmed.",
["Private transactions options"]
FLAG flag_private_enabled: (bool) = false, or |c: &Config| c.private_tx.as_ref()?.enabled,
"--private-tx-enabled",
"Enable private transactions.",
ARG arg_private_signer: (Option<String>) = None, or |c: &Config| c.private_tx.as_ref()?.signer.clone(),
"--private-signer=[ACCOUNT]",
"Specify the account for signing public transaction created upon verified private transaction.",
ARG arg_private_validators: (Option<String>) = None, or |c: &Config| c.private_tx.as_ref()?.validators.as_ref().map(|vec| vec.join(",")),
"--private-validators=[ACCOUNTS]",
"Specify the accounts for validating private transactions. ACCOUNTS is a comma-delimited list of addresses.",
ARG arg_private_account: (Option<String>) = None, or |c: &Config| c.private_tx.as_ref()?.account.clone(),
"--private-account=[ACCOUNT]",
"Specify the account for signing requests to secret store.",
ARG arg_private_sstore_url: (Option<String>) = None, or |c: &Config| c.private_tx.as_ref()?.sstore_url.clone(),
"--private-sstore-url=[URL]",
"Specify secret store URL used for encrypting private transactions.",
ARG arg_private_sstore_threshold: (Option<u32>) = None, or |c: &Config| c.private_tx.as_ref()?.sstore_threshold.clone(),
"--private-sstore-threshold=[NUM]",
"Specify secret store threshold used for encrypting private transactions.",
ARG arg_private_passwords: (Option<String>) = None, or |c: &Config| c.private_tx.as_ref()?.passwords.clone(),
"--private-passwords=[FILE]...",
"Provide a file containing passwords for unlocking accounts (signer, private account, validators).",
["UI options"] ["UI options"]
FLAG flag_force_ui: (bool) = false, or |c: &Config| c.ui.as_ref()?.force.clone(), FLAG flag_force_ui: (bool) = false, or |c: &Config| c.ui.as_ref()?.force.clone(),
"--force-ui", "--force-ui",
@ -462,7 +491,7 @@ usage! {
"--jsonrpc-interface=[IP]", "--jsonrpc-interface=[IP]",
"Specify the hostname portion of the JSONRPC API server, IP should be an interface's IP address, or all (all interfaces) or local.", "Specify the hostname portion of the JSONRPC API server, IP should be an interface's IP address, or all (all interfaces) or local.",
ARG arg_jsonrpc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,traces,rpc,shh,shh_pubsub", or |c: &Config| c.rpc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")), ARG arg_jsonrpc_apis: (String) = "web3,eth,pubsub,net,parity,private,parity_pubsub,traces,rpc,shh,shh_pubsub", or |c: &Config| c.rpc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")),
"--jsonrpc-apis=[APIS]", "--jsonrpc-apis=[APIS]",
"Specify the APIs available through the JSONRPC interface using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, rpc, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. safe contains following apis: web3, net, eth, pubsub, parity, parity_pubsub, traces, rpc, shh, shh_pubsub", "Specify the APIs available through the JSONRPC interface using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, rpc, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. safe contains following apis: web3, net, eth, pubsub, parity, parity_pubsub, traces, rpc, shh, shh_pubsub",
@ -495,7 +524,7 @@ usage! {
"--ws-interface=[IP]", "--ws-interface=[IP]",
"Specify the hostname portion of the WebSockets server, IP should be an interface's IP address, or all (all interfaces) or local.", "Specify the hostname portion of the WebSockets server, IP should be an interface's IP address, or all (all interfaces) or local.",
ARG arg_ws_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,traces,rpc,shh,shh_pubsub", or |c: &Config| c.websockets.as_ref()?.apis.as_ref().map(|vec| vec.join(",")), ARG arg_ws_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,private,traces,rpc,shh,shh_pubsub", or |c: &Config| c.websockets.as_ref()?.apis.as_ref().map(|vec| vec.join(",")),
"--ws-apis=[APIS]", "--ws-apis=[APIS]",
"Specify the APIs available through the WebSockets interface using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, rpc, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. safe contains following apis: web3, net, eth, pubsub, parity, parity_pubsub, traces, rpc, shh, shh_pubsub", "Specify the APIs available through the WebSockets interface using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, rpc, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. safe contains following apis: web3, net, eth, pubsub, parity, parity_pubsub, traces, rpc, shh, shh_pubsub",
@ -520,7 +549,7 @@ usage! {
"--ipc-path=[PATH]", "--ipc-path=[PATH]",
"Specify custom path for JSON-RPC over IPC service.", "Specify custom path for JSON-RPC over IPC service.",
ARG arg_ipc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,parity_accounts,traces,rpc,shh,shh_pubsub", or |c: &Config| c.ipc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")), ARG arg_ipc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,parity_accounts,private,traces,rpc,shh,shh_pubsub", or |c: &Config| c.ipc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")),
"--ipc-apis=[APIS]", "--ipc-apis=[APIS]",
"Specify custom API set available via JSON-RPC over IPC using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, rpc, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. safe contains: web3, net, eth, pubsub, parity, parity_pubsub, traces, rpc, shh, shh_pubsub", "Specify custom API set available via JSON-RPC over IPC using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, rpc, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. safe contains: web3, net, eth, pubsub, parity, parity_pubsub, traces, rpc, shh, shh_pubsub",
@ -1014,6 +1043,7 @@ struct Config {
ipc: Option<Ipc>, ipc: Option<Ipc>,
dapps: Option<Dapps>, dapps: Option<Dapps>,
secretstore: Option<SecretStore>, secretstore: Option<SecretStore>,
private_tx: Option<PrivateTransactions>,
ipfs: Option<Ipfs>, ipfs: Option<Ipfs>,
mining: Option<Mining>, mining: Option<Mining>,
footprint: Option<Footprint>, footprint: Option<Footprint>,
@ -1057,6 +1087,18 @@ struct Account {
fast_unlock: Option<bool>, fast_unlock: Option<bool>,
} }
#[derive(Default, Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
struct PrivateTransactions {
enabled: Option<bool>,
signer: Option<String>,
validators: Option<Vec<String>>,
account: Option<String>,
passwords: Option<String>,
sstore_url: Option<String>,
sstore_threshold: Option<u32>,
}
#[derive(Default, Debug, PartialEq, Deserialize)] #[derive(Default, Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct Ui { struct Ui {
@ -1501,6 +1543,15 @@ mod tests {
flag_no_hardware_wallets: false, flag_no_hardware_wallets: false,
flag_fast_unlock: false, flag_fast_unlock: false,
// -- Private Transactions Options
flag_private_enabled: true,
arg_private_signer: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
arg_private_validators: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
arg_private_passwords: Some("~/.safe/password.file".into()),
arg_private_account: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
arg_private_sstore_url: Some("http://localhost:8082".into()),
arg_private_sstore_threshold: Some(0),
flag_force_ui: false, flag_force_ui: false,
flag_no_ui: false, flag_no_ui: false,
arg_ui_port: 8180u16, arg_ui_port: 8180u16,
@ -1834,6 +1885,7 @@ mod tests {
http_port: Some(8082), http_port: Some(8082),
path: None, path: None,
}), }),
private_tx: None,
ipfs: Some(Ipfs { ipfs: Some(Ipfs {
enable: Some(false), enable: Some(false),
port: Some(5001), port: Some(5001),

View File

@ -23,6 +23,15 @@ unlock = ["0xdeadbeefcafe0000000000000000000000000000"]
password = ["~/.safe/password.file"] password = ["~/.safe/password.file"]
keys_iterations = 10240 keys_iterations = 10240
[private_tx]
enabled = true
signer = "0xdeadbeefcafe0000000000000000000000000000"
validators = ["0xdeadbeefcafe0000000000000000000000000000"]
passwords = "~/.safe/password.file"
account = "0xdeadbeefcafe0000000000000000000000000000"
sstore_url = "http://localhost:8082"
sstore_threshold = 0
[ui] [ui]
force = false force = false
disable = false disable = false

View File

@ -38,13 +38,14 @@ use rpc_apis::ApiSet;
use parity_rpc::NetworkSettings; use parity_rpc::NetworkSettings;
use cache::CacheConfig; use cache::CacheConfig;
use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, geth_ipc_path, parity_ipc_path, use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, geth_ipc_path, parity_ipc_path,
to_bootnodes, to_addresses, to_address, to_gas_limit, to_queue_strategy}; to_bootnodes, to_addresses, to_address, to_gas_limit, to_queue_strategy, passwords_from_files};
use dir::helpers::{replace_home, replace_home_and_local}; use dir::helpers::{replace_home, replace_home_and_local};
use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras, SpecType}; use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras, SpecType};
use ethcore_logger::Config as LogConfig; use ethcore_logger::Config as LogConfig;
use dir::{self, Directories, default_hypervisor_path, default_local_path, default_data_path}; use dir::{self, Directories, default_hypervisor_path, default_local_path, default_data_path};
use dapps::Configuration as DappsConfiguration; use dapps::Configuration as DappsConfiguration;
use ipfs::Configuration as IpfsConfiguration; use ipfs::Configuration as IpfsConfiguration;
use ethcore_private_tx::{ProviderConfig, EncryptorConfig};
use secretstore::{NodeSecretKey, Configuration as SecretStoreConfiguration, ContractAddress as SecretStoreContractAddress}; use secretstore::{NodeSecretKey, Configuration as SecretStoreConfiguration, ContractAddress as SecretStoreContractAddress};
use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack};
use run::RunCmd; use run::RunCmd;
@ -341,6 +342,7 @@ impl Configuration {
let verifier_settings = self.verifier_settings(); let verifier_settings = self.verifier_settings();
let whisper_config = self.whisper_config(); let whisper_config = self.whisper_config();
let (private_provider_conf, private_enc_conf, private_tx_enabled) = self.private_provider_config()?;
let run_cmd = RunCmd { let run_cmd = RunCmd {
cache_config: cache_config, cache_config: cache_config,
@ -379,6 +381,9 @@ impl Configuration {
ipfs_conf: ipfs_conf, ipfs_conf: ipfs_conf,
ui_conf: ui_conf, ui_conf: ui_conf,
secretstore_conf: secretstore_conf, secretstore_conf: secretstore_conf,
private_provider_conf: private_provider_conf,
private_encryptor_conf: private_enc_conf,
private_tx_enabled,
dapp: self.dapp_to_open()?, dapp: self.dapp_to_open()?,
ui: self.args.cmd_ui, ui: self.args.cmd_ui,
name: self.args.arg_identity, name: self.args.arg_identity,
@ -934,6 +939,29 @@ impl Configuration {
Ok(conf) Ok(conf)
} }
fn private_provider_config(&self) -> Result<(ProviderConfig, EncryptorConfig, bool), String> {
let provider_conf = ProviderConfig {
validator_accounts: to_addresses(&self.args.arg_private_validators)?,
signer_account: self.args.arg_private_signer.clone().and_then(|account| to_address(Some(account)).ok()),
passwords: match self.args.arg_private_passwords.clone() {
Some(file) => passwords_from_files(&vec![file].as_slice())?,
None => Vec::new(),
},
};
let encryptor_conf = EncryptorConfig {
base_url: self.args.arg_private_sstore_url.clone(),
threshold: self.args.arg_private_sstore_threshold.unwrap_or(0),
key_server_account: self.args.arg_private_account.clone().and_then(|account| to_address(Some(account)).ok()),
passwords: match self.args.arg_private_passwords.clone() {
Some(file) => passwords_from_files(&vec![file].as_slice())?,
None => Vec::new(),
},
};
Ok((provider_conf, encryptor_conf, self.args.flag_private_enabled))
}
fn network_settings(&self) -> Result<NetworkSettings, String> { fn network_settings(&self) -> Result<NetworkSettings, String> {
let http_conf = self.http_config()?; let http_conf = self.http_config()?;
let net_addresses = self.net_addresses()?; let net_addresses = self.net_addresses()?;
@ -1468,6 +1496,9 @@ mod tests {
ipfs_conf: Default::default(), ipfs_conf: Default::default(),
ui_conf: Default::default(), ui_conf: Default::default(),
secretstore_conf: Default::default(), secretstore_conf: Default::default(),
private_provider_conf: Default::default(),
private_encryptor_conf: Default::default(),
private_tx_enabled: false,
ui: false, ui: false,
dapp: None, dapp: None,
name: "".into(), name: "".into(),

View File

@ -53,14 +53,15 @@ extern crate ethcore_logger;
extern crate ethcore_migrations as migrations; extern crate ethcore_migrations as migrations;
extern crate ethcore_miner as miner; extern crate ethcore_miner as miner;
extern crate ethcore_network as network; extern crate ethcore_network as network;
extern crate ethcore_private_tx;
extern crate ethcore_service; extern crate ethcore_service;
extern crate ethcore_transaction as transaction; extern crate ethcore_transaction as transaction;
extern crate ethereum_types; extern crate ethereum_types;
extern crate migration as migr;
extern crate kvdb;
extern crate kvdb_rocksdb;
extern crate ethkey; extern crate ethkey;
extern crate ethsync; extern crate ethsync;
extern crate kvdb;
extern crate kvdb_rocksdb;
extern crate migration as migr;
extern crate node_health; extern crate node_health;
extern crate panic_hook; extern crate panic_hook;
extern crate parity_hash_fetch as hash_fetch; extern crate parity_hash_fetch as hash_fetch;

View File

@ -21,7 +21,7 @@ use ethsync::{self, AttachedProtocol, SyncConfig, NetworkConfiguration, Params,
use ethcore::snapshot::SnapshotService; use ethcore::snapshot::SnapshotService;
use light::Provider; use light::Provider;
pub use ethsync::{EthSync, SyncProvider, ManageNetwork}; pub use ethsync::{EthSync, SyncProvider, ManageNetwork, PrivateTxHandler};
pub use ethcore::client::ChainNotify; pub use ethcore::client::ChainNotify;
use ethcore_logger::Config as LogConfig; use ethcore_logger::Config as LogConfig;
@ -32,6 +32,7 @@ pub fn sync(
net_cfg: NetworkConfiguration, net_cfg: NetworkConfiguration,
client: Arc<BlockChainClient>, client: Arc<BlockChainClient>,
snapshot_service: Arc<SnapshotService>, snapshot_service: Arc<SnapshotService>,
private_tx_handler: Arc<PrivateTxHandler>,
provider: Arc<Provider>, provider: Arc<Provider>,
_log_settings: &LogConfig, _log_settings: &LogConfig,
attached_protos: Vec<AttachedProtocol>, attached_protos: Vec<AttachedProtocol>,
@ -42,6 +43,7 @@ pub fn sync(
chain: client, chain: client,
provider: provider, provider: provider,
snapshot_service: snapshot_service, snapshot_service: snapshot_service,
private_tx_handler,
network_config: net_cfg, network_config: net_cfg,
attached_protos: attached_protos, attached_protos: attached_protos,
}, },

View File

@ -22,6 +22,7 @@ use std::sync::{Arc, Weak};
pub use parity_rpc::signer::SignerService; pub use parity_rpc::signer::SignerService;
pub use parity_rpc::dapps::{DappsService, LocalDapp}; pub use parity_rpc::dapps::{DappsService, LocalDapp};
use ethcore_service::PrivateTxService;
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use ethcore::client::Client; use ethcore::client::Client;
use ethcore::miner::Miner; use ethcore::miner::Miner;
@ -40,6 +41,7 @@ use parity_rpc::dispatch::{FullDispatcher, LightDispatcher};
use parity_rpc::informant::{ActivityNotifier, ClientNotifier}; use parity_rpc::informant::{ActivityNotifier, ClientNotifier};
use parity_rpc::{Metadata, NetworkSettings, Host}; use parity_rpc::{Metadata, NetworkSettings, Host};
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use ethcore_private_tx::Provider as PrivateTransactionManager;
use updater::Updater; use updater::Updater;
#[derive(Debug, PartialEq, Clone, Eq, Hash)] #[derive(Debug, PartialEq, Clone, Eq, Hash)]
@ -70,6 +72,8 @@ pub enum Api {
Rpc, Rpc,
/// SecretStore (UNSAFE: arbitrary hash signing) /// SecretStore (UNSAFE: arbitrary hash signing)
SecretStore, SecretStore,
/// Private transaction manager (Safe)
Private,
/// Whisper (Safe) /// Whisper (Safe)
// TODO: _if_ someone guesses someone else's key or filter IDs they can remove // TODO: _if_ someone guesses someone else's key or filter IDs they can remove
// BUT these are all ephemeral so it seems fine. // BUT these are all ephemeral so it seems fine.
@ -98,6 +102,7 @@ impl FromStr for Api {
"traces" => Ok(Traces), "traces" => Ok(Traces),
"rpc" => Ok(Rpc), "rpc" => Ok(Rpc),
"secretstore" => Ok(SecretStore), "secretstore" => Ok(SecretStore),
"private" => Ok(Private),
"shh" => Ok(Whisper), "shh" => Ok(Whisper),
"shh_pubsub" => Ok(WhisperPubSub), "shh_pubsub" => Ok(WhisperPubSub),
api => Err(format!("Unknown api: {}", api)) api => Err(format!("Unknown api: {}", api))
@ -183,6 +188,7 @@ fn to_modules(apis: &HashSet<Api>) -> BTreeMap<String, String> {
Api::Traces => ("traces", "1.0"), Api::Traces => ("traces", "1.0"),
Api::Rpc => ("rpc", "1.0"), Api::Rpc => ("rpc", "1.0"),
Api::SecretStore => ("secretstore", "1.0"), Api::SecretStore => ("secretstore", "1.0"),
Api::Private => ("private", "1.0"),
Api::Whisper => ("shh", "1.0"), Api::Whisper => ("shh", "1.0"),
Api::WhisperPubSub => ("shh_pubsub", "1.0"), Api::WhisperPubSub => ("shh_pubsub", "1.0"),
}; };
@ -214,6 +220,7 @@ pub struct FullDependencies {
pub sync: Arc<SyncProvider>, pub sync: Arc<SyncProvider>,
pub net: Arc<ManageNetwork>, pub net: Arc<ManageNetwork>,
pub secret_store: Option<Arc<AccountProvider>>, pub secret_store: Option<Arc<AccountProvider>>,
pub private_tx_service: Option<Arc<PrivateTxService>>,
pub miner: Arc<Miner>, pub miner: Arc<Miner>,
pub external_miner: Arc<ExternalMiner>, pub external_miner: Arc<ExternalMiner>,
pub logger: Arc<RotatingLogger>, pub logger: Arc<RotatingLogger>,
@ -385,7 +392,10 @@ impl FullDependencies {
); );
} }
} }
} },
Api::Private => {
handler.extend_with(PrivateClient::new(self.private_tx_service.as_ref().map(|p| p.provider())).to_delegate());
},
} }
} }
} }
@ -437,6 +447,7 @@ pub struct LightDependencies<T> {
pub geth_compatibility: bool, pub geth_compatibility: bool,
pub remote: parity_reactor::Remote, pub remote: parity_reactor::Remote,
pub whisper_rpc: Option<::whisper::RpcFactory>, pub whisper_rpc: Option<::whisper::RpcFactory>,
pub private_tx_service: Option<Arc<PrivateTransactionManager>>,
pub gas_price_percentile: usize, pub gas_price_percentile: usize,
} }
@ -587,12 +598,18 @@ impl<C: LightChainClient + 'static> LightDependencies<C> {
let whisper = whisper_rpc.make_handler(self.net.clone()); let whisper = whisper_rpc.make_handler(self.net.clone());
handler.extend_with(::parity_whisper::rpc::Whisper::to_delegate(whisper)); handler.extend_with(::parity_whisper::rpc::Whisper::to_delegate(whisper));
} }
} },
Api::WhisperPubSub => { Api::WhisperPubSub => {
if let Some(ref whisper_rpc) = self.whisper_rpc { if let Some(ref whisper_rpc) = self.whisper_rpc {
let whisper = whisper_rpc.make_handler(self.net.clone()); let whisper = whisper_rpc.make_handler(self.net.clone());
handler.extend_with(::parity_whisper::rpc::WhisperPubSub::to_delegate(whisper)); handler.extend_with(::parity_whisper::rpc::WhisperPubSub::to_delegate(whisper));
} }
},
Api::Private => {
if let Some(ref tx_manager) = self.private_tx_service {
let private_tx_service = Some(tx_manager.clone());
handler.extend_with(PrivateClient::new(private_tx_service).to_delegate());
}
} }
} }
} }
@ -629,6 +646,7 @@ impl ApiSet {
Api::Rpc, Api::Rpc,
Api::Whisper, Api::Whisper,
Api::WhisperPubSub, Api::WhisperPubSub,
Api::Private,
].into_iter().cloned().collect(); ].into_iter().cloned().collect();
match *self { match *self {
@ -693,6 +711,7 @@ mod test {
assert_eq!(Api::Traces, "traces".parse().unwrap()); assert_eq!(Api::Traces, "traces".parse().unwrap());
assert_eq!(Api::Rpc, "rpc".parse().unwrap()); assert_eq!(Api::Rpc, "rpc".parse().unwrap());
assert_eq!(Api::SecretStore, "secretstore".parse().unwrap()); assert_eq!(Api::SecretStore, "secretstore".parse().unwrap());
assert_eq!(Api::Private, "private".parse().unwrap());
assert_eq!(Api::Whisper, "shh".parse().unwrap()); assert_eq!(Api::Whisper, "shh".parse().unwrap());
assert_eq!(Api::WhisperPubSub, "shh_pubsub".parse().unwrap()); assert_eq!(Api::WhisperPubSub, "shh_pubsub".parse().unwrap());
assert!("rp".parse::<Api>().is_err()); assert!("rp".parse::<Api>().is_err());
@ -712,7 +731,7 @@ mod test {
fn test_api_set_unsafe_context() { fn test_api_set_unsafe_context() {
let expected = vec![ let expected = vec![
// make sure this list contains only SAFE methods // make sure this list contains only SAFE methods
Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::Whisper, Api::WhisperPubSub, Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::Whisper, Api::WhisperPubSub, Api::Private,
].into_iter().collect(); ].into_iter().collect();
assert_eq!(ApiSet::UnsafeContext.list_apis(), expected); assert_eq!(ApiSet::UnsafeContext.list_apis(), expected);
} }
@ -721,7 +740,7 @@ mod test {
fn test_api_set_ipc_context() { fn test_api_set_ipc_context() {
let expected = vec![ let expected = vec![
// safe // safe
Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::Whisper, Api::WhisperPubSub, Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::Whisper, Api::WhisperPubSub, Api::Private,
// semi-safe // semi-safe
Api::ParityAccounts Api::ParityAccounts
].into_iter().collect(); ].into_iter().collect();
@ -732,7 +751,7 @@ mod test {
fn test_api_set_safe_context() { fn test_api_set_safe_context() {
let expected = vec![ let expected = vec![
// safe // safe
Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::SecretStore, Api::Whisper, Api::WhisperPubSub, Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::SecretStore, Api::Whisper, Api::WhisperPubSub, Api::Private,
// semi-safe // semi-safe
Api::ParityAccounts, Api::ParityAccounts,
// Unsafe // Unsafe
@ -747,7 +766,8 @@ mod test {
Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::SecretStore, Api::Whisper, Api::WhisperPubSub, Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::SecretStore, Api::Whisper, Api::WhisperPubSub,
Api::ParityAccounts, Api::ParityAccounts,
Api::ParitySet, Api::Signer, Api::ParitySet, Api::Signer,
Api::Personal Api::Personal,
Api::Private,
].into_iter().collect())); ].into_iter().collect()));
} }
@ -757,13 +777,14 @@ mod test {
Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::SecretStore, Api::Whisper, Api::WhisperPubSub, Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::SecretStore, Api::Whisper, Api::WhisperPubSub,
Api::ParityAccounts, Api::ParityAccounts,
Api::ParitySet, Api::Signer, Api::ParitySet, Api::Signer,
Api::Private
].into_iter().collect())); ].into_iter().collect()));
} }
#[test] #[test]
fn test_safe_parsing() { fn test_safe_parsing() {
assert_eq!("safe".parse::<ApiSet>().unwrap(), ApiSet::List(vec![ assert_eq!("safe".parse::<ApiSet>().unwrap(), ApiSet::List(vec![
Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::Whisper, Api::WhisperPubSub, Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::Whisper, Api::WhisperPubSub, Api::Private,
].into_iter().collect())); ].into_iter().collect()));
} }
} }

View File

@ -50,7 +50,7 @@ use parity_rpc::{NetworkSettings, informant, is_major_importing};
use parking_lot::{Condvar, Mutex}; use parking_lot::{Condvar, Mutex};
use updater::{UpdatePolicy, Updater}; use updater::{UpdatePolicy, Updater};
use parity_version::version; use parity_version::version;
use ethcore_private_tx::{ProviderConfig, EncryptorConfig, SecretStoreEncryptor};
use params::{ use params::{
SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch, SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch,
tracing_switch_to_bool, fatdb_switch_to_bool, mode_switch_to_bool tracing_switch_to_bool, fatdb_switch_to_bool, mode_switch_to_bool
@ -120,6 +120,9 @@ pub struct RunCmd {
pub ipfs_conf: ipfs::Configuration, pub ipfs_conf: ipfs::Configuration,
pub ui_conf: rpc::UiConfiguration, pub ui_conf: rpc::UiConfiguration,
pub secretstore_conf: secretstore::Configuration, pub secretstore_conf: secretstore::Configuration,
pub private_provider_conf: ProviderConfig,
pub private_encryptor_conf: EncryptorConfig,
pub private_tx_enabled: bool,
pub dapp: Option<String>, pub dapp: Option<String>,
pub ui: bool, pub ui: bool,
pub name: String, pub name: String,
@ -388,6 +391,7 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<Runnin
geth_compatibility: cmd.geth_compatibility, geth_compatibility: cmd.geth_compatibility,
remote: event_loop.remote(), remote: event_loop.remote(),
whisper_rpc: whisper_factory, whisper_rpc: whisper_factory,
private_tx_service: None, //TODO: add this to client.
gas_price_percentile: cmd.gas_price_percentile, gas_price_percentile: cmd.gas_price_percentile,
}); });
@ -622,6 +626,9 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
restoration_db_handler, restoration_db_handler,
&cmd.dirs.ipc_path(), &cmd.dirs.ipc_path(),
miner.clone(), miner.clone(),
account_provider.clone(),
Box::new(SecretStoreEncryptor::new(cmd.private_encryptor_conf, fetch.clone()).map_err(|e| e.to_string())?),
cmd.private_provider_conf,
).map_err(|e| format!("Client service error: {:?}", e))?; ).map_err(|e| format!("Client service error: {:?}", e))?;
let connection_filter_address = spec.params().node_permission_contract; let connection_filter_address = spec.params().node_permission_contract;
@ -630,6 +637,9 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
// take handle to client // take handle to client
let client = service.client(); let client = service.client();
// take handle to private transactions service
let private_tx_service = service.private_tx_service();
let private_tx_provider = private_tx_service.provider();
let connection_filter = connection_filter_address.map(|a| Arc::new(NodeFilter::new(Arc::downgrade(&client) as Weak<BlockChainClient>, a))); let connection_filter = connection_filter_address.map(|a| Arc::new(NodeFilter::new(Arc::downgrade(&client) as Weak<BlockChainClient>, a)));
let snapshot_service = service.snapshot_service(); let snapshot_service = service.snapshot_service();
@ -697,6 +707,7 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
net_conf.clone().into(), net_conf.clone().into(),
client.clone(), client.clone(),
snapshot_service.clone(), snapshot_service.clone(),
private_tx_service.clone(),
client.clone(), client.clone(),
&cmd.logger_config, &cmd.logger_config,
attached_protos, attached_protos,
@ -705,6 +716,15 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
service.add_notify(chain_notify.clone()); service.add_notify(chain_notify.clone());
// provider not added to a notification center is effectively disabled
// TODO [debris] refactor it later on
if cmd.private_tx_enabled {
service.add_notify(private_tx_provider.clone());
// TODO [ToDr] PrivateTX should use separate notifications
// re-using ChainNotify for this is a bit abusive.
private_tx_provider.add_notify(chain_notify.clone());
}
// start network // start network
if network_enabled { if network_enabled {
chain_notify.start(); chain_notify.start();
@ -796,6 +816,7 @@ fn execute_impl<Cr, Rr>(cmd: RunCmd, logger: Arc<RotatingLogger>, on_client_rq:
pool: cpu_pool.clone(), pool: cpu_pool.clone(),
remote: event_loop.remote(), remote: event_loop.remote(),
whisper_rpc: whisper_factory, whisper_rpc: whisper_factory,
private_tx_service: Some(private_tx_service.clone()),
gas_price_percentile: cmd.gas_price_percentile, gas_price_percentile: cmd.gas_price_percentile,
}); });

View File

@ -21,6 +21,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use hash::keccak; use hash::keccak;
use ethcore::account_provider::AccountProvider;
use ethcore::snapshot::{Progress, RestorationStatus, SnapshotService as SS}; use ethcore::snapshot::{Progress, RestorationStatus, SnapshotService as SS};
use ethcore::snapshot::io::{SnapshotReader, PackedReader, PackedWriter}; use ethcore::snapshot::io::{SnapshotReader, PackedReader, PackedWriter};
use ethcore::snapshot::service::Service as SnapshotService; use ethcore::snapshot::service::Service as SnapshotService;
@ -35,6 +36,7 @@ use helpers::{to_client_config, execute_upgrades, client_db_config, open_client_
use dir::Directories; use dir::Directories;
use user_defaults::UserDefaults; use user_defaults::UserDefaults;
use fdlimit; use fdlimit;
use ethcore_private_tx;
/// Kinds of snapshot commands. /// Kinds of snapshot commands.
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
@ -192,7 +194,10 @@ impl SnapshotCommand {
&snapshot_path, &snapshot_path,
restoration_db_handler, restoration_db_handler,
&self.dirs.ipc_path(), &self.dirs.ipc_path(),
Arc::new(Miner::with_spec(&spec)) Arc::new(Miner::with_spec(&spec)),
Arc::new(AccountProvider::transient_provider()),
Box::new(ethcore_private_tx::NoopEncryptor),
Default::default()
).map_err(|e| format!("Client service error: {:?}", e))?; ).map_err(|e| format!("Client service error: {:?}", e))?;
Ok(service) Ok(service)

View File

@ -96,7 +96,7 @@ impl<F: Fetch> Client<F> {
/// Gets the current ETH price and calls `set_price` with the result. /// Gets the current ETH price and calls `set_price` with the result.
pub fn get<G: Fn(PriceInfo) + Sync + Send + 'static>(&self, set_price: G) { pub fn get<G: Fn(PriceInfo) + Sync + Send + 'static>(&self, set_price: G) {
let future = self.fetch.fetch(&self.api_endpoint, fetch::Abort::default()) let future = self.fetch.get(&self.api_endpoint, fetch::Abort::default())
.from_err() .from_err()
.and_then(|response| { .and_then(|response| {
if !response.is_success() { if !response.is_success() {
@ -140,7 +140,7 @@ mod test {
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use fetch; use fetch;
use fetch::{Fetch, Url}; use fetch::{Fetch, Url, Method};
use futures_cpupool::CpuPool; use futures_cpupool::CpuPool;
use futures::future::{self, FutureResult}; use futures::future::{self, FutureResult};
use Client; use Client;
@ -158,7 +158,7 @@ mod test {
impl Fetch for FakeFetch { impl Fetch for FakeFetch {
type Result = FutureResult<fetch::Response, fetch::Error>; type Result = FutureResult<fetch::Response, fetch::Error>;
fn fetch(&self, url: &str, abort: fetch::Abort) -> Self::Result { fn fetch(&self, url: &str, _method: Method, abort: fetch::Abort) -> Self::Result {
assert_eq!(url, "https://api.etherscan.io/api?module=stats&action=ethprice"); assert_eq!(url, "https://api.etherscan.io/api?module=stats&action=ethprice");
let u = Url::parse(url).unwrap(); let u = Url::parse(url).unwrap();
let mut val = self.1.lock(); let mut val = self.1.lock();

View File

@ -44,6 +44,7 @@ ethcore-io = { path = "../util/io" }
ethcore-light = { path = "../ethcore/light" } ethcore-light = { path = "../ethcore/light" }
ethcore-logger = { path = "../logger" } ethcore-logger = { path = "../logger" }
ethcore-miner = { path = "../miner" } ethcore-miner = { path = "../miner" }
ethcore-private-tx = { path = "../ethcore/private-tx" }
ethcore-transaction = { path = "../ethcore/transaction" } ethcore-transaction = { path = "../ethcore/transaction" }
ethereum-types = "0.3" ethereum-types = "0.3"

View File

@ -67,6 +67,7 @@ extern crate rlp;
extern crate stats; extern crate stats;
extern crate keccak_hash as hash; extern crate keccak_hash as hash;
extern crate hardware_wallet; extern crate hardware_wallet;
extern crate ethcore_private_tx;
extern crate patricia_trie as trie; extern crate patricia_trie as trie;
#[macro_use] #[macro_use]

View File

@ -23,6 +23,7 @@ use ethcore::error::{Error as EthcoreError, CallError};
use jsonrpc_core::{futures, Error, ErrorCode, Value}; use jsonrpc_core::{futures, Error, ErrorCode, Value};
use rlp::DecoderError; use rlp::DecoderError;
use transaction::Error as TransactionError; use transaction::Error as TransactionError;
use ethcore_private_tx::Error as PrivateTransactionError;
mod codes { mod codes {
// NOTE [ToDr] Codes from [-32099, -32000] // NOTE [ToDr] Codes from [-32099, -32000]
@ -39,6 +40,7 @@ mod codes {
pub const ACCOUNT_LOCKED: i64 = -32020; pub const ACCOUNT_LOCKED: i64 = -32020;
pub const PASSWORD_INVALID: i64 = -32021; pub const PASSWORD_INVALID: i64 = -32021;
pub const ACCOUNT_ERROR: i64 = -32023; pub const ACCOUNT_ERROR: i64 = -32023;
pub const PRIVATE_ERROR: i64 = -32024;
pub const REQUEST_REJECTED: i64 = -32040; pub const REQUEST_REJECTED: i64 = -32040;
pub const REQUEST_REJECTED_LIMIT: i64 = -32041; pub const REQUEST_REJECTED_LIMIT: i64 = -32041;
pub const REQUEST_NOT_FOUND: i64 = -32042; pub const REQUEST_NOT_FOUND: i64 = -32042;
@ -288,6 +290,22 @@ pub fn password(error: AccountError) -> Error {
} }
} }
pub fn private_message(error: PrivateTransactionError) -> Error {
Error {
code: ErrorCode::ServerError(codes::PRIVATE_ERROR),
message: "Private transactions call failed.".into(),
data: Some(Value::String(format!("{:?}", error))),
}
}
pub fn private_message_block_id_not_supported() -> Error {
Error {
code: ErrorCode::ServerError(codes::PRIVATE_ERROR),
message: "Pending block id not supported.".into(),
data: None,
}
}
pub fn transaction_message(error: TransactionError) -> String { pub fn transaction_message(error: TransactionError) -> String {
use self::TransactionError::*; use self::TransactionError::*;

View File

@ -128,7 +128,7 @@ impl<F: Fetch> ParitySet for ParitySetClient<F> {
} }
fn hash_content(&self, url: String) -> BoxFuture<H256> { fn hash_content(&self, url: String) -> BoxFuture<H256> {
let future = self.fetch.fetch(&url, Default::default()).then(move |result| { let future = self.fetch.get(&url, Default::default()).then(move |result| {
result result
.map_err(errors::fetch) .map_err(errors::fetch)
.and_then(move |response| { .and_then(move |response| {

View File

@ -32,6 +32,7 @@ mod rpc;
mod secretstore; mod secretstore;
mod traces; mod traces;
mod web3; mod web3;
mod private;
pub mod light; pub mod light;
@ -51,3 +52,4 @@ pub use self::traces::TracesClient;
pub use self::web3::Web3Client; pub use self::web3::Web3Client;
pub use self::rpc::RpcClient; pub use self::rpc::RpcClient;
pub use self::secretstore::SecretStoreClient; pub use self::secretstore::SecretStoreClient;
pub use self::private::PrivateClient;

View File

@ -170,7 +170,7 @@ impl<C, M, U, F> ParitySet for ParitySetClient<C, M, U, F> where
} }
fn hash_content(&self, url: String) -> BoxFuture<H256> { fn hash_content(&self, url: String) -> BoxFuture<H256> {
let future = self.fetch.fetch(&url, Default::default()).then(move |result| { let future = self.fetch.get(&url, Default::default()).then(move |result| {
result result
.map_err(errors::fetch) .map_err(errors::fetch)
.and_then(move |response| { .and_then(move |response| {

122
rpc/src/v1/impls/private.rs Normal file
View File

@ -0,0 +1,122 @@
// 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 <http://www.gnu.org/licenses/>.
//! Privte transaction signing RPC implementation.
use std::sync::Arc;
use rlp::UntrustedRlp;
use ethcore_private_tx::Provider as PrivateTransactionManager;
use ethereum_types::Address;
use transaction::SignedTransaction;
use jsonrpc_core::{Error};
use v1::types::{Bytes, PrivateTransactionReceipt, H160, H256, TransactionRequest, U256,
BlockNumber, PrivateTransactionReceiptAndTransaction, CallRequest, block_number_to_id};
use v1::traits::Private;
use v1::metadata::Metadata;
use v1::helpers::{errors, fake_sign};
/// Private transaction manager API endpoint implementation.
pub struct PrivateClient {
private: Option<Arc<PrivateTransactionManager>>,
}
impl PrivateClient {
/// Creates a new instance.
pub fn new(private: Option<Arc<PrivateTransactionManager>>) -> Self {
PrivateClient {
private,
}
}
fn unwrap_manager(&self) -> Result<&PrivateTransactionManager, Error> {
match self.private {
Some(ref arc) => Ok(&**arc),
None => Err(errors::public_unsupported(None)),
}
}
}
impl Private for PrivateClient {
type Metadata = Metadata;
fn send_transaction(&self, request: Bytes) -> Result<PrivateTransactionReceipt, Error> {
let signed_transaction = UntrustedRlp::new(&request.into_vec()).as_val()
.map_err(errors::rlp)
.and_then(|tx| SignedTransaction::new(tx).map_err(errors::transaction))?;
let client = self.unwrap_manager()?;
let receipt = client.create_private_transaction(signed_transaction).map_err(|e| errors::private_message(e))?;
Ok(receipt.into())
}
fn compose_deployment_transaction(&self, block_number: BlockNumber, request: Bytes, validators: Vec<H160>, gas_price: U256) -> Result<PrivateTransactionReceiptAndTransaction, Error> {
let signed_transaction = UntrustedRlp::new(&request.into_vec()).as_val()
.map_err(errors::rlp)
.and_then(|tx| SignedTransaction::new(tx).map_err(errors::transaction))?;
let client = self.unwrap_manager()?;
let addresses: Vec<Address> = validators.into_iter().map(Into::into).collect();
let id = match block_number {
BlockNumber::Pending => return Err(errors::private_message_block_id_not_supported()),
num => block_number_to_id(num)
};
let (transaction, contract_address) = client.public_creation_transaction(id, &signed_transaction, addresses.as_slice(), gas_price.into())
.map_err(|e| errors::private_message(e))?;
let tx_hash = transaction.hash(None);
let request = TransactionRequest {
from: Some(signed_transaction.sender().into()),
to: None,
nonce: Some(transaction.nonce.into()),
gas_price: Some(transaction.gas_price.into()),
gas: Some(transaction.gas.into()),
value: Some(transaction.value.into()),
data: Some(transaction.data.into()),
condition: None,
};
Ok(PrivateTransactionReceiptAndTransaction {
transaction: request,
receipt: PrivateTransactionReceipt {
transaction_hash: tx_hash.into(),
contract_address: contract_address.map(|address| address.into()),
status_code: 0,
}
})
}
fn private_call(&self, meta: Self::Metadata, block_number: BlockNumber, request: CallRequest) -> Result<Bytes, Error> {
let id = match block_number {
BlockNumber::Pending => return Err(errors::private_message_block_id_not_supported()),
num => block_number_to_id(num)
};
let request = CallRequest::into(request);
let signed = fake_sign::sign_call(request, meta.is_dapp())?;
let client = self.unwrap_manager()?;
let executed_result = client.private_call(id, &signed).map_err(|e| errors::private_message(e))?;
Ok(executed_result.output.into())
}
fn private_contract_key(&self, contract_address: H160) -> Result<H256, Error> {
let client = self.unwrap_manager()?;
let key = client.contract_key_id(&contract_address.into()).map_err(|e| errors::private_message(e))?;
Ok(key.into())
}
}

View File

@ -41,7 +41,7 @@ pub mod informant;
pub mod metadata; pub mod metadata;
pub mod traits; pub mod traits;
pub use self::traits::{Web3, Eth, EthFilter, EthPubSub, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, PubSub, Signer, Personal, Traces, Rpc, SecretStore}; pub use self::traits::{Web3, Eth, EthFilter, EthPubSub, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, PubSub, Signer, Personal, Traces, Rpc, SecretStore, Private};
pub use self::impls::*; pub use self::impls::*;
pub use self::helpers::{NetworkSettings, block_import, dispatch}; pub use self::helpers::{NetworkSettings, block_import, dispatch};
pub use self::metadata::Metadata; pub use self::metadata::Metadata;

View File

@ -18,7 +18,7 @@
use std::thread; use std::thread;
use jsonrpc_core::futures::{self, Future}; use jsonrpc_core::futures::{self, Future};
use fetch::{self, Fetch, Url}; use fetch::{self, Fetch, Url, Method};
use hyper; use hyper;
/// Test implementation of fetcher. Will always return the same file. /// Test implementation of fetcher. Will always return the same file.
@ -28,7 +28,7 @@ pub struct TestFetch;
impl Fetch for TestFetch { impl Fetch for TestFetch {
type Result = Box<Future<Item = fetch::Response, Error = fetch::Error> + Send + 'static>; type Result = Box<Future<Item = fetch::Response, Error = fetch::Error> + Send + 'static>;
fn fetch(&self, url: &str, abort: fetch::Abort) -> Self::Result { fn fetch(&self, url: &str, _method: Method, abort: fetch::Abort) -> Self::Result {
let u = Url::parse(url).unwrap(); let u = Url::parse(url).unwrap();
let (tx, rx) = futures::oneshot(); let (tx, rx) = futures::oneshot();
thread::spawn(move || { thread::spawn(move || {

View File

@ -31,6 +31,7 @@ pub mod signer;
pub mod traces; pub mod traces;
pub mod rpc; pub mod rpc;
pub mod secretstore; pub mod secretstore;
pub mod private;
pub use self::web3::Web3; pub use self::web3::Web3;
pub use self::eth::{Eth, EthFilter}; pub use self::eth::{Eth, EthFilter};
@ -47,3 +48,4 @@ pub use self::signer::Signer;
pub use self::traces::Traces; pub use self::traces::Traces;
pub use self::rpc::Rpc; pub use self::rpc::Rpc;
pub use self::secretstore::SecretStore; pub use self::secretstore::SecretStore;
pub use self::private::Private;

View File

@ -0,0 +1,45 @@
// 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 <http://www.gnu.org/licenses/>.
//! SecretStore-specific rpc interface.
use jsonrpc_core::Error;
use v1::types::{Bytes, PrivateTransactionReceipt, H160, H256, U256, BlockNumber,
PrivateTransactionReceiptAndTransaction, CallRequest};
build_rpc_trait! {
/// Private transaction management RPC interface.
pub trait Private {
type Metadata;
/// Sends private transaction; Transaction will be added to the validation queue and sent out when ready.
#[rpc(name = "private_sendTransaction")]
fn send_transaction(&self, Bytes) -> Result<PrivateTransactionReceipt, Error>;
/// Creates a transaction for contract's deployment from origin (signed transaction)
#[rpc(name = "private_composeDeploymentTransaction")]
fn compose_deployment_transaction(&self, BlockNumber, Bytes, Vec<H160>, U256) -> Result<PrivateTransactionReceiptAndTransaction, Error>;
/// Make a call to the private contract
#[rpc(meta, name = "private_call")]
fn private_call(&self, Self::Metadata, BlockNumber, CallRequest) -> Result<Bytes, Error>;
/// Retrieve the id of the key associated with the contract
#[rpc(name = "private_contractKey")]
fn private_contract_key(&self, H160) -> Result<H256, Error>;
}
}

View File

@ -44,6 +44,7 @@ mod transaction_request;
mod transaction_condition; mod transaction_condition;
mod uint; mod uint;
mod work; mod work;
mod private_receipt;
pub mod pubsub; pub mod pubsub;
@ -80,6 +81,7 @@ pub use self::transaction_request::TransactionRequest;
pub use self::transaction_condition::TransactionCondition; pub use self::transaction_condition::TransactionCondition;
pub use self::uint::{U128, U256, U64}; pub use self::uint::{U128, U256, U64};
pub use self::work::Work; pub use self::work::Work;
pub use self::private_receipt::{PrivateTransactionReceipt, PrivateTransactionReceiptAndTransaction};
// TODO [ToDr] Refactor to a proper type Vec of enums? // TODO [ToDr] Refactor to a proper type Vec of enums?
/// Expected tracing type. /// Expected tracing type.

View File

@ -0,0 +1,54 @@
// 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 <http://www.gnu.org/licenses/>.
use v1::types::{H160, H256, TransactionRequest};
use ethcore_private_tx::{Receipt as EthPrivateReceipt};
/// Receipt
#[derive(Debug, Serialize)]
pub struct PrivateTransactionReceipt {
/// Transaction Hash
#[serde(rename="transactionHash")]
pub transaction_hash: H256,
/// Private contract address
#[serde(rename="contractAddress")]
pub contract_address: Option<H160>,
/// Status code
#[serde(rename="status")]
pub status_code: u8,
}
impl From<EthPrivateReceipt> for PrivateTransactionReceipt {
fn from(r: EthPrivateReceipt) -> Self {
PrivateTransactionReceipt {
transaction_hash: r.hash.into(),
contract_address: r.contract_address.map(Into::into),
status_code: r.status_code.into(),
}
}
}
/// Receipt and Transaction
#[derive(Debug, Serialize)]
pub struct PrivateTransactionReceiptAndTransaction {
/// Receipt
#[serde(rename="receipt")]
pub receipt: PrivateTransactionReceipt,
/// Transaction
#[serde(rename="transaction")]
pub transaction: TransactionRequest,
}

View File

@ -18,6 +18,7 @@ ethcore = { path = "../ethcore" }
ethereum-types = "0.3" ethereum-types = "0.3"
plain_hasher = { path = "../util/plain_hasher" } plain_hasher = { path = "../util/plain_hasher" }
rlp = { path = "../util/rlp" } rlp = { path = "../util/rlp" }
rustc-hex = "1.0"
keccak-hash = { path = "../util/hash" } keccak-hash = { path = "../util/hash" }
triehash = { path = "../util/triehash" } triehash = { path = "../util/triehash" }
kvdb = { path = "../util/kvdb" } kvdb = { path = "../util/kvdb" }

View File

@ -24,7 +24,7 @@ use network::{NetworkProtocolHandler, NetworkContext, HostInfo, PeerId, Protocol
use ethereum_types::{H256, H512, U256}; use ethereum_types::{H256, H512, U256};
use io::{TimerToken}; use io::{TimerToken};
use ethcore::ethstore::ethkey::Secret; use ethcore::ethstore::ethkey::Secret;
use ethcore::client::{BlockChainClient, ChainNotify}; use ethcore::client::{BlockChainClient, ChainNotify, ChainMessageType};
use ethcore::snapshot::SnapshotService; use ethcore::snapshot::SnapshotService;
use ethcore::header::BlockNumber; use ethcore::header::BlockNumber;
use sync_io::NetSyncIo; use sync_io::NetSyncIo;
@ -32,11 +32,13 @@ use chain::{ChainSync, SyncStatus as EthSyncStatus};
use std::net::{SocketAddr, AddrParseError}; use std::net::{SocketAddr, AddrParseError};
use std::str::FromStr; use std::str::FromStr;
use parking_lot::RwLock; use parking_lot::RwLock;
use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT}; use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT, ETH_PROTOCOL_VERSION_63, ETH_PROTOCOL_VERSION_62,
PAR_PROTOCOL_VERSION_1, PAR_PROTOCOL_VERSION_2, PAR_PROTOCOL_VERSION_3};
use light::client::AsLightClient; use light::client::AsLightClient;
use light::Provider; use light::Provider;
use light::net::{self as light_net, LightProtocol, Params as LightParams, Capabilities, Handler as LightHandler, EventContext}; use light::net::{self as light_net, LightProtocol, Params as LightParams, Capabilities, Handler as LightHandler, EventContext};
use network::IpFilter; use network::IpFilter;
use private_tx::PrivateTxHandler;
/// Parity sync protocol /// Parity sync protocol
pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par";
@ -227,6 +229,8 @@ pub struct Params {
pub chain: Arc<BlockChainClient>, pub chain: Arc<BlockChainClient>,
/// Snapshot service. /// Snapshot service.
pub snapshot_service: Arc<SnapshotService>, pub snapshot_service: Arc<SnapshotService>,
/// Private tx service.
pub private_tx_handler: Arc<PrivateTxHandler>,
/// Light data provider. /// Light data provider.
pub provider: Arc<::light::Provider>, pub provider: Arc<::light::Provider>,
/// Network layer configuration. /// Network layer configuration.
@ -288,7 +292,7 @@ impl EthSync {
}) })
}; };
let chain_sync = ChainSync::new(params.config, &*params.chain); let chain_sync = ChainSync::new(params.config, &*params.chain, params.private_tx_handler.clone());
let service = NetworkService::new(params.network_config.clone().into_basic()?, connection_filter)?; let service = NetworkService::new(params.network_config.clone().into_basic()?, connection_filter)?;
let sync = Arc::new(EthSync { let sync = Arc::new(EthSync {
@ -392,9 +396,10 @@ impl NetworkProtocolHandler for SyncProtocolHandler {
} }
fn timeout(&self, io: &NetworkContext, _timer: TimerToken) { fn timeout(&self, io: &NetworkContext, _timer: TimerToken) {
self.sync.write().maintain_peers(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay)); let mut io = NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay);
self.sync.write().maintain_sync(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay)); self.sync.write().maintain_peers(&mut io);
self.sync.write().propagate_new_transactions(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay)); self.sync.write().maintain_sync(&mut io);
self.sync.write().propagate_new_transactions(&mut io);
} }
} }
@ -411,7 +416,8 @@ impl ChainNotify for EthSync {
use light::net::Announcement; use light::net::Announcement;
self.network.with_context(self.subprotocol_name, |context| { self.network.with_context(self.subprotocol_name, |context| {
let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain, &*self.eth_handler.snapshot_service, &self.eth_handler.overlay); let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain, &*self.eth_handler.snapshot_service,
&self.eth_handler.overlay);
self.eth_handler.sync.write().chain_new_blocks( self.eth_handler.sync.write().chain_new_blocks(
&mut sync_io, &mut sync_io,
&imported, &imported,
@ -448,10 +454,10 @@ impl ChainNotify for EthSync {
Err(err) => warn!("Error starting network: {}", err), Err(err) => warn!("Error starting network: {}", err),
_ => {}, _ => {},
} }
self.network.register_protocol(self.eth_handler.clone(), self.subprotocol_name, ETH_PACKET_COUNT, &[62u8, 63u8]) self.network.register_protocol(self.eth_handler.clone(), self.subprotocol_name, ETH_PACKET_COUNT, &[ETH_PROTOCOL_VERSION_62, ETH_PROTOCOL_VERSION_63])
.unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e)); .unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e));
// register the warp sync subprotocol // register the warp sync subprotocol
self.network.register_protocol(self.eth_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8, 2u8]) self.network.register_protocol(self.eth_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[PAR_PROTOCOL_VERSION_1, PAR_PROTOCOL_VERSION_2, PAR_PROTOCOL_VERSION_3])
.unwrap_or_else(|e| warn!("Error registering snapshot sync protocol: {:?}", e)); .unwrap_or_else(|e| warn!("Error registering snapshot sync protocol: {:?}", e));
// register the light protocol. // register the light protocol.
@ -469,10 +475,14 @@ impl ChainNotify for EthSync {
self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e));
} }
fn broadcast(&self, message: Vec<u8>) { fn broadcast(&self, message_type: ChainMessageType) {
self.network.with_context(WARP_SYNC_PROTOCOL_ID, |context| { self.network.with_context(WARP_SYNC_PROTOCOL_ID, |context| {
let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain, &*self.eth_handler.snapshot_service, &self.eth_handler.overlay); let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain, &*self.eth_handler.snapshot_service, &self.eth_handler.overlay);
self.eth_handler.sync.write().propagate_consensus_packet(&mut sync_io, message.clone()); match message_type {
ChainMessageType::Consensus(message) => self.eth_handler.sync.write().propagate_consensus_packet(&mut sync_io, message),
ChainMessageType::PrivateTransaction(message) => self.eth_handler.sync.write().propagate_private_transaction(&mut sync_io, message),
ChainMessageType::SignedPrivateTransaction(message) => self.eth_handler.sync.write().propagate_signed_private_transaction(&mut sync_io, message),
}
}); });
} }

View File

@ -88,6 +88,7 @@
/// All other messages are ignored. /// All other messages are ignored.
/// ///
use std::sync::Arc;
use std::collections::{HashSet, HashMap}; use std::collections::{HashSet, HashMap};
use std::cmp; use std::cmp;
use std::time::Instant; use std::time::Instant;
@ -111,15 +112,23 @@ use rand::Rng;
use snapshot::{Snapshot, ChunkType}; use snapshot::{Snapshot, ChunkType};
use api::{EthProtocolInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID}; use api::{EthProtocolInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID};
use transactions_stats::{TransactionsStats, Stats as TransactionStats}; use transactions_stats::{TransactionsStats, Stats as TransactionStats};
use private_tx::PrivateTxHandler;
known_heap_size!(0, PeerInfo); known_heap_size!(0, PeerInfo);
type PacketDecodeError = DecoderError; type PacketDecodeError = DecoderError;
const PROTOCOL_VERSION_63: u8 = 63; /// 63 version of Ethereum protocol.
const PROTOCOL_VERSION_62: u8 = 62; pub const ETH_PROTOCOL_VERSION_63: u8 = 63;
const PROTOCOL_VERSION_1: u8 = 1; /// 62 version of Ethereum protocol.
const PROTOCOL_VERSION_2: u8 = 2; pub const ETH_PROTOCOL_VERSION_62: u8 = 62;
/// 1 version of Parity protocol.
pub const PAR_PROTOCOL_VERSION_1: u8 = 1;
/// 2 version of Parity protocol (consensus messages added).
pub const PAR_PROTOCOL_VERSION_2: u8 = 2;
/// 3 version of Parity protocol (private transactions messages added).
pub const PAR_PROTOCOL_VERSION_3: u8 = 3;
const MAX_BODIES_TO_SEND: usize = 256; const MAX_BODIES_TO_SEND: usize = 256;
const MAX_HEADERS_TO_SEND: usize = 512; const MAX_HEADERS_TO_SEND: usize = 512;
const MAX_NODE_DATA_TO_SEND: usize = 1024; const MAX_NODE_DATA_TO_SEND: usize = 1024;
@ -160,8 +169,10 @@ const SNAPSHOT_MANIFEST_PACKET: u8 = 0x12;
const GET_SNAPSHOT_DATA_PACKET: u8 = 0x13; const GET_SNAPSHOT_DATA_PACKET: u8 = 0x13;
const SNAPSHOT_DATA_PACKET: u8 = 0x14; const SNAPSHOT_DATA_PACKET: u8 = 0x14;
const CONSENSUS_DATA_PACKET: u8 = 0x15; const CONSENSUS_DATA_PACKET: u8 = 0x15;
const PRIVATE_TRANSACTION_PACKET: u8 = 0x16;
const SIGNED_PRIVATE_TRANSACTION_PACKET: u8 = 0x17;
pub const SNAPSHOT_SYNC_PACKET_COUNT: u8 = 0x16; pub const SNAPSHOT_SYNC_PACKET_COUNT: u8 = 0x18;
const MAX_SNAPSHOT_CHUNKS_DOWNLOAD_AHEAD: usize = 3; const MAX_SNAPSHOT_CHUNKS_DOWNLOAD_AHEAD: usize = 3;
@ -384,6 +395,8 @@ pub struct ChainSync {
transactions_stats: TransactionsStats, transactions_stats: TransactionsStats,
/// Enable ancient block downloading /// Enable ancient block downloading
download_old_blocks: bool, download_old_blocks: bool,
/// Shared private tx service.
private_tx_handler: Arc<PrivateTxHandler>,
/// Enable warp sync. /// Enable warp sync.
warp_sync: WarpSync, warp_sync: WarpSync,
} }
@ -392,7 +405,7 @@ type RlpResponseResult = Result<Option<(PacketId, RlpStream)>, PacketDecodeError
impl ChainSync { impl ChainSync {
/// Create a new instance of syncing strategy. /// Create a new instance of syncing strategy.
pub fn new(config: SyncConfig, chain: &BlockChainClient) -> ChainSync { pub fn new(config: SyncConfig, chain: &BlockChainClient, private_tx_handler: Arc<PrivateTxHandler>) -> ChainSync {
let chain_info = chain.chain_info(); let chain_info = chain.chain_info();
let best_block = chain.chain_info().best_block_number; let best_block = chain.chain_info().best_block_number;
let state = match config.warp_sync { let state = match config.warp_sync {
@ -417,6 +430,7 @@ impl ChainSync {
snapshot: Snapshot::new(), snapshot: Snapshot::new(),
sync_start_time: None, sync_start_time: None,
transactions_stats: TransactionsStats::default(), transactions_stats: TransactionsStats::default(),
private_tx_handler,
warp_sync: config.warp_sync, warp_sync: config.warp_sync,
}; };
sync.update_targets(chain); sync.update_targets(chain);
@ -428,7 +442,7 @@ impl ChainSync {
let last_imported_number = self.new_blocks.last_imported_block_number(); let last_imported_number = self.new_blocks.last_imported_block_number();
SyncStatus { SyncStatus {
state: self.state.clone(), state: self.state.clone(),
protocol_version: PROTOCOL_VERSION_63, protocol_version: ETH_PROTOCOL_VERSION_63,
network_id: self.network_id, network_id: self.network_id,
start_block_number: self.starting_block, start_block_number: self.starting_block,
last_imported_block_number: Some(last_imported_number), last_imported_block_number: Some(last_imported_number),
@ -662,7 +676,8 @@ impl ChainSync {
trace!(target: "sync", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, self.network_id, peer.network_id); trace!(target: "sync", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, self.network_id, peer.network_id);
return Ok(()); return Ok(());
} }
if (warp_protocol && peer.protocol_version != PROTOCOL_VERSION_1 && peer.protocol_version != PROTOCOL_VERSION_2) || (!warp_protocol && peer.protocol_version != PROTOCOL_VERSION_63 && peer.protocol_version != PROTOCOL_VERSION_62) { if (warp_protocol && peer.protocol_version != PAR_PROTOCOL_VERSION_1 && peer.protocol_version != PAR_PROTOCOL_VERSION_2 && peer.protocol_version != PAR_PROTOCOL_VERSION_3)
|| (!warp_protocol && peer.protocol_version != ETH_PROTOCOL_VERSION_63 && peer.protocol_version != ETH_PROTOCOL_VERSION_62) {
io.disable_peer(peer_id); io.disable_peer(peer_id);
trace!(target: "sync", "Peer {} unsupported eth protocol ({})", peer_id, peer.protocol_version); trace!(target: "sync", "Peer {} unsupported eth protocol ({})", peer_id, peer.protocol_version);
return Ok(()); return Ok(());
@ -1493,6 +1508,7 @@ impl ChainSync {
} }
if !self.peers.get(&peer_id).map_or(false, |p| p.can_sync()) { if !self.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
trace!(target: "sync", "{} Ignoring transactions from unconfirmed/unknown peer", peer_id); trace!(target: "sync", "{} Ignoring transactions from unconfirmed/unknown peer", peer_id);
return Ok(());
} }
let item_count = r.item_count()?; let item_count = r.item_count()?;
@ -1515,7 +1531,7 @@ impl ChainSync {
fn send_status(&mut self, io: &mut SyncIo, peer: PeerId) -> Result<(), network::Error> { fn send_status(&mut self, io: &mut SyncIo, peer: PeerId) -> Result<(), network::Error> {
let warp_protocol_version = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer); let warp_protocol_version = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer);
let warp_protocol = warp_protocol_version != 0; let warp_protocol = warp_protocol_version != 0;
let protocol = if warp_protocol { warp_protocol_version } else { PROTOCOL_VERSION_63 }; let protocol = if warp_protocol { warp_protocol_version } else { ETH_PROTOCOL_VERSION_63 };
trace!(target: "sync", "Sending status to {}, protocol version {}", peer, protocol); trace!(target: "sync", "Sending status to {}, protocol version {}", peer, protocol);
let mut packet = RlpStream::new_list(if warp_protocol { 7 } else { 5 }); let mut packet = RlpStream::new_list(if warp_protocol { 7 } else { 5 });
let chain = io.chain().chain_info(); let chain = io.chain().chain_info();
@ -1792,6 +1808,8 @@ impl ChainSync {
NEW_BLOCK_HASHES_PACKET => self.on_peer_new_hashes(io, peer, &rlp), NEW_BLOCK_HASHES_PACKET => self.on_peer_new_hashes(io, peer, &rlp),
SNAPSHOT_MANIFEST_PACKET => self.on_snapshot_manifest(io, peer, &rlp), SNAPSHOT_MANIFEST_PACKET => self.on_snapshot_manifest(io, peer, &rlp),
SNAPSHOT_DATA_PACKET => self.on_snapshot_data(io, peer, &rlp), SNAPSHOT_DATA_PACKET => self.on_snapshot_data(io, peer, &rlp),
PRIVATE_TRANSACTION_PACKET => self.on_private_transaction(io, peer, &rlp),
SIGNED_PRIVATE_TRANSACTION_PACKET => self.on_signed_private_transaction(io, peer, &rlp),
_ => { _ => {
debug!(target: "sync", "{}: Unknown packet {}", peer, packet_id); debug!(target: "sync", "{}: Unknown packet {}", peer, packet_id);
Ok(()) Ok(())
@ -1945,7 +1963,11 @@ impl ChainSync {
} }
fn get_consensus_peers(&self) -> Vec<PeerId> { fn get_consensus_peers(&self) -> Vec<PeerId> {
self.peers.iter().filter_map(|(id, p)| if p.protocol_version == PROTOCOL_VERSION_2 { Some(*id) } else { None }).collect() self.peers.iter().filter_map(|(id, p)| if p.protocol_version >= PAR_PROTOCOL_VERSION_2 { Some(*id) } else { None }).collect()
}
fn get_private_transaction_peers(&self) -> Vec<PeerId> {
self.peers.iter().filter_map(|(id, p)| if p.protocol_version >= PAR_PROTOCOL_VERSION_3 { Some(*id) } else { None }).collect()
} }
/// propagates latest block to a set of peers /// propagates latest block to a set of peers
@ -2223,6 +2245,54 @@ impl ChainSync {
self.send_packet(io, peer_id, CONSENSUS_DATA_PACKET, packet.clone()); self.send_packet(io, peer_id, CONSENSUS_DATA_PACKET, packet.clone());
} }
} }
/// Called when peer sends us new private transaction packet
fn on_private_transaction(&self, _io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> {
if !self.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
trace!(target: "sync", "{} Ignoring packet from unconfirmed/unknown peer", peer_id);
return Ok(());
}
trace!(target: "sync", "Received private transaction packet from {:?}", peer_id);
if let Err(e) = self.private_tx_handler.import_private_transaction(r.as_raw()) {
trace!(target: "sync", "Ignoring the message, error queueing: {}", e);
}
Ok(())
}
/// Broadcast private transaction message to peers.
pub fn propagate_private_transaction(&mut self, io: &mut SyncIo, packet: Bytes) {
let lucky_peers = ChainSync::select_random_peers(&self.get_private_transaction_peers());
trace!(target: "sync", "Sending private transaction packet to {:?}", lucky_peers);
for peer_id in lucky_peers {
self.send_packet(io, peer_id, PRIVATE_TRANSACTION_PACKET, packet.clone());
}
}
/// Called when peer sends us signed private transaction packet
fn on_signed_private_transaction(&self, _io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> {
if !self.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
trace!(target: "sync", "{} Ignoring packet from unconfirmed/unknown peer", peer_id);
return Ok(());
}
trace!(target: "sync", "Received signed private transaction packet from {:?}", peer_id);
if let Err(e) = self.private_tx_handler.import_signed_private_transaction(r.as_raw()) {
trace!(target: "sync", "Ignoring the message, error queueing: {}", e);
}
Ok(())
}
/// Broadcast signed private transaction message to peers.
pub fn propagate_signed_private_transaction(&mut self, io: &mut SyncIo, packet: Bytes) {
let lucky_peers = ChainSync::select_random_peers(&self.get_private_transaction_peers());
trace!(target: "sync", "Sending signed private transaction packet to {:?}", lucky_peers);
for peer_id in lucky_peers {
self.send_packet(io, peer_id, SIGNED_PRIVATE_TRANSACTION_PACKET, packet.clone());
}
}
} }
/// Checks if peer is able to process service transactions /// Checks if peer is able to process service transactions
@ -2247,7 +2317,7 @@ mod tests {
use std::collections::{HashSet, VecDeque}; use std::collections::{HashSet, VecDeque};
use ethkey; use ethkey;
use network::PeerId; use network::PeerId;
use tests::helpers::*; use tests::helpers::{TestIo};
use tests::snapshot::TestSnapshotService; use tests::snapshot::TestSnapshotService;
use ethereum_types::{H256, U256, Address}; use ethereum_types::{H256, U256, Address};
use parking_lot::RwLock; use parking_lot::RwLock;
@ -2260,6 +2330,7 @@ mod tests {
use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient, ChainInfo, BlockInfo}; use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient, ChainInfo, BlockInfo};
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use transaction::UnverifiedTransaction; use transaction::UnverifiedTransaction;
use private_tx::NoopPrivateTxHandler;
fn get_dummy_block(order: u32, parent_hash: H256) -> Bytes { fn get_dummy_block(order: u32, parent_hash: H256) -> Bytes {
let mut header = Header::new(); let mut header = Header::new();
@ -2483,7 +2554,7 @@ mod tests {
} }
fn dummy_sync_with_peer(peer_latest_hash: H256, client: &BlockChainClient) -> ChainSync { fn dummy_sync_with_peer(peer_latest_hash: H256, client: &BlockChainClient) -> ChainSync {
let mut sync = ChainSync::new(SyncConfig::default(), client); let mut sync = ChainSync::new(SyncConfig::default(), client, Arc::new(NoopPrivateTxHandler));
insert_dummy_peer(&mut sync, 0, peer_latest_hash); insert_dummy_peer(&mut sync, 0, peer_latest_hash);
sync sync
} }
@ -2608,7 +2679,7 @@ mod tests {
client.add_blocks(2, EachBlockWith::Uncle); client.add_blocks(2, EachBlockWith::Uncle);
let queue = RwLock::new(VecDeque::new()); let queue = RwLock::new(VecDeque::new());
let block = client.block(BlockId::Latest).unwrap().into_inner(); let block = client.block(BlockId::Latest).unwrap().into_inner();
let mut sync = ChainSync::new(SyncConfig::default(), &client); let mut sync = ChainSync::new(SyncConfig::default(), &client, Arc::new(NoopPrivateTxHandler));
sync.peers.insert(0, sync.peers.insert(0,
PeerInfo { PeerInfo {
// Messaging protocol // Messaging protocol
@ -2695,7 +2766,7 @@ mod tests {
client.add_blocks(100, EachBlockWith::Uncle); client.add_blocks(100, EachBlockWith::Uncle);
client.insert_transaction_to_queue(); client.insert_transaction_to_queue();
// Sync with no peers // Sync with no peers
let mut sync = ChainSync::new(SyncConfig::default(), &client); let mut sync = ChainSync::new(SyncConfig::default(), &client, Arc::new(NoopPrivateTxHandler));
let queue = RwLock::new(VecDeque::new()); let queue = RwLock::new(VecDeque::new());
let ss = TestSnapshotService::new(); let ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &ss, &queue, None); let mut io = TestIo::new(&mut client, &ss, &queue, None);
@ -2765,7 +2836,7 @@ mod tests {
let mut client = TestBlockChainClient::new(); let mut client = TestBlockChainClient::new();
client.insert_transaction_with_gas_price_to_queue(U256::zero()); client.insert_transaction_with_gas_price_to_queue(U256::zero());
let block_hash = client.block_hash_delta_minus(1); let block_hash = client.block_hash_delta_minus(1);
let mut sync = ChainSync::new(SyncConfig::default(), &client); let mut sync = ChainSync::new(SyncConfig::default(), &client, Arc::new(NoopPrivateTxHandler));
let queue = RwLock::new(VecDeque::new()); let queue = RwLock::new(VecDeque::new());
let ss = TestSnapshotService::new(); let ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &ss, &queue, None); let mut io = TestIo::new(&mut client, &ss, &queue, None);
@ -2798,7 +2869,7 @@ mod tests {
let tx1_hash = client.insert_transaction_to_queue(); let tx1_hash = client.insert_transaction_to_queue();
let tx2_hash = client.insert_transaction_with_gas_price_to_queue(U256::zero()); let tx2_hash = client.insert_transaction_with_gas_price_to_queue(U256::zero());
let block_hash = client.block_hash_delta_minus(1); let block_hash = client.block_hash_delta_minus(1);
let mut sync = ChainSync::new(SyncConfig::default(), &client); let mut sync = ChainSync::new(SyncConfig::default(), &client, Arc::new(NoopPrivateTxHandler));
let queue = RwLock::new(VecDeque::new()); let queue = RwLock::new(VecDeque::new());
let ss = TestSnapshotService::new(); let ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &ss, &queue, None); let mut io = TestIo::new(&mut client, &ss, &queue, None);

View File

@ -44,6 +44,7 @@ extern crate ethcore_light as light;
#[cfg(test)] extern crate ethkey; #[cfg(test)] extern crate ethkey;
#[cfg(test)] extern crate kvdb_memorydb; #[cfg(test)] extern crate kvdb_memorydb;
#[cfg(test)] extern crate rustc_hex;
#[macro_use] #[macro_use]
extern crate macros; extern crate macros;
@ -56,6 +57,7 @@ mod chain;
mod blocks; mod blocks;
mod block_sync; mod block_sync;
mod sync_io; mod sync_io;
mod private_tx;
mod snapshot; mod snapshot;
mod transactions_stats; mod transactions_stats;
@ -70,3 +72,4 @@ pub use api::*;
pub use chain::{SyncStatus, SyncState}; pub use chain::{SyncStatus, SyncState};
pub use devp2p::{validate_node_url, ConnectionFilter, ConnectionDirection}; pub use devp2p::{validate_node_url, ConnectionFilter, ConnectionDirection};
pub use network::{NonReservedPeerMode, Error, ErrorKind}; pub use network::{NonReservedPeerMode, Error, ErrorKind};
pub use private_tx::{PrivateTxHandler, NoopPrivateTxHandler, SimplePrivateTxHandler};

View File

@ -205,6 +205,10 @@ impl PeerLike for Peer {
} }
fn restart_sync(&self) { } fn restart_sync(&self) { }
fn process_all_io_messages(&self) { }
fn process_all_new_block_messages(&self) { }
} }
impl TestNet<Peer> { impl TestNet<Peer> {

60
sync/src/private_tx.rs Normal file
View File

@ -0,0 +1,60 @@
// 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 <http://www.gnu.org/licenses/>.
use parking_lot::Mutex;
/// Trait which should be implemented by a private transaction handler.
pub trait PrivateTxHandler: Send + Sync + 'static {
/// Function called on new private transaction received.
fn import_private_transaction(&self, rlp: &[u8]) -> Result<(), String>;
/// Function called on new signed private transaction received.
fn import_signed_private_transaction(&self, rlp: &[u8]) -> Result<(), String>;
}
/// Nonoperative private transaction handler.
pub struct NoopPrivateTxHandler;
impl PrivateTxHandler for NoopPrivateTxHandler {
fn import_private_transaction(&self, _rlp: &[u8]) -> Result<(), String> {
Ok(())
}
fn import_signed_private_transaction(&self, _rlp: &[u8]) -> Result<(), String> {
Ok(())
}
}
/// Simple private transaction handler. Used for tests.
#[derive(Default)]
pub struct SimplePrivateTxHandler {
/// imported private transactions
pub txs: Mutex<Vec<Vec<u8>>>,
/// imported signed private transactions
pub signed_txs: Mutex<Vec<Vec<u8>>>,
}
impl PrivateTxHandler for SimplePrivateTxHandler {
fn import_private_transaction(&self, rlp: &[u8]) -> Result<(), String> {
self.txs.lock().push(rlp.to_vec());
Ok(())
}
fn import_signed_private_transaction(&self, rlp: &[u8]) -> Result<(), String> {
self.signed_txs.lock().push(rlp.to_vec());
Ok(())
}
}

View File

@ -17,8 +17,8 @@
use std::sync::Arc; use std::sync::Arc;
use hash::keccak; use hash::keccak;
use ethereum_types::{U256, Address}; use ethereum_types::{U256, Address};
use io::{IoHandler, IoContext, IoChannel}; use io::{IoHandler, IoChannel};
use ethcore::client::{Client, ChainInfo, ClientIoMessage}; use ethcore::client::{ChainInfo, ClientIoMessage};
use ethcore::spec::Spec; use ethcore::spec::Spec;
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
@ -27,21 +27,6 @@ use transaction::{Action, PendingTransaction, Transaction};
use super::helpers::*; use super::helpers::*;
use SyncConfig; use SyncConfig;
struct TestIoHandler {
client: Arc<Client>,
}
impl IoHandler<ClientIoMessage> for TestIoHandler {
fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) {
match *net_message {
ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) {
panic!("Invalid message received: {}", e);
},
_ => {} // ignore other messages
}
}
}
fn new_tx(secret: &Secret, nonce: U256, chain_id: u64) -> PendingTransaction { fn new_tx(secret: &Secret, nonce: U256, chain_id: u64) -> PendingTransaction {
let signed = Transaction { let signed = Transaction {
nonce: nonce.into(), nonce: nonce.into(),
@ -63,9 +48,9 @@ fn authority_round() {
ap.insert_account(s1.secret().clone(), "").unwrap(); ap.insert_account(s1.secret().clone(), "").unwrap();
let chain_id = Spec::new_test_round().chain_id(); let chain_id = Spec::new_test_round().chain_id();
let mut net = TestNet::with_spec_and_accounts(2, SyncConfig::default(), Spec::new_test_round, Some(ap)); let mut net = TestNet::with_spec_and_accounts(2, SyncConfig::default(), Spec::new_test_round, Some(ap), false);
let io_handler0: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler { client: net.peer(0).chain.clone() }); let io_handler0: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler::new(net.peer(0).chain.clone()));
let io_handler1: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler { client: net.peer(1).chain.clone() }); let io_handler1: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler::new(net.peer(1).chain.clone()));
// Push transaction to both clients. Only one of them gets lucky to produce a block. // 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(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(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap();
@ -150,9 +135,9 @@ fn tendermint() {
ap.insert_account(s1.secret().clone(), "").unwrap(); ap.insert_account(s1.secret().clone(), "").unwrap();
let chain_id = Spec::new_test_tendermint().chain_id(); let chain_id = Spec::new_test_tendermint().chain_id();
let mut net = TestNet::with_spec_and_accounts(2, SyncConfig::default(), Spec::new_test_tendermint, Some(ap)); let mut net = TestNet::with_spec_and_accounts(2, SyncConfig::default(), Spec::new_test_tendermint, Some(ap), false);
let io_handler0: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler { client: net.peer(0).chain.clone() }); let io_handler0: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler::new(net.peer(0).chain.clone()));
let io_handler1: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler { client: net.peer(1).chain.clone() }); let io_handler1: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler::new(net.peer(1).chain.clone()));
// Push transaction to both clients. Only one of them issues a proposal. // Push transaction to both clients. Only one of them issues a proposal.
net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap(); net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap();
trace!(target: "poa", "Peer 0 is {}.", s0.address()); trace!(target: "poa", "Peer 0 is {}.", s0.address());

View File

@ -17,21 +17,23 @@
use std::collections::{VecDeque, HashSet, HashMap}; use std::collections::{VecDeque, HashSet, HashMap};
use std::sync::Arc; use std::sync::Arc;
use ethereum_types::H256; use ethereum_types::H256;
use parking_lot::RwLock; use parking_lot::{RwLock, Mutex};
use bytes::Bytes; use bytes::Bytes;
use network::{self, PeerId, ProtocolId, PacketId, SessionInfo}; use network::{self, PeerId, ProtocolId, PacketId, SessionInfo};
use tests::snapshot::*; use tests::snapshot::*;
use ethcore::client::{TestBlockChainClient, BlockChainClient, Client as EthcoreClient, ClientConfig, ChainNotify}; use ethcore::client::{TestBlockChainClient, BlockChainClient, Client as EthcoreClient,
ClientConfig, ChainNotify, ChainMessageType, ClientIoMessage};
use ethcore::header::BlockNumber; use ethcore::header::BlockNumber;
use ethcore::snapshot::SnapshotService; use ethcore::snapshot::SnapshotService;
use ethcore::spec::Spec; use ethcore::spec::Spec;
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use ethcore::miner::Miner; use ethcore::miner::Miner;
use sync_io::SyncIo; use sync_io::SyncIo;
use io::IoChannel; use io::{IoChannel, IoContext, IoHandler};
use api::WARP_SYNC_PROTOCOL_ID; use api::WARP_SYNC_PROTOCOL_ID;
use chain::ChainSync; use chain::{ChainSync, ETH_PROTOCOL_VERSION_63, PAR_PROTOCOL_VERSION_3};
use ::SyncConfig; use SyncConfig;
use private_tx::{NoopPrivateTxHandler, PrivateTxHandler, SimplePrivateTxHandler};
pub trait FlushingBlockChainClient: BlockChainClient { pub trait FlushingBlockChainClient: BlockChainClient {
fn flush(&self) {} fn flush(&self) {}
@ -131,11 +133,11 @@ impl<'p, C> SyncIo for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p {
} }
fn eth_protocol_version(&self, _peer: PeerId) -> u8 { fn eth_protocol_version(&self, _peer: PeerId) -> u8 {
63 ETH_PROTOCOL_VERSION_63
} }
fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8 { fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8 {
if protocol == &WARP_SYNC_PROTOCOL_ID { 2 } else { self.eth_protocol_version(peer_id) } if protocol == &WARP_SYNC_PROTOCOL_ID { PAR_PROTOCOL_VERSION_3 } else { self.eth_protocol_version(peer_id) }
} }
fn chain_overlay(&self) -> &RwLock<HashMap<BlockNumber, Bytes>> { fn chain_overlay(&self) -> &RwLock<HashMap<BlockNumber, Bytes>> {
@ -143,6 +145,16 @@ impl<'p, C> SyncIo for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p {
} }
} }
/// Mock for emulution of async run of new blocks
struct NewBlockMessage {
imported: Vec<H256>,
invalid: Vec<H256>,
enacted: Vec<H256>,
retracted: Vec<H256>,
sealed: Vec<H256>,
proposed: Vec<Bytes>,
}
/// Abstract messages between peers. /// Abstract messages between peers.
pub trait Message { pub trait Message {
/// The intended recipient of this message. /// The intended recipient of this message.
@ -184,6 +196,12 @@ pub trait Peer {
/// Restart sync for a peer. /// Restart sync for a peer.
fn restart_sync(&self); fn restart_sync(&self);
/// Process the queue of pending io messages
fn process_all_io_messages(&self);
/// Process the queue of new block messages
fn process_all_new_block_messages(&self);
} }
pub struct EthPeer<C> where C: FlushingBlockChainClient { pub struct EthPeer<C> where C: FlushingBlockChainClient {
@ -191,6 +209,41 @@ pub struct EthPeer<C> where C: FlushingBlockChainClient {
pub snapshot_service: Arc<TestSnapshotService>, pub snapshot_service: Arc<TestSnapshotService>,
pub sync: RwLock<ChainSync>, pub sync: RwLock<ChainSync>,
pub queue: RwLock<VecDeque<TestPacket>>, pub queue: RwLock<VecDeque<TestPacket>>,
pub private_tx_handler: Arc<PrivateTxHandler>,
pub io_queue: RwLock<VecDeque<ChainMessageType>>,
new_blocks_queue: RwLock<VecDeque<NewBlockMessage>>,
}
impl<C> EthPeer<C> where C: FlushingBlockChainClient {
fn is_io_queue_empty(&self) -> bool {
self.io_queue.read().is_empty()
}
fn is_new_blocks_queue_empty(&self) -> bool {
self.new_blocks_queue.read().is_empty()
}
fn process_io_message(&self, message: ChainMessageType) {
let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None);
match message {
ChainMessageType::Consensus(data) => self.sync.write().propagate_consensus_packet(&mut io, data),
ChainMessageType::PrivateTransaction(data) => self.sync.write().propagate_private_transaction(&mut io, data),
ChainMessageType::SignedPrivateTransaction(data) => self.sync.write().propagate_signed_private_transaction(&mut io, data),
}
}
fn process_new_block_message(&self, message: NewBlockMessage) {
let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None);
self.sync.write().chain_new_blocks(
&mut io,
&message.imported,
&message.invalid,
&message.enacted,
&message.retracted,
&message.sealed,
&message.proposed
);
}
} }
impl<C: FlushingBlockChainClient> Peer for EthPeer<C> { impl<C: FlushingBlockChainClient> Peer for EthPeer<C> {
@ -198,7 +251,12 @@ impl<C: FlushingBlockChainClient> Peer for EthPeer<C> {
fn on_connect(&self, other: PeerId) { fn on_connect(&self, other: PeerId) {
self.sync.write().update_targets(&*self.chain); self.sync.write().update_targets(&*self.chain);
self.sync.write().on_peer_connected(&mut TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, Some(other)), other); self.sync.write().on_peer_connected(&mut TestIo::new(
&*self.chain,
&self.snapshot_service,
&self.queue,
Some(other)),
other);
} }
fn on_disconnect(&self, other: PeerId) { fn on_disconnect(&self, other: PeerId) {
@ -219,7 +277,7 @@ impl<C: FlushingBlockChainClient> Peer for EthPeer<C> {
} }
fn is_done(&self) -> bool { fn is_done(&self) -> bool {
self.queue.read().is_empty() self.queue.read().is_empty() && self.is_io_queue_empty() && self.is_new_blocks_queue_empty()
} }
fn sync_step(&self) { fn sync_step(&self) {
@ -232,6 +290,22 @@ impl<C: FlushingBlockChainClient> Peer for EthPeer<C> {
fn restart_sync(&self) { fn restart_sync(&self) {
self.sync.write().restart(&mut TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None)); self.sync.write().restart(&mut TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None));
} }
fn process_all_io_messages(&self) {
if !self.is_io_queue_empty() {
while let Some(message) = self.io_queue.write().pop_front() {
self.process_io_message(message);
}
}
}
fn process_all_new_block_messages(&self) {
if !self.is_new_blocks_queue_empty() {
while let Some(message) = self.new_blocks_queue.write().pop_front() {
self.process_new_block_message(message);
}
}
}
} }
pub struct TestNet<P> { pub struct TestNet<P> {
@ -260,20 +334,35 @@ impl TestNet<EthPeer<TestBlockChainClient>> {
for _ in 0..n { for _ in 0..n {
let chain = TestBlockChainClient::new(); let chain = TestBlockChainClient::new();
let ss = Arc::new(TestSnapshotService::new()); let ss = Arc::new(TestSnapshotService::new());
let sync = ChainSync::new(config.clone(), &chain); let private_tx_handler = Arc::new(NoopPrivateTxHandler);
let sync = ChainSync::new(config.clone(), &chain, private_tx_handler.clone());
net.peers.push(Arc::new(EthPeer { net.peers.push(Arc::new(EthPeer {
sync: RwLock::new(sync), sync: RwLock::new(sync),
snapshot_service: ss, snapshot_service: ss,
chain: Arc::new(chain), chain: Arc::new(chain),
queue: RwLock::new(VecDeque::new()), queue: RwLock::new(VecDeque::new()),
private_tx_handler,
io_queue: RwLock::new(VecDeque::new()),
new_blocks_queue: RwLock::new(VecDeque::new()),
})); }));
} }
net net
} }
// relies on Arc uniqueness, which is only true when we haven't registered a ChainNotify.
pub fn peer_mut(&mut self, i: usize) -> &mut EthPeer<TestBlockChainClient> {
Arc::get_mut(&mut self.peers[i]).expect("Arc never exposed externally")
}
} }
impl TestNet<EthPeer<EthcoreClient>> { impl TestNet<EthPeer<EthcoreClient>> {
pub fn with_spec_and_accounts<F>(n: usize, config: SyncConfig, spec_factory: F, accounts: Option<Arc<AccountProvider>>) -> Self pub fn with_spec_and_accounts<F>(
n: usize,
config: SyncConfig,
spec_factory: F,
accounts: Option<Arc<AccountProvider>>,
private_tx_handler: bool,
) -> Self
where F: Fn() -> Spec where F: Fn() -> Spec
{ {
let mut net = TestNet { let mut net = TestNet {
@ -282,11 +371,42 @@ impl TestNet<EthPeer<EthcoreClient>> {
disconnect_events: Vec::new(), disconnect_events: Vec::new(),
}; };
for _ in 0..n { for _ in 0..n {
if private_tx_handler {
net.add_peer_with_private_config(config.clone(), spec_factory(), accounts.clone());
} else {
net.add_peer(config.clone(), spec_factory(), accounts.clone()); net.add_peer(config.clone(), spec_factory(), accounts.clone());
} }
}
net net
} }
pub fn add_peer_with_private_config(&mut self, config: SyncConfig, spec: Spec, accounts: Option<Arc<AccountProvider>>) {
let channel = IoChannel::disconnected();
let client = EthcoreClient::new(
ClientConfig::default(),
&spec,
Arc::new(::kvdb_memorydb::create(::ethcore::db::NUM_COLUMNS.unwrap_or(0))),
Arc::new(Miner::with_spec_and_accounts(&spec, accounts.clone())),
channel.clone()
).unwrap();
let private_tx_handler = Arc::new(SimplePrivateTxHandler::default());
let ss = Arc::new(TestSnapshotService::new());
let sync = ChainSync::new(config, &*client, private_tx_handler.clone());
let peer = Arc::new(EthPeer {
sync: RwLock::new(sync),
snapshot_service: ss,
chain: client,
queue: RwLock::new(VecDeque::new()),
private_tx_handler,
io_queue: RwLock::new(VecDeque::new()),
new_blocks_queue: RwLock::new(VecDeque::new()),
});
peer.chain.add_notify(peer.clone());
//private_provider.add_notify(peer.clone());
self.peers.push(peer);
}
pub fn add_peer(&mut self, config: SyncConfig, spec: Spec, accounts: Option<Arc<AccountProvider>>) { pub fn add_peer(&mut self, config: SyncConfig, spec: Spec, accounts: Option<Arc<AccountProvider>>) {
let client = EthcoreClient::new( let client = EthcoreClient::new(
ClientConfig::default(), ClientConfig::default(),
@ -297,12 +417,16 @@ impl TestNet<EthPeer<EthcoreClient>> {
).unwrap(); ).unwrap();
let ss = Arc::new(TestSnapshotService::new()); let ss = Arc::new(TestSnapshotService::new());
let sync = ChainSync::new(config, &*client); let private_tx_handler = Arc::new(NoopPrivateTxHandler);
let sync = ChainSync::new(config, &*client, private_tx_handler.clone());
let peer = Arc::new(EthPeer { let peer = Arc::new(EthPeer {
sync: RwLock::new(sync), sync: RwLock::new(sync),
snapshot_service: ss, snapshot_service: ss,
chain: client, chain: client,
queue: RwLock::new(VecDeque::new()), queue: RwLock::new(VecDeque::new()),
private_tx_handler,
io_queue: RwLock::new(VecDeque::new()),
new_blocks_queue: RwLock::new(VecDeque::new()),
}); });
peer.chain.add_notify(peer.clone()); peer.chain.add_notify(peer.clone());
self.peers.push(peer); self.peers.push(peer);
@ -366,6 +490,8 @@ impl<P> TestNet<P> where P: Peer {
let mut total_steps = 0; let mut total_steps = 0;
while !self.done() { while !self.done() {
self.sync_step(); self.sync_step();
self.deliver_io_messages();
self.deliver_new_block_messages();
total_steps += 1; total_steps += 1;
} }
total_steps total_steps
@ -378,15 +504,20 @@ impl<P> TestNet<P> where P: Peer {
} }
} }
pub fn done(&self) -> bool { pub fn deliver_io_messages(&mut self) {
self.peers.iter().all(|p| p.is_done()) for peer in self.peers.iter() {
peer.process_all_io_messages();
} }
} }
impl TestNet<EthPeer<TestBlockChainClient>> { pub fn deliver_new_block_messages(&mut self) {
// relies on Arc uniqueness, which is only true when we haven't registered a ChainNotify. for peer in self.peers.iter() {
pub fn peer_mut(&mut self, i: usize) -> &mut EthPeer<TestBlockChainClient> { peer.process_all_new_block_messages();
Arc::get_mut(&mut self.peers[i]).expect("Arc never exposed externally") }
}
pub fn done(&self) -> bool {
self.peers.iter().all(|p| p.is_done())
} }
} }
@ -397,6 +528,34 @@ impl<C: FlushingBlockChainClient> TestNet<EthPeer<C>> {
} }
} }
pub struct TestIoHandler {
pub client: Arc<EthcoreClient>,
pub private_tx_queued: Mutex<usize>,
}
impl TestIoHandler {
pub fn new(client: Arc<EthcoreClient>) -> Self {
TestIoHandler {
client,
private_tx_queued: Mutex::default(),
}
}
}
impl IoHandler<ClientIoMessage> for TestIoHandler {
fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) {
match *net_message {
ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) {
panic!("Invalid message received: {}", e);
},
ClientIoMessage::NewPrivateTransaction => {
*self.private_tx_queued.lock() += 1;
},
_ => {} // ignore other messages
}
}
}
impl ChainNotify for EthPeer<EthcoreClient> { impl ChainNotify for EthPeer<EthcoreClient> {
fn new_blocks(&self, fn new_blocks(&self,
imported: Vec<H256>, imported: Vec<H256>,
@ -407,23 +566,21 @@ impl ChainNotify for EthPeer<EthcoreClient> {
proposed: Vec<Bytes>, proposed: Vec<Bytes>,
_duration: u64) _duration: u64)
{ {
let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None); self.new_blocks_queue.write().push_back(NewBlockMessage {
self.sync.write().chain_new_blocks( imported,
&mut io, invalid,
&imported, enacted,
&invalid, retracted,
&enacted, sealed,
&retracted, proposed,
&sealed, });
&proposed);
} }
fn start(&self) {} fn start(&self) {}
fn stop(&self) {} fn stop(&self) {}
fn broadcast(&self, message: Vec<u8>) { fn broadcast(&self, message_type: ChainMessageType) {
let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None); self.io_queue.write().push_back(message_type)
self.sync.write().propagate_consensus_packet(&mut io, message.clone());
} }
} }

View File

@ -119,13 +119,23 @@ pub trait Fetch: Clone + Send + Sync + 'static {
/// The result future. /// The result future.
type Result: Future<Item=Response, Error=Error> + Send + 'static; type Result: Future<Item=Response, Error=Error> + Send + 'static;
/// Make a request to given URL
fn fetch(&self, url: &str, method: Method, abort: Abort) -> Self::Result;
/// Get content from some URL. /// Get content from some URL.
fn fetch(&self, url: &str, abort: Abort) -> Self::Result; fn get(&self, url: &str, abort: Abort) -> Self::Result {
self.fetch(url, Method::Get, abort)
}
/// Post content to some URL.
fn post(&self, url: &str, abort: Abort) -> Self::Result {
self.fetch(url, Method::Post, abort)
}
} }
type TxResponse = oneshot::Sender<Result<Response, Error>>; type TxResponse = oneshot::Sender<Result<Response, Error>>;
type TxStartup = std::sync::mpsc::SyncSender<Result<(), io::Error>>; type TxStartup = std::sync::mpsc::SyncSender<Result<(), io::Error>>;
type ChanItem = Option<(Url, Abort, TxResponse)>; type ChanItem = Option<(Url, Method, Abort, TxResponse)>;
/// An implementation of `Fetch` using a `hyper` client. /// An implementation of `Fetch` using a `hyper` client.
// Due to the `Send` bound of `Fetch` we spawn a background thread for // Due to the `Send` bound of `Fetch` we spawn a background thread for
@ -203,17 +213,17 @@ impl Client {
let future = rx_proto.take_while(|item| Ok(item.is_some())) let future = rx_proto.take_while(|item| Ok(item.is_some()))
.map(|item| item.expect("`take_while` is only passing on channel items != None; qed")) .map(|item| item.expect("`take_while` is only passing on channel items != None; qed"))
.for_each(|(url, abort, sender)| .for_each(|(url, method, abort, sender)|
{ {
trace!(target: "fetch", "new request to {}", url); trace!(target: "fetch", "new request to {}", url);
if abort.is_aborted() { if abort.is_aborted() {
return future::ok(sender.send(Err(Error::Aborted)).unwrap_or(())) return future::ok(sender.send(Err(Error::Aborted)).unwrap_or(()))
} }
let ini = (hyper.clone(), url, abort, 0); let ini = (hyper.clone(), url, method, abort, 0);
let fut = future::loop_fn(ini, |(client, url, abort, redirects)| { let fut = future::loop_fn(ini, |(client, url, method, abort, redirects)| {
let url2 = url.clone(); let url2 = url.clone();
let abort2 = abort.clone(); let abort2 = abort.clone();
client.request(get(&url)) client.request(build_request(&url, method.clone()))
.map(move |resp| Response::new(url2, resp, abort2)) .map(move |resp| Response::new(url2, resp, abort2))
.from_err() .from_err()
.and_then(move |resp| { .and_then(move |resp| {
@ -225,7 +235,7 @@ impl Client {
if redirects >= abort.max_redirects() { if redirects >= abort.max_redirects() {
return Err(Error::TooManyRedirects) return Err(Error::TooManyRedirects)
} }
Ok(Loop::Continue((client, next_url, abort, redirects + 1))) Ok(Loop::Continue((client, next_url, method, abort, redirects + 1)))
} else { } else {
let content_len = resp.headers.get::<ContentLength>().cloned(); let content_len = resp.headers.get::<ContentLength>().cloned();
if content_len.map(|n| *n > abort.max_size() as u64).unwrap_or(false) { if content_len.map(|n| *n > abort.max_size() as u64).unwrap_or(false) {
@ -257,7 +267,7 @@ impl Client {
impl Fetch for Client { impl Fetch for Client {
type Result = Box<Future<Item=Response, Error=Error> + Send>; type Result = Box<Future<Item=Response, Error=Error> + Send>;
fn fetch(&self, url: &str, abort: Abort) -> Self::Result { fn fetch(&self, url: &str, method: Method, abort: Abort) -> Self::Result {
debug!(target: "fetch", "fetching: {:?}", url); debug!(target: "fetch", "fetching: {:?}", url);
if abort.is_aborted() { if abort.is_aborted() {
return Box::new(future::err(Error::Aborted)) return Box::new(future::err(Error::Aborted))
@ -269,7 +279,7 @@ impl Fetch for Client {
let (tx_res, rx_res) = oneshot::channel(); let (tx_res, rx_res) = oneshot::channel();
let maxdur = abort.max_duration(); let maxdur = abort.max_duration();
let sender = self.core.clone(); let sender = self.core.clone();
let future = sender.send(Some((url.clone(), abort, tx_res))) let future = sender.send(Some((url.clone(), method, abort, tx_res)))
.map_err(|e| { .map_err(|e| {
error!(target: "fetch", "failed to schedule request: {}", e); error!(target: "fetch", "failed to schedule request: {}", e);
Error::BackgroundThreadDead Error::BackgroundThreadDead
@ -308,10 +318,10 @@ fn redirect_location(u: Url, r: &Response) -> Option<Url> {
} }
} }
// Build a simple GET request for the given Url. // Build a simple request for the given Url and method
fn get(u: &Url) -> hyper::Request { fn build_request(u: &Url, method: Method) -> hyper::Request {
let uri = u.as_ref().parse().expect("Every valid URL is aso a URI."); let uri = u.as_ref().parse().expect("Every valid URL is aso a URI.");
let mut rq = Request::new(Method::Get, uri); let mut rq = Request::new(method, uri);
rq.headers_mut().set(UserAgent::new("Parity Fetch Neo")); rq.headers_mut().set(UserAgent::new("Parity Fetch Neo"));
rq rq
} }
@ -350,6 +360,11 @@ impl Response {
self.status() == StatusCode::Ok self.status() == StatusCode::Ok
} }
/// Status code == 404.
pub fn is_not_found(&self) -> bool {
self.status() == StatusCode::NotFound
}
/// Is the content-type text/html? /// Is the content-type text/html?
pub fn is_html(&self) -> bool { pub fn is_html(&self) -> bool {
if let Some(ref mime) = self.content_type() { if let Some(ref mime) = self.content_type() {
@ -512,7 +527,7 @@ mod test {
use futures::future; use futures::future;
use futures::sync::mpsc; use futures::sync::mpsc;
use futures_timer::Delay; use futures_timer::Delay;
use hyper::StatusCode; use hyper::{StatusCode, Method};
use hyper::server::{Http, Request, Response, Service}; use hyper::server::{Http, Request, Response, Service};
use std; use std;
use std::io::Read; use std::io::Read;
@ -524,7 +539,7 @@ mod test {
fn it_should_fetch() { fn it_should_fetch() {
let server = TestServer::run(); let server = TestServer::run();
let client = Client::new().unwrap(); let client = Client::new().unwrap();
let future = client.fetch(&format!("http://{}?123", server.addr()), Default::default()); let future = client.fetch(&format!("http://{}?123", server.addr()), Method::Get, Default::default());
let resp = future.wait().unwrap(); let resp = future.wait().unwrap();
assert!(resp.is_success()); assert!(resp.is_success());
let body = resp.concat2().wait().unwrap(); let body = resp.concat2().wait().unwrap();
@ -536,7 +551,7 @@ mod test {
let server = TestServer::run(); let server = TestServer::run();
let client = Client::new().unwrap(); let client = Client::new().unwrap();
let abort = Abort::default().with_max_duration(Duration::from_secs(1)); let abort = Abort::default().with_max_duration(Duration::from_secs(1));
match client.fetch(&format!("http://{}/delay?3", server.addr()), abort).wait() { match client.fetch(&format!("http://{}/delay?3", server.addr()), Method::Get, abort).wait() {
Err(Error::Timeout) => {} Err(Error::Timeout) => {}
other => panic!("expected timeout, got {:?}", other) other => panic!("expected timeout, got {:?}", other)
} }
@ -547,7 +562,7 @@ mod test {
let server = TestServer::run(); let server = TestServer::run();
let client = Client::new().unwrap(); let client = Client::new().unwrap();
let abort = Abort::default(); let abort = Abort::default();
let future = client.fetch(&format!("http://{}/redirect?http://{}/", server.addr(), server.addr()), abort); let future = client.fetch(&format!("http://{}/redirect?http://{}/", server.addr(), server.addr()), Method::Get, abort);
assert!(future.wait().unwrap().is_success()) assert!(future.wait().unwrap().is_success())
} }
@ -556,7 +571,7 @@ mod test {
let server = TestServer::run(); let server = TestServer::run();
let client = Client::new().unwrap(); let client = Client::new().unwrap();
let abort = Abort::default().with_max_redirects(4); let abort = Abort::default().with_max_redirects(4);
let future = client.fetch(&format!("http://{}/redirect?/", server.addr()), abort); let future = client.fetch(&format!("http://{}/redirect?/", server.addr()), Method::Get, abort);
assert!(future.wait().unwrap().is_success()) assert!(future.wait().unwrap().is_success())
} }
@ -565,7 +580,7 @@ mod test {
let server = TestServer::run(); let server = TestServer::run();
let client = Client::new().unwrap(); let client = Client::new().unwrap();
let abort = Abort::default().with_max_redirects(3); let abort = Abort::default().with_max_redirects(3);
match client.fetch(&format!("http://{}/loop", server.addr()), abort).wait() { match client.fetch(&format!("http://{}/loop", server.addr()), Method::Get, abort).wait() {
Err(Error::TooManyRedirects) => {} Err(Error::TooManyRedirects) => {}
other => panic!("expected too many redirects error, got {:?}", other) other => panic!("expected too many redirects error, got {:?}", other)
} }
@ -576,7 +591,7 @@ mod test {
let server = TestServer::run(); let server = TestServer::run();
let client = Client::new().unwrap(); let client = Client::new().unwrap();
let abort = Abort::default(); let abort = Abort::default();
let future = client.fetch(&format!("http://{}?abcdefghijklmnopqrstuvwxyz", server.addr()), abort); let future = client.fetch(&format!("http://{}?abcdefghijklmnopqrstuvwxyz", server.addr()), Method::Get, abort);
let resp = future.wait().unwrap(); let resp = future.wait().unwrap();
assert!(resp.is_success()); assert!(resp.is_success());
assert_eq!(&resp.concat2().wait().unwrap()[..], b"abcdefghijklmnopqrstuvwxyz") assert_eq!(&resp.concat2().wait().unwrap()[..], b"abcdefghijklmnopqrstuvwxyz")
@ -587,7 +602,7 @@ mod test {
let server = TestServer::run(); let server = TestServer::run();
let client = Client::new().unwrap(); let client = Client::new().unwrap();
let abort = Abort::default().with_max_size(3); let abort = Abort::default().with_max_size(3);
let resp = client.fetch(&format!("http://{}/?1234", server.addr()), abort).wait().unwrap(); let resp = client.fetch(&format!("http://{}/?1234", server.addr()), Method::Get, abort).wait().unwrap();
assert!(resp.is_success()); assert!(resp.is_success());
match resp.concat2().wait() { match resp.concat2().wait() {
Err(Error::SizeLimit) => {} Err(Error::SizeLimit) => {}
@ -600,7 +615,7 @@ mod test {
let server = TestServer::run(); let server = TestServer::run();
let client = Client::new().unwrap(); let client = Client::new().unwrap();
let abort = Abort::default().with_max_size(3); let abort = Abort::default().with_max_size(3);
let resp = client.fetch(&format!("http://{}/?1234", server.addr()), abort).wait().unwrap(); let resp = client.fetch(&format!("http://{}/?1234", server.addr()), Method::Get, abort).wait().unwrap();
assert!(resp.is_success()); assert!(resp.is_success());
let mut buffer = Vec::new(); let mut buffer = Vec::new();
let mut reader = BodyReader::new(resp); let mut reader = BodyReader::new(resp);

View File

@ -36,4 +36,5 @@ pub mod client;
pub use url::Url; pub use url::Url;
pub use self::client::{Client, Fetch, Error, Response, Abort, BodyReader}; pub use self::client::{Client, Fetch, Error, Response, Abort, BodyReader};
pub use hyper::Method;