diff --git a/Cargo.lock b/Cargo.lock index cbc2c25de..eb147e6c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,6 @@ dependencies = [ "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -346,7 +345,6 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -354,7 +352,6 @@ name = "ethcore-ipc-nano" version = "1.3.0" dependencies = [ "ethcore-ipc 1.3.0", - "jsonrpc-core 2.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", ] @@ -521,7 +518,6 @@ dependencies = [ "parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -928,7 +924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "parity-dapps" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -942,7 +938,7 @@ dependencies = [ [[package]] name = "parity-dapps-home" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -950,7 +946,7 @@ dependencies = [ [[package]] name = "parity-dapps-signer" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -958,7 +954,7 @@ dependencies = [ [[package]] name = "parity-dapps-status" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -966,7 +962,7 @@ dependencies = [ [[package]] name = "parity-dapps-wallet" version = "0.6.0" -source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f" +source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a" dependencies = [ "parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)", ] diff --git a/Cargo.toml b/Cargo.toml index 162194b6b..8ae1b7510 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,8 +57,8 @@ default = ["ui", "use-precompiled-js"] ui = ["dapps", "ethcore-signer/ui"] use-precompiled-js = ["ethcore-dapps/use-precompiled-js", "ethcore-signer/use-precompiled-js"] dapps = ["ethcore-dapps"] -dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"] ipc = ["ethcore/ipc"] +dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"] json-tests = ["ethcore/json-tests"] [[bin]] diff --git a/db/src/database.rs b/db/src/database.rs index 777ec3bbc..185618f99 100644 --- a/db/src/database.rs +++ b/db/src/database.rs @@ -25,12 +25,6 @@ use std::mem; use ipc::binary::BinaryConvertError; use std::collections::{VecDeque, HashMap, BTreeMap}; -impl From for Error { - fn from(s: String) -> Error { - Error::RocksDb(s) - } -} - enum WriteCacheEntry { Remove, Write(Vec), diff --git a/db/src/traits.rs b/db/src/traits.rs index dd5743fe5..bab132450 100644 --- a/db/src/traits.rs +++ b/db/src/traits.rs @@ -31,8 +31,8 @@ pub struct KeyValue { pub value: Vec, } - #[derive(Debug, Binary)] - pub enum Error { +#[derive(Debug, Binary)] +pub enum Error { AlreadyOpen, IsClosed, RocksDb(String), @@ -41,6 +41,12 @@ pub struct KeyValue { UncommitedTransactions, } +impl From for Error { + fn from(s: String) -> Error { + Error::RocksDb(s) + } +} + /// Database configuration #[derive(Binary)] pub struct DatabaseConfig { @@ -68,7 +74,7 @@ impl DatabaseConfig { } } - pub trait DatabaseService : Sized { +pub trait DatabaseService : Sized { /// Opens database in the specified path fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error>; diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 07fbccf55..7bde3525f 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -8,7 +8,6 @@ authors = ["Ethcore "] build = "build.rs" [build-dependencies] -syntex = "*" "ethcore-ipc-codegen" = { path = "../ipc/codegen" } [dependencies] @@ -35,7 +34,6 @@ ethcore-ipc = { path = "../ipc/rpc" } ethstore = { path = "../ethstore" } ethcore-ipc-nano = { path = "../ipc/nano" } - [dependencies.hyper] git = "https://github.com/ethcore/hyper" default-features = false diff --git a/ethcore/build.rs b/ethcore/build.rs index b9b884fac..2e07cbc2f 100644 --- a/ethcore/build.rs +++ b/ethcore/build.rs @@ -14,48 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -extern crate syntex; -extern crate ethcore_ipc_codegen as codegen; - -use std::env; -use std::path::Path; +extern crate ethcore_ipc_codegen; fn main() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - // serialization pass - { - let src = Path::new("src/types/mod.rs.in"); - let dst = Path::new(&out_dir).join("types.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &dst).unwrap(); - } - - // blockchain client interface - { - let src = Path::new("src/client/traits.rs"); - let intermediate = Path::new(&out_dir).join("traits.intermediate.rs.in"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &intermediate).unwrap(); - - let dst = Path::new(&out_dir).join("traits.ipc.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &intermediate, &dst).unwrap(); - } - - // chain notify interface - { - let src = Path::new("src/client/chain_notify.rs"); - let intermediate = Path::new(&out_dir).join("chain_notify.intermediate.rs.in"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &intermediate).unwrap(); - - let dst = Path::new(&out_dir).join("chain_notify.ipc.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &intermediate, &dst).unwrap(); - } + ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); + ethcore_ipc_codegen::derive_ipc("src/client/traits.rs").unwrap(); + ethcore_ipc_codegen::derive_ipc("src/client/chain_notify.rs").unwrap(); } diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 0ae1c138e..f76bfc050 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -665,8 +665,8 @@ impl BlockChain { let mut write_hashes = self.pending_block_hashes.write(); let mut write_txs = self.pending_transaction_addresses.write(); - batch.extend_with_cache(DB_COL_EXTRA, &mut *write_hashes, update.block_hashes, CacheUpdatePolicy::Remove); - batch.extend_with_cache(DB_COL_EXTRA, &mut *write_txs, update.transactions_addresses, CacheUpdatePolicy::Remove); + batch.extend_with_cache(DB_COL_EXTRA, &mut *write_hashes, update.block_hashes, CacheUpdatePolicy::Overwrite); + batch.extend_with_cache(DB_COL_EXTRA, &mut *write_txs, update.transactions_addresses, CacheUpdatePolicy::Overwrite); } } @@ -1111,6 +1111,7 @@ mod tests { let ir3b = bc.insert_block(&batch, &b3b, vec![]); bc.commit(); db.write(batch).unwrap(); + assert_eq!(bc.block_hash(3).unwrap(), b3b_hash); let batch = db.transaction(); let ir3a = bc.insert_block(&batch, &b3a, vec![]); bc.commit(); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index bb878b834..7c66cc5d3 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -178,15 +178,15 @@ impl Client { db_config.compaction = config.db_compaction.compaction_profile(); db_config.wal = config.db_wal; - let db = Arc::new(Database::open(&db_config, &path.to_str().unwrap()).expect("Error opening database")); + let db = Arc::new(try!(Database::open(&db_config, &path.to_str().unwrap()).map_err(ClientError::Database))); let chain = Arc::new(BlockChain::new(config.blockchain, &gb, db.clone())); let tracedb = Arc::new(try!(TraceDB::new(config.tracing, db.clone(), chain.clone()))); let mut state_db = journaldb::new(db.clone(), config.pruning, DB_COL_STATE); if state_db.is_empty() && spec.ensure_db_good(state_db.as_hashdb_mut()) { let batch = DBTransaction::new(&db); - state_db.commit(&batch, 0, &spec.genesis_header().hash(), None).expect("Error commiting genesis state to state DB"); - db.write(batch).expect("Error writing genesis state to state DB"); + try!(state_db.commit(&batch, 0, &spec.genesis_header().hash(), None)); + try!(db.write(batch).map_err(ClientError::Database)); } if !chain.block_header(&chain.best_block_hash()).map_or(true, |h| state_db.contains(h.state_root())) { diff --git a/ethcore/src/client/error.rs b/ethcore/src/client/error.rs index fde22cb10..4c5b465b6 100644 --- a/ethcore/src/client/error.rs +++ b/ethcore/src/client/error.rs @@ -1,4 +1,5 @@ use trace::Error as TraceError; +use util::UtilError; use std::fmt::{Display, Formatter, Error as FmtError}; /// Client configuration errors. @@ -6,6 +7,10 @@ use std::fmt::{Display, Formatter, Error as FmtError}; pub enum Error { /// TraceDB configuration error. Trace(TraceError), + /// Database error + Database(String), + /// Util error + Util(UtilError), } impl From for Error { @@ -14,10 +19,18 @@ impl From for Error { } } +impl From for Error { + fn from(err: UtilError) -> Self { + Error::Util(err) + } +} + impl Display for Error { fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { match *self { - Error::Trace(ref err) => write!(f, "{}", err) + Error::Trace(ref err) => write!(f, "{}", err), + Error::Util(ref err) => write!(f, "{}", err), + Error::Database(ref s) => write!(f, "Database error: {}", s), } } } diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 4bcec0169..710fb5768 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -40,13 +40,13 @@ pub use self::traits::{BlockChainClient, MiningBlockChainClient, RemoteClient}; mod traits { #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues - include!(concat!(env!("OUT_DIR"), "/traits.ipc.rs")); + include!(concat!(env!("OUT_DIR"), "/traits.rs")); } pub mod chain_notify { //! Chain notify interface #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues - include!(concat!(env!("OUT_DIR"), "/chain_notify.ipc.rs")); + include!(concat!(env!("OUT_DIR"), "/chain_notify.rs")); } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index cdffe4302..8b942f777 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -257,7 +257,7 @@ pub fn get_temp_journal_db() -> GuardedTempResult> { } impl MiningBlockChainClient for TestBlockChainClient { - fn prepare_open_block(&self, _author: Address, _gas_range_target: (U256, U256), _extra_data: Bytes) -> OpenBlock { + fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { let engine = &self.spec.engine; let genesis_header = self.spec.genesis_header(); let mut db_result = get_temp_journal_db(); @@ -265,7 +265,7 @@ impl MiningBlockChainClient for TestBlockChainClient { self.spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; - OpenBlock::new( + let mut open_block = OpenBlock::new( engine.deref(), self.vm_factory(), Default::default(), @@ -273,10 +273,13 @@ impl MiningBlockChainClient for TestBlockChainClient { db, &genesis_header, last_hashes, - Address::zero(), - (3141562.into(), 31415620.into()), - vec![] - ).expect("Opening block for tests will not fail.") + author, + gas_range_target, + extra_data + ).expect("Opening block for tests will not fail."); + // TODO [todr] Override timestamp for predictability (set_timestamp_now kind of sucks) + open_block.set_timestamp(10_000_000); + open_block } fn vm_factory(&self) -> &EvmFactory { diff --git a/ethcore/src/miner/external.rs b/ethcore/src/miner/external.rs index ef6875930..c3fcf723f 100644 --- a/ethcore/src/miner/external.rs +++ b/ethcore/src/miner/external.rs @@ -16,7 +16,8 @@ use std::collections::HashMap; use std::sync::Arc; -use util::{RwLock, U256, H256}; +use std::time::{Instant, Duration}; +use util::{Mutex, U256, H256}; /// External miner interface. pub trait ExternalMinerService: Send + Sync { @@ -25,50 +26,50 @@ pub trait ExternalMinerService: Send + Sync { /// Total hashrate. fn hashrate(&self) -> U256; - - /// Returns true if external miner is mining. - fn is_mining(&self) -> bool; } /// External Miner. pub struct ExternalMiner { - hashrates: Arc>>, + hashrates: Arc>>, } impl Default for ExternalMiner { fn default() -> Self { ExternalMiner { - hashrates: Arc::new(RwLock::new(HashMap::new())), + hashrates: Arc::new(Mutex::new(HashMap::new())), } } } impl ExternalMiner { /// Creates new external miner with prefilled hashrates. - pub fn new(hashrates: Arc>>) -> Self { + pub fn new(hashrates: Arc>>) -> Self { ExternalMiner { - hashrates: hashrates + hashrates: hashrates, } } } +const ENTRY_TIMEOUT: u64 = 2; + impl ExternalMinerService for ExternalMiner { fn submit_hashrate(&self, hashrate: U256, id: H256) { - self.hashrates.write().insert(id, hashrate); + self.hashrates.lock().insert(id, (Instant::now() + Duration::from_secs(ENTRY_TIMEOUT), hashrate)); } fn hashrate(&self) -> U256 { - self.hashrates.read().iter().fold(U256::from(0), |sum, (_, v)| sum + *v) - } - - fn is_mining(&self) -> bool { - !self.hashrates.read().is_empty() + let mut hashrates = self.hashrates.lock(); + let h = hashrates.drain().filter(|&(_, (t, _))| t > Instant::now()).collect(); + *hashrates = h; + hashrates.iter().fold(U256::from(0), |sum, (_, &(_, v))| sum + v) } } #[cfg(test)] mod tests { use super::*; + use std::thread::sleep; + use std::time::Duration; use util::{H256, U256}; fn miner() -> ExternalMiner { @@ -76,16 +77,18 @@ mod tests { } #[test] - fn should_return_that_is_mining_if_there_is_at_least_one_entry() { + fn it_should_forget_old_hashrates() { // given let m = miner(); - assert_eq!(m.is_mining(), false); + assert_eq!(m.hashrate(), U256::from(0)); + m.submit_hashrate(U256::from(10), H256::from(1)); + assert_eq!(m.hashrate(), U256::from(10)); // when - m.submit_hashrate(U256::from(10), H256::from(1)); + sleep(Duration::from_secs(3)); // then - assert_eq!(m.is_mining(), true); + assert_eq!(m.hashrate(), U256::from(0)); } #[test] diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 1ebae894e..cc2e6d1e6 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -780,6 +780,10 @@ impl MinerService for Miner { } } + fn is_sealing(&self) -> bool { + self.sealing_work.lock().queue.is_in_use() + } + fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { trace!(target: "miner", "map_sealing_work: entering"); self.enable_and_prepare_sealing(chain); diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 4b8f01c0e..15933901b 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -150,6 +150,9 @@ pub trait MinerService : Send + Sync { /// Returns highest transaction nonce for given address. fn last_nonce(&self, address: &Address) -> Option; + /// Is it currently sealing? + fn is_sealing(&self) -> bool; + /// Suggested gas price. fn sensible_gas_price(&self) -> U256 { 20000000000u64.into() } diff --git a/ethcore/src/types/mod.rs b/ethcore/src/types/mod.rs index 112f79c32..d01829ea0 100644 --- a/ethcore/src/types/mod.rs +++ b/ethcore/src/types/mod.rs @@ -17,4 +17,4 @@ //! Types used in the public api #![allow(dead_code, unused_assignments, unused_variables)] // codegen issues -include!(concat!(env!("OUT_DIR"), "/types.rs")); +include!(concat!(env!("OUT_DIR"), "/mod.rs.in")); diff --git a/ethstore/src/bin/ethstore.rs b/ethstore/src/bin/ethstore.rs index 5683a8116..748480069 100644 --- a/ethstore/src/bin/ethstore.rs +++ b/ethstore/src/bin/ethstore.rs @@ -138,7 +138,7 @@ fn execute(command: I) -> Result where I: IntoIterator Result<(), SymmetricCipherError> { + pub fn decrypt_cbc(k: &[u8], iv: &[u8], encrypted: &[u8], dest: &mut [u8]) -> Result { let mut encryptor = CbcDecryptor::new(AesSafe128Decryptor::new(k), PkcsPadding, iv.to_vec()); - try!(encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut RefWriteBuffer::new(dest), true)); - Ok(()) + let len = dest.len(); + let mut buffer = RefWriteBuffer::new(dest); + try!(encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut buffer, true)); + Ok(len - buffer.remaining()) } } diff --git a/ethstore/src/json/hash.rs b/ethstore/src/json/hash.rs index 2edc7b80b..0079b4f81 100644 --- a/ethstore/src/json/hash.rs +++ b/ethstore/src/json/hash.rs @@ -109,4 +109,3 @@ macro_rules! impl_hash { impl_hash!(H128, 16); impl_hash!(H160, 20); impl_hash!(H256, 32); -impl_hash!(H768, 96); diff --git a/ethstore/src/json/mod.rs.in b/ethstore/src/json/mod.rs.in index 7272d7e2e..4f9fdbfe3 100644 --- a/ethstore/src/json/mod.rs.in +++ b/ethstore/src/json/mod.rs.in @@ -11,10 +11,10 @@ mod version; pub use self::cipher::{Cipher, CipherSer, CipherSerParams, Aes128Ctr}; pub use self::crypto::Crypto; pub use self::error::Error; -pub use self::hash::{H128, H160, H256, H768}; +pub use self::hash::{H128, H160, H256}; pub use self::id::UUID; pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams}; pub use self::key_file::KeyFile; -pub use self::presale::PresaleWallet; +pub use self::presale::{PresaleWallet, Encseed}; pub use self::version::Version; diff --git a/ethstore/src/json/presale.rs b/ethstore/src/json/presale.rs index cba50695f..77394fcb1 100644 --- a/ethstore/src/json/presale.rs +++ b/ethstore/src/json/presale.rs @@ -1,10 +1,34 @@ use std::io::Read; +use std::ops::Deref; use serde_json; -use super::{H160, H768}; +use serde::{Deserialize, Deserializer, Error}; +use rustc_serialize::hex::FromHex; +use super::{H160}; + +#[derive(Debug, PartialEq)] +pub struct Encseed(Vec); + +impl Deref for Encseed { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Deserialize for Encseed { + fn deserialize(deserializer: &mut D) -> Result + where D: Deserializer + { + let s = try!(String::deserialize(deserializer)); + let data = try!(s.from_hex().map_err(|e| Error::custom(format!("Invalid hex value {}", e)))); + Ok(Encseed(data)) + } +} #[derive(Debug, PartialEq, Deserialize)] pub struct PresaleWallet { - pub encseed: H768, + pub encseed: Encseed, #[serde(rename = "ethaddr")] pub address: H160, } @@ -19,7 +43,8 @@ impl PresaleWallet { mod tests { use std::str::FromStr; use serde_json; - use json::{PresaleWallet, H160, H768}; + use rustc_serialize::hex::FromHex; + use json::{PresaleWallet, H160, Encseed}; #[test] fn presale_wallet() { @@ -32,7 +57,27 @@ mod tests { } "#; let expected = PresaleWallet { - encseed: H768::from_str("137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066").unwrap(), + encseed: Encseed("137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066".from_hex().unwrap()), + address: H160::from_str("ede84640d1a1d3e06902048e67aa7db8d52c2ce1").unwrap(), + }; + + let wallet: PresaleWallet = serde_json::from_str(json).unwrap(); + assert_eq!(expected, wallet); + } + + #[test] + fn long_presale_wallet() { + let json = r#" + { + "encseed": + "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0d", + "ethaddr": "ede84640d1a1d3e06902048e67aa7db8d52c2ce1", + "email": "123@gmail.com", + "btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH" + } "#; + + let expected = PresaleWallet { + encseed: Encseed("137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0d".from_hex().unwrap()), address: H160::from_str("ede84640d1a1d3e06902048e67aa7db8d52c2ce1").unwrap(), }; diff --git a/ethstore/src/presale.rs b/ethstore/src/presale.rs index 09c86abea..2904db6ef 100644 --- a/ethstore/src/presale.rs +++ b/ethstore/src/presale.rs @@ -10,7 +10,7 @@ use {crypto, Error}; pub struct PresaleWallet { iv: [u8; 16], - ciphertext: [u8; 80], + ciphertext: Vec, address: Address, } @@ -19,8 +19,8 @@ impl From for PresaleWallet { let mut iv = [0u8; 16]; iv.copy_from_slice(&wallet.encseed[..16]); - let mut ciphertext = [0u8; 80]; - ciphertext.copy_from_slice(&wallet.encseed[16..]); + let mut ciphertext = vec![]; + ciphertext.extend_from_slice(&wallet.encseed[16..]); PresaleWallet { iv: iv, @@ -42,10 +42,11 @@ impl PresaleWallet { let mut derived_key = vec![0u8; 16]; pbkdf2(&mut h_mac, password.as_bytes(), 2000, &mut derived_key); - let mut key = [0u8; 64]; - try!(crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword)); + let mut key = vec![0; self.ciphertext.len()]; + let len = try!(crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword)); + let unpadded = &key[..len]; - let secret = Secret::from(key.keccak256()); + let secret = Secret::from(unpadded.keccak256()); if let Ok(kp) = KeyPair::from_secret(secret) { if kp.address() == self.address { return Ok(kp) diff --git a/ipc/codegen/src/lib.rs b/ipc/codegen/src/lib.rs index afa7979d0..8a3b4ba56 100644 --- a/ipc/codegen/src/lib.rs +++ b/ipc/codegen/src/lib.rs @@ -95,3 +95,58 @@ pub fn register(reg: &mut rustc_plugin::Registry) { reg.register_attribute("ipc".to_owned(), AttributeType::Normal); } + +#[derive(Debug)] +pub enum Error { InvalidFileName, ExpandFailure } + +pub fn derive_ipc(src_path: &str) -> Result<(), Error> { + use std::env; + use std::path::{Path, PathBuf}; + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())); + + let mut intermediate_file_name = file_name.clone(); + intermediate_file_name.push_str(".rpc.in"); + + let intermediate_path = Path::new(&out_dir).join(&intermediate_file_name); + let final_path = Path::new(&out_dir).join(&file_name); + + { + let mut registry = syntex::Registry::new(); + register(&mut registry); + if let Err(_) = registry.expand("", &Path::new(src_path), &intermediate_path) { + // will be reported by compiler + return Err(Error::ExpandFailure) + } + } + + { + let mut registry = syntex::Registry::new(); + register(&mut registry); + if let Err(_) = registry.expand("", &intermediate_path, &final_path) { + // will be reported by compiler + return Err(Error::ExpandFailure) + } + } + + Ok(()) +} + +pub fn derive_binary(src_path: &str) -> Result<(), Error> { + use std::env; + use std::path::{Path, PathBuf}; + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())); + let final_path = Path::new(&out_dir).join(&file_name); + + let mut registry = syntex::Registry::new(); + register(&mut registry); + if let Err(_) = registry.expand("", &Path::new(src_path), &final_path) { + // will be reported by compiler + return Err(Error::ExpandFailure) + } + + Ok(()) +} diff --git a/ipc/hypervisor/Cargo.toml b/ipc/hypervisor/Cargo.toml index 54edac7ec..a4c462bd0 100644 --- a/ipc/hypervisor/Cargo.toml +++ b/ipc/hypervisor/Cargo.toml @@ -15,5 +15,4 @@ semver = "0.2" log = "0.3" [build-dependencies] -syntex = "*" ethcore-ipc-codegen = { path = "../codegen" } diff --git a/ipc/hypervisor/build.rs b/ipc/hypervisor/build.rs index 75ccd2e8c..391ac648d 100644 --- a/ipc/hypervisor/build.rs +++ b/ipc/hypervisor/build.rs @@ -14,30 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -extern crate syntex; -extern crate ethcore_ipc_codegen as codegen; - -use std::env; -use std::path::Path; +extern crate ethcore_ipc_codegen; fn main() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - - // ipc pass - { - let src = Path::new("src/service.rs.in"); - let dst = Path::new(&out_dir).join("hypervisor_service_ipc.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &dst).unwrap(); - } - - // serialization pass - { - let src = Path::new(&out_dir).join("hypervisor_service_ipc.rs"); - let dst = Path::new(&out_dir).join("hypervisor_service_cg.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &dst).unwrap(); - } + ethcore_ipc_codegen::derive_ipc("src/service.rs.in").unwrap(); } diff --git a/ipc/hypervisor/src/service.rs b/ipc/hypervisor/src/service.rs index 75830d213..ffc83dc7c 100644 --- a/ipc/hypervisor/src/service.rs +++ b/ipc/hypervisor/src/service.rs @@ -17,4 +17,4 @@ //! Parity interprocess hypervisor IPC service #![allow(dead_code, unused_assignments, unused_variables)] // codegen issues -include!(concat!(env!("OUT_DIR"), "/hypervisor_service_cg.rs")); +include!(concat!(env!("OUT_DIR"), "/service.rs.in")); diff --git a/ipc/nano/Cargo.toml b/ipc/nano/Cargo.toml index 4096ed443..a0e915767 100644 --- a/ipc/nano/Cargo.toml +++ b/ipc/nano/Cargo.toml @@ -7,7 +7,6 @@ license = "GPL-3.0" [features] [dependencies] -jsonrpc-core = "2.0" ethcore-ipc = { path = "../rpc" } nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" } log = "0.3" diff --git a/ipc/nano/src/lib.rs b/ipc/nano/src/lib.rs index ee5dd500d..4759217b0 100644 --- a/ipc/nano/src/lib.rs +++ b/ipc/nano/src/lib.rs @@ -19,14 +19,11 @@ extern crate ethcore_ipc as ipc; extern crate nanomsg; #[macro_use] extern crate log; -extern crate jsonrpc_core; -use jsonrpc_core::IoHandler; pub use ipc::{WithSocket, IpcInterface, IpcConfig}; pub use nanomsg::Socket as NanoSocket; use std::sync::*; -use std::sync::atomic::*; use nanomsg::{Socket, Protocol, Error, Endpoint, PollRequest, PollFd, PollInOut}; use std::ops::Deref; @@ -218,149 +215,14 @@ impl Worker where S: IpcInterface { } } -/// Error in handling JSON RPC request -pub enum IoHandlerError { - BadRequest, - HandlerError, -} - -/// Worker to handle JSON RPC requests -pub struct IoHandlerWorker { - handler: Arc, - socket: Socket, - _endpoint: Endpoint, - poll: Vec, - buf: Vec, -} - -/// IPC server for json-rpc handler (single thread) -pub struct IoHandlerServer { - is_stopping: Arc, - is_stopped: Arc, - handler: Arc, - socket_addr: String, -} - -impl IoHandlerServer { - /// New IPC server for JSON RPC `handler` and ipc socket address `socket_addr` - pub fn new(handler: &Arc, socket_addr: &str) -> IoHandlerServer { - IoHandlerServer { - handler: handler.clone(), - is_stopping: Arc::new(AtomicBool::new(false)), - is_stopped: Arc::new(AtomicBool::new(true)), - socket_addr: socket_addr.to_owned(), - } - } - - /// IPC Server starts (non-blocking, in seprate thread) - pub fn start(&self) -> Result<(), SocketError> { - let mut worker = try!(IoHandlerWorker::new(&self.handler, &self.socket_addr)); - self.is_stopping.store(false, Ordering::Relaxed); - let worker_is_stopping = self.is_stopping.clone(); - let worker_is_stopped = self.is_stopped.clone(); - - ::std::thread::spawn(move || { - worker_is_stopped.store(false, Ordering::Relaxed); - while !worker_is_stopping.load(Ordering::Relaxed) { - worker.poll() - } - worker_is_stopped.store(true, Ordering::Relaxed); - }); - - Ok(()) - } - - /// IPC server stop (func will wait until effective stop) - pub fn stop(&self) { - self.is_stopping.store(true, Ordering::Relaxed); - while !self.is_stopped.load(Ordering::Relaxed) { - std::thread::sleep(std::time::Duration::from_millis(50)); - } - } -} - -impl Drop for IoHandlerServer { - fn drop(&mut self) { - self.stop() - } -} - -impl IoHandlerWorker { - pub fn new(handler: &Arc, socket_addr: &str) -> Result { - let mut socket = try!(Socket::new(Protocol::Rep).map_err(|e| { - warn!(target: "ipc", "Failed to create ipc socket: {:?}", e); - SocketError::RequestLink - })); - - let endpoint = try!(socket.bind(socket_addr).map_err(|e| { - warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", socket_addr, e); - SocketError::RequestLink - })); - - let poll = vec![socket.new_pollfd(PollInOut::In)]; - - Ok(IoHandlerWorker { - handler: handler.clone(), - socket: socket, - _endpoint: endpoint, - poll: poll, - buf: Vec::with_capacity(1024), - }) - } - - pub fn poll(&mut self) { - let mut request = PollRequest::new(&mut self.poll[..]); - let _result_guard = Socket::poll(&mut request, POLL_TIMEOUT); - let fd = request.get_fds()[0]; // guaranteed to exist and be the only one - // because contains only immutable socket field as a member - if !fd.can_read() { - return; - } - - unsafe { self.buf.set_len(0); } - match self.socket.nb_read_to_end(&mut self.buf) { - Ok(0) => { - warn!(target: "ipc", "RPC empty message received"); - return; - }, - Ok(_) => { - let rpc_msg = match String::from_utf8(self.buf.clone()) { - Ok(val) => val, - Err(e) => { - warn!(target: "ipc", "RPC decoding error (utf-8): {:?}", e); - return; - } - }; - let response: Option = self.handler.handle_request(&rpc_msg); - if let Some(response_str) = response { - let response_bytes = response_str.into_bytes(); - if let Err(e) = self.socket.nb_write(&response_bytes) { - warn!(target: "ipc", "Failed to write response: {:?}", e); - } - } - }, - Err(Error::TryAgain) => { - // no data - }, - Err(x) => { - warn!(target: "ipc", "Error polling connections {:?}", x); - panic!("IPC RPC fatal error"); - }, - } - } - -} - #[cfg(test)] mod service_tests { - use super::{Worker, IoHandlerServer}; + use super::Worker; use ipc::*; use std::io::{Read, Write}; use std::sync::{Arc, RwLock}; use nanomsg::{Socket, Protocol, Endpoint}; - use jsonrpc_core; - use jsonrpc_core::{IoHandler, Value, Params, MethodCommand}; struct TestInvoke { method_num: u16, @@ -400,15 +262,6 @@ mod service_tests { (socket, endpoint) } - fn dummy_request(addr: &str, buf: &[u8]) -> Vec { - let mut socket = Socket::new(Protocol::Req).unwrap(); - let _endpoint = socket.connect(addr).unwrap(); - socket.write(buf).unwrap(); - let mut buf = Vec::new(); - socket.read_to_end(&mut buf).unwrap(); - buf - } - #[test] fn can_create_worker() { let worker = Worker::::new(&Arc::new(DummyService::new())); @@ -462,29 +315,4 @@ mod service_tests { assert_eq!(0, worker.service.methods_stack.read().unwrap()[0].method_num); assert_eq!(vec![0u8; 1024*1024-2], worker.service.methods_stack.read().unwrap()[0].params); } - - #[test] - fn test_jsonrpc_handler() { - let url = "ipc:///tmp/parity-test50.ipc"; - - struct SayHello; - impl MethodCommand for SayHello { - fn execute(&self, _params: Params) -> Result { - Ok(Value::String("hello".to_string())) - } - } - - let io = Arc::new(IoHandler::new()); - io.add_method("say_hello", SayHello); - - let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":"hello","id":1}"#; - - let server = IoHandlerServer::new(&io, url); - server.start().unwrap(); - - assert_eq!(String::from_utf8(dummy_request(url, request.as_bytes())).unwrap(), response.to_string()); - - server.stop(); - } } diff --git a/ipc/tests/binary.rs b/ipc/tests/binary.rs index ab4adee39..c8d100c6e 100644 --- a/ipc/tests/binary.rs +++ b/ipc/tests/binary.rs @@ -16,4 +16,4 @@ #![allow(dead_code, unused_assignments, unused_variables)] // codegen issues -include!(concat!(env!("OUT_DIR"), "/binary.rs")); +include!(concat!(env!("OUT_DIR"), "/binary.rs.in")); diff --git a/ipc/tests/build.rs b/ipc/tests/build.rs index 6538f56e1..688d139be 100644 --- a/ipc/tests/build.rs +++ b/ipc/tests/build.rs @@ -14,76 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -extern crate syntex; extern crate ethcore_ipc_codegen as codegen; -use std::env; -use std::path::Path; -use std::process::exit; - pub fn main() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - - // rpc pass - if { - let src = Path::new("nested.rs.in"); - let dst = Path::new(&out_dir).join("nested_ipc.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &dst).is_ok() - } - // serialization pass - { - let src = Path::new(&out_dir).join("nested_ipc.rs"); - let dst = Path::new(&out_dir).join("nested_cg.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &dst).unwrap(); - } - - // rpc pass - if { - let src = Path::new("service.rs.in"); - let dst = Path::new(&out_dir).join("service_ipc.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &dst).is_ok() - } - // serialization pass - { - let src = Path::new(&out_dir).join("service_ipc.rs"); - let dst = Path::new(&out_dir).join("service_cg.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &dst).unwrap(); - } - - // rpc pass - if { - let src = Path::new("with_attrs.rs.in"); - let dst = Path::new(&out_dir).join("with_attrs_ipc.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &dst).is_ok() - } - // serialization pass - { - let src = Path::new(&out_dir).join("with_attrs_ipc.rs"); - let dst = Path::new(&out_dir).join("with_attrs_cg.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &dst).unwrap(); - } - - // rpc pass - { - let src = Path::new("binary.rs.in"); - let dst = Path::new(&out_dir).join("binary.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - if let Err(err_msg) = registry.expand("", &src, &dst) { - println!("error: {}", err_msg); - exit(1); - } - } + codegen::derive_ipc("nested.rs.in").unwrap(); + codegen::derive_ipc("service.rs.in").unwrap(); + codegen::derive_ipc("with_attrs.rs.in").unwrap(); + codegen::derive_binary("binary.rs.in").unwrap(); } diff --git a/ipc/tests/nested.rs b/ipc/tests/nested.rs index 5c7504ad8..6e6e3942f 100644 --- a/ipc/tests/nested.rs +++ b/ipc/tests/nested.rs @@ -15,4 +15,4 @@ // along with Parity. If not, see . #![allow(dead_code, unused_assignments, unused_variables)] // codegen issues -include!(concat!(env!("OUT_DIR"), "/nested_cg.rs")); +include!(concat!(env!("OUT_DIR"), "/nested.rs.in")); diff --git a/ipc/tests/service.rs b/ipc/tests/service.rs index 7e778c615..3d5159a9b 100644 --- a/ipc/tests/service.rs +++ b/ipc/tests/service.rs @@ -15,4 +15,4 @@ // along with Parity. If not, see . #![allow(dead_code, unused_assignments, unused_variables)] // codegen issues -include!(concat!(env!("OUT_DIR"), "/service_cg.rs")); +include!(concat!(env!("OUT_DIR"), "/service.rs.in")); diff --git a/ipc/tests/with_attrs.rs b/ipc/tests/with_attrs.rs index e9b3e0d76..bda734368 100644 --- a/ipc/tests/with_attrs.rs +++ b/ipc/tests/with_attrs.rs @@ -15,4 +15,4 @@ // along with Parity. If not, see . #![allow(dead_code, unused_assignments, unused_variables)] // codegen issues -include!(concat!(env!("OUT_DIR"), "/with_attrs_cg.rs")); +include!(concat!(env!("OUT_DIR"), "/with_attrs.rs.in")); diff --git a/parity/cli.rs b/parity/cli.rs index d3627fda2..b76bf9d5d 100644 --- a/parity/cli.rs +++ b/parity/cli.rs @@ -74,6 +74,9 @@ Account Options: [default: 8180]. --signer-path PATH Specify directory where Signer UIs tokens should be stored. [default: $HOME/.parity/signer] + --signer-no-validation Disable Origin and Host headers validation for + Trusted Signer. WARNING: INSECURE. Used only for + development. Networking Options: --no-network Disable p2p networking. @@ -212,7 +215,7 @@ Footprint Options: the entire system, overrides other cache and queue options. --fast-and-loose Disables DB WAL, which gives a significant speed up - but means an unclean exit is unrecoverable. + but means an unclean exit is unrecoverable. --db-compaction TYPE Database compaction type. TYPE may be one of: ssd - suitable for SSDs and fast HDDs; hdd - suitable for slow HDDs [default: ssd]. @@ -337,6 +340,7 @@ pub struct Args { pub flag_no_signer: bool, pub flag_signer_port: u16, pub flag_signer_path: String, + pub flag_signer_no_validation: bool, pub flag_force_sealing: bool, pub flag_reseal_on_txs: String, pub flag_reseal_min_period: u64, diff --git a/parity/configuration.rs b/parity/configuration.rs index 81b3cb6c6..7ffdaa6c9 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -303,6 +303,7 @@ impl Configuration { enabled: self.signer_enabled(), port: self.args.flag_signer_port, signer_path: self.directories().signer, + skip_origin_validation: self.args.flag_signer_no_validation, } } @@ -789,6 +790,19 @@ mod tests { assert_eq!(conf0.signer_enabled(), false); } + #[test] + fn should_parse_signer_allow_all_flag() { + // given + + // when + let conf0 = parse(&["parity", "--signer-no-validation"]); + let conf1 = parse(&["parity"]); + + // then + assert_eq!(conf0.args.flag_signer_no_validation, true); + assert_eq!(conf1.args.flag_signer_no_validation, false); + } + #[test] fn should_not_bail_on_empty_line_in_reserved_peers() { let temp = RandomTempPath::new(); diff --git a/parity/helpers.rs b/parity/helpers.rs index 39a88be33..ad67e1436 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -102,7 +102,7 @@ pub fn to_address(s: Option) -> Result { pub fn to_addresses(s: &Option) -> Result, String> { match *s { - Some(ref adds) if adds.is_empty() => adds.split(',') + Some(ref adds) if !adds.is_empty() => adds.split(',') .map(|a| clean_0x(a).parse().map_err(|_| format!("Invalid address: {:?}", a))) .collect(), _ => Ok(Vec::new()), @@ -299,7 +299,7 @@ mod tests { use util::{U256}; use ethcore::client::{Mode, BlockID}; use ethcore::miner::PendingSet; - use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_price, geth_ipc_path, to_bootnodes}; + use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_addresses, to_price, geth_ipc_path, to_bootnodes}; #[test] fn test_to_duration() { @@ -370,6 +370,18 @@ mod tests { assert_eq!(to_address(None).unwrap(), Default::default()); } + #[test] + fn test_to_addresses() { + let addresses = to_addresses(&Some("0xD9A111feda3f362f55Ef1744347CDC8Dd9964a41,D9A111feda3f362f55Ef1744347CDC8Dd9964a42".into())).unwrap(); + assert_eq!( + addresses, + vec![ + "D9A111feda3f362f55Ef1744347CDC8Dd9964a41".parse().unwrap(), + "D9A111feda3f362f55Ef1744347CDC8Dd9964a42".parse().unwrap(), + ] + ); + } + #[test] #[cfg_attr(feature = "dev", allow(float_cmp))] fn test_to_price() { diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 66a59a86b..275d8a1a4 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -104,8 +104,8 @@ pub struct Dependencies { pub external_miner: Arc, pub logger: Arc, pub settings: Arc, - pub allow_pending_receipt_query: bool, pub net_service: Arc, + pub geth_compatibility: bool, } fn to_modules(apis: &[Api]) -> BTreeMap { @@ -163,7 +163,10 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet &deps.secret_store, &deps.miner, &deps.external_miner, - deps.allow_pending_receipt_query + EthClientOptions { + allow_pending_receipt_query: !deps.geth_compatibility, + send_block_number_in_get_work: !deps.geth_compatibility, + } ); server.add_delegate(client.to_delegate()); diff --git a/parity/run.rs b/parity/run.rs index 45b844ef0..6412d83ff 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -211,8 +211,8 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> { external_miner: external_miner.clone(), logger: logger.clone(), settings: Arc::new(cmd.net_settings.clone()), - allow_pending_receipt_query: !cmd.geth_compatibility, - net_service: manage_network.clone() + net_service: manage_network.clone(), + geth_compatibility: cmd.geth_compatibility, }); let dependencies = rpc::Dependencies { @@ -311,7 +311,7 @@ fn prepare_account_provider(dirs: &Directories, cfg: AccountsConfig) -> Result Result. -use util::{Address, U256}; +use util::{Address, U256, Bytes, H256}; /// Transaction request coming from RPC #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] @@ -30,18 +30,42 @@ pub struct TransactionRequest { /// Value of transaction in wei pub value: Option, /// Additional data sent with transaction - pub data: Option>, + pub data: Option, /// Transaction's nonce pub nonce: Option, } -/// Transaction confirmation waiting in a queue +/// Transaction request coming from RPC with default values filled in. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] -pub struct TransactionConfirmation { - /// Id of this confirmation - pub id: U256, - /// TransactionRequest - pub transaction: TransactionRequest, +pub struct FilledTransactionRequest { + /// Sender + pub from: Address, + /// Recipient + pub to: Option
, + /// Gas Price + pub gas_price: U256, + /// Gas + pub gas: U256, + /// Value of transaction in wei + pub value: U256, + /// Additional data sent with transaction + pub data: Bytes, + /// Transaction's nonce + pub nonce: Option, +} + +impl From for TransactionRequest { + fn from(r: FilledTransactionRequest) -> Self { + TransactionRequest { + from: r.from, + to: r.to, + gas_price: Some(r.gas_price), + gas: Some(r.gas), + value: Some(r.value), + data: Some(r.data), + nonce: r.nonce, + } + } } /// Call request @@ -62,3 +86,21 @@ pub struct CallRequest { /// Nonce pub nonce: Option, } + +/// Confirmation object +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct ConfirmationRequest { + /// Id of this confirmation + pub id: U256, + /// Payload to confirm + pub payload: ConfirmationPayload, +} + +/// Payload to confirm in Trusted Signer +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum ConfirmationPayload { + /// Transaction + Transaction(FilledTransactionRequest), + /// Sign request + Sign(Address, H256), +} diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index 96c546052..616d35364 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -17,10 +17,10 @@ use std::thread; use std::time::{Instant, Duration}; use std::sync::{mpsc, Arc}; -use std::collections::HashMap; +use std::collections::BTreeMap; use jsonrpc_core; use util::{Mutex, RwLock, U256}; -use v1::helpers::{TransactionRequest, TransactionConfirmation}; +use v1::helpers::{ConfirmationRequest, ConfirmationPayload}; /// Result that can be returned from JSON RPC. pub type RpcResult = Result; @@ -54,41 +54,41 @@ pub type QueueEventReceiver = mpsc::Receiver; pub trait SigningQueue: Send + Sync { /// Add new request to the queue. /// Returns a `ConfirmationPromise` that can be used to await for resolution of given request. - fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise; + fn add_request(&self, request: ConfirmationPayload) -> ConfirmationPromise; /// Removes a request from the queue. - /// Notifies possible token holders that transaction was rejected. - fn request_rejected(&self, id: U256) -> Option; + /// Notifies possible token holders that request was rejected. + fn request_rejected(&self, id: U256) -> Option; /// Removes a request from the queue. - /// Notifies possible token holders that transaction was confirmed and given hash was assigned. - fn request_confirmed(&self, id: U256, result: RpcResult) -> Option; + /// Notifies possible token holders that request was confirmed and given hash was assigned. + fn request_confirmed(&self, id: U256, result: RpcResult) -> Option; /// Returns a request if it is contained in the queue. - fn peek(&self, id: &U256) -> Option; + fn peek(&self, id: &U256) -> Option; /// Return copy of all the requests in the queue. - fn requests(&self) -> Vec; + fn requests(&self) -> Vec; - /// Returns number of transactions awaiting confirmation. + /// Returns number of requests awaiting confirmation. fn len(&self) -> usize; - /// Returns true if there are no transactions awaiting confirmation. + /// Returns true if there are no requests awaiting confirmation. fn is_empty(&self) -> bool; } #[derive(Debug, Clone, PartialEq)] -/// Result of a pending transaction. +/// Result of a pending confirmation request. pub enum ConfirmationResult { - /// The transaction has not yet been confirmed nor rejected. + /// The request has not yet been confirmed nor rejected. Waiting, - /// The transaction has been rejected. + /// The request has been rejected. Rejected, - /// The transaction has been confirmed. + /// The request has been confirmed. Confirmed(RpcResult), } -/// Time you need to confirm the transaction in UI. +/// Time you need to confirm the request in UI. /// This is the amount of time token holder will wait before /// returning `None`. /// Unless we have a multi-threaded RPC this will lock @@ -100,12 +100,14 @@ const QUEUE_TIMEOUT_DURATION_SEC : u64 = 20; pub struct ConfirmationToken { result: Arc>, handle: thread::Thread, - request: TransactionConfirmation, + request: ConfirmationRequest, + timeout: Duration, } pub struct ConfirmationPromise { id: U256, result: Arc>, + timeout: Duration, } impl ConfirmationToken { @@ -121,6 +123,7 @@ impl ConfirmationToken { ConfirmationPromise { id: self.request.id, result: self.result.clone(), + timeout: self.timeout, } } } @@ -134,8 +137,7 @@ impl ConfirmationPromise { /// Returns `None` if transaction was rejected or timeout reached. /// Returns `Some(result)` if transaction was confirmed. pub fn wait_with_timeout(&self) -> Option { - let timeout = Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC); - let res = self.wait_until(Instant::now() + timeout); + let res = self.wait_until(Instant::now() + self.timeout); match res { ConfirmationResult::Confirmed(h) => Some(h), ConfirmationResult::Rejected | ConfirmationResult::Waiting => None, @@ -146,16 +148,16 @@ impl ConfirmationPromise { pub fn result(&self) -> ConfirmationResult { self.wait_until(Instant::now()) } /// Blocks current thread and awaits for - /// resolution of the transaction (rejected / confirmed) - /// Returns `None` if transaction was rejected or timeout reached. - /// Returns `Some(result)` if transaction was confirmed. + /// resolution of the request (rejected / confirmed) + /// Returns `None` if request was rejected or timeout reached. + /// Returns `Some(result)` if request was confirmed. pub fn wait_until(&self, deadline: Instant) -> ConfirmationResult { - trace!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", self.id); + trace!(target: "own_tx", "Signer: Awaiting confirmation... ({:?}).", self.id); loop { let now = Instant::now(); // Check the result... match *self.result.lock() { - // Waiting and deadline not yet passed continue looping. + // Waiting and deadline not yet passed continue looping. ConfirmationResult::Waiting if now < deadline => {} // Anything else - return. ref a => return a.clone(), @@ -166,12 +168,13 @@ impl ConfirmationPromise { } } -/// Queue for all unconfirmed transactions. +/// Queue for all unconfirmed requests. pub struct ConfirmationsQueue { id: Mutex, - queue: RwLock>, + queue: RwLock>, sender: Mutex>, receiver: Mutex>>, + timeout: Duration, } impl Default for ConfirmationsQueue { @@ -180,14 +183,23 @@ impl Default for ConfirmationsQueue { ConfirmationsQueue { id: Mutex::new(U256::from(0)), - queue: RwLock::new(HashMap::new()), + queue: RwLock::new(BTreeMap::new()), sender: Mutex::new(send), receiver: Mutex::new(Some(recv)), + timeout: Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC), } } } impl ConfirmationsQueue { + #[cfg(test)] + /// Creates new confirmations queue with specified timeout + pub fn with_timeout(timeout: Duration) -> Self { + let mut queue = Self::default(); + queue.timeout = timeout; + queue + } + /// Blocks the thread and starts listening for notifications regarding all actions in the queue. /// For each event, `listener` callback will be invoked. /// This method can be used only once (only single consumer of events can exist). @@ -221,9 +233,9 @@ impl ConfirmationsQueue { let _ = self.sender.lock().send(message); } - /// Removes transaction from this queue and notifies `ConfirmationPromise` holders about the result. + /// Removes requests from this queue and notifies `ConfirmationPromise` holders about the result. /// Notifies also a receiver about that event. - fn remove(&self, id: U256, result: Option) -> Option { + fn remove(&self, id: U256, result: Option) -> Option { let token = self.queue.write().remove(&id); if let Some(token) = token { @@ -248,7 +260,7 @@ impl Drop for ConfirmationsQueue { } impl SigningQueue for ConfirmationsQueue { - fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise { + fn add_request(&self, request: ConfirmationPayload) -> ConfirmationPromise { // Increment id let id = { let mut last_id = self.id.lock(); @@ -257,16 +269,19 @@ impl SigningQueue for ConfirmationsQueue { }; // Add request to queue let res = { + debug!(target: "own_tx", "Signer: New entry ({:?}) in confirmation queue.", id); + trace!(target: "own_tx", "Signer: ({:?}) : {:?}", id, request); + let mut queue = self.queue.write(); queue.insert(id, ConfirmationToken { result: Arc::new(Mutex::new(ConfirmationResult::Waiting)), handle: thread::current(), - request: TransactionConfirmation { + request: ConfirmationRequest { id: id, - transaction: transaction, + payload: request, }, + timeout: self.timeout, }); - debug!(target: "own_tx", "Signer: New transaction ({:?}) in confirmation queue.", id); queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.") }; // Notify listeners @@ -275,21 +290,21 @@ impl SigningQueue for ConfirmationsQueue { } - fn peek(&self, id: &U256) -> Option { + fn peek(&self, id: &U256) -> Option { self.queue.read().get(id).map(|token| token.request.clone()) } - fn request_rejected(&self, id: U256) -> Option { - debug!(target: "own_tx", "Signer: Transaction rejected ({:?}).", id); + fn request_rejected(&self, id: U256) -> Option { + debug!(target: "own_tx", "Signer: Request rejected ({:?}).", id); self.remove(id, None) } - fn request_confirmed(&self, id: U256, result: RpcResult) -> Option { + fn request_confirmed(&self, id: U256, result: RpcResult) -> Option { debug!(target: "own_tx", "Signer: Transaction confirmed ({:?}).", id); self.remove(id, Some(result)) } - fn requests(&self) -> Vec { + fn requests(&self) -> Vec { let queue = self.queue.read(); queue.values().map(|token| token.request.clone()).collect() } @@ -312,20 +327,20 @@ mod test { use std::thread; use std::sync::Arc; use util::{Address, U256, H256, Mutex}; - use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, TransactionRequest}; + use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, FilledTransactionRequest, ConfirmationPayload}; use v1::types::H256 as NH256; use jsonrpc_core::to_value; - fn request() -> TransactionRequest { - TransactionRequest { + fn request() -> ConfirmationPayload { + ConfirmationPayload::Transaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from(2)), - gas_price: None, - gas: None, - value: Some(U256::from(10_000_000)), - data: None, + gas_price: 0.into(), + gas: 10_000.into(), + value: 10_000_000.into(), + data: vec![], nonce: None, - } + }) } #[test] @@ -391,6 +406,6 @@ mod test { assert_eq!(all.len(), 1); let el = all.get(0).unwrap(); assert_eq!(el.id, U256::from(1)); - assert_eq!(el.transaction, request); + assert_eq!(el.payload, request); } } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 4ad66e082..c704650a3 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -47,6 +47,23 @@ use v1::helpers::CallRequest as CRequest; use v1::impls::{default_gas_price, dispatch_transaction, error_codes}; use serde; +/// Eth RPC options +pub struct EthClientOptions { + /// Returns receipt from pending blocks + pub allow_pending_receipt_query: bool, + /// Send additional block number when asking for work + pub send_block_number_in_get_work: bool, +} + +impl Default for EthClientOptions { + fn default() -> Self { + EthClientOptions { + allow_pending_receipt_query: true, + send_block_number_in_get_work: true, + } + } +} + /// Eth rpc implementation. pub struct EthClient where C: MiningBlockChainClient, @@ -60,7 +77,7 @@ pub struct EthClient where miner: Weak, external_miner: Arc, seed_compute: Mutex, - allow_pending_receipt_query: bool, + options: EthClientOptions, } impl EthClient where @@ -70,7 +87,7 @@ impl EthClient where EM: ExternalMinerService { /// Creates new EthClient. - pub fn new(client: &Arc, sync: &Arc, accounts: &Arc, miner: &Arc, em: &Arc, allow_pending_receipt_query: bool) + pub fn new(client: &Arc, sync: &Arc, accounts: &Arc, miner: &Arc, em: &Arc, options: EthClientOptions) -> EthClient { EthClient { client: Arc::downgrade(client), @@ -79,7 +96,7 @@ impl EthClient where accounts: Arc::downgrade(accounts), external_miner: em.clone(), seed_compute: Mutex::new(SeedHashCompute::new()), - allow_pending_receipt_query: allow_pending_receipt_query, + options: options, } } @@ -316,7 +333,7 @@ impl Eth for EthClient where fn is_mining(&self, params: Params) -> Result { try!(self.active()); match params { - Params::None => to_value(&self.external_miner.is_mining()), + Params::None => to_value(&(take_weak!(self.miner).is_sealing())), _ => Err(Error::invalid_params()) } } @@ -496,7 +513,7 @@ impl Eth for EthClient where let miner = take_weak!(self.miner); let hash: H256 = hash.into(); match miner.pending_receipts().get(&hash) { - Some(receipt) if self.allow_pending_receipt_query => to_value(&Receipt::from(receipt.clone())), + Some(receipt) if self.options.allow_pending_receipt_query => to_value(&Receipt::from(receipt.clone())), _ => { let client = take_weak!(self.client); let receipt = client.transaction_receipt(TransactionID::Hash(hash)); @@ -582,8 +599,13 @@ impl Eth for EthClient where let pow_hash = b.hash(); let target = Ethash::difficulty_to_boundary(b.block().header().difficulty()); let seed_hash = self.seed_compute.lock().get_seedhash(b.block().header().number()); - let block_number = RpcU256::from(b.block().header().number()); - to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target), block_number)) + + if self.options.send_block_number_in_get_work { + let block_number = RpcU256::from(b.block().header().number()); + to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target), block_number)) + } else { + to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target))) + } }).unwrap_or(Err(Error::internal_error())) // no work found. }, _ => Err(Error::invalid_params()) diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs index 61817c89f..c4eb187fe 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/eth_signing.rs @@ -23,24 +23,21 @@ use ethcore::client::MiningBlockChainClient; use util::{U256, Address, H256, Mutex}; use transient_hashmap::TransientHashMap; use ethcore::account_provider::AccountProvider; -use v1::helpers::{SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, TransactionRequest as TRequest}; +use v1::helpers::{SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest}; use v1::traits::EthSigning; use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, U256 as RpcU256}; -use v1::impls::{default_gas_price, sign_and_dispatch, transaction_rejected_error}; +use v1::impls::{default_gas_price, sign_and_dispatch, transaction_rejected_error, signer_disabled_error}; -fn fill_optional_fields(request: &mut TRequest, client: &C, miner: &M) +fn fill_optional_fields(request: TRequest, client: &C, miner: &M) -> FilledRequest where C: MiningBlockChainClient, M: MinerService { - if request.value.is_none() { - request.value = Some(U256::from(0)); - } - if request.gas.is_none() { - request.gas = Some(miner.sensible_gas_limit()); - } - if request.gas_price.is_none() { - request.gas_price = Some(default_gas_price(client, miner)); - } - if request.data.is_none() { - request.data = Some(Vec::new()); + FilledRequest { + from: request.from, + to: request.to, + nonce: request.nonce, + gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)), + gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), + value: request.value.unwrap_or_else(|| 0.into()), + data: request.data.unwrap_or_else(Vec::new), } } @@ -74,10 +71,26 @@ impl EthSigningQueueClient where C: MiningBlockChainClient, M: Miner Ok(()) } - fn dispatch Result>(&self, params: Params, f: F) -> Result { + fn dispatch_sign Result>(&self, params: Params, f: F) -> Result { + from_params::<(RpcH160, RpcH256)>(params).and_then(|(address, msg)| { + let address: Address = address.into(); + let msg: H256 = msg.into(); + + let accounts = take_weak!(self.accounts); + if accounts.is_unlocked(address) { + return to_value(&accounts.sign(address, msg).ok().map_or_else(RpcH520::default, Into::into)); + } + + let queue = take_weak!(self.queue); + let promise = queue.add_request(ConfirmationPayload::Sign(address, msg)); + f(promise) + }) + } + + fn dispatch_transaction Result>(&self, params: Params, f: F) -> Result { from_params::<(TransactionRequest, )>(params) .and_then(|(request, )| { - let mut request: TRequest = request.into(); + let request: TRequest = request.into(); let accounts = take_weak!(self.accounts); let (client, miner) = (take_weak!(self.client), take_weak!(self.miner)); @@ -87,8 +100,8 @@ impl EthSigningQueueClient where C: MiningBlockChainClient, M: Miner } let queue = take_weak!(self.queue); - fill_optional_fields(&mut request, &*client, &*miner); - let promise = queue.add_request(request); + let request = fill_optional_fields(request, &*client, &*miner); + let promise = queue.add_request(ConfirmationPayload::Transaction(request)); f(promise) }) } @@ -98,23 +111,32 @@ impl EthSigning for EthSigningQueueClient where C: MiningBlockChainClient + 'static, M: MinerService + 'static { - fn sign(&self, _params: Params) -> Result { + fn sign(&self, params: Params) -> Result { try!(self.active()); - warn!("Invoking eth_sign is not yet supported with signer enabled."); - // TODO [ToDr] Implement sign when rest of the signing queue is ready. - rpc_unimplemented!() + self.dispatch_sign(params, |promise| { + promise.wait_with_timeout().unwrap_or_else(|| to_value(&RpcH520::default())) + }) + } + + fn post_sign(&self, params: Params) -> Result { + try!(self.active()); + self.dispatch_sign(params, |promise| { + let id = promise.id(); + self.pending.lock().insert(id, promise); + to_value(&RpcU256::from(id)) + }) } fn send_transaction(&self, params: Params) -> Result { try!(self.active()); - self.dispatch(params, |promise| { + self.dispatch_transaction(params, |promise| { promise.wait_with_timeout().unwrap_or_else(|| to_value(&RpcH256::default())) }) } fn post_transaction(&self, params: Params) -> Result { try!(self.active()); - self.dispatch(params, |promise| { + self.dispatch_transaction(params, |promise| { let id = promise.id(); self.pending.lock().insert(id, promise); to_value(&RpcU256::from(id)) @@ -193,13 +215,18 @@ impl EthSigning for EthSigningUnsafeClient where }) } + fn post_sign(&self, _: Params) -> Result { + // We don't support this in non-signer mode. + Err(signer_disabled_error()) + } + fn post_transaction(&self, _: Params) -> Result { // We don't support this in non-signer mode. - Err(Error::invalid_params()) + Err(signer_disabled_error()) } fn check_transaction(&self, _: Params) -> Result { // We don't support this in non-signer mode. - Err(Error::invalid_params()) + Err(signer_disabled_error()) } } diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index b62e21b0c..acbb72b3d 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -27,7 +27,7 @@ use ethcore::miner::MinerService; use v1::traits::Ethcore; use v1::types::{Bytes, U256}; use v1::helpers::{SigningQueue, ConfirmationsQueue}; -use v1::impls::error_codes; +use v1::impls::signer_disabled_error; /// Ethcore implementation. pub struct EthcoreClient where @@ -152,11 +152,7 @@ impl Ethcore for EthcoreClient where M: MinerService + 'static, C: M fn unsigned_transactions_count(&self, _params: Params) -> Result { try!(self.active()); match self.confirmations_queue { - None => Err(Error { - code: ErrorCode::ServerError(error_codes::SIGNER_DISABLED), - message: "Trusted Signer is disabled. This API is not available.".into(), - data: None - }), + None => Err(signer_disabled_error()), Some(ref queue) => to_value(&queue.len()), } } diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index 54136cfea..a9dddf843 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -42,7 +42,7 @@ mod traces; mod rpc; pub use self::web3::Web3Client; -pub use self::eth::EthClient; +pub use self::eth::{EthClient, EthClientOptions}; pub use self::eth_filter::EthFilterClient; pub use self::eth_signing::{EthSigningUnsafeClient, EthSigningQueueClient}; pub use self::net::NetClient; @@ -54,7 +54,7 @@ pub use self::traces::TracesClient; pub use self::rpc::RpcClient; use v1::helpers::TransactionRequest; -use v1::types::H256 as NH256; +use v1::types::{H256 as RpcH256, H520 as RpcH520}; use ethcore::error::Error as EthcoreError; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; @@ -80,7 +80,7 @@ mod error_codes { fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result where C: MiningBlockChainClient, M: MinerService { - let hash = NH256::from(signed_transaction.hash()); + let hash = RpcH256::from(signed_transaction.hash()); let import = miner.import_own_transaction(client, signed_transaction); @@ -89,6 +89,12 @@ fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedT .and_then(|_| to_value(&hash)) } +fn signature_with_password(accounts: &AccountProvider, address: Address, hash: H256, pass: String) -> Result { + accounts.sign_with_password(address, pass, hash) + .map_err(password_error) + .and_then(|hash| to_value(&RpcH520::from(hash))) +} + fn prepare_transaction(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService { Transaction { nonce: request.nonce @@ -105,9 +111,10 @@ fn prepare_transaction(client: &C, miner: &M, request: TransactionRequest) } } -fn unlock_sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address, password: String) -> Result +fn unlock_sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, password: String) -> Result where C: MiningBlockChainClient, M: MinerService { + let address = request.from; let signed_transaction = { let t = prepare_transaction(client, miner, request); let hash = t.hash(); @@ -140,6 +147,14 @@ fn default_gas_price(client: &C, miner: &M) -> U256 where C: MiningBlockCh .unwrap_or_else(|_| miner.sensible_gas_price()) } +fn signer_disabled_error() -> Error { + Error { + code: ErrorCode::ServerError(error_codes::SIGNER_DISABLED), + message: "Trusted Signer is disabled. This API is not available.".into(), + data: None + } +} + fn signing_error(error: AccountError) -> Error { Error { code: ErrorCode::ServerError(error_codes::ACCOUNT_LOCKED), diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 15624c7d3..eacb6525a 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -105,10 +105,9 @@ impl Personal for PersonalClient where C: MiningBl from_params::<(TransactionRequest, String)>(params) .and_then(|(request, password)| { let request: TRequest = request.into(); - let sender = request.from; let accounts = take_weak!(self.accounts); - unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, sender, password) + unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, password) }) } diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs index 823e20577..2dfce57af 100644 --- a/rpc/src/v1/impls/personal_signer.rs +++ b/rpc/src/v1/impls/personal_signer.rs @@ -22,9 +22,9 @@ use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; use ethcore::miner::MinerService; use v1::traits::PersonalSigner; -use v1::types::{TransactionModification, TransactionConfirmation, U256}; -use v1::impls::unlock_sign_and_dispatch; -use v1::helpers::{SigningQueue, ConfirmationsQueue}; +use v1::types::{TransactionModification, ConfirmationRequest, U256}; +use v1::impls::{unlock_sign_and_dispatch, signature_with_password}; +use v1::helpers::{SigningQueue, ConfirmationsQueue, ConfirmationPayload}; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -55,14 +55,16 @@ impl SignerClient where C: MiningBlockChainClient, impl PersonalSigner for SignerClient where C: MiningBlockChainClient, M: MinerService { - fn transactions_to_confirm(&self, _params: Params) -> Result { + fn requests_to_confirm(&self, _params: Params) -> Result { try!(self.active()); let queue = take_weak!(self.queue); - to_value(&queue.requests().into_iter().map(From::from).collect::>()) + to_value(&queue.requests().into_iter().map(From::from).collect::>()) } - fn confirm_transaction(&self, params: Params) -> Result { + fn confirm_request(&self, params: Params) -> Result { try!(self.active()); + // TODO [ToDr] TransactionModification is redundant for some calls + // might be better to replace it in future from_params::<(U256, TransactionModification, String)>(params).and_then( |(id, modification, pass)| { let id = id.into(); @@ -70,17 +72,23 @@ impl PersonalSigner for SignerClient where C: Mini let queue = take_weak!(self.queue); let client = take_weak!(self.client); let miner = take_weak!(self.miner); - queue.peek(&id).map(|confirmation| { - let mut request = confirmation.transaction; - // apply modification - if let Some(gas_price) = modification.gas_price { - request.gas_price = Some(gas_price.into()); - } - let sender = request.from; - let result = unlock_sign_and_dispatch(&*client, &*miner, request, &*accounts, sender, pass); - if let Ok(ref hash) = result { - queue.request_confirmed(id, Ok(hash.clone())); + queue.peek(&id).map(|confirmation| { + let result = match confirmation.payload { + ConfirmationPayload::Transaction(mut request) => { + // apply modification + if let Some(gas_price) = modification.gas_price { + request.gas_price = gas_price.into(); + } + + unlock_sign_and_dispatch(&*client, &*miner, request.into(), &*accounts, pass) + }, + ConfirmationPayload::Sign(address, hash) => { + signature_with_password(&*accounts, address, hash, pass) + } + }; + if let Ok(ref response) = result { + queue.request_confirmed(id, Ok(response.clone())); } result }).unwrap_or_else(|| Err(Error::invalid_params())) @@ -88,7 +96,7 @@ impl PersonalSigner for SignerClient where C: Mini ) } - fn reject_transaction(&self, params: Params) -> Result { + fn reject_request(&self, params: Params) -> Result { try!(self.active()); from_params::<(U256, )>(params).and_then( |(id, )| { diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 8656a4e49..7e871048e 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -121,7 +121,7 @@ impl EthTester { &account_provider, &miner_service, &external_miner, - true + Default::default(), ); let eth_sign = EthSigningUnsafeClient::new( &client, diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 21b4d5bba..5cce14533 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -181,8 +181,9 @@ impl MinerService for TestMinerService { unimplemented!(); } - fn map_sealing_work(&self, _chain: &MiningBlockChainClient, _f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { - None + fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { + let open_block = chain.prepare_open_block(self.author(), *self.gas_range_target.write(), self.extra_data()); + Some(f(&open_block.close())) } fn transaction(&self, hash: &H256) -> Option { @@ -205,6 +206,10 @@ impl MinerService for TestMinerService { self.last_nonces.read().get(address).cloned() } + fn is_sealing(&self) -> bool { + false + } + /// Submit `seal` as a valid solution for the header of `pow_hash`. /// Will check the seal, but not actually insert the block into the chain. fn submit_seal(&self, _chain: &MiningBlockChainClient, _pow_hash: H256, _seal: Vec) -> Result<(), Error> { diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 0a9275bd8..9e819c1b0 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -17,10 +17,11 @@ use std::str::FromStr; use std::collections::HashMap; use std::sync::Arc; +use std::time::{Instant, Duration}; use jsonrpc_core::IoHandler; use util::hash::{Address, H256, FixedHash}; use util::numbers::{Uint, U256}; -use util::RwLock; +use util::Mutex; use ethcore::account_provider::AccountProvider; use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionID}; use ethcore::log_entry::{LocalizedLogEntry, LogEntry}; @@ -28,7 +29,7 @@ use ethcore::receipt::LocalizedReceipt; use ethcore::transaction::{Transaction, Action}; use ethcore::miner::{ExternalMiner, MinerService}; use ethsync::SyncState; -use v1::{Eth, EthClient, EthSigning, EthSigningUnsafeClient}; +use v1::{Eth, EthClient, EthClientOptions, EthSigning, EthSigningUnsafeClient}; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService}; use rustc_serialize::hex::ToHex; @@ -57,19 +58,25 @@ struct EthTester { pub sync: Arc, pub accounts_provider: Arc, pub miner: Arc, - hashrates: Arc>>, + hashrates: Arc>>, pub io: IoHandler, } impl Default for EthTester { fn default() -> Self { + Self::new_with_options(Default::default()) + } +} + +impl EthTester { + pub fn new_with_options(options: EthClientOptions) -> Self { let client = blockchain_client(); let sync = sync_provider(); let ap = accounts_provider(); let miner = miner_service(); - let hashrates = Arc::new(RwLock::new(HashMap::new())); + let hashrates = Arc::new(Mutex::new(HashMap::new())); let external_miner = Arc::new(ExternalMiner::new(hashrates.clone())); - let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner, true).to_delegate(); + let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner, options).to_delegate(); let sign = EthSigningUnsafeClient::new(&client, &ap, &miner).to_delegate(); let io = IoHandler::new(); io.add_delegate(eth); @@ -133,9 +140,9 @@ fn rpc_eth_syncing() { #[test] fn rpc_eth_hashrate() { let tester = EthTester::default(); - tester.hashrates.write().insert(H256::from(0), U256::from(0xfffa)); - tester.hashrates.write().insert(H256::from(0), U256::from(0xfffb)); - tester.hashrates.write().insert(H256::from(1), U256::from(0x1)); + tester.hashrates.lock().insert(H256::from(0), (Instant::now() + Duration::from_secs(2), U256::from(0xfffa))); + tester.hashrates.lock().insert(H256::from(0), (Instant::now() + Duration::from_secs(2), U256::from(0xfffb))); + tester.hashrates.lock().insert(H256::from(1), (Instant::now() + Duration::from_secs(2), U256::from(0x1))); let request = r#"{"jsonrpc": "2.0", "method": "eth_hashrate", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"0xfffc","id":1}"#; @@ -158,8 +165,8 @@ fn rpc_eth_submit_hashrate() { let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); - assert_eq!(tester.hashrates.read().get(&H256::from("0x59daa26581d0acd1fce254fb7e85952f4c09d0915afd33d3886cd914bc7d283c")).cloned(), - Some(U256::from(0x500_000))); + assert_eq!(tester.hashrates.lock().get(&H256::from("0x59daa26581d0acd1fce254fb7e85952f4c09d0915afd33d3886cd914bc7d283c")).cloned().unwrap().1, + U256::from(0x500_000)); } #[test] @@ -210,16 +217,11 @@ fn rpc_eth_author() { #[test] fn rpc_eth_mining() { let tester = EthTester::default(); + tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()); let request = r#"{"jsonrpc": "2.0", "method": "eth_mining", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); - - tester.hashrates.write().insert(H256::from(1), U256::from(0x1)); - - let request = r#"{"jsonrpc": "2.0", "method": "eth_mining", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; - assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); } #[test] @@ -794,15 +796,26 @@ fn returns_no_work_if_cant_mine() { } #[test] -fn returns_error_if_can_mine_and_no_closed_block() { - use ethsync::{SyncState}; - +fn returns_correct_work_package() { let eth_tester = EthTester::default(); eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()); - eth_tester.sync.status.write().state = SyncState::Idle; let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":null},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":["0x3bbe93f74e7b97ae00784aeff8819c5cb600dd87e8b282a5d3446f3f871f0347","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000800000000000000000000000000000000000000000000000000000000000","0x01"],"id":1}"#; + + assert_eq!(eth_tester.io.handle_request(request), Some(response.to_owned())); +} + +#[test] +fn should_not_return_block_number() { + let eth_tester = EthTester::new_with_options(EthClientOptions { + allow_pending_receipt_query: true, + send_block_number_in_get_work: false, + }); + eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()); + + let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":["0x3bbe93f74e7b97ae00784aeff8819c5cb600dd87e8b282a5d3446f3f871f0347","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000800000000000000000000000000000000000000000000000000000000000"],"id":1}"#; assert_eq!(eth_tester.io.handle_request(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs index 794d5fc93..69a21cab5 100644 --- a/rpc/src/v1/tests/mocked/eth_signing.rs +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -16,13 +16,14 @@ use std::str::FromStr; use std::sync::Arc; +use std::time::Duration; use jsonrpc_core::IoHandler; use v1::impls::EthSigningQueueClient; use v1::traits::EthSigning; use v1::helpers::{ConfirmationsQueue, SigningQueue}; use v1::tests::helpers::TestMinerService; use util::{Address, FixedHash}; -use util::numbers::{Uint, U256}; +use util::numbers::{Uint, U256, H256}; use ethcore::account_provider::AccountProvider; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; @@ -37,7 +38,7 @@ struct EthSigningTester { impl Default for EthSigningTester { fn default() -> Self { - let queue = Arc::new(ConfirmationsQueue::default()); + let queue = Arc::new(ConfirmationsQueue::with_timeout(Duration::from_millis(1))); let client = Arc::new(TestBlockChainClient::default()); let miner = Arc::new(TestMinerService::default()); let accounts = Arc::new(AccountProvider::transient_provider()); @@ -58,6 +59,78 @@ fn eth_signing() -> EthSigningTester { EthSigningTester::default() } +#[test] +fn should_add_sign_to_queue() { + // given + let tester = eth_signing(); + let address = Address::random(); + assert_eq!(tester.queue.requests().len(), 0); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_sign", + "params": [ + ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 1); +} + +#[test] +fn should_post_sign_to_queue() { + // given + let tester = eth_signing(); + let address = Address::random(); + assert_eq!(tester.queue.requests().len(), 0); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_postSign", + "params": [ + ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x01","id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 1); +} + +#[test] +fn should_sign_if_account_is_unlocked() { + // given + let tester = eth_signing(); + let hash: H256 = 5.into(); + let acc = tester.accounts.new_account("test").unwrap(); + tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap(); + + let signature = tester.accounts.sign(acc, hash).unwrap(); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_sign", + "params": [ + ""#.to_owned() + format!("0x{:?}", acc).as_ref() + r#"", + ""# + format!("0x{:?}", hash).as_ref() + r#"" + ], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", signature).as_ref() + r#"","id":1}"#; + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 0); +} #[test] fn should_add_transaction_to_queue() { @@ -81,7 +154,6 @@ fn should_add_transaction_to_queue() { }"#; let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":1}"#; - // then assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); assert_eq!(tester.queue.requests().len(), 1); diff --git a/rpc/src/v1/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/personal_signer.rs index c3a0c070d..b0d3ec735 100644 --- a/rpc/src/v1/tests/mocked/personal_signer.rs +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -23,7 +23,7 @@ use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; use v1::{SignerClient, PersonalSigner}; use v1::tests::helpers::TestMinerService; -use v1::helpers::{SigningQueue, ConfirmationsQueue, TransactionRequest}; +use v1::helpers::{SigningQueue, ConfirmationsQueue, FilledTransactionRequest, ConfirmationPayload}; struct PersonalSignerTester { queue: Arc, @@ -68,22 +68,28 @@ fn signer_tester() -> PersonalSignerTester { #[test] -fn should_return_list_of_transactions_in_queue() { +fn should_return_list_of_items_to_confirm() { // given let tester = signer_tester(); - tester.queue.add_request(TransactionRequest { + tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), - gas_price: Some(U256::from(10_000)), - gas: Some(U256::from(10_000_000)), - value: Some(U256::from(1)), - data: None, + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], nonce: None, - }); + })); + tester.queue.add_request(ConfirmationPayload::Sign(1.into(), 5.into())); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_transactionsToConfirm","params":[],"id":1}"#; - let response = r#"{"jsonrpc":"2.0","result":[{"id":"0x01","transaction":{"data":null,"from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"personal_requestsToConfirm","params":[],"id":1}"#; + let response = concat!( + r#"{"jsonrpc":"2.0","result":["#, + r#"{"id":"0x01","payload":{"transaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}},"#, + r#"{"id":"0x02","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","hash":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#, + r#"],"id":1}"# + ); // then assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); @@ -94,19 +100,19 @@ fn should_return_list_of_transactions_in_queue() { fn should_reject_transaction_from_queue_without_dispatching() { // given let tester = signer_tester(); - tester.queue.add_request(TransactionRequest { + tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), - gas_price: Some(U256::from(10_000)), - gas: Some(U256::from(10_000_000)), - value: Some(U256::from(1)), - data: None, + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], nonce: None, - }); + })); assert_eq!(tester.queue.requests().len(), 1); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_rejectTransaction","params":["0x01"],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"personal_rejectRequest","params":["0x01"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; // then @@ -119,19 +125,35 @@ fn should_reject_transaction_from_queue_without_dispatching() { fn should_not_remove_transaction_if_password_is_invalid() { // given let tester = signer_tester(); - tester.queue.add_request(TransactionRequest { + tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { from: Address::from(1), to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), - gas_price: Some(U256::from(10_000)), - gas: Some(U256::from(10_000_000)), - value: Some(U256::from(1)), - data: None, + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], nonce: None, - }); + })); assert_eq!(tester.queue.requests().len(), 1); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_confirmTransaction","params":["0x01",{},"xxx"],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x01",{},"xxx"],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 1); +} + +#[test] +fn should_not_remove_sign_if_password_is_invalid() { + // given + let tester = signer_tester(); + tester.queue.add_request(ConfirmationPayload::Sign(0.into(), 5.into())); + assert_eq!(tester.queue.requests().len(), 1); + + // when + let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x01",{},"xxx"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#; // then @@ -145,15 +167,15 @@ fn should_confirm_transaction_and_dispatch() { let tester = signer_tester(); let address = tester.accounts.new_account("test").unwrap(); let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); - tester.queue.add_request(TransactionRequest { + tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest { from: address, to: Some(recipient), - gas_price: Some(U256::from(10_000)), - gas: Some(U256::from(10_000_000)), - value: Some(U256::from(1)), - data: None, + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], nonce: None, - }); + })); let t = Transaction { nonce: U256::zero(), @@ -172,7 +194,7 @@ fn should_confirm_transaction_and_dispatch() { // when let request = r#"{ "jsonrpc":"2.0", - "method":"personal_confirmTransaction", + "method":"personal_confirmRequest", "params":["0x01", {"gasPrice":"0x1000"}, "test"], "id":1 }"#; diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 60d39d8ba..72dc17962 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -206,26 +206,31 @@ pub trait EthSigning: Sized + Send + Sync + 'static { /// Signs the data with given address signature. fn sign(&self, _: Params) -> Result; + /// Posts sign request asynchronously. + /// Will return a confirmation ID for later use with check_transaction. + fn post_sign(&self, _: Params) -> Result; + /// Sends transaction; will block for 20s to try to return the /// transaction hash. /// If it cannot yet be signed, it will return a transaction ID for - /// later use with check_transaction. + /// later use with check_transaction. fn send_transaction(&self, _: Params) -> Result; /// Posts transaction asynchronously. - /// Will return a transaction ID for later use with check_transaction. + /// Will return a transaction ID for later use with check_transaction. fn post_transaction(&self, _: Params) -> Result; /// Checks the progress of a previously posted transaction. /// Should be given a valid send_transaction ID. /// Returns the transaction hash, the zero hash (not yet available), - /// or an error. + /// or an error. fn check_transaction(&self, _: Params) -> Result; /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); delegate.add_method("eth_sign", EthSigning::sign); + delegate.add_method("eth_postSign", EthSigning::post_sign); delegate.add_method("eth_sendTransaction", EthSigning::send_transaction); delegate.add_method("eth_postTransaction", EthSigning::post_transaction); delegate.add_method("eth_checkTransaction", EthSigning::check_transaction); diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index 948ae6b26..9c16f692f 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -61,24 +61,24 @@ pub trait Personal: Sized + Send + Sync + 'static { } } -/// Personal extension for transactions confirmations rpc interface. +/// Personal extension for confirmations rpc interface. pub trait PersonalSigner: Sized + Send + Sync + 'static { - /// Returns a list of transactions to confirm. - fn transactions_to_confirm(&self, _: Params) -> Result; + /// Returns a list of items to confirm. + fn requests_to_confirm(&self, _: Params) -> Result; - /// Confirm and send a specific transaction. - fn confirm_transaction(&self, _: Params) -> Result; + /// Confirm specific request. + fn confirm_request(&self, _: Params) -> Result; - /// Reject the transaction request. - fn reject_transaction(&self, _: Params) -> Result; + /// Reject the confirmation request. + fn reject_request(&self, _: Params) -> Result; /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); - delegate.add_method("personal_transactionsToConfirm", PersonalSigner::transactions_to_confirm); - delegate.add_method("personal_confirmTransaction", PersonalSigner::confirm_transaction); - delegate.add_method("personal_rejectTransaction", PersonalSigner::reject_transaction); + delegate.add_method("personal_requestsToConfirm", PersonalSigner::requests_to_confirm); + delegate.add_method("personal_confirmRequest", PersonalSigner::confirm_request); + delegate.add_method("personal_rejectRequest", PersonalSigner::reject_request); delegate } } diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs new file mode 100644 index 000000000..e2cea32de --- /dev/null +++ b/rpc/src/v1/types/confirmations.rs @@ -0,0 +1,150 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Types used in Confirmations queue (Trusted Signer) + +use v1::types::{U256, TransactionRequest, H160, H256}; +use v1::helpers; + + +/// Confirmation waiting in a queue +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] +pub struct ConfirmationRequest { + /// Id of this confirmation + pub id: U256, + /// Payload + pub payload: ConfirmationPayload, +} + +impl From for ConfirmationRequest { + fn from(c: helpers::ConfirmationRequest) -> Self { + ConfirmationRequest { + id: c.id.into(), + payload: c.payload.into(), + } + } +} + +/// Sign request +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] +pub struct SignRequest { + /// Address + pub address: H160, + /// Hash to sign + pub hash: H256, +} + +/// Confirmation payload, i.e. the thing to be confirmed +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] +pub enum ConfirmationPayload { + /// Transaction + #[serde(rename="transaction")] + Transaction(TransactionRequest), + /// Signature + #[serde(rename="sign")] + Sign(SignRequest), +} + +impl From for ConfirmationPayload { + fn from(c: helpers::ConfirmationPayload) -> Self { + match c { + helpers::ConfirmationPayload::Transaction(t) => ConfirmationPayload::Transaction(t.into()), + helpers::ConfirmationPayload::Sign(address, hash) => ConfirmationPayload::Sign(SignRequest { + address: address.into(), + hash: hash.into(), + }), + } + } +} + +/// Possible modifications to the confirmed transaction sent by `Trusted Signer` +#[derive(Debug, PartialEq, Deserialize)] +pub struct TransactionModification { + /// Modified gas price + #[serde(rename="gasPrice")] + pub gas_price: Option, +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use serde_json; + use v1::types::U256; + use v1::helpers; + use super::*; + + #[test] + fn should_serialize_sign_confirmation() { + // given + let request = helpers::ConfirmationRequest { + id: 15.into(), + payload: helpers::ConfirmationPayload::Sign(1.into(), 5.into()), + }; + + // when + let res = serde_json::to_string(&ConfirmationRequest::from(request)); + let expected = r#"{"id":"0x0f","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","hash":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#; + + // then + assert_eq!(res.unwrap(), expected.to_owned()); + } + + #[test] + fn should_serialize_transaction_confirmation() { + // given + let request = helpers::ConfirmationRequest { + id: 15.into(), + payload: helpers::ConfirmationPayload::Transaction(helpers::FilledTransactionRequest { + from: 0.into(), + to: None, + gas: 15_000.into(), + gas_price: 10_000.into(), + value: 100_000.into(), + data: vec![1, 2, 3], + nonce: Some(1.into()), + }), + }; + + // when + let res = serde_json::to_string(&ConfirmationRequest::from(request)); + let expected = r#"{"id":"0x0f","payload":{"transaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x0186a0","data":"0x010203","nonce":"0x01"}}}"#; + + // then + assert_eq!(res.unwrap(), expected.to_owned()); + } + + #[test] + fn should_deserialize_modification() { + // given + let s1 = r#"{ + "gasPrice":"0x0ba43b7400" + }"#; + let s2 = r#"{}"#; + + // when + let res1: TransactionModification = serde_json::from_str(s1).unwrap(); + let res2: TransactionModification = serde_json::from_str(s2).unwrap(); + + // then + assert_eq!(res1, TransactionModification { + gas_price: Some(U256::from_str("0ba43b7400").unwrap()), + }); + assert_eq!(res2, TransactionModification { + gas_price: None, + }); + } +} + diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 75f78906b..f51271123 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -17,6 +17,8 @@ mod bytes; mod block; mod block_number; +mod call_request; +mod confirmations; mod filter; mod hash; mod index; @@ -24,7 +26,6 @@ mod log; mod sync; mod transaction; mod transaction_request; -mod call_request; mod receipt; mod trace; mod trace_filter; @@ -33,14 +34,15 @@ mod uint; pub use self::bytes::Bytes; pub use self::block::{Block, BlockTransactions}; pub use self::block_number::BlockNumber; +pub use self::call_request::CallRequest; +pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, TransactionModification}; pub use self::filter::Filter; pub use self::hash::{H64, H160, H256, H520, H2048}; pub use self::index::Index; pub use self::log::Log; pub use self::sync::{SyncStatus, SyncInfo}; pub use self::transaction::Transaction; -pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification}; -pub use self::call_request::CallRequest; +pub use self::transaction_request::TransactionRequest; pub use self::receipt::Receipt; pub use self::trace::{LocalizedTrace, TraceResults}; pub use self::trace_filter::TraceFilter; diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 12e2c81fe..b7ee1f47d 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -17,7 +17,7 @@ //! `TransactionRequest` type use v1::types::{Bytes, H160, U256}; -use v1::helpers::{TransactionRequest as Request, TransactionConfirmation as Confirmation}; +use v1::helpers; /// Transaction request coming from RPC #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] @@ -39,8 +39,8 @@ pub struct TransactionRequest { pub nonce: Option, } -impl From for TransactionRequest { - fn from(r: Request) -> Self { +impl From for TransactionRequest { + fn from(r: helpers::TransactionRequest) -> Self { TransactionRequest { from: r.from.into(), to: r.to.map(Into::into), @@ -53,9 +53,23 @@ impl From for TransactionRequest { } } -impl Into for TransactionRequest { - fn into(self) -> Request { - Request { +impl From for TransactionRequest { + fn from(r: helpers::FilledTransactionRequest) -> Self { + TransactionRequest { + from: r.from.into(), + to: r.to.map(Into::into), + gas_price: Some(r.gas_price.into()), + gas: Some(r.gas.into()), + value: Some(r.value.into()), + data: Some(r.data.into()), + nonce: r.nonce.map(Into::into), + } + } +} + +impl Into for TransactionRequest { + fn into(self) -> helpers::TransactionRequest { + helpers::TransactionRequest { from: self.from.into(), to: self.to.map(Into::into), gas_price: self.gas_price.map(Into::into), @@ -67,32 +81,6 @@ impl Into for TransactionRequest { } } -/// Transaction confirmation waiting in a queue -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize)] -pub struct TransactionConfirmation { - /// Id of this confirmation - pub id: U256, - /// TransactionRequest - pub transaction: TransactionRequest, -} - -impl From for TransactionConfirmation { - fn from(c: Confirmation) -> Self { - TransactionConfirmation { - id: c.id.into(), - transaction: c.transaction.into(), - } - } -} - -/// Possible modifications to the confirmed transaction sent by `SignerUI` -#[derive(Debug, PartialEq, Deserialize)] -pub struct TransactionModification { - /// Modified gas price - #[serde(rename="gasPrice")] - pub gas_price: Option, -} - #[cfg(test)] mod tests { @@ -188,7 +176,6 @@ mod tests { }); } - #[test] fn transaction_request_deserialize_error() { let s = r#"{ @@ -203,26 +190,5 @@ mod tests { assert!(deserialized.is_err(), "Should be error because to is empty"); } - - #[test] - fn should_deserialize_modification() { - // given - let s1 = r#"{ - "gasPrice":"0x0ba43b7400" - }"#; - let s2 = r#"{}"#; - - // when - let res1: TransactionModification = serde_json::from_str(s1).unwrap(); - let res2: TransactionModification = serde_json::from_str(s2).unwrap(); - - // then - assert_eq!(res1, TransactionModification { - gas_price: Some(U256::from_str("0ba43b7400").unwrap()), - }); - assert_eq!(res2, TransactionModification { - gas_price: None, - }); - } } diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index 40f9ee4fe..e99986205 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -53,6 +53,7 @@ pub struct ServerBuilder { queue: Arc, handler: Arc, authcodes_path: PathBuf, + skip_origin_validation: bool, } impl Extendable for ServerBuilder { @@ -68,13 +69,21 @@ impl ServerBuilder { queue: queue, handler: Arc::new(IoHandler::new()), authcodes_path: authcodes_path, + skip_origin_validation: false, } } + /// If set to `true` server will not verify Origin of incoming requests. + /// Not recommended. Use only for development. + pub fn skip_origin_validation(mut self, skip: bool) -> Self { + self.skip_origin_validation = skip; + self + } + /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. pub fn start(self, addr: SocketAddr) -> Result { - Server::start(addr, self.handler, self.queue, self.authcodes_path) + Server::start(addr, self.handler, self.queue, self.authcodes_path, self.skip_origin_validation) } } @@ -89,10 +98,10 @@ pub struct Server { impl Server { /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. - fn start(addr: SocketAddr, handler: Arc, queue: Arc, authcodes_path: PathBuf) -> Result { + fn start(addr: SocketAddr, handler: Arc, queue: Arc, authcodes_path: PathBuf, skip_origin_validation: bool) -> Result { let config = { let mut config = ws::Settings::default(); - // It's also used for handling min-sysui requests (browser can make many of them in paralel) + // accept only handshakes beginning with GET config.method_strict = true; // Was shutting down server when suspending on linux: config.shutdown_on_interrupt = false; @@ -101,7 +110,9 @@ impl Server { // Create WebSocket let origin = format!("{}", addr); - let ws = try!(ws::Builder::new().with_settings(config).build(session::Factory::new(handler, origin, authcodes_path))); + let ws = try!(ws::Builder::new().with_settings(config).build( + session::Factory::new(handler, origin, authcodes_path, skip_origin_validation) + )); let panic_handler = PanicHandler::new_in_arc(); let ph = panic_handler.clone(); diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs index dd54baf30..af3e03bd1 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -96,6 +96,7 @@ fn add_headers(mut response: ws::Response, mime: &str) -> ws::Response { pub struct Session { out: ws::Sender, + skip_origin_validation: bool, self_origin: String, authcodes_path: PathBuf, handler: Arc, @@ -107,9 +108,11 @@ impl ws::Handler for Session { let host = req.header("host").or_else(|| req.header("Host")).map(|x| &x[..]); // Check request origin and host header. - if !origin_is_allowed(&self.self_origin, origin) && !(origin.is_none() && origin_is_allowed(&self.self_origin, host)) { - warn!(target: "signer", "Blocked connection to Signer API from untrusted origin."); - return Ok(ws::Response::forbidden(format!("You are not allowed to access system ui. Use: http://{}", self.self_origin))); + if !self.skip_origin_validation { + if !origin_is_allowed(&self.self_origin, origin) && !(origin.is_none() && origin_is_allowed(&self.self_origin, host)) { + warn!(target: "signer", "Blocked connection to Signer API from untrusted origin."); + return Ok(ws::Response::forbidden(format!("You are not allowed to access system ui. Use: http://{}", self.self_origin))); + } } // Detect if it's a websocket request. @@ -150,14 +153,16 @@ impl ws::Handler for Session { pub struct Factory { handler: Arc, + skip_origin_validation: bool, self_origin: String, authcodes_path: PathBuf, } impl Factory { - pub fn new(handler: Arc, self_origin: String, authcodes_path: PathBuf) -> Self { + pub fn new(handler: Arc, self_origin: String, authcodes_path: PathBuf, skip_origin_validation: bool) -> Self { Factory { handler: handler, + skip_origin_validation: skip_origin_validation, self_origin: self_origin, authcodes_path: authcodes_path, } @@ -171,6 +176,7 @@ impl ws::Factory for Factory { Session { out: sender, handler: self.handler.clone(), + skip_origin_validation: self.skip_origin_validation, self_origin: self.self_origin.clone(), authcodes_path: self.authcodes_path.clone(), } diff --git a/sync/Cargo.toml b/sync/Cargo.toml index 3077ee78b..71ae2a686 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -9,7 +9,6 @@ build = "build.rs" [lib] [build-dependencies] -syntex = "0.33" ethcore-ipc-codegen = { path = "../ipc/codegen" } [dependencies] diff --git a/sync/build.rs b/sync/build.rs index e0c418ea1..cdb717e0e 100644 --- a/sync/build.rs +++ b/sync/build.rs @@ -14,26 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -extern crate syntex; -extern crate ethcore_ipc_codegen as codegen; - -use std::env; -use std::path::Path; +extern crate ethcore_ipc_codegen; fn main() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - - // sync interface - { - let src = Path::new("src/api.rs"); - let intermediate = Path::new(&out_dir).join("api.intermediate.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &src, &intermediate).unwrap(); - - let dst = Path::new(&out_dir).join("api.ipc.rs"); - let mut registry = syntex::Registry::new(); - codegen::register(&mut registry); - registry.expand("", &intermediate, &dst).unwrap(); - } + ethcore_ipc_codegen::derive_ipc("src/api.rs").unwrap(); } diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 29a07393c..f6884235b 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -84,7 +84,7 @@ mod tests; mod api { #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues - include!(concat!(env!("OUT_DIR"), "/api.ipc.rs")); + include!(concat!(env!("OUT_DIR"), "/api.rs")); } pub use api::{EthSync, SyncProvider, SyncClient, NetworkManagerClient, ManageNetwork, SyncConfig, diff --git a/util/src/bytes.rs b/util/src/bytes.rs index a3a546d7a..3c022e8bf 100644 --- a/util/src/bytes.rs +++ b/util/src/bytes.rs @@ -36,7 +36,7 @@ use std::fmt; use std::slice; use std::ops::{Deref, DerefMut}; -/// Slie pretty print helper +/// Slice pretty print helper pub struct PrettySlice<'a> (&'a [u8]); impl<'a> fmt::Debug for PrettySlice<'a> { diff --git a/util/using_queue/src/lib.rs b/util/using_queue/src/lib.rs index 0daa66f59..24dae94b9 100644 --- a/util/using_queue/src/lib.rs +++ b/util/using_queue/src/lib.rs @@ -71,6 +71,9 @@ impl UsingQueue where T: Clone { self.pending = Some(b); } + /// Is there anything in the queue currently? + pub fn is_in_use(&self) -> bool { self.in_use.len() > 0 } + /// Clears everything; the queue is entirely reset. pub fn reset(&mut self) { self.pending = None;