diff --git a/Cargo.lock b/Cargo.lock index 403b468f8..28a4b8a7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -519,6 +519,7 @@ dependencies = [ "ethkey 0.3.0", "ethstore 0.2.0", "evm 0.1.0", + "fetch 0.1.0", "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "hardware-wallet 1.11.0", "hashdb 0.1.1", @@ -720,6 +721,40 @@ dependencies = [ "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]] name = "ethcore-secretstore" version = "1.0.0" @@ -762,8 +797,11 @@ name = "ethcore-service" version = "0.1.0" dependencies = [ "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-io 1.11.0", + "ethcore-private-tx 1.0.0", + "ethsync 1.11.0", "kvdb 0.1.0", "kvdb-rocksdb 0.1.0", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -948,6 +986,7 @@ dependencies = [ "plain_hasher 0.1.0", "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "smallvec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "triehash 0.1.0", @@ -1939,6 +1978,7 @@ dependencies = [ "ethcore-migrations 0.1.0", "ethcore-miner 1.11.0", "ethcore-network 1.11.0", + "ethcore-private-tx 1.0.0", "ethcore-secretstore 1.0.0", "ethcore-service 0.1.0", "ethcore-stratum 1.11.0", @@ -2145,6 +2185,7 @@ dependencies = [ "ethcore-logger 1.11.0", "ethcore-miner 1.11.0", "ethcore-network 1.11.0", + "ethcore-private-tx 1.0.0", "ethcore-transaction 0.1.0", "ethcrypto 0.1.0", "ethereum-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 87daa076c..202215ab3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ ethcore-logger = { path = "logger" } ethcore-migrations = { path = "ethcore/migrations" } ethcore-miner = { path = "miner" } ethcore-network = { path = "util/network" } +ethcore-private-tx = { path = "ethcore/private-tx" } ethcore-service = { path = "ethcore/service" } ethcore-stratum = { path = "stratum" } ethcore-transaction = { path = "ethcore/transaction" } diff --git a/dapps/src/handlers/fetch.rs b/dapps/src/handlers/fetch.rs index 94fe60881..1408d634d 100644 --- a/dapps/src/handlers/fetch.rs +++ b/dapps/src/handlers/fetch.rs @@ -24,7 +24,7 @@ use fetch::{self, Fetch}; use futures::sync::oneshot; use futures::{self, Future}; use futures_cpupool::CpuPool; -use hyper::{self, Method, StatusCode}; +use hyper::{self, StatusCode}; use parking_lot::Mutex; use endpoint::{self, EndpointPath}; @@ -261,7 +261,7 @@ impl ContentFetcherHandler { // Validation of method let status = match *method { // Start fetching content - Method::Get => { + hyper::Method::Get => { trace!(target: "dapps", "Fetching content from: {:?}", url); FetchState::InProgress(Self::fetch_content( pool, @@ -295,7 +295,7 @@ impl ContentFetcherHandler { ) -> Box + Send> { // Start fetching the content 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); Ok(match result { Ok(response) => match installer.validate_and_install(response) { diff --git a/dapps/src/tests/helpers/fetch.rs b/dapps/src/tests/helpers/fetch.rs index dfe523200..d65d9d09b 100644 --- a/dapps/src/tests/helpers/fetch.rs +++ b/dapps/src/tests/helpers/fetch.rs @@ -20,7 +20,7 @@ use parking_lot::Mutex; use hyper; use futures::{self, Future}; -use fetch::{self, Fetch, Url}; +use fetch::{self, Fetch, Url, Method}; pub struct FetchControl { sender: mpsc::Sender<()>, @@ -97,7 +97,7 @@ impl FakeFetch { impl Fetch for FakeFetch { type Result = Box + 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(); self.requested.lock().push(url.into()); let manual = self.manual.clone(); diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 9c61fd8ea..c028f8e6b 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -16,6 +16,7 @@ crossbeam = "0.3" ethash = { path = "../ethash" } ethcore-bloom-journal = { path = "../util/bloom" } ethcore-bytes = { path = "../util/bytes" } +fetch = { path = "../util/fetch" } hashdb = { path = "../util/hashdb" } memorydb = { path = "../util/memorydb" } patricia-trie = { path = "../util/patricia_trie" } @@ -52,6 +53,7 @@ rlp_compress = { path = "../util/rlp_compress" } rlp_derive = { path = "../util/rlp_derive" } kvdb = { path = "../util/kvdb" } kvdb-memorydb = { path = "../util/kvdb-memorydb" } +kvdb-rocksdb = { path = "../util/kvdb-rocksdb" } util-error = { path = "../util/error" } snappy = { git = "https://github.com/paritytech/rust-snappy" } stop-guard = { path = "../util/stop-guard" } @@ -72,7 +74,6 @@ journaldb = { path = "../util/journaldb" } [dev-dependencies] tempdir = "0.3" trie-standardmap = { path = "../util/trie-standardmap" } -kvdb-rocksdb = { path = "../util/kvdb-rocksdb" } [features] evm-debug = ["slow-blocks"] diff --git a/ethcore/private-tx/Cargo.toml b/ethcore/private-tx/Cargo.toml new file mode 100644 index 000000000..6ecfdb546 --- /dev/null +++ b/ethcore/private-tx/Cargo.toml @@ -0,0 +1,36 @@ +[package] +description = "Parity Private Transactions" +name = "ethcore-private-tx" +version = "1.0.0" +license = "GPL-3.0" +authors = ["Parity Technologies "] + +[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" diff --git a/ethcore/private-tx/res/private.evm b/ethcore/private-tx/res/private.evm new file mode 100644 index 000000000..cd19d757a --- /dev/null +++ b/ethcore/private-tx/res/private.evm @@ -0,0 +1 @@ +6060604052341561000f57600080fd5b604051610b0d380380610b0d833981016040528080518201919060200180518201919060200180518201919050508260009080519060200190610053929190610092565b50816002908051906020019061006a92919061011c565b50806001908051906020019061008192919061011c565b506001600381905550505050610204565b82805482825590600052602060002090810192821561010b579160200282015b8281111561010a5782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550916020019190600101906100b2565b5b509050610118919061019c565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061015d57805160ff191683800117855561018b565b8280016001018555821561018b579182015b8281111561018a57825182559160200191906001019061016f565b5b50905061019891906101df565b5090565b6101dc91905b808211156101d857600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016101a2565b5090565b90565b61020191905b808211156101fd5760008160009055506001016101e5565b5090565b90565b6108fa806102136000396000f300606060405260043610610078576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806317ac53a21461007d57806324c12bf61461019a57806335aa2e4414610228578063affed0e01461028b578063b7ab4db5146102b4578063c19d93fb1461031e575b600080fd5b341561008857600080fd5b610198600480803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091908035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437820191505050505050919050506103ac565b005b34156101a557600080fd5b6101ad610600565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101ed5780820151818401526020810190506101d2565b50505050905090810190601f16801561021a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561023357600080fd5b610249600480803590602001909190505061069e565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561029657600080fd5b61029e6106dd565b6040518082815260200191505060405180910390f35b34156102bf57600080fd5b6102c76106e3565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561030a5780820151818401526020810190506102ef565b505050509050019250505060405180910390f35b341561032957600080fd5b610331610777565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610371578082015181840152602081019050610356565b50505050905090810190601f16801561039e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000806040805190810160405280876040518082805190602001908083835b6020831015156103f057805182526020820191506020810190506020830392506103cb565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390206000191660001916815260200160035460010260001916600019168152506040518082600260200280838360005b8381101561046657808201518184015260208101905061044b565b5050505090500191505060405180910390209150600090505b6000805490508110156105d55760008181548110151561049b57fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660018387848151811015156104ee57fe5b90602001906020020151878581518110151561050657fe5b90602001906020020151878681518110151561051e57fe5b90602001906020020151604051600081526020016040526000604051602001526040518085600019166000191681526020018460ff1660ff16815260200183600019166000191681526020018260001916600019168152602001945050505050602060405160208103908084039060008661646e5a03f115156105a057600080fd5b50506020604051035173ffffffffffffffffffffffffffffffffffffffff161415156105c857fe5b808060010191505061047f565b85600190805190602001906105eb929190610815565b50600160035401600381905550505050505050565b60028054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156106965780601f1061066b57610100808354040283529160200191610696565b820191906000526020600020905b81548152906001019060200180831161067957829003601f168201915b505050505081565b6000818154811015156106ad57fe5b90600052602060002090016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60035481565b6106eb610895565b600080548060200260200160405190810160405280929190818152602001828054801561076d57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610723575b5050505050905090565b60018054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561080d5780601f106107e25761010080835404028352916020019161080d565b820191906000526020600020905b8154815290600101906020018083116107f057829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061085657805160ff1916838001178555610884565b82800160010185558215610884579182015b82811115610883578251825591602001919060010190610868565b5b50905061089191906108a9565b5090565b602060405190810160405280600081525090565b6108cb91905b808211156108c75760008160009055506001016108af565b5090565b905600a165627a7a723058200ae0215fae320b646a22fdd58278b328f46d915bd65ddbfeb5b4a09643d6e0220029 diff --git a/ethcore/private-tx/res/private.json b/ethcore/private-tx/res/private.json new file mode 100644 index 000000000..82a5e86bc --- /dev/null +++ b/ethcore/private-tx/res/private.json @@ -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"}] diff --git a/ethcore/private-tx/src/encryptor.rs b/ethcore/private-tx/src/encryptor.rs new file mode 100644 index 000000000..385356227 --- /dev/null +++ b/ethcore/private-tx/src/encryptor.rs @@ -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 . + +//! 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; + + /// Decrypt data using previously generated contract key. + fn decrypt( + &self, + contract_address: &Address, + accounts: &AccountProvider, + cypher: &[u8], + ) -> Result; +} + +/// Configurtion for key server encryptor +#[derive(Default, PartialEq, Debug, Clone)] +pub struct EncryptorConfig { + /// URL to key server + pub base_url: Option, + /// Key server's threshold + pub threshold: u32, + /// Account used for signing requests to key server + pub key_server_account: Option
, + /// Passwords used to unlock accounts + pub passwords: Vec, +} + +struct EncryptionSession { + key: Bytes, + end_time: Instant, +} + +/// SecretStore-based encryption/decryption operations. +pub struct SecretStoreEncryptor { + config: EncryptorConfig, + client: FetchClient, + sessions: Mutex>, +} + +impl SecretStoreEncryptor { + /// Create new encryptor + pub fn new(config: EncryptorConfig, client: FetchClient) -> Result { + 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 { + // 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, ðcrypto::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 { + 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 { + // 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 { + // 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 { + // 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 { + Ok(data.to_vec()) + } + + fn decrypt( + &self, + _contract_address: &Address, + _accounts: &AccountProvider, + data: &[u8], + ) -> Result { + Ok(data.to_vec()) + } +} diff --git a/ethcore/private-tx/src/error.rs b/ethcore/private-tx/src/error.rs new file mode 100644 index 000000000..3b3c881a9 --- /dev/null +++ b/ethcore/private-tx/src/error.rs @@ -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 . + +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 for Error { + fn from(err: SignError) -> Self { + ErrorKind::Sign(err).into() + } +} + +impl From for Error { + fn from(err: KeyError) -> Self { + ErrorKind::Key(err).into() + } +} + +impl From for Error { + fn from(err: ExecutionError) -> Self { + ErrorKind::Execution(err).into() + } +} + +impl From for Error { + fn from(err: TransactionError) -> Self { + ErrorKind::Transaction(err).into() + } +} + +impl From for Error { + fn from(err: EthcoreError) -> Self { + ErrorKind::Ethcore(err).into() + } +} + +impl From> for Error where Error: From { + fn from(err: Box) -> Error { + Error::from(*err) + } +} + diff --git a/ethcore/private-tx/src/lib.rs b/ethcore/private-tx/src/lib.rs new file mode 100644 index 000000000..4fa1f8789 --- /dev/null +++ b/ethcore/private-tx/src/lib.rs @@ -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 . + +//! 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 { + 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
, + /// Account used for signing public transactions created from private transactions + pub signer_account: Option
, + /// Passwords used to unlock accounts + pub passwords: Vec, +} + +#[derive(Debug)] +/// Private transaction execution receipt. +pub struct Receipt { + /// Private transaction hash. + pub hash: H256, + /// Created contract address if any. + pub contract_address: Option
, + /// Execution status. + pub status_code: u8, +} + +/// Manager of private transactions +pub struct Provider { + encryptor: Box, + validator_accounts: HashSet
, + signer_account: Option
, + passwords: Vec, + notify: RwLock>>, + transactions_for_signing: Mutex, + transactions_for_verification: Mutex, + client: Arc, + accounts: Arc, + channel: IoChannel, +} + +#[derive(Debug)] +pub struct PrivateExecutionResult where T: Tracer, V: VMTracer { + code: Option, + state: Bytes, + contract_address: Option
, + result: Executed, +} + +impl Provider where { + /// Create a new provider. + pub fn new( + client: Arc, + accounts: Arc, + encryptor: Box, + config: ProviderConfig, + channel: IoChannel, + ) -> Result { + 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) { + self.notify.write().push(Arc::downgrade(&target)); + } + + fn notify(&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 { + 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 { + 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 = 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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) -> 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(&self, transaction: &SignedTransaction, options: TransactOptions, block: BlockId) -> Result, 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::>(), 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::>(), + signatures.iter().map(|s| s.r()).collect::>(), + signatures.iter().map(|s| s.s()).collect::>() + ) + } + + /// Returns the key from the key server associated with the contract + pub fn contract_key_id(&self, contract_address: &Address) -> Result { + //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
), 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 { + 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 { + 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 { + 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, 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, account_provider: &AccountProvider, account: &Address) -> Option { + 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, _invalid: Vec, _enacted: Vec, _retracted: Vec, _sealed: Vec, _proposed: Vec, _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); + } + } + } +} + diff --git a/ethcore/private-tx/src/messages.rs b/ethcore/private-tx/src/messages.rs new file mode 100644 index 000000000..f465f752b --- /dev/null +++ b/ethcore/private-tx/src/messages.rs @@ -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 . + +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) -> 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 + } +} diff --git a/ethcore/private-tx/src/private_transactions.rs b/ethcore/private-tx/src/private_transactions.rs new file mode 100644 index 000000000..502694294 --- /dev/null +++ b/ethcore/private-tx/src/private_transactions.rs @@ -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 . + +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, + /// 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 { + // 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(&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
, + /// Already obtained signatures + pub received_signatures: Vec, + /// 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, +} + +impl SigningStore { + /// Adds new private transaction into the store for signing + pub fn add_transaction( + &mut self, + private_hash: H256, + transaction: SignedTransaction, + validators: Vec
, + 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 { + 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(()) + } +} diff --git a/ethcore/private-tx/tests/private_contract.rs b/ethcore/private-tx/tests/private_contract.rs new file mode 100644 index 000000000..ba918c963 --- /dev/null +++ b/ethcore/private-tx/tests/private_contract.rs @@ -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 . + +//! 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()); +} diff --git a/ethcore/service/Cargo.toml b/ethcore/service/Cargo.toml index 4b53f5d00..608d591e0 100644 --- a/ethcore/service/Cargo.toml +++ b/ethcore/service/Cargo.toml @@ -5,8 +5,11 @@ authors = ["Parity Technologies "] [dependencies] ansi_term = "0.10" +error-chain = { version = "0.11", default-features = false } ethcore = { path = ".." } ethcore-io = { path = "../../util/io" } +ethcore-private-tx = { path = "../private-tx" } +ethsync = { path = "../../sync" } kvdb = { path = "../../util/kvdb" } log = "0.3" stop-guard = { path = "../../util/stop-guard" } diff --git a/ethcore/service/src/error.rs b/ethcore/service/src/error.rs new file mode 100644 index 000000000..bb403d0bf --- /dev/null +++ b/ethcore/service/src/error.rs @@ -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 . + +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); + } +} diff --git a/ethcore/service/src/lib.rs b/ethcore/service/src/lib.rs index 83d9a8fe1..5bd1c39de 100644 --- a/ethcore/service/src/lib.rs +++ b/ethcore/service/src/lib.rs @@ -17,18 +17,25 @@ extern crate ansi_term; extern crate ethcore; extern crate ethcore_io as io; +extern crate ethsync; extern crate kvdb; +extern crate ethcore_private_tx; extern crate stop_guard; +#[macro_use] +extern crate error_chain; + #[macro_use] extern crate log; #[cfg(test)] extern crate tempdir; +mod error; +mod service; + #[cfg(test)] extern crate kvdb_rocksdb; -mod service; - -pub use service::ClientService; +pub use error::{Error, ErrorKind}; +pub use service::{ClientService, PrivateTxService}; diff --git a/ethcore/service/src/service.rs b/ethcore/service/src/service.rs index 4337996e2..27f58b421 100644 --- a/ethcore/service/src/service.rs +++ b/ethcore/service/src/service.rs @@ -24,18 +24,50 @@ use io::{IoContext, TimerToken, IoHandler, IoService, IoError}; use kvdb::{KeyValueDB, KeyValueDBHandler}; use stop_guard::StopGuard; +use ethsync::PrivateTxHandler; use ethcore::client::{Client, ClientConfig, ChainNotify, ClientIoMessage}; -use ethcore::error::Error; use ethcore::miner::Miner; use ethcore::snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams}; use ethcore::snapshot::{RestorationStatus}; use ethcore::spec::Spec; +use ethcore::account_provider::AccountProvider; + +use ethcore_private_tx; +use Error; + +pub struct PrivateTxService { + provider: Arc, +} + +impl PrivateTxService { + fn new(provider: Arc) -> Self { + PrivateTxService { + provider, + } + } + + /// Returns underlying provider. + pub fn provider(&self) -> Arc { + 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. pub struct ClientService { io_service: Arc>, client: Arc, snapshot: Arc, + private_tx: Arc, database: Arc, _stop_guard: StopGuard, } @@ -50,6 +82,9 @@ impl ClientService { restoration_db_handler: Box, _ipc_path: &Path, miner: Arc, + account_provider: Arc, + encryptor: Box, + private_tx_conf: ethcore_private_tx::ProviderConfig, ) -> Result { let io_service = IoService::::start()?; @@ -70,9 +105,13 @@ impl ClientService { }; 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 { client: client.clone(), snapshot: snapshot.clone(), + private_tx: private_tx.clone(), }); io_service.register_handler(client_io)?; @@ -84,6 +123,7 @@ impl ClientService { io_service: Arc::new(io_service), client: client, snapshot: snapshot, + private_tx, database: client_db, _stop_guard: stop_guard, }) @@ -104,6 +144,11 @@ impl ClientService { self.snapshot.clone() } + /// Get private transaction service. + pub fn private_tx_service(&self) -> Arc { + self.private_tx.clone() + } + /// Get network service component pub fn io(&self) -> Arc> { self.io_service.clone() @@ -122,6 +167,7 @@ impl ClientService { struct ClientIoHandler { client: Arc, snapshot: Arc, + private_tx: Arc, } const CLIENT_TICK_TIMER: TimerToken = 0; @@ -180,6 +226,9 @@ impl IoHandler for ClientIoHandler { ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) { 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 } } @@ -192,6 +241,7 @@ mod tests { use tempdir::TempDir; + use ethcore::account_provider::AccountProvider; use ethcore::client::ClientConfig; use ethcore::miner::Miner; use ethcore::spec::Spec; @@ -200,6 +250,8 @@ mod tests { use kvdb_rocksdb::{Database, DatabaseConfig, CompactionProfile}; use super::*; + use ethcore_private_tx; + #[test] fn it_can_be_started() { let tempdir = TempDir::new("").unwrap(); @@ -241,6 +293,9 @@ mod tests { restoration_db_handler, tempdir.path(), Arc::new(Miner::with_spec(&spec)), + Arc::new(AccountProvider::transient_provider()), + Box::new(ethcore_private_tx::NoopEncryptor), + Default::default() ); assert!(service.is_ok()); drop(service.unwrap()); diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 2280b40de..8e1fac179 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -645,7 +645,7 @@ pub fn enact_verified( #[cfg(test)] mod tests { - use tests::helpers::get_temp_state_db; + use test_helpers::get_temp_state_db; use super::*; use engines::EthEngine; use vm::LastHashes; diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index ae5ac18eb..5dffc9ec3 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -1421,7 +1421,7 @@ mod tests { use ethereum_types::*; use receipt::{Receipt, TransactionOutcome}; use blockchain::{BlockProvider, BlockChain, Config, ImportRoute}; - use tests::helpers::{ + use test_helpers::{ generate_dummy_blockchain, generate_dummy_blockchain_with_extra, generate_dummy_empty_blockchain }; diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index d05c95b8c..bdd8d0027 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -17,6 +17,16 @@ use ethereum_types::H256; use bytes::Bytes; +/// Messages to broadcast via chain +pub enum ChainMessageType { + /// Consensus message + Consensus(Vec), + /// Message with private transaction + PrivateTransaction(Vec), + /// Message with signed private transaction + SignedPrivateTransaction(Vec), +} + /// Represents what has to be handled by actor listening to chain events pub trait ChainNotify : Send + Sync { /// fires when chain has new blocks. @@ -45,7 +55,7 @@ pub trait ChainNotify : Send + Sync { } /// fires when chain broadcasts a message - fn broadcast(&self, _data: Vec) {} + fn broadcast(&self, _message_type: ChainMessageType) {} /// fires when new transactions are received from a peer fn transactions_received(&self, diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 31f8f2f25..a8ce3f65c 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -45,7 +45,7 @@ use client::{ use client::{ BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, PruningInfo, ProvingBlockChainClient, EngineInfo + ChainNotify, PruningInfo, ProvingBlockChainClient, EngineInfo, ChainMessageType }; use encoded; use engines::{EthEngine, EpochTransition}; @@ -2126,7 +2126,7 @@ impl super::traits::EngineClient for Client { } 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> { @@ -2237,7 +2237,7 @@ mod tests { #[test] fn should_not_cache_details_before_commit() { 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::time::Duration; diff --git a/ethcore/src/client/io_message.rs b/ethcore/src/client/io_message.rs index c2823a39f..e19d3054f 100644 --- a/ethcore/src/client/io_message.rs +++ b/ethcore/src/client/io_message.rs @@ -36,6 +36,8 @@ pub enum ClientIoMessage { /// Take a snapshot for the block with given number. TakeSnapshot(u64), /// New consensus message received. - NewMessage(Bytes) + NewMessage(Bytes), + /// New private transaction arrived + NewPrivateTransaction, } diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index b92240591..2ae3436aa 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -31,11 +31,12 @@ pub use self::error::Error; pub use self::evm_test_client::{EvmTestClient, EvmTestError, TransactResult}; pub use self::io_message::ClientIoMessage; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; -pub use self::chain_notify::ChainNotify; +pub use self::chain_notify::{ChainNotify, ChainMessageType}; pub use self::traits::{ Nonce, Balance, ChainInfo, BlockInfo, ReopenBlock, PrepareOpenBlock, CallContract, TransactionInfo, RegistryInfo, ScheduleInfo, ImportSealedBlock, BroadcastProposalBlock, ImportBlock, StateOrBlock, StateClient, Call, EngineInfo, AccountData, BlockChain, BlockProducer, SealedBlockImporter }; +//pub use self::private_notify::PrivateNotify; pub use state::StateInfo; pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient, ProvingBlockChainClient}; @@ -53,3 +54,4 @@ pub use verification::VerifierType; mod traits; mod chain_notify; +mod private_notify; diff --git a/ethcore/src/client/private_notify.rs b/ethcore/src/client/private_notify.rs new file mode 100644 index 000000000..2b865a9e2 --- /dev/null +++ b/ethcore/src/client/private_notify.rs @@ -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 . + +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>; +} diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 0acde347e..8282e5738 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -1326,7 +1326,7 @@ mod tests { use header::Header; use rlp::encode; use block::*; - use tests::helpers::{ + use test_helpers::{ generate_dummy_client_with_spec_and_accounts, get_temp_state_db, generate_dummy_client, TestNotify }; diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 4d1f38dce..c4eafd851 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -199,7 +199,7 @@ mod tests { use hash::keccak; use ethereum_types::H520; use block::*; - use tests::helpers::get_temp_state_db; + use test_helpers::get_temp_state_db; use account_provider::AccountProvider; use header::Header; use spec::Spec; diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index af997c022..40a96e2b7 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -67,7 +67,7 @@ impl Engine for InstantSeal mod tests { use std::sync::Arc; use ethereum_types::{H520, Address}; - use tests::helpers::{get_temp_state_db}; + use test_helpers::get_temp_state_db; use spec::Spec; use header::Header; use block::*; diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4d0656749..011e3f8d4 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -514,7 +514,6 @@ impl Engine for Tendermint { fn fmt_err(x: T) -> EngineError { EngineError::MalformedMessage(format!("{:?}", x)) } - let rlp = UntrustedRlp::new(rlp); let message: ConsensusMessage = rlp.as_val().map_err(fmt_err)?; if !self.votes.is_old_or_known(&message) { @@ -783,7 +782,7 @@ mod tests { use header::Header; use client::ChainInfo; use miner::MinerService; - use tests::helpers::{ + use test_helpers::{ TestNotify, get_temp_state_db, generate_dummy_client, generate_dummy_client_with_spec_and_accounts }; diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 319d96db9..fb9fccf3e 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -145,8 +145,8 @@ mod tests { use account_provider::AccountProvider; use miner::MinerService; use types::ids::BlockId; + use test_helpers::generate_dummy_client_with_spec_and_accounts; use client::{BlockChainClient, ChainInfo, BlockInfo, CallContract}; - use tests::helpers::generate_dummy_client_with_spec_and_accounts; use super::super::ValidatorSet; use super::ValidatorContract; diff --git a/ethcore/src/engines/validator_set/multi.rs b/ethcore/src/engines/validator_set/multi.rs index 2794b57a2..9c287f9a3 100644 --- a/ethcore/src/engines/validator_set/multi.rs +++ b/ethcore/src/engines/validator_set/multi.rs @@ -155,7 +155,7 @@ mod tests { use header::Header; use miner::MinerService; 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 ethereum_types::Address; diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 668feac26..82befdadf 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -459,7 +459,7 @@ mod tests { use client::{ChainInfo, BlockInfo, ImportBlock}; use ethkey::Secret; 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::{ValidatorSafeContract, EVENT_NAME_HASH}; diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index aae224116..defb301dc 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -16,7 +16,7 @@ //! General error types for use in ethcore. -use std::fmt; +use std::{fmt, error}; use kvdb; use ethereum_types::{H256, U256, Address, Bloom}; 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. pub type ImportResult = Result; diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 68a07d0cd..41ab777d4 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -487,7 +487,7 @@ mod tests { use std::sync::Arc; use ethereum_types::{H64, H256, U256, Address}; use block::*; - use tests::helpers::get_temp_state_db; + use test_helpers::get_temp_state_db; use error::{BlockError, Error}; use header::Header; use spec::Spec; diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index aea53ecae..1bdcb01e1 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -145,7 +145,7 @@ mod tests { use ethereum_types::U256; use state::*; use super::*; - use tests::helpers::get_temp_state_db; + use test_helpers::get_temp_state_db; use views::BlockView; #[test] diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index c33eb2ecd..a97ee3708 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -701,7 +701,7 @@ mod tests { use error::ExecutionError; use machine::EthereumMachine; 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::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer}; 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.balance(&sender).unwrap(), U256::from(0xf9)); assert_eq!(state.balance(&address).unwrap(), U256::from(0x7)); - // 0 cause contract hasn't returned assert_eq!(substate.contracts_created.len(), 0); // TODO: just test state root. diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 732d3499a..5d35d1109 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -414,7 +414,7 @@ mod tests { use ethereum_types::{U256, Address}; use evm::{EnvInfo, Ext, CallType}; use state::{State, Substate}; - use tests::helpers::get_temp_state; + use test_helpers::get_temp_state; use super::*; use trace::{NoopTracer, NoopVMTracer}; diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 0ec60d493..404b1c25e 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -25,7 +25,7 @@ use vm::{ CreateContractAddress, ReturnData, }; use externalities::*; -use tests::helpers::get_temp_state; +use test_helpers::get_temp_state; use ethjson; use trace::{Tracer, NoopTracer}; use trace::{VMTracer, NoopVMTracer}; diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index d822195d3..c71a266f7 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -94,6 +94,7 @@ extern crate ansi_term; extern crate unexpected; extern crate kvdb; extern crate kvdb_memorydb; +extern crate kvdb_rocksdb; extern crate util_error; extern crate snappy; @@ -128,9 +129,6 @@ extern crate trace_time; #[cfg_attr(test, macro_use)] extern crate evm; -#[cfg(test)] -extern crate kvdb_rocksdb; - pub extern crate ethstore; pub mod account_provider; @@ -142,6 +140,7 @@ pub mod engines; pub mod error; pub mod ethereum; pub mod executed; +pub mod executive; pub mod header; pub mod machine; pub mod miner; @@ -150,6 +149,8 @@ pub mod snapshot; pub mod spec; pub mod state; pub mod state_db; +// Test helpers made public for usage outside ethcore +pub mod test_helpers; pub mod trace; pub mod verification; pub mod views; @@ -159,7 +160,6 @@ mod blooms; mod pod_account; mod account_db; mod builtin; -mod executive; mod externalities; mod blockchain; mod factory; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index b0628b3e0..96094dce4 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -24,7 +24,7 @@ use ethereum_types::{H256, U256, Address}; use parking_lot::{Mutex, RwLock}; use bytes::Bytes; use engines::{EthEngine, Seal}; -use error::*; +use error::{ExecutionError, Error}; use ethcore_miner::banning_queue::{BanningTransactionQueue, Threshold}; use ethcore_miner::local_transactions::{Status as LocalTransactionStatus}; use ethcore_miner::transaction_queue::{ @@ -1327,8 +1327,7 @@ mod tests { use spec::Spec; use transaction::{SignedTransaction, Transaction, PendingTransaction, Action}; use miner::MinerService; - - use tests::helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts}; + use test_helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts}; #[test] fn should_prepare_block_to_seal() { diff --git a/ethcore/src/snapshot/account.rs b/ethcore/src/snapshot/account.rs index b85f8f401..222875b84 100644 --- a/ethcore/src/snapshot/account.rs +++ b/ethcore/src/snapshot/account.rs @@ -210,7 +210,7 @@ pub fn from_fat_rlp( mod tests { use account_db::{AccountDB, AccountDBMut}; 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 hash::{KECCAK_EMPTY, KECCAK_NULL_RLP, keccak}; diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index fad150645..7d5eea1ef 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -635,7 +635,7 @@ mod tests { use snapshot::{ManifestData, RestorationStatus, SnapshotService}; use super::*; use tempdir::TempDir; - use tests::helpers::restoration_db_handler; + use test_helpers::restoration_db_handler; struct NoopDBRestore; impl DatabaseRestore for NoopDBRestore { diff --git a/ethcore/src/snapshot/tests/proof_of_authority.rs b/ethcore/src/snapshot/tests/proof_of_authority.rs index 101de5c58..7283a0586 100644 --- a/ethcore/src/snapshot/tests/proof_of_authority.rs +++ b/ethcore/src/snapshot/tests/proof_of_authority.rs @@ -25,7 +25,7 @@ use client::{Client, BlockChainClient, ChainInfo}; use ethkey::Secret; use snapshot::tests::helpers as snapshot_helpers; use spec::Spec; -use tests::helpers; +use test_helpers::generate_dummy_client_with_spec_and_accounts; use transaction::{Transaction, Action, SignedTransaction}; use tempdir::TempDir; @@ -89,7 +89,7 @@ enum Transition { // create a chain with the given transitions and some blocks beyond that transition. fn make_chain(accounts: Arc, blocks_beyond: usize, transitions: Vec) -> Arc { - 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())); let mut cur_signers = vec![*RICH_ADDR]; diff --git a/ethcore/src/snapshot/tests/service.rs b/ethcore/src/snapshot/tests/service.rs index 4548741f3..7e81b0579 100644 --- a/ethcore/src/snapshot/tests/service.rs +++ b/ethcore/src/snapshot/tests/service.rs @@ -24,7 +24,7 @@ use ids::BlockId; use snapshot::service::{Service, ServiceParams}; use snapshot::{self, ManifestData, SnapshotService}; 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 kvdb_rocksdb::{Database, DatabaseConfig}; diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index f0b3b9203..ec1b5edf3 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -892,7 +892,7 @@ impl Spec { mod tests { use super::*; use state::State; - use tests::helpers::get_temp_state_db; + use test_helpers::get_temp_state_db; use views::BlockView; use tempdir::TempDir; diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index 9d72ed158..f9c8f258e 100755 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -180,6 +180,16 @@ impl Account { self.init_code(code); } + /// Reset this account's code and storage to given values. + pub fn reset_code_and_storage(&mut self, code: Arc, storage: HashMap) { + 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`. pub fn set_storage(&mut self, key: H256, value: H256) { self.storage_changes.insert(key, value); diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 757f54511..9fd3fd852 100755 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -494,6 +494,13 @@ impl State { (self.root, self.db) } + /// Destroy the current object and return single account data. + pub fn into_account(self, account: &Address) -> trie::Result<(Option>, HashMap)> { + // TODO: deconstruct without cloning. + let account = self.require(account, true)?; + Ok((account.code().clone(), account.storage_changes().clone())) + } + /// Return reference to root pub fn root(&self) -> &H256 { &self.root @@ -1014,6 +1021,11 @@ impl State { } })) } + + /// Replace account code and storage. Creates account if it does not exist. + pub fn patch_account(&self, a: &Address, code: Arc, storage: HashMap) -> trie::Result<()> { + Ok(self.require(a, false)?.reset_code_and_storage(code, storage)) + } } // State proof implementations; useful for light client protocols. @@ -1099,7 +1111,7 @@ mod tests { use super::*; use ethkey::Secret; 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 vm::EnvInfo; use spec::*; diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index 346ad9cc2..3b00a42ee 100755 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -480,7 +480,7 @@ unsafe impl Sync for SyncAccount {} mod tests { use ethereum_types::{H256, U256, Address}; use kvdb::DBTransaction; - use tests::helpers::{get_temp_state_db}; + use test_helpers::get_temp_state_db; use state::{Account, Backend}; use ethcore_logger::init_log; diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/test_helpers.rs similarity index 82% rename from ethcore/src/tests/helpers.rs rename to ethcore/src/test_helpers.rs index 14dcf3630..4c013dd25 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/test_helpers.rs @@ -14,12 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Set of different helpers for client tests + use account_provider::AccountProvider; -use ethereum_types::{H256, U256}; +use ethereum_types::{H256, U256, Address}; use block::{OpenBlock, Drain}; use blockchain::{BlockChain, Config as BlockChainConfig}; use bytes::Bytes; -use client::{Client, ClientConfig, ChainInfo, ImportBlock, ChainNotify}; +use client::{Client, ClientConfig, ChainInfo, ImportBlock, ChainNotify, ChainMessageType, PrepareOpenBlock}; use ethkey::KeyPair; use evm::Factory as EvmFactory; use factory::Factories; @@ -39,6 +41,7 @@ use views::BlockView; use kvdb::{KeyValueDB, KeyValueDBHandler}; use kvdb_rocksdb::{Database, DatabaseConfig}; +/// Creates test block with corresponding header pub fn create_test_block(header: &Header) -> Bytes { let mut rlp = RlpStream::new_list(3); 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)) } +/// Creates test block with corresponding header and data pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransaction], uncles: &[Header]) -> Bytes { let mut rlp = RlpStream::new_list(3); rlp.append(header); @@ -87,23 +91,27 @@ pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransa rlp.out() } +/// Generates dummy client (not test client) with corresponding amount of blocks pub fn generate_dummy_client(block_number: u32) -> Arc { 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 { 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(test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc where F: Fn()->Spec { 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(test_spec: F, accounts: Option>) -> Arc where F: Fn()->Spec { 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(test_spec: F, accounts: Option>, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc where F: Fn()->Spec { let test_spec = test_spec(); let client_db = new_db(); @@ -174,6 +182,7 @@ pub fn generate_dummy_client_with_spec_accounts_and_data(test_spec: F, accoun client } +/// Adds blocks to the client pub fn push_blocks_to_client(client: &Arc, timestamp_salt: u64, starting_number: usize, block_number: usize) { let test_spec = Spec::new_test(); let state_root = test_spec.genesis_header().state_root().clone(); @@ -203,6 +212,29 @@ pub fn push_blocks_to_client(client: &Arc, timestamp_salt: u64, starting } } +/// Adds one block with transactions +pub fn push_block_with_transactions(client: &Arc, 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) -> Arc { let test_spec = Spec::new_test(); 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))) } +/// Generates dummy blockchain with corresponding amount of blocks pub fn generate_dummy_blockchain(block_number: u32) -> BlockChain { let db = new_db(); 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 } +/// 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 { let db = new_db(); 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 } +/// Returns empty dummy blockchain pub fn generate_dummy_empty_blockchain() -> BlockChain { let db = new_db(); let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); bc } +/// Returns temp state pub fn get_temp_state() -> State<::state_db::StateDB> { let journal_db = get_temp_state_db(); 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> { let journal_db = get_temp_state_db(); 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) } +/// Returns temp state db pub fn get_temp_state_db() -> StateDB { let db = new_db(); let journal_db = ::journaldb::new(db, ::journaldb::Algorithm::EarlyMerge, ::db::COL_STATE); 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 { let test_spec = Spec::new_test(); 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 { let test_spec = Spec::new_test(); 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 } +/// Returns hash and header of the correct dummy block pub fn get_good_dummy_block_hash() -> (H256, Bytes) { let mut block_header = Header::new(); 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)) } +/// Returns hash of the correct dummy block pub fn get_good_dummy_block() -> Bytes { let (_, bytes) = get_good_dummy_block_hash(); bytes } +/// Returns hash of the dummy block with incorrect state root pub fn get_bad_state_dummy_block() -> Bytes { let mut block_header = Header::new(); let test_spec = Spec::new_test(); @@ -342,17 +385,25 @@ pub fn get_bad_state_dummy_block() -> Bytes { create_test_block(&block_header) } +/// Test actor for chain events #[derive(Default)] pub struct TestNotify { + /// Messages store pub messages: RwLock>, } impl ChainNotify for TestNotify { - fn broadcast(&self, data: Vec) { + 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); } } +/// Creates new instance of KeyValueDBHandler pub fn restoration_db_handler(config: DatabaseConfig) -> Box { use kvdb::Error; diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 8091ae73e..e6333f56a 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -23,7 +23,7 @@ use state::{self, State, CleanupMode}; use executive::{Executive, TransactOptions}; use ethereum; 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_with_data, get_good_dummy_block, get_bad_state_dummy_block }; diff --git a/ethcore/src/tests/evm.rs b/ethcore/src/tests/evm.rs index 60d5e12b0..4f4ad4241 100644 --- a/ethcore/src/tests/evm.rs +++ b/ethcore/src/tests/evm.rs @@ -22,7 +22,7 @@ use vm::{EnvInfo, ActionParams, ActionValue, CallType, ParamsType}; use evm::{Factory, VMType}; use executive::Executive; use state::Substate; -use tests::helpers::get_temp_state_with_factory; +use test_helpers::get_temp_state_with_factory; use trace::{NoopVMTracer, NoopTracer}; use transaction::SYSTEM_ADDRESS; diff --git a/ethcore/src/tests/mod.rs b/ethcore/src/tests/mod.rs index eec71efaf..8b509d2af 100644 --- a/ethcore/src/tests/mod.rs +++ b/ethcore/src/tests/mod.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -pub mod helpers; mod client; mod evm; mod trace; diff --git a/ethcore/src/tests/trace.rs b/ethcore/src/tests/trace.rs index 5483c6263..4646bc091 100644 --- a/ethcore/src/tests/trace.rs +++ b/ethcore/src/tests/trace.rs @@ -24,7 +24,7 @@ use ethereum_types::{U256, Address}; use io::*; use spec::*; use client::*; -use tests::helpers::get_temp_state_db; +use test_helpers::get_temp_state_db; use client::{BlockChainClient, Client, ClientConfig}; use kvdb_rocksdb::{Database, DatabaseConfig}; use std::sync::Arc; diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index 58b1bb7bb..b6e60d042 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -731,7 +731,7 @@ mod tests { use spec::Spec; use super::{BlockQueue, Config, State}; 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 views::*; diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index e6289c2b0..e50eb7919 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -359,7 +359,7 @@ mod tests { use error::BlockError::*; use ethkey::{Random, Generator}; 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 types::log_entry::{LogEntry, LocalizedLogEntry}; use rlp; diff --git a/ethcore/transaction/src/transaction.rs b/ethcore/transaction/src/transaction.rs index d458e1f60..4df63294b 100644 --- a/ethcore/transaction/src/transaction.rs +++ b/ethcore/transaction/src/transaction.rs @@ -77,6 +77,25 @@ pub enum Condition { 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 { + 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 /// or contract creation operation. #[derive(Default, Debug, Clone, PartialEq, Eq)] @@ -186,7 +205,7 @@ impl Transaction { unsigned: self, r: sig.r().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(), }.compute_hash() } @@ -330,8 +349,7 @@ impl UnverifiedTransaction { &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 { match self.v { v if v == 27 || v == 28 || v > 36 => ((v - 1) % 2) as u8, _ => 4 } } + pub fn standard_v(&self) -> u8 { signature::check_replay_protection(self.v) } /// The `v` value that appears in the RLP. pub fn original_v(&self) -> u64 { self.v } diff --git a/hash-fetch/src/client.rs b/hash-fetch/src/client.rs index b3a9356ea..ce2e44054 100644 --- a/hash-fetch/src/client.rs +++ b/hash-fetch/src/client.rs @@ -153,7 +153,7 @@ impl HashFetch for Client { .into_future() .and_then(move |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| { if !response.is_success() { @@ -199,7 +199,7 @@ mod tests { use parking_lot::Mutex; use futures::future; use futures_cpupool::CpuPool; - use fetch::{self, Fetch, Url}; + use fetch::{self, Fetch, Url, Method}; use parity_reactor::Remote; use urlhint::tests::{FakeRegistrar, URLHINT}; use super::{Error, Client, HashFetch, random_temp_path}; @@ -214,7 +214,7 @@ mod tests { impl Fetch for FakeFetch { type Result = future::Ok; - 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"); let u = Url::parse(url).unwrap(); future::ok(if self.return_success { diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 26eae4597..c2c6f2aa5 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -25,6 +25,7 @@ use hash::{keccak, KECCAK_NULL_RLP}; use ethereum_types::{U256, H256, Address}; use bytes::ToPretty; use rlp::PayloadInfo; +use ethcore::account_provider::AccountProvider; use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, Nonce, Balance, BlockChainClient, BlockId, BlockInfo, ImportBlock}; use ethcore::db::NUM_COLUMNS; 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 user_defaults::UserDefaults; use fdlimit; +use ethcore_private_tx; #[derive(Debug, PartialEq)] pub enum DataFormat { @@ -389,6 +391,9 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> { restoration_db_handler, &cmd.dirs.ipc_path(), 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))?; // free up the spec in memory. @@ -576,6 +581,9 @@ fn start_client( restoration_db_handler, &dirs.ipc_path(), 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))?; drop(spec); diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 2cc0ef7c2..a160661b9 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -350,6 +350,35 @@ usage! { "--password=[FILE]...", "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) = 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) = 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) = 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) = 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) = 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) = 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"] FLAG flag_force_ui: (bool) = false, or |c: &Config| c.ui.as_ref()?.force.clone(), "--force-ui", @@ -462,7 +491,7 @@ usage! { "--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.", - 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]", "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]", "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]", "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]", "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]", "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, dapps: Option, secretstore: Option, + private_tx: Option, ipfs: Option, mining: Option, footprint: Option, @@ -1057,6 +1087,18 @@ struct Account { fast_unlock: Option, } +#[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +struct PrivateTransactions { + enabled: Option, + signer: Option, + validators: Option>, + account: Option, + passwords: Option, + sstore_url: Option, + sstore_threshold: Option, +} + #[derive(Default, Debug, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] struct Ui { @@ -1501,6 +1543,15 @@ mod tests { flag_no_hardware_wallets: 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_no_ui: false, arg_ui_port: 8180u16, @@ -1834,6 +1885,7 @@ mod tests { http_port: Some(8082), path: None, }), + private_tx: None, ipfs: Some(Ipfs { enable: Some(false), port: Some(5001), diff --git a/parity/cli/tests/config.full.toml b/parity/cli/tests/config.full.toml index 2e546b5f1..08df68ee5 100644 --- a/parity/cli/tests/config.full.toml +++ b/parity/cli/tests/config.full.toml @@ -23,6 +23,15 @@ unlock = ["0xdeadbeefcafe0000000000000000000000000000"] password = ["~/.safe/password.file"] 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] force = false disable = false diff --git a/parity/configuration.rs b/parity/configuration.rs index eb6ce713d..7ce3db124 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -38,13 +38,14 @@ use rpc_apis::ApiSet; use parity_rpc::NetworkSettings; 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, -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 params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras, SpecType}; use ethcore_logger::Config as LogConfig; use dir::{self, Directories, default_hypervisor_path, default_local_path, default_data_path}; use dapps::Configuration as DappsConfiguration; use ipfs::Configuration as IpfsConfiguration; +use ethcore_private_tx::{ProviderConfig, EncryptorConfig}; use secretstore::{NodeSecretKey, Configuration as SecretStoreConfiguration, ContractAddress as SecretStoreContractAddress}; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; use run::RunCmd; @@ -341,6 +342,7 @@ impl Configuration { let verifier_settings = self.verifier_settings(); let whisper_config = self.whisper_config(); + let (private_provider_conf, private_enc_conf, private_tx_enabled) = self.private_provider_config()?; let run_cmd = RunCmd { cache_config: cache_config, @@ -379,6 +381,9 @@ impl Configuration { ipfs_conf: ipfs_conf, ui_conf: ui_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()?, ui: self.args.cmd_ui, name: self.args.arg_identity, @@ -934,6 +939,29 @@ impl Configuration { 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 { let http_conf = self.http_config()?; let net_addresses = self.net_addresses()?; @@ -1468,6 +1496,9 @@ mod tests { ipfs_conf: Default::default(), ui_conf: Default::default(), secretstore_conf: Default::default(), + private_provider_conf: Default::default(), + private_encryptor_conf: Default::default(), + private_tx_enabled: false, ui: false, dapp: None, name: "".into(), diff --git a/parity/main.rs b/parity/main.rs index 30c3cf0e9..9ff3d97ab 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -53,14 +53,15 @@ extern crate ethcore_logger; extern crate ethcore_migrations as migrations; extern crate ethcore_miner as miner; extern crate ethcore_network as network; +extern crate ethcore_private_tx; extern crate ethcore_service; extern crate ethcore_transaction as transaction; extern crate ethereum_types; -extern crate migration as migr; -extern crate kvdb; -extern crate kvdb_rocksdb; extern crate ethkey; extern crate ethsync; +extern crate kvdb; +extern crate kvdb_rocksdb; +extern crate migration as migr; extern crate node_health; extern crate panic_hook; extern crate parity_hash_fetch as hash_fetch; diff --git a/parity/modules.rs b/parity/modules.rs index 36cffe2ec..22f8afe9f 100644 --- a/parity/modules.rs +++ b/parity/modules.rs @@ -21,7 +21,7 @@ use ethsync::{self, AttachedProtocol, SyncConfig, NetworkConfiguration, Params, use ethcore::snapshot::SnapshotService; use light::Provider; -pub use ethsync::{EthSync, SyncProvider, ManageNetwork}; +pub use ethsync::{EthSync, SyncProvider, ManageNetwork, PrivateTxHandler}; pub use ethcore::client::ChainNotify; use ethcore_logger::Config as LogConfig; @@ -32,6 +32,7 @@ pub fn sync( net_cfg: NetworkConfiguration, client: Arc, snapshot_service: Arc, + private_tx_handler: Arc, provider: Arc, _log_settings: &LogConfig, attached_protos: Vec, @@ -42,6 +43,7 @@ pub fn sync( chain: client, provider: provider, snapshot_service: snapshot_service, + private_tx_handler, network_config: net_cfg, attached_protos: attached_protos, }, diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 5ba18dc09..23a69755f 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -22,6 +22,7 @@ use std::sync::{Arc, Weak}; pub use parity_rpc::signer::SignerService; pub use parity_rpc::dapps::{DappsService, LocalDapp}; +use ethcore_service::PrivateTxService; use ethcore::account_provider::AccountProvider; use ethcore::client::Client; use ethcore::miner::Miner; @@ -40,6 +41,7 @@ use parity_rpc::dispatch::{FullDispatcher, LightDispatcher}; use parity_rpc::informant::{ActivityNotifier, ClientNotifier}; use parity_rpc::{Metadata, NetworkSettings, Host}; use parking_lot::{Mutex, RwLock}; +use ethcore_private_tx::Provider as PrivateTransactionManager; use updater::Updater; #[derive(Debug, PartialEq, Clone, Eq, Hash)] @@ -70,6 +72,8 @@ pub enum Api { Rpc, /// SecretStore (UNSAFE: arbitrary hash signing) SecretStore, + /// Private transaction manager (Safe) + Private, /// Whisper (Safe) // TODO: _if_ someone guesses someone else's key or filter IDs they can remove // BUT these are all ephemeral so it seems fine. @@ -98,6 +102,7 @@ impl FromStr for Api { "traces" => Ok(Traces), "rpc" => Ok(Rpc), "secretstore" => Ok(SecretStore), + "private" => Ok(Private), "shh" => Ok(Whisper), "shh_pubsub" => Ok(WhisperPubSub), api => Err(format!("Unknown api: {}", api)) @@ -183,6 +188,7 @@ fn to_modules(apis: &HashSet) -> BTreeMap { Api::Traces => ("traces", "1.0"), Api::Rpc => ("rpc", "1.0"), Api::SecretStore => ("secretstore", "1.0"), + Api::Private => ("private", "1.0"), Api::Whisper => ("shh", "1.0"), Api::WhisperPubSub => ("shh_pubsub", "1.0"), }; @@ -214,6 +220,7 @@ pub struct FullDependencies { pub sync: Arc, pub net: Arc, pub secret_store: Option>, + pub private_tx_service: Option>, pub miner: Arc, pub external_miner: Arc, pub logger: Arc, @@ -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 { pub geth_compatibility: bool, pub remote: parity_reactor::Remote, pub whisper_rpc: Option<::whisper::RpcFactory>, + pub private_tx_service: Option>, pub gas_price_percentile: usize, } @@ -587,12 +598,18 @@ impl LightDependencies { let whisper = whisper_rpc.make_handler(self.net.clone()); handler.extend_with(::parity_whisper::rpc::Whisper::to_delegate(whisper)); } - } + }, Api::WhisperPubSub => { if let Some(ref whisper_rpc) = self.whisper_rpc { let whisper = whisper_rpc.make_handler(self.net.clone()); 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::Whisper, Api::WhisperPubSub, + Api::Private, ].into_iter().cloned().collect(); match *self { @@ -693,6 +711,7 @@ mod test { assert_eq!(Api::Traces, "traces".parse().unwrap()); assert_eq!(Api::Rpc, "rpc".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::WhisperPubSub, "shh_pubsub".parse().unwrap()); assert!("rp".parse::().is_err()); @@ -712,7 +731,7 @@ mod test { fn test_api_set_unsafe_context() { let expected = vec![ // 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(); assert_eq!(ApiSet::UnsafeContext.list_apis(), expected); } @@ -721,7 +740,7 @@ mod test { fn test_api_set_ipc_context() { let expected = vec![ // 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 Api::ParityAccounts ].into_iter().collect(); @@ -732,7 +751,7 @@ mod test { fn test_api_set_safe_context() { let expected = vec![ // 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 Api::ParityAccounts, // 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::ParityAccounts, Api::ParitySet, Api::Signer, - Api::Personal + Api::Personal, + Api::Private, ].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::ParityAccounts, Api::ParitySet, Api::Signer, + Api::Private ].into_iter().collect())); } #[test] fn test_safe_parsing() { assert_eq!("safe".parse::().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())); } } diff --git a/parity/run.rs b/parity/run.rs index 816ddd4dd..fbc24a90d 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -50,7 +50,7 @@ use parity_rpc::{NetworkSettings, informant, is_major_importing}; use parking_lot::{Condvar, Mutex}; use updater::{UpdatePolicy, Updater}; use parity_version::version; - +use ethcore_private_tx::{ProviderConfig, EncryptorConfig, SecretStoreEncryptor}; use params::{ SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch, 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 ui_conf: rpc::UiConfiguration, pub secretstore_conf: secretstore::Configuration, + pub private_provider_conf: ProviderConfig, + pub private_encryptor_conf: EncryptorConfig, + pub private_tx_enabled: bool, pub dapp: Option, pub ui: bool, pub name: String, @@ -388,6 +391,7 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc) -> Result(cmd: RunCmd, logger: Arc, on_client_rq: restoration_db_handler, &cmd.dirs.ipc_path(), 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))?; let connection_filter_address = spec.params().node_permission_contract; @@ -630,6 +637,9 @@ fn execute_impl(cmd: RunCmd, logger: Arc, on_client_rq: // take handle to 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, a))); let snapshot_service = service.snapshot_service(); @@ -697,6 +707,7 @@ fn execute_impl(cmd: RunCmd, logger: Arc, on_client_rq: net_conf.clone().into(), client.clone(), snapshot_service.clone(), + private_tx_service.clone(), client.clone(), &cmd.logger_config, attached_protos, @@ -705,6 +716,15 @@ fn execute_impl(cmd: RunCmd, logger: Arc, on_client_rq: 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 if network_enabled { chain_notify.start(); @@ -796,6 +816,7 @@ fn execute_impl(cmd: RunCmd, logger: Arc, on_client_rq: pool: cpu_pool.clone(), remote: event_loop.remote(), whisper_rpc: whisper_factory, + private_tx_service: Some(private_tx_service.clone()), gas_price_percentile: cmd.gas_price_percentile, }); diff --git a/parity/snapshot.rs b/parity/snapshot.rs index ae7a0698b..909673280 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -21,6 +21,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use hash::keccak; +use ethcore::account_provider::AccountProvider; use ethcore::snapshot::{Progress, RestorationStatus, SnapshotService as SS}; use ethcore::snapshot::io::{SnapshotReader, PackedReader, PackedWriter}; 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 user_defaults::UserDefaults; use fdlimit; +use ethcore_private_tx; /// Kinds of snapshot commands. #[derive(Debug, PartialEq, Clone, Copy)] @@ -192,7 +194,10 @@ impl SnapshotCommand { &snapshot_path, restoration_db_handler, &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))?; Ok(service) diff --git a/price-info/src/lib.rs b/price-info/src/lib.rs index e233062b4..9be27a131 100644 --- a/price-info/src/lib.rs +++ b/price-info/src/lib.rs @@ -96,7 +96,7 @@ impl Client { /// Gets the current ETH price and calls `set_price` with the result. pub fn get(&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() .and_then(|response| { if !response.is_success() { @@ -140,7 +140,7 @@ mod test { use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use fetch; - use fetch::{Fetch, Url}; + use fetch::{Fetch, Url, Method}; use futures_cpupool::CpuPool; use futures::future::{self, FutureResult}; use Client; @@ -158,7 +158,7 @@ mod test { impl Fetch for FakeFetch { type Result = FutureResult; - 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"); let u = Url::parse(url).unwrap(); let mut val = self.1.lock(); diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 51f2e8ff3..ed59a91e8 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -44,6 +44,7 @@ ethcore-io = { path = "../util/io" } ethcore-light = { path = "../ethcore/light" } ethcore-logger = { path = "../logger" } ethcore-miner = { path = "../miner" } +ethcore-private-tx = { path = "../ethcore/private-tx" } ethcore-transaction = { path = "../ethcore/transaction" } ethereum-types = "0.3" diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index a08a1a88a..79911938b 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -67,6 +67,7 @@ extern crate rlp; extern crate stats; extern crate keccak_hash as hash; extern crate hardware_wallet; +extern crate ethcore_private_tx; extern crate patricia_trie as trie; #[macro_use] diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 9393fc53a..e86ab630f 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -23,6 +23,7 @@ use ethcore::error::{Error as EthcoreError, CallError}; use jsonrpc_core::{futures, Error, ErrorCode, Value}; use rlp::DecoderError; use transaction::Error as TransactionError; +use ethcore_private_tx::Error as PrivateTransactionError; mod codes { // NOTE [ToDr] Codes from [-32099, -32000] @@ -39,6 +40,7 @@ mod codes { pub const ACCOUNT_LOCKED: i64 = -32020; pub const PASSWORD_INVALID: i64 = -32021; pub const ACCOUNT_ERROR: i64 = -32023; + pub const PRIVATE_ERROR: i64 = -32024; pub const REQUEST_REJECTED: i64 = -32040; pub const REQUEST_REJECTED_LIMIT: i64 = -32041; 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 { use self::TransactionError::*; diff --git a/rpc/src/v1/impls/light/parity_set.rs b/rpc/src/v1/impls/light/parity_set.rs index a1344fa69..0a6c2c5a5 100644 --- a/rpc/src/v1/impls/light/parity_set.rs +++ b/rpc/src/v1/impls/light/parity_set.rs @@ -128,7 +128,7 @@ impl ParitySet for ParitySetClient { } fn hash_content(&self, url: String) -> BoxFuture { - let future = self.fetch.fetch(&url, Default::default()).then(move |result| { + let future = self.fetch.get(&url, Default::default()).then(move |result| { result .map_err(errors::fetch) .and_then(move |response| { diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index d3e0554c2..4edaf6bcd 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -32,6 +32,7 @@ mod rpc; mod secretstore; mod traces; mod web3; +mod private; pub mod light; @@ -51,3 +52,4 @@ pub use self::traces::TracesClient; pub use self::web3::Web3Client; pub use self::rpc::RpcClient; pub use self::secretstore::SecretStoreClient; +pub use self::private::PrivateClient; diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index 9d0711872..1be136e1d 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -170,7 +170,7 @@ impl ParitySet for ParitySetClient where } fn hash_content(&self, url: String) -> BoxFuture { - let future = self.fetch.fetch(&url, Default::default()).then(move |result| { + let future = self.fetch.get(&url, Default::default()).then(move |result| { result .map_err(errors::fetch) .and_then(move |response| { diff --git a/rpc/src/v1/impls/private.rs b/rpc/src/v1/impls/private.rs new file mode 100644 index 000000000..bf797d5a5 --- /dev/null +++ b/rpc/src/v1/impls/private.rs @@ -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 . + +//! 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>, +} + +impl PrivateClient { + /// Creates a new instance. + pub fn new(private: Option>) -> 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 { + 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, gas_price: U256) -> Result { + 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
= 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 { + 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 { + let client = self.unwrap_manager()?; + let key = client.contract_key_id(&contract_address.into()).map_err(|e| errors::private_message(e))?; + Ok(key.into()) + } +} + diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index b5e1cfa4d..154317eb2 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -41,7 +41,7 @@ pub mod informant; pub mod metadata; 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::helpers::{NetworkSettings, block_import, dispatch}; pub use self::metadata::Metadata; diff --git a/rpc/src/v1/tests/helpers/fetch.rs b/rpc/src/v1/tests/helpers/fetch.rs index 316d06825..f3b7543f7 100644 --- a/rpc/src/v1/tests/helpers/fetch.rs +++ b/rpc/src/v1/tests/helpers/fetch.rs @@ -18,7 +18,7 @@ use std::thread; use jsonrpc_core::futures::{self, Future}; -use fetch::{self, Fetch, Url}; +use fetch::{self, Fetch, Url, Method}; use hyper; /// Test implementation of fetcher. Will always return the same file. @@ -28,7 +28,7 @@ pub struct TestFetch; impl Fetch for TestFetch { type Result = Box + 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 (tx, rx) = futures::oneshot(); thread::spawn(move || { diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index 528463a4a..26a43fa3f 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -31,6 +31,7 @@ pub mod signer; pub mod traces; pub mod rpc; pub mod secretstore; +pub mod private; pub use self::web3::Web3; pub use self::eth::{Eth, EthFilter}; @@ -47,3 +48,4 @@ pub use self::signer::Signer; pub use self::traces::Traces; pub use self::rpc::Rpc; pub use self::secretstore::SecretStore; +pub use self::private::Private; diff --git a/rpc/src/v1/traits/private.rs b/rpc/src/v1/traits/private.rs new file mode 100644 index 000000000..7106e0bf4 --- /dev/null +++ b/rpc/src/v1/traits/private.rs @@ -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 . + +//! 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; + + /// Creates a transaction for contract's deployment from origin (signed transaction) + #[rpc(name = "private_composeDeploymentTransaction")] + fn compose_deployment_transaction(&self, BlockNumber, Bytes, Vec, U256) -> Result; + + /// Make a call to the private contract + #[rpc(meta, name = "private_call")] + fn private_call(&self, Self::Metadata, BlockNumber, CallRequest) -> Result; + + /// Retrieve the id of the key associated with the contract + #[rpc(name = "private_contractKey")] + fn private_contract_key(&self, H160) -> Result; + } +} diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index 7f1e51fad..e7f764b8b 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -44,6 +44,7 @@ mod transaction_request; mod transaction_condition; mod uint; mod work; +mod private_receipt; pub mod pubsub; @@ -80,6 +81,7 @@ pub use self::transaction_request::TransactionRequest; pub use self::transaction_condition::TransactionCondition; pub use self::uint::{U128, U256, U64}; pub use self::work::Work; +pub use self::private_receipt::{PrivateTransactionReceipt, PrivateTransactionReceiptAndTransaction}; // TODO [ToDr] Refactor to a proper type Vec of enums? /// Expected tracing type. diff --git a/rpc/src/v1/types/private_receipt.rs b/rpc/src/v1/types/private_receipt.rs new file mode 100644 index 000000000..328013d7f --- /dev/null +++ b/rpc/src/v1/types/private_receipt.rs @@ -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 . + +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, + /// Status code + #[serde(rename="status")] + pub status_code: u8, +} + +impl From 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, +} + diff --git a/sync/Cargo.toml b/sync/Cargo.toml index c5384de71..23cbbcf34 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -18,6 +18,7 @@ ethcore = { path = "../ethcore" } ethereum-types = "0.3" plain_hasher = { path = "../util/plain_hasher" } rlp = { path = "../util/rlp" } +rustc-hex = "1.0" keccak-hash = { path = "../util/hash" } triehash = { path = "../util/triehash" } kvdb = { path = "../util/kvdb" } diff --git a/sync/src/api.rs b/sync/src/api.rs index 735b6eb03..62a237da4 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -24,7 +24,7 @@ use network::{NetworkProtocolHandler, NetworkContext, HostInfo, PeerId, Protocol use ethereum_types::{H256, H512, U256}; use io::{TimerToken}; use ethcore::ethstore::ethkey::Secret; -use ethcore::client::{BlockChainClient, ChainNotify}; +use ethcore::client::{BlockChainClient, ChainNotify, ChainMessageType}; use ethcore::snapshot::SnapshotService; use ethcore::header::BlockNumber; use sync_io::NetSyncIo; @@ -32,11 +32,13 @@ use chain::{ChainSync, SyncStatus as EthSyncStatus}; use std::net::{SocketAddr, AddrParseError}; use std::str::FromStr; 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::Provider; use light::net::{self as light_net, LightProtocol, Params as LightParams, Capabilities, Handler as LightHandler, EventContext}; use network::IpFilter; +use private_tx::PrivateTxHandler; /// Parity sync protocol pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; @@ -227,6 +229,8 @@ pub struct Params { pub chain: Arc, /// Snapshot service. pub snapshot_service: Arc, + /// Private tx service. + pub private_tx_handler: Arc, /// Light data provider. pub provider: Arc<::light::Provider>, /// 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 sync = Arc::new(EthSync { @@ -392,9 +396,10 @@ impl NetworkProtocolHandler for SyncProtocolHandler { } fn timeout(&self, io: &NetworkContext, _timer: TimerToken) { - self.sync.write().maintain_peers(&mut 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().propagate_new_transactions(&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_peers(&mut io); + 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; 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( &mut sync_io, &imported, @@ -448,10 +454,10 @@ impl ChainNotify for EthSync { 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)); // 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)); // register the light protocol. @@ -469,10 +475,14 @@ impl ChainNotify for EthSync { self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } - fn broadcast(&self, message: Vec) { + fn broadcast(&self, message_type: ChainMessageType) { 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); - 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), + } }); } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 978024591..b2494e1f0 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -88,6 +88,7 @@ /// All other messages are ignored. /// +use std::sync::Arc; use std::collections::{HashSet, HashMap}; use std::cmp; use std::time::Instant; @@ -111,15 +112,23 @@ use rand::Rng; use snapshot::{Snapshot, ChunkType}; use api::{EthProtocolInfo as PeerInfoDigest, WARP_SYNC_PROTOCOL_ID}; use transactions_stats::{TransactionsStats, Stats as TransactionStats}; +use private_tx::PrivateTxHandler; known_heap_size!(0, PeerInfo); type PacketDecodeError = DecoderError; -const PROTOCOL_VERSION_63: u8 = 63; -const PROTOCOL_VERSION_62: u8 = 62; -const PROTOCOL_VERSION_1: u8 = 1; -const PROTOCOL_VERSION_2: u8 = 2; +/// 63 version of Ethereum protocol. +pub const ETH_PROTOCOL_VERSION_63: u8 = 63; +/// 62 version of Ethereum protocol. +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_HEADERS_TO_SEND: usize = 512; 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 SNAPSHOT_DATA_PACKET: u8 = 0x14; 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; @@ -384,6 +395,8 @@ pub struct ChainSync { transactions_stats: TransactionsStats, /// Enable ancient block downloading download_old_blocks: bool, + /// Shared private tx service. + private_tx_handler: Arc, /// Enable warp sync. warp_sync: WarpSync, } @@ -392,7 +405,7 @@ type RlpResponseResult = Result, PacketDecodeError impl ChainSync { /// 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) -> ChainSync { let chain_info = chain.chain_info(); let best_block = chain.chain_info().best_block_number; let state = match config.warp_sync { @@ -417,6 +430,7 @@ impl ChainSync { snapshot: Snapshot::new(), sync_start_time: None, transactions_stats: TransactionsStats::default(), + private_tx_handler, warp_sync: config.warp_sync, }; sync.update_targets(chain); @@ -428,7 +442,7 @@ impl ChainSync { let last_imported_number = self.new_blocks.last_imported_block_number(); SyncStatus { state: self.state.clone(), - protocol_version: PROTOCOL_VERSION_63, + protocol_version: ETH_PROTOCOL_VERSION_63, network_id: self.network_id, start_block_number: self.starting_block, 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); 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); trace!(target: "sync", "Peer {} unsupported eth protocol ({})", peer_id, peer.protocol_version); return Ok(()); @@ -1493,6 +1508,7 @@ impl ChainSync { } if !self.peers.get(&peer_id).map_or(false, |p| p.can_sync()) { trace!(target: "sync", "{} Ignoring transactions from unconfirmed/unknown peer", peer_id); + return Ok(()); } 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> { let warp_protocol_version = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer); 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); let mut packet = RlpStream::new_list(if warp_protocol { 7 } else { 5 }); let chain = io.chain().chain_info(); @@ -1792,6 +1808,8 @@ impl ChainSync { NEW_BLOCK_HASHES_PACKET => self.on_peer_new_hashes(io, peer, &rlp), SNAPSHOT_MANIFEST_PACKET => self.on_snapshot_manifest(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); Ok(()) @@ -1945,7 +1963,11 @@ impl ChainSync { } fn get_consensus_peers(&self) -> Vec { - 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 { + 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 @@ -2223,6 +2245,54 @@ impl ChainSync { 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 @@ -2247,7 +2317,7 @@ mod tests { use std::collections::{HashSet, VecDeque}; use ethkey; use network::PeerId; - use tests::helpers::*; + use tests::helpers::{TestIo}; use tests::snapshot::TestSnapshotService; use ethereum_types::{H256, U256, Address}; use parking_lot::RwLock; @@ -2260,6 +2330,7 @@ mod tests { use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient, ChainInfo, BlockInfo}; use ethcore::miner::MinerService; use transaction::UnverifiedTransaction; + use private_tx::NoopPrivateTxHandler; fn get_dummy_block(order: u32, parent_hash: H256) -> Bytes { let mut header = Header::new(); @@ -2483,7 +2554,7 @@ mod tests { } 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); sync } @@ -2608,7 +2679,7 @@ mod tests { client.add_blocks(2, EachBlockWith::Uncle); let queue = RwLock::new(VecDeque::new()); 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, PeerInfo { // Messaging protocol @@ -2695,7 +2766,7 @@ mod tests { client.add_blocks(100, EachBlockWith::Uncle); client.insert_transaction_to_queue(); // 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 ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &queue, None); @@ -2765,7 +2836,7 @@ mod tests { let mut client = TestBlockChainClient::new(); client.insert_transaction_with_gas_price_to_queue(U256::zero()); 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 ss = TestSnapshotService::new(); 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 tx2_hash = client.insert_transaction_with_gas_price_to_queue(U256::zero()); 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 ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &queue, None); diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 9aee8f3d4..a6e0c7db2 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -44,6 +44,7 @@ extern crate ethcore_light as light; #[cfg(test)] extern crate ethkey; #[cfg(test)] extern crate kvdb_memorydb; +#[cfg(test)] extern crate rustc_hex; #[macro_use] extern crate macros; @@ -56,6 +57,7 @@ mod chain; mod blocks; mod block_sync; mod sync_io; +mod private_tx; mod snapshot; mod transactions_stats; @@ -70,3 +72,4 @@ pub use api::*; pub use chain::{SyncStatus, SyncState}; pub use devp2p::{validate_node_url, ConnectionFilter, ConnectionDirection}; pub use network::{NonReservedPeerMode, Error, ErrorKind}; +pub use private_tx::{PrivateTxHandler, NoopPrivateTxHandler, SimplePrivateTxHandler}; diff --git a/sync/src/light_sync/tests/test_net.rs b/sync/src/light_sync/tests/test_net.rs index f93568057..badd35668 100644 --- a/sync/src/light_sync/tests/test_net.rs +++ b/sync/src/light_sync/tests/test_net.rs @@ -205,6 +205,10 @@ impl PeerLike for Peer { } fn restart_sync(&self) { } + + fn process_all_io_messages(&self) { } + + fn process_all_new_block_messages(&self) { } } impl TestNet { diff --git a/sync/src/private_tx.rs b/sync/src/private_tx.rs new file mode 100644 index 000000000..ded5de2d8 --- /dev/null +++ b/sync/src/private_tx.rs @@ -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 . + +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>>, + /// imported signed private transactions + pub signed_txs: Mutex>>, +} + +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(()) + } +} diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 951c027af..f4a58a18a 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -17,8 +17,8 @@ use std::sync::Arc; use hash::keccak; use ethereum_types::{U256, Address}; -use io::{IoHandler, IoContext, IoChannel}; -use ethcore::client::{Client, ChainInfo, ClientIoMessage}; +use io::{IoHandler, IoChannel}; +use ethcore::client::{ChainInfo, ClientIoMessage}; use ethcore::spec::Spec; use ethcore::miner::MinerService; use ethcore::account_provider::AccountProvider; @@ -27,21 +27,6 @@ use transaction::{Action, PendingTransaction, Transaction}; use super::helpers::*; use SyncConfig; -struct TestIoHandler { - client: Arc, -} - -impl IoHandler for TestIoHandler { - fn message(&self, _io: &IoContext, 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 { let signed = Transaction { nonce: nonce.into(), @@ -63,9 +48,9 @@ fn authority_round() { ap.insert_account(s1.secret().clone(), "").unwrap(); 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 io_handler0: Arc> = Arc::new(TestIoHandler { client: net.peer(0).chain.clone() }); - let io_handler1: Arc> = Arc::new(TestIoHandler { client: net.peer(1).chain.clone() }); + let mut net = TestNet::with_spec_and_accounts(2, SyncConfig::default(), Spec::new_test_round, Some(ap), false); + let io_handler0: Arc> = Arc::new(TestIoHandler::new(net.peer(0).chain.clone())); + let io_handler1: Arc> = Arc::new(TestIoHandler::new(net.peer(1).chain.clone())); // Push transaction to both clients. Only one of them gets lucky to produce a block. net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap(); net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); @@ -150,9 +135,9 @@ fn tendermint() { ap.insert_account(s1.secret().clone(), "").unwrap(); 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 io_handler0: Arc> = Arc::new(TestIoHandler { client: net.peer(0).chain.clone() }); - let io_handler1: Arc> = Arc::new(TestIoHandler { client: net.peer(1).chain.clone() }); + let mut net = TestNet::with_spec_and_accounts(2, SyncConfig::default(), Spec::new_test_tendermint, Some(ap), false); + let io_handler0: Arc> = Arc::new(TestIoHandler::new(net.peer(0).chain.clone())); + let io_handler1: Arc> = Arc::new(TestIoHandler::new(net.peer(1).chain.clone())); // 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(); trace!(target: "poa", "Peer 0 is {}.", s0.address()); diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index 32a90b414..495603020 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -17,21 +17,23 @@ use std::collections::{VecDeque, HashSet, HashMap}; use std::sync::Arc; use ethereum_types::H256; -use parking_lot::RwLock; +use parking_lot::{RwLock, Mutex}; use bytes::Bytes; use network::{self, PeerId, ProtocolId, PacketId, SessionInfo}; 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::snapshot::SnapshotService; use ethcore::spec::Spec; use ethcore::account_provider::AccountProvider; use ethcore::miner::Miner; use sync_io::SyncIo; -use io::IoChannel; +use io::{IoChannel, IoContext, IoHandler}; use api::WARP_SYNC_PROTOCOL_ID; -use chain::ChainSync; -use ::SyncConfig; +use chain::{ChainSync, ETH_PROTOCOL_VERSION_63, PAR_PROTOCOL_VERSION_3}; +use SyncConfig; +use private_tx::{NoopPrivateTxHandler, PrivateTxHandler, SimplePrivateTxHandler}; pub trait FlushingBlockChainClient: BlockChainClient { 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 { - 63 + ETH_PROTOCOL_VERSION_63 } 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> { @@ -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, + invalid: Vec, + enacted: Vec, + retracted: Vec, + sealed: Vec, + proposed: Vec, +} + /// Abstract messages between peers. pub trait Message { /// The intended recipient of this message. @@ -184,6 +196,12 @@ pub trait Peer { /// Restart sync for a peer. 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 where C: FlushingBlockChainClient { @@ -191,6 +209,41 @@ pub struct EthPeer where C: FlushingBlockChainClient { pub snapshot_service: Arc, pub sync: RwLock, pub queue: RwLock>, + pub private_tx_handler: Arc, + pub io_queue: RwLock>, + new_blocks_queue: RwLock>, +} + +impl EthPeer 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 Peer for EthPeer { @@ -198,7 +251,12 @@ impl Peer for EthPeer { fn on_connect(&self, other: PeerId) { 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) { @@ -219,7 +277,7 @@ impl Peer for EthPeer { } 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) { @@ -232,6 +290,22 @@ impl Peer for EthPeer { fn restart_sync(&self) { 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

{ @@ -260,20 +334,35 @@ impl TestNet> { for _ in 0..n { let chain = TestBlockChainClient::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 { sync: RwLock::new(sync), snapshot_service: ss, chain: Arc::new(chain), queue: RwLock::new(VecDeque::new()), + private_tx_handler, + io_queue: RwLock::new(VecDeque::new()), + new_blocks_queue: RwLock::new(VecDeque::new()), })); } 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 { + Arc::get_mut(&mut self.peers[i]).expect("Arc never exposed externally") + } } impl TestNet> { - pub fn with_spec_and_accounts(n: usize, config: SyncConfig, spec_factory: F, accounts: Option>) -> Self + pub fn with_spec_and_accounts( + n: usize, + config: SyncConfig, + spec_factory: F, + accounts: Option>, + private_tx_handler: bool, + ) -> Self where F: Fn() -> Spec { let mut net = TestNet { @@ -282,11 +371,42 @@ impl TestNet> { disconnect_events: Vec::new(), }; for _ in 0..n { - net.add_peer(config.clone(), spec_factory(), accounts.clone()); + 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 } + pub fn add_peer_with_private_config(&mut self, config: SyncConfig, spec: Spec, accounts: Option>) { + 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>) { let client = EthcoreClient::new( ClientConfig::default(), @@ -297,12 +417,16 @@ impl TestNet> { ).unwrap(); 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 { 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()); self.peers.push(peer); @@ -366,6 +490,8 @@ impl

TestNet

where P: Peer { let mut total_steps = 0; while !self.done() { self.sync_step(); + self.deliver_io_messages(); + self.deliver_new_block_messages(); total_steps += 1; } total_steps @@ -378,18 +504,23 @@ impl

TestNet

where P: Peer { } } + pub fn deliver_io_messages(&mut self) { + for peer in self.peers.iter() { + peer.process_all_io_messages(); + } + } + + pub fn deliver_new_block_messages(&mut self) { + for peer in self.peers.iter() { + peer.process_all_new_block_messages(); + } + } + pub fn done(&self) -> bool { self.peers.iter().all(|p| p.is_done()) } } -impl TestNet> { - // 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 { - Arc::get_mut(&mut self.peers[i]).expect("Arc never exposed externally") - } -} - impl TestNet> { pub fn trigger_chain_new_blocks(&mut self, peer_id: usize) { let peer = &mut self.peers[peer_id]; @@ -397,6 +528,34 @@ impl TestNet> { } } +pub struct TestIoHandler { + pub client: Arc, + pub private_tx_queued: Mutex, +} + +impl TestIoHandler { + pub fn new(client: Arc) -> Self { + TestIoHandler { + client, + private_tx_queued: Mutex::default(), + } + } +} + +impl IoHandler for TestIoHandler { + fn message(&self, _io: &IoContext, 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 { fn new_blocks(&self, imported: Vec, @@ -407,23 +566,21 @@ impl ChainNotify for EthPeer { proposed: Vec, _duration: u64) { - let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None); - self.sync.write().chain_new_blocks( - &mut io, - &imported, - &invalid, - &enacted, - &retracted, - &sealed, - &proposed); + self.new_blocks_queue.write().push_back(NewBlockMessage { + imported, + invalid, + enacted, + retracted, + sealed, + proposed, + }); } fn start(&self) {} fn stop(&self) {} - fn broadcast(&self, message: Vec) { - let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None); - self.sync.write().propagate_consensus_packet(&mut io, message.clone()); + fn broadcast(&self, message_type: ChainMessageType) { + self.io_queue.write().push_back(message_type) } } diff --git a/util/fetch/src/client.rs b/util/fetch/src/client.rs index fee3130c4..b3274ca4f 100644 --- a/util/fetch/src/client.rs +++ b/util/fetch/src/client.rs @@ -119,13 +119,23 @@ pub trait Fetch: Clone + Send + Sync + 'static { /// The result future. type Result: Future + Send + 'static; + /// Make a request to given URL + fn fetch(&self, url: &str, method: Method, abort: Abort) -> Self::Result; + /// 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>; type TxStartup = std::sync::mpsc::SyncSender>; -type ChanItem = Option<(Url, Abort, TxResponse)>; +type ChanItem = Option<(Url, Method, Abort, TxResponse)>; /// An implementation of `Fetch` using a `hyper` client. // 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())) .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); if abort.is_aborted() { return future::ok(sender.send(Err(Error::Aborted)).unwrap_or(())) } - let ini = (hyper.clone(), url, abort, 0); - let fut = future::loop_fn(ini, |(client, url, abort, redirects)| { + let ini = (hyper.clone(), url, method, abort, 0); + let fut = future::loop_fn(ini, |(client, url, method, abort, redirects)| { let url2 = url.clone(); let abort2 = abort.clone(); - client.request(get(&url)) + client.request(build_request(&url, method.clone())) .map(move |resp| Response::new(url2, resp, abort2)) .from_err() .and_then(move |resp| { @@ -225,7 +235,7 @@ impl Client { if redirects >= abort.max_redirects() { return Err(Error::TooManyRedirects) } - Ok(Loop::Continue((client, next_url, abort, redirects + 1))) + Ok(Loop::Continue((client, next_url, method, abort, redirects + 1))) } else { let content_len = resp.headers.get::().cloned(); if content_len.map(|n| *n > abort.max_size() as u64).unwrap_or(false) { @@ -257,7 +267,7 @@ impl Client { impl Fetch for Client { type Result = Box + 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); if abort.is_aborted() { return Box::new(future::err(Error::Aborted)) @@ -269,7 +279,7 @@ impl Fetch for Client { let (tx_res, rx_res) = oneshot::channel(); let maxdur = abort.max_duration(); 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| { error!(target: "fetch", "failed to schedule request: {}", e); Error::BackgroundThreadDead @@ -308,10 +318,10 @@ fn redirect_location(u: Url, r: &Response) -> Option { } } -// Build a simple GET request for the given Url. -fn get(u: &Url) -> hyper::Request { +// Build a simple request for the given Url and method +fn build_request(u: &Url, method: Method) -> hyper::Request { 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 } @@ -350,6 +360,11 @@ impl Response { self.status() == StatusCode::Ok } + /// Status code == 404. + pub fn is_not_found(&self) -> bool { + self.status() == StatusCode::NotFound + } + /// Is the content-type text/html? pub fn is_html(&self) -> bool { if let Some(ref mime) = self.content_type() { @@ -512,7 +527,7 @@ mod test { use futures::future; use futures::sync::mpsc; use futures_timer::Delay; - use hyper::StatusCode; + use hyper::{StatusCode, Method}; use hyper::server::{Http, Request, Response, Service}; use std; use std::io::Read; @@ -524,7 +539,7 @@ mod test { fn it_should_fetch() { let server = TestServer::run(); 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(); assert!(resp.is_success()); let body = resp.concat2().wait().unwrap(); @@ -536,7 +551,7 @@ mod test { let server = TestServer::run(); let client = Client::new().unwrap(); 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) => {} other => panic!("expected timeout, got {:?}", other) } @@ -547,7 +562,7 @@ mod test { let server = TestServer::run(); let client = Client::new().unwrap(); 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()) } @@ -556,7 +571,7 @@ mod test { let server = TestServer::run(); let client = Client::new().unwrap(); 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()) } @@ -565,7 +580,7 @@ mod test { let server = TestServer::run(); let client = Client::new().unwrap(); 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) => {} other => panic!("expected too many redirects error, got {:?}", other) } @@ -576,7 +591,7 @@ mod test { let server = TestServer::run(); let client = Client::new().unwrap(); 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(); assert!(resp.is_success()); assert_eq!(&resp.concat2().wait().unwrap()[..], b"abcdefghijklmnopqrstuvwxyz") @@ -587,7 +602,7 @@ mod test { let server = TestServer::run(); let client = Client::new().unwrap(); 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()); match resp.concat2().wait() { Err(Error::SizeLimit) => {} @@ -600,7 +615,7 @@ mod test { let server = TestServer::run(); let client = Client::new().unwrap(); 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()); let mut buffer = Vec::new(); let mut reader = BodyReader::new(resp); diff --git a/util/fetch/src/lib.rs b/util/fetch/src/lib.rs index a5722dfe2..55785633f 100644 --- a/util/fetch/src/lib.rs +++ b/util/fetch/src/lib.rs @@ -36,4 +36,5 @@ pub mod client; pub use url::Url; pub use self::client::{Client, Fetch, Error, Response, Abort, BodyReader}; +pub use hyper::Method;