diff --git a/Cargo.lock b/Cargo.lock index 1467edd23..aa904abe7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,11 @@ name = "ansi_term" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "antidote" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "app_dirs" version = "1.1.1" @@ -285,6 +290,11 @@ name = "dtoa" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "dtoa" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "either" version = "1.0.2" @@ -784,7 +794,8 @@ dependencies = [ "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -901,6 +912,35 @@ dependencies = [ "vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hyper" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hyper-native-tls" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "idna" version = "0.1.0" @@ -962,6 +1002,11 @@ name = "itoa" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "itoa" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "jsonrpc-core" version = "5.0.0" @@ -1522,7 +1567,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#cb8dc7c61dd7976f062863182a1c6d77ba319a36" +source = "git+https://github.com/ethcore/js-precompiled.git#416d00db677b8219f7548bb4dfa2f25c4b19f36e" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1720,15 +1765,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "reqwest" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1914,6 +1959,11 @@ name = "serde" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "serde" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde_codegen" version = "0.8.19" @@ -1946,11 +1996,24 @@ dependencies = [ ] [[package]] -name = "serde_urlencoded" -version = "0.3.0" +name = "serde_json" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", + "dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_urlencoded" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2410,6 +2473,7 @@ dependencies = [ "checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" "checksum aho-corasick 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67077478f0a03952bed2e6786338d400d40c25e9836e08ad50af96607317fd03" "checksum ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1f46cd5b1d660c938e3f92dfe7a73d832b3281479363dd0cd9c1c2fbf60f7962" +"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" "checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "16e3bdb2f54b3ace0285975d59a97cf8ed3855294b2b6bc651fcf22a9c352975" "checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a" @@ -2438,6 +2502,7 @@ dependencies = [ "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf" "checksum docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4cc0acb4ce0828c6a5a11d47baa432fe885881c27428c3a4e473e454ffe57a76" "checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d" +"checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" "checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91" "checksum elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)" = "" "checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5" @@ -2455,13 +2520,16 @@ dependencies = [ "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" "checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" "checksum hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)" = "" +"checksum hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "220407e5a263f110ec30a071787c9535918fdfc97def5680c90013c3f30c38c1" "checksum hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb3fc65554155980167fb821d05c7c66177f92464976c0b676a19d9e03387a7" +"checksum hyper-native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "afe68f772f0497a7205e751626bb8e1718568b58534b6108c73a74ef80483409" "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" "checksum igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8c12b1795b8b168f577c45fa10379b3814dcb11b7ab702406001f0d63f40484" "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" "checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76" "checksum itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a" "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" +"checksum itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5" "checksum jsonrpc-core 5.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" "checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" "checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "" @@ -2536,7 +2604,7 @@ dependencies = [ "checksum rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "655df67c314c30fa3055a365eae276eb88aa4f3413a352a1ab32c1320eda41ea" "checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29" "checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9" -"checksum reqwest 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83186fee0d4dbeb95e610b77b05b05cf5b31703dd375222acb74c3dff4be957c" +"checksum reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bef9ed8fdfcc30947d6b774938dc0c3f369a474efe440df2c7f278180b2d2e6" "checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "" "checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "" @@ -2555,10 +2623,12 @@ dependencies = [ "checksum semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae2ff60ecdb19c255841c066cbfa5f8c2a4ada1eb3ae47c77ab6667128da71f5" "checksum semver-parser 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e88e43a5a74dd2a11707f9c21dfd4a423c66bd871df813227bb0a3e78f3a1ae9" "checksum serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)" = "58a19c0871c298847e6b68318484685cd51fa5478c0c905095647540031356e5" +"checksum serde 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4d8f810025e9d09c4eaa49c16eaf878f34a947889e878cd7d3b5bef3197cc119" "checksum serde_codegen 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)" = "ce29a6ae259579707650ec292199b5fed2c0b8e2a4bdc994452d24d1bcf2242a" "checksum serde_codegen_internals 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "59933a62554548c690d2673c5164f0c4a46be7c5731edfd94b0ecb1048940732" "checksum serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7d3c184d35801fb8b32b46a7d58d57dbcc150b0eb2b46a1eb79645e8ecfd5b" -"checksum serde_urlencoded 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "53d4ebaa8d1d4f90d1b63dfca81ccd98ac20e1e479dbae393cbaf60f6fecd8d8" +"checksum serde_json 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fea48f4d4df4e620e3c81fd2bf28c93dd0d266361a76bac4f254b71f0e13f3cd" +"checksum serde_urlencoded 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a81f15da4b9780e1524697f73b09076b6e42298ef673bead9ca8f848b334ef84" "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d" "checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd" diff --git a/ethcore/light/src/client/header_chain.rs b/ethcore/light/src/client/header_chain.rs index bc007785e..53c726b69 100644 --- a/ethcore/light/src/client/header_chain.rs +++ b/ethcore/light/src/client/header_chain.rs @@ -214,6 +214,7 @@ impl HeaderChain { pub fn get_header(&self, id: BlockId) -> Option { match id { BlockId::Earliest | BlockId::Number(0) => Some(self.genesis_header.clone()), + BlockId::Latest if self.headers.read().is_empty() => Some(self.genesis_header.clone()), BlockId::Hash(hash) => self.headers.read().get(&hash).map(|x| x.to_vec()), BlockId::Number(num) => { if self.best_block.read().number < num { return None } diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index d0d8a7bb9..c2b57be24 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -21,8 +21,9 @@ use ethcore::block_status::BlockStatus; use ethcore::client::ClientReport; use ethcore::ids::BlockId; use ethcore::header::Header; +use ethcore::views::HeaderView; use ethcore::verification::queue::{self, HeaderQueue}; -use ethcore::transaction::PendingTransaction; +use ethcore::transaction::{PendingTransaction, Condition as TransactionCondition}; use ethcore::blockchain_info::BlockChainInfo; use ethcore::spec::Spec; use ethcore::service::ClientIoMessage; @@ -34,6 +35,7 @@ use util::{Bytes, Mutex, RwLock}; use provider::Provider; use request; +use time; use self::header_chain::HeaderChain; @@ -110,7 +112,11 @@ impl Client { let best_num = self.chain.best_block().number; self.tx_pool.lock() .values() - .filter(|t| t.min_block.as_ref().map_or(true, |x| x <= &best_num)) + .filter(|t| match t.condition { + Some(TransactionCondition::Number(ref x)) => x <= &best_num, + Some(TransactionCondition::Timestamp(ref x)) => *x <= time::get_time().sec as u64, + None => true, + }) .cloned() .collect() } @@ -135,6 +141,7 @@ impl Client { genesis_hash: genesis_hash, best_block_hash: best_block.hash, best_block_number: best_block.number, + best_block_timestamp: HeaderView::new(&self.chain.get_header(BlockId::Latest).expect("Latest hash is always in the chain")).timestamp(), ancient_block_hash: if first_block.is_some() { Some(genesis_hash) } else { None }, ancient_block_number: if first_block.is_some() { Some(0) } else { None }, first_block_hash: first_block.as_ref().map(|first| first.hash), diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index a1262fa33..83372fea5 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -9,7 +9,11 @@ "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" ] - } + }, + "timeoutPropose": 10000, + "timeoutPrevote": 10000, + "timeoutPrecommit": 10000, + "timeoutCommit": 10000 } } }, diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index c9e6a0fc6..46690fabd 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -21,7 +21,7 @@ mod stores; use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy}; use std::fmt; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::time::{Instant, Duration}; use util::RwLock; use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, @@ -114,8 +114,8 @@ impl AccountProvider { pub fn new(sstore: Box) -> Self { AccountProvider { unlocked: RwLock::new(HashMap::new()), - address_book: RwLock::new(AddressBook::new(sstore.local_path().into())), - dapps_settings: RwLock::new(DappsSettingsStore::new(sstore.local_path().into())), + address_book: RwLock::new(AddressBook::new(&sstore.local_path())), + dapps_settings: RwLock::new(DappsSettingsStore::new(&sstore.local_path())), sstore: sstore, transient_sstore: transient_sstore(), } @@ -216,7 +216,7 @@ impl AccountProvider { Some(accounts) => Ok(accounts), None => match dapps.policy() { NewDappsPolicy::AllAccounts => self.accounts(), - NewDappsPolicy::Whitelist(accounts) => Ok(accounts), + NewDappsPolicy::Whitelist(accounts) => self.filter_addresses(accounts), } } } @@ -231,28 +231,42 @@ impl AccountProvider { /// Sets addresses visile for dapp. pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec
) -> Result<(), Error> { + let addresses = self.filter_addresses(addresses)?; self.dapps_settings.write().set_accounts(dapp, addresses); Ok(()) } - /// Returns each address along with metadata. - pub fn addresses_info(&self) -> Result, Error> { - Ok(self.address_book.read().get()) + /// Removes addresses that are neither accounts nor in address book. + fn filter_addresses(&self, addresses: Vec
) -> Result, Error> { + let valid = self.addresses_info().into_iter() + .map(|(address, _)| address) + .chain(self.accounts()?) + .collect::>(); + + Ok(addresses.into_iter() + .filter(|a| valid.contains(&a)) + .collect() + ) } /// Returns each address along with metadata. - pub fn set_address_name(&self, account: Address, name: String) -> Result<(), Error> { - Ok(self.address_book.write().set_name(account, name)) + pub fn addresses_info(&self) -> HashMap { + self.address_book.read().get() } /// Returns each address along with metadata. - pub fn set_address_meta(&self, account: Address, meta: String) -> Result<(), Error> { - Ok(self.address_book.write().set_meta(account, meta)) + pub fn set_address_name(&self, account: Address, name: String) { + self.address_book.write().set_name(account, name) + } + + /// Returns each address along with metadata. + pub fn set_address_meta(&self, account: Address, meta: String) { + self.address_book.write().set_meta(account, meta) } /// Removes and address from the addressbook - pub fn remove_address(&self, addr: Address) -> Result<(), Error> { - Ok(self.address_book.write().remove(addr)) + pub fn remove_address(&self, addr: Address) { + self.address_book.write().remove(addr) } /// Returns each account along with name and meta. @@ -502,9 +516,12 @@ mod tests { let app = DappId("app1".into()); // set `AllAccounts` policy ap.set_new_dapps_whitelist(None).unwrap(); + // add accounts to address book + ap.set_address_name(1.into(), "1".into()); + ap.set_address_name(2.into(), "2".into()); // when - ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into()]).unwrap(); + ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into(), 3.into()]).unwrap(); // then assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); @@ -515,6 +532,7 @@ mod tests { // given let ap = AccountProvider::transient_provider(); let address = ap.new_account("test").unwrap(); + ap.set_address_name(1.into(), "1".into()); // When returning nothing ap.set_new_dapps_whitelist(Some(vec![])).unwrap(); @@ -524,6 +542,10 @@ mod tests { ap.set_new_dapps_whitelist(None).unwrap(); assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![address]); + // change to non-existent account + ap.set_new_dapps_whitelist(Some(vec![2.into()])).unwrap(); + assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![]); + // change to a whitelist ap.set_new_dapps_whitelist(Some(vec![1.into()])).unwrap(); assert_eq!(ap.dapps_addresses("app1".into()).unwrap(), vec![1.into()]); diff --git a/ethcore/src/account_provider/stores.rs b/ethcore/src/account_provider/stores.rs index d5d55e57b..e4bd7e1b9 100644 --- a/ethcore/src/account_provider/stores.rs +++ b/ethcore/src/account_provider/stores.rs @@ -19,7 +19,7 @@ use std::{fs, fmt, hash, ops}; use std::sync::atomic::{self, AtomicUsize}; use std::collections::HashMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use ethstore::ethkey::Address; use ethjson::misc::{ @@ -37,9 +37,9 @@ pub struct AddressBook { impl AddressBook { /// Creates new address book at given directory. - pub fn new(path: String) -> Self { + pub fn new(path: &Path) -> Self { let mut r = AddressBook { - cache: DiskMap::new(path, "address_book.json".into()) + cache: DiskMap::new(path, "address_book.json") }; r.cache.revert(AccountMeta::read); r @@ -200,11 +200,11 @@ pub struct DappsSettingsStore { impl DappsSettingsStore { /// Creates new store at given directory path. - pub fn new(path: String) -> Self { + pub fn new(path: &Path) -> Self { let mut r = DappsSettingsStore { - settings: DiskMap::new(path.clone(), "dapps_accounts.json".into()), - policy: DiskMap::new(path.clone(), "dapps_policy.json".into()), - history: DiskMap::new(path.clone(), "dapps_history.json".into()), + settings: DiskMap::new(path, "dapps_accounts.json".into()), + policy: DiskMap::new(path, "dapps_policy.json".into()), + history: DiskMap::new(path, "dapps_history.json".into()), time: TimeProvider::Clock, }; r.settings.revert(JsonSettings::read); @@ -297,9 +297,8 @@ impl ops::DerefMut for DiskMap { } impl DiskMap { - pub fn new(path: String, file_name: String) -> Self { - trace!(target: "diskmap", "new({})", path); - let mut path: PathBuf = path.into(); + pub fn new(path: &Path, file_name: &str) -> Self { + let mut path = path.to_owned(); path.push(file_name); trace!(target: "diskmap", "path={:?}", path); DiskMap { @@ -310,7 +309,7 @@ impl DiskMap { } pub fn transient() -> Self { - let mut map = DiskMap::new(Default::default(), "diskmap.json".into()); + let mut map = DiskMap::new(&PathBuf::new(), "diskmap.json".into()); map.transient = true; map } @@ -354,27 +353,25 @@ mod tests { #[test] fn should_save_and_reload_address_book() { - let temp = RandomTempPath::create_dir(); - let path = temp.as_str().to_owned(); - let mut b = AddressBook::new(path.clone()); + let path = RandomTempPath::create_dir(); + let mut b = AddressBook::new(&path); b.set_name(1.into(), "One".to_owned()); b.set_meta(1.into(), "{1:1}".to_owned()); - let b = AddressBook::new(path); + let b = AddressBook::new(&path); assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]); } #[test] fn should_remove_address() { - let temp = RandomTempPath::create_dir(); - let path = temp.as_str().to_owned(); - let mut b = AddressBook::new(path.clone()); + let path = RandomTempPath::create_dir(); + let mut b = AddressBook::new(&path); b.set_name(1.into(), "One".to_owned()); b.set_name(2.into(), "Two".to_owned()); b.set_name(3.into(), "Three".to_owned()); b.remove(2.into()); - let b = AddressBook::new(path); + let b = AddressBook::new(&path); assert_eq!(b.get(), hash_map![ 1.into() => AccountMeta{name: "One".to_owned(), meta: "{}".to_owned(), uuid: None}, 3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None} @@ -384,15 +381,14 @@ mod tests { #[test] fn should_save_and_reload_dapps_settings() { // given - let temp = RandomTempPath::create_dir(); - let path = temp.as_str().to_owned(); - let mut b = DappsSettingsStore::new(path.clone()); + let path = RandomTempPath::create_dir(); + let mut b = DappsSettingsStore::new(&path); // when b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]); // then - let b = DappsSettingsStore::new(path); + let b = DappsSettingsStore::new(&path); assert_eq!(b.settings(), hash_map![ "dappOne".into() => DappsSettings { accounts: vec![1.into(), 2.into()], @@ -422,9 +418,8 @@ mod tests { #[test] fn should_store_dapps_policy() { // given - let temp = RandomTempPath::create_dir(); - let path = temp.as_str().to_owned(); - let mut store = DappsSettingsStore::new(path.clone()); + let path = RandomTempPath::create_dir(); + let mut store = DappsSettingsStore::new(&path); // Test default policy assert_eq!(store.policy(), NewDappsPolicy::AllAccounts); @@ -433,7 +428,7 @@ mod tests { store.set_policy(NewDappsPolicy::Whitelist(vec![1.into(), 2.into()])); // then - let store = DappsSettingsStore::new(path); + let store = DappsSettingsStore::new(&path); assert_eq!(store.policy.clone(), hash_map![ "default".into() => NewDappsPolicy::Whitelist(vec![1.into(), 2.into()]) ]); diff --git a/ethcore/src/blockchain/best_block.rs b/ethcore/src/blockchain/best_block.rs index 4dad72fe2..e857a99b6 100644 --- a/ethcore/src/blockchain/best_block.rs +++ b/ethcore/src/blockchain/best_block.rs @@ -24,6 +24,8 @@ pub struct BestBlock { pub hash: H256, /// Best block number. pub number: BlockNumber, + /// Best block timestamp. + pub timestamp: u64, /// Best block total difficulty. pub total_difficulty: U256, /// Best block uncompressed bytes diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 3876a8c72..776c9e752 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -485,6 +485,7 @@ impl BlockChain { let best_block_number = bc.block_number(&best_block_hash).unwrap(); let best_block_total_difficulty = bc.block_details(&best_block_hash).unwrap().total_difficulty; let best_block_rlp = bc.block(&best_block_hash).unwrap().into_inner(); + let best_block_timestamp = BlockView::new(&best_block_rlp).header().timestamp(); let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map(|v| v.to_vec()); let mut best_ancient = bc.db.get(db::COL_EXTRA, b"ancient").unwrap().map(|h| H256::from_slice(&h)); @@ -533,6 +534,7 @@ impl BlockChain { number: best_block_number, total_difficulty: best_block_total_difficulty, hash: best_block_hash, + timestamp: best_block_timestamp, block: best_block_rlp, }; @@ -585,6 +587,7 @@ impl BlockChain { number: extras.number - 1, total_difficulty: best_block_total_difficulty, hash: hash, + timestamp: BlockView::new(&best_block_rlp).header().timestamp(), block: best_block_rlp, }; // update parent extras @@ -738,6 +741,7 @@ impl BlockChain { blocks_blooms: self.prepare_block_blooms_update(bytes, &info), transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info), info: info, + timestamp: header.timestamp(), block: bytes }, is_best); @@ -786,6 +790,7 @@ impl BlockChain { blocks_blooms: self.prepare_block_blooms_update(bytes, &info), transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info), info: info, + timestamp: header.timestamp(), block: bytes, }, is_best); true @@ -850,6 +855,7 @@ impl BlockChain { blocks_blooms: self.prepare_block_blooms_update(bytes, &info), transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info), info: info.clone(), + timestamp: header.timestamp(), block: bytes, }, true); @@ -921,6 +927,7 @@ impl BlockChain { hash: update.info.hash, number: update.info.number, total_difficulty: update.info.total_difficulty, + timestamp: update.timestamp, block: update.block.to_vec(), }); }, @@ -1206,6 +1213,11 @@ impl BlockChain { self.best_block.read().number } + /// Get best block timestamp. + pub fn best_block_timestamp(&self) -> u64 { + self.best_block.read().timestamp + } + /// Get best block total difficulty. pub fn best_block_total_difficulty(&self) -> U256 { self.best_block.read().total_difficulty @@ -1293,6 +1305,7 @@ impl BlockChain { genesis_hash: self.genesis_hash(), best_block_hash: best_block.hash.clone(), best_block_number: best_block.number, + best_block_timestamp: best_block.timestamp, first_block_hash: self.first_block(), first_block_number: From::from(self.first_block_number()), ancient_block_hash: best_ancient_block.as_ref().map(|b| b.hash.clone()), diff --git a/ethcore/src/blockchain/update.rs b/ethcore/src/blockchain/update.rs index 0d7d1dbed..914b8aa99 100644 --- a/ethcore/src/blockchain/update.rs +++ b/ethcore/src/blockchain/update.rs @@ -9,6 +9,8 @@ use super::extras::{BlockDetails, BlockReceipts, TransactionAddress, LogGroupPos pub struct ExtrasUpdate<'a> { /// Block info. pub info: BlockInfo, + /// Block timestamp. + pub timestamp: u64, /// Current block uncompressed rlp bytes pub block: &'a [u8], /// Modified block hashes. diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index f2ad08819..77189c3fd 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1406,7 +1406,11 @@ impl BlockChainClient for Client { } fn ready_transactions(&self) -> Vec { - self.miner.ready_transactions(self.chain.read().best_block_number()) + let (number, timestamp) = { + let chain = self.chain.read(); + (chain.best_block_number(), chain.best_block_timestamp()) + }; + self.miner.ready_transactions(number, timestamp) } fn queue_consensus_message(&self, message: Bytes) { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index af2f97cee..46785a6cb 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -669,12 +669,14 @@ impl BlockChainClient for TestBlockChainClient { } fn chain_info(&self) -> BlockChainInfo { + let number = self.blocks.read().len() as BlockNumber - 1; BlockChainInfo { total_difficulty: *self.difficulty.read(), pending_total_difficulty: *self.difficulty.read(), genesis_hash: self.genesis_hash.clone(), best_block_hash: self.last_hash.read().clone(), - best_block_number: self.blocks.read().len() as BlockNumber - 1, + best_block_number: number, + best_block_timestamp: number, first_block_hash: self.first_block.read().as_ref().map(|x| x.0), first_block_number: self.first_block.read().as_ref().map(|x| x.1), ancient_block_hash: self.ancient_block.read().as_ref().map(|x| x.0), @@ -709,7 +711,8 @@ impl BlockChainClient for TestBlockChainClient { } fn ready_transactions(&self) -> Vec { - self.miner.ready_transactions(self.chain_info().best_block_number) + let info = self.chain_info(); + self.miner.ready_transactions(info.best_block_number, info.best_block_timestamp) } fn signing_network_id(&self) -> Option { None } diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 583b054e3..9c69ce6ad 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -159,7 +159,7 @@ impl IoHandler<()> for TransitionHandler { fn initialize(&self, io: &IoContext<()>) { if let Some(engine) = self.engine.upgrade() { io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) - .unwrap_or_else(|e| warn!(target: "poa", "Failed to start consensus step timer: {}.", e)) + .unwrap_or_else(|e| warn!(target: "engine", "Failed to start consensus step timer: {}.", e)) } } @@ -168,7 +168,7 @@ impl IoHandler<()> for TransitionHandler { if let Some(engine) = self.engine.upgrade() { engine.step(); io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) - .unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) + .unwrap_or_else(|e| warn!(target: "engine", "Failed to restart consensus step timer: {}.", e)) } } } @@ -234,14 +234,14 @@ impl Engine for AuthorityRound { let step = self.step.load(AtomicOrdering::SeqCst); if self.is_step_proposer(step, header.author()) { if let Ok(signature) = self.signer.sign(header.bare_hash()) { - trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); + trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step); self.proposed.store(true, AtomicOrdering::SeqCst); return Seal::Regular(vec![encode(&step).to_vec(), encode(&(&H520::from(signature) as &[u8])).to_vec()]); } else { - warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable."); + warn!(target: "engine", "generate_seal: FAIL: Accounts secret key unavailable."); } } else { - trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step); + trace!(target: "engine", "generate_seal: Not a proposer for step {}.", step); } Seal::None } @@ -260,7 +260,7 @@ impl Engine for AuthorityRound { /// Check the number of seal fields. fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { if header.seal().len() != self.seal_fields() { - trace!(target: "poa", "verify_block_basic: wrong number of seal fields"); + trace!(target: "engine", "verify_block_basic: wrong number of seal fields"); Err(From::from(BlockError::InvalidSealArity( Mismatch { expected: self.seal_fields(), found: header.seal().len() } ))) @@ -279,11 +279,11 @@ impl Engine for AuthorityRound { if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? { Ok(()) } else { - trace!(target: "poa", "verify_block_unordered: bad proposer for step: {}", header_step); + trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step); Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? } } else { - trace!(target: "poa", "verify_block_unordered: block from the future"); + trace!(target: "engine", "verify_block_unordered: block from the future"); self.validators.report_benign(header.author()); Err(BlockError::InvalidSeal)? } @@ -297,7 +297,7 @@ impl Engine for AuthorityRound { let step = header_step(header)?; // Check if parent is from a previous step. if step == header_step(parent)? { - trace!(target: "poa", "Multiple blocks proposed for step {}.", step); + trace!(target: "engine", "Multiple blocks proposed for step {}.", step); self.validators.report_malicious(header.author()); Err(EngineError::DoubleVote(header.author().clone()))?; } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e8a05166c..a8f953d6f 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -159,13 +159,13 @@ impl Tendermint { let message = ConsensusMessage::new(signature, h, r, *s, block_hash); let validator = self.signer.address(); self.votes.vote(message.clone(), &validator); - debug!(target: "poa", "Generated {:?} as {}.", message, validator); + debug!(target: "engine", "Generated {:?} as {}.", message, validator); self.handle_valid_message(&message); Some(message_rlp) }, Err(e) => { - trace!(target: "poa", "Could not sign the message {}", e); + trace!(target: "engine", "Could not sign the message {}", e); None }, } @@ -186,7 +186,7 @@ impl Tendermint { fn to_next_height(&self, height: Height) { let new_height = height + 1; - debug!(target: "poa", "Received a Commit, transitioning to height {}.", new_height); + debug!(target: "engine", "Received a Commit, transitioning to height {}.", new_height); self.last_lock.store(0, AtomicOrdering::SeqCst); self.height.store(new_height, AtomicOrdering::SeqCst); self.view.store(0, AtomicOrdering::SeqCst); @@ -196,7 +196,7 @@ impl Tendermint { /// Use via step_service to transition steps. fn to_step(&self, step: Step) { if let Err(io_err) = self.step_service.send_message(step) { - warn!(target: "poa", "Could not proceed to step {}.", io_err) + warn!(target: "engine", "Could not proceed to step {}.", io_err) } *self.step.write() = step; match step { @@ -212,10 +212,10 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Precommit => { - trace!(target: "poa", "to_step: Precommit."); + trace!(target: "engine", "to_step: Precommit."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.is_view(m) && m.block_hash.is_some() => { - trace!(target: "poa", "Setting last lock: {}", m.vote_step.view); + trace!(target: "engine", "Setting last lock: {}", m.vote_step.view); self.last_lock.store(m.vote_step.view, AtomicOrdering::SeqCst); m.block_hash }, @@ -224,7 +224,7 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Commit => { - trace!(target: "poa", "to_step: Commit."); + trace!(target: "engine", "to_step: Commit."); // Commit the block using a complete signature set. let view = self.view.load(AtomicOrdering::SeqCst); let height = self.height.load(AtomicOrdering::SeqCst); @@ -234,7 +234,7 @@ impl Tendermint { let proposal_step = VoteStep::new(height, view, Step::Propose); let precommit_step = VoteStep::new(proposal_step.height, proposal_step.view, Step::Precommit); if let Some(seal) = self.votes.seal_signatures(proposal_step, precommit_step, &block_hash) { - trace!(target: "poa", "Collected seal: {:?}", seal); + trace!(target: "engine", "Collected seal: {:?}", seal); let seal = vec![ ::rlp::encode(&view).to_vec(), ::rlp::encode(&seal.proposal).to_vec(), @@ -243,7 +243,7 @@ impl Tendermint { self.submit_seal(block_hash, seal); self.to_next_height(height); } else { - warn!(target: "poa", "Not enough votes found!"); + warn!(target: "engine", "Not enough votes found!"); } } } @@ -262,7 +262,7 @@ impl Tendermint { /// Find the designated for the given view. fn view_proposer(&self, height: Height, view: View) -> Address { let proposer_nonce = height + view; - trace!(target: "poa", "Proposer nonce: {}", proposer_nonce); + trace!(target: "engine", "Proposer nonce: {}", proposer_nonce); self.validators.get(proposer_nonce) } @@ -291,7 +291,7 @@ impl Tendermint { } fn increment_view(&self, n: View) { - trace!(target: "poa", "increment_view: New view."); + trace!(target: "engine", "increment_view: New view."); self.view.fetch_add(n, AtomicOrdering::SeqCst); } @@ -331,7 +331,7 @@ impl Tendermint { && message.block_hash.is_some() && self.has_enough_aligned_votes(message); if lock_change { - trace!(target: "poa", "handle_valid_message: Lock change."); + trace!(target: "engine", "handle_valid_message: Lock change."); *self.lock_change.write() = Some(message.clone()); } // Check if it can affect the step transition. @@ -349,7 +349,7 @@ impl Tendermint { self.increment_view(vote_step.view - self.view.load(AtomicOrdering::SeqCst)); Some(Step::Precommit) }, - // Avoid counting twice. + // Avoid counting votes twice. Step::Prevote if lock_change => Some(Step::Precommit), Step::Prevote if self.has_enough_aligned_votes(message) => Some(Step::Precommit), Step::Prevote if self.has_enough_future_step_votes(&vote_step) => { @@ -360,7 +360,7 @@ impl Tendermint { }; if let Some(step) = next_step { - trace!(target: "poa", "Transition to {:?} triggered.", step); + trace!(target: "engine", "Transition to {:?} triggered.", step); self.to_step(step); } } @@ -429,7 +429,7 @@ impl Engine for Tendermint { let vote_info = message_info_rlp(&VoteStep::new(height, view, Step::Propose), bh.clone()); if let Ok(signature) = self.signer.sign(vote_info.sha3()).map(Into::into) { // Insert Propose vote. - debug!(target: "poa", "Submitting proposal {} at height {} view {}.", header.bare_hash(), height, view); + debug!(target: "engine", "Submitting proposal {} at height {} view {}.", header.bare_hash(), height, view); self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author); // Remember proposal for later seal submission. *self.proposal.write() = bh; @@ -439,7 +439,7 @@ impl Engine for Tendermint { ::rlp::EMPTY_LIST_RLP.to_vec() ]) } else { - warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); + warn!(target: "engine", "generate_seal: FAIL: accounts secret key unavailable"); Seal::None } } @@ -457,7 +457,7 @@ impl Engine for Tendermint { self.validators.report_malicious(&sender); Err(EngineError::DoubleVote(sender))? } - trace!(target: "poa", "Handling a valid {:?} from {}.", message, sender); + trace!(target: "engine", "Handling a valid {:?} from {}.", message, sender); self.handle_valid_message(&message); } Ok(()) @@ -519,7 +519,7 @@ impl Engine for Tendermint { if origins.insert(address) { signature_count += 1; } else { - warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address); + warn!(target: "engine", "verify_block_unordered: Duplicate signature from {} on the seal.", address); Err(BlockError::InvalidSeal)?; } } @@ -577,12 +577,12 @@ impl Engine for Tendermint { let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed"); if signatures_len != 1 { // New Commit received, skip to next height. - trace!(target: "poa", "Received a commit: {:?}.", proposal.vote_step); + trace!(target: "engine", "Received a commit: {:?}.", proposal.vote_step); self.to_next_height(proposal.vote_step.height); return false; } let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed"); - debug!(target: "poa", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer); + debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer); if self.is_view(&proposal) { *self.proposal.write() = proposal.block_hash.clone(); } @@ -594,7 +594,7 @@ impl Engine for Tendermint { fn step(&self) { let next_step = match *self.step.read() { Step::Propose => { - trace!(target: "poa", "Propose timeout."); + trace!(target: "engine", "Propose timeout."); if self.proposal.read().is_none() { // Report the proposer if no proposal was received. let current_proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); @@ -603,26 +603,26 @@ impl Engine for Tendermint { Step::Prevote }, Step::Prevote if self.has_enough_any_votes() => { - trace!(target: "poa", "Prevote timeout."); + trace!(target: "engine", "Prevote timeout."); Step::Precommit }, Step::Prevote => { - trace!(target: "poa", "Prevote timeout without enough votes."); + trace!(target: "engine", "Prevote timeout without enough votes."); self.broadcast_old_messages(); Step::Prevote }, Step::Precommit if self.has_enough_any_votes() => { - trace!(target: "poa", "Precommit timeout."); + trace!(target: "engine", "Precommit timeout."); self.increment_view(1); Step::Propose }, Step::Precommit => { - trace!(target: "poa", "Precommit timeout without enough votes."); + trace!(target: "engine", "Precommit timeout without enough votes."); self.broadcast_old_messages(); Step::Precommit }, Step::Commit => { - trace!(target: "poa", "Commit timeout."); + trace!(target: "engine", "Commit timeout."); Step::Propose }, }; @@ -838,7 +838,6 @@ mod tests { let (b, seal) = propose_default(&spec, proposer); assert!(b.lock().try_seal(spec.engine.as_ref(), seal).is_ok()); - spec.engine.stop(); } #[test] @@ -850,7 +849,6 @@ mod tests { let (b, seal) = propose_default(&spec, proposer); let sealed = b.lock().seal(spec.engine.as_ref(), seal).unwrap(); assert!(spec.engine.is_proposal(sealed.header())); - spec.engine.stop(); } #[test] @@ -858,7 +856,7 @@ mod tests { let (spec, tap) = setup(); let engine = spec.engine.clone(); - let v0 = insert_and_register(&tap, engine.as_ref(), "0"); + let v0 = insert_and_unlock(&tap, "0"); let v1 = insert_and_register(&tap, engine.as_ref(), "1"); let h = 1; @@ -883,7 +881,6 @@ mod tests { assert!(notify.messages.read().contains(&prevote_current)); assert!(notify.messages.read().contains(&precommit_current)); assert!(notify.messages.read().contains(&prevote_future)); - engine.stop(); } #[test] @@ -933,7 +930,5 @@ mod tests { // Last precommit. vote(engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); assert_eq!(client.chain_info().best_block_number, 1); - - engine.stop(); } } diff --git a/ethcore/src/engines/transition.rs b/ethcore/src/engines/transition.rs index c61ed203d..0211d21a8 100644 --- a/ethcore/src/engines/transition.rs +++ b/ethcore/src/engines/transition.rs @@ -56,7 +56,9 @@ fn set_timeout(io: &IoContext, timeout: Duration) { impl IoHandler for TransitionHandler where S: Sync + Send + Clone + 'static { fn initialize(&self, io: &IoContext) { - set_timeout(io, self.timeouts.initial()); + let initial = self.timeouts.initial(); + trace!(target: "engine", "Setting the initial timeout to {}.", initial); + set_timeout(io, initial); } /// Call step after timeout. diff --git a/ethcore/src/engines/vote_collector.rs b/ethcore/src/engines/vote_collector.rs index 3735db3ff..3f1354ad5 100644 --- a/ethcore/src/engines/vote_collector.rs +++ b/ethcore/src/engines/vote_collector.rs @@ -118,13 +118,13 @@ impl VoteCollector { .get(&message.round()) .map_or(false, |c| { let is_known = c.messages.contains(message); - if is_known { trace!(target: "poa", "Known message: {:?}.", message); } + if is_known { trace!(target: "engine", "Known message: {:?}.", message); } is_known }) || { let guard = self.votes.read(); let is_old = guard.keys().next().map_or(true, |oldest| message.round() <= oldest); - if is_old { trace!(target: "poa", "Old message {:?}.", message); } + if is_old { trace!(target: "engine", "Old message {:?}.", message); } is_old } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 44a23993b..06949f4bd 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -25,7 +25,7 @@ use client::TransactionImportResult; use executive::contract_address; use block::{ClosedBlock, IsBlock, Block}; use error::*; -use transaction::{Action, UnverifiedTransaction, PendingTransaction, SignedTransaction}; +use transaction::{Action, UnverifiedTransaction, PendingTransaction, SignedTransaction, Condition as TransactionCondition}; use receipt::{Receipt, RichReceipt}; use spec::Spec; use engines::{Engine, Seal}; @@ -325,7 +325,7 @@ impl Miner { let _timer = PerfTimer::new("prepare_block"); let chain_info = chain.chain_info(); let (transactions, mut open_block, original_work_hash) = { - let transactions = {self.transaction_queue.lock().top_transactions_at(chain_info.best_block_number)}; + let transactions = {self.transaction_queue.lock().top_transactions_at(chain_info.best_block_number, chain_info.best_block_timestamp)}; let mut sealing_work = self.sealing_work.lock(); let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); let best_hash = chain_info.best_block_hash; @@ -597,7 +597,7 @@ impl Miner { client: &MiningBlockChainClient, transactions: Vec, default_origin: TransactionOrigin, - min_block: Option, + condition: Option, transaction_queue: &mut BanningTransactionQueue, ) -> Vec> { let accounts = self.accounts.as_ref() @@ -635,7 +635,7 @@ impl Miner { let details_provider = TransactionDetailsProvider::new(client, &self.service_transaction_action); match origin { TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { - transaction_queue.add(transaction, origin, insertion_time, min_block, &details_provider) + transaction_queue.add(transaction, origin, insertion_time, condition.clone(), &details_provider) }, TransactionOrigin::External => { transaction_queue.add_with_banlist(transaction, insertion_time, &details_provider) @@ -892,7 +892,7 @@ impl MinerService for Miner { let mut transaction_queue = self.transaction_queue.lock(); // We need to re-validate transactions let import = self.add_transactions_to_queue( - chain, vec![pending.transaction.into()], TransactionOrigin::Local, pending.min_block, &mut transaction_queue + chain, vec![pending.transaction.into()], TransactionOrigin::Local, pending.condition, &mut transaction_queue ).pop().expect("one result returned per added transaction; one added => one result; qed"); match import { @@ -927,7 +927,7 @@ impl MinerService for Miner { fn pending_transactions(&self) -> Vec { let queue = self.transaction_queue.lock(); - queue.pending_transactions(BlockNumber::max_value()) + queue.pending_transactions(BlockNumber::max_value(), u64::max_value()) } fn local_transactions(&self) -> BTreeMap { @@ -942,14 +942,14 @@ impl MinerService for Miner { self.transaction_queue.lock().future_transactions() } - fn ready_transactions(&self, best_block: BlockNumber) -> Vec { + fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec { let queue = self.transaction_queue.lock(); match self.options.pending_set { - PendingSet::AlwaysQueue => queue.pending_transactions(best_block), + PendingSet::AlwaysQueue => queue.pending_transactions(best_block, best_block_timestamp), PendingSet::SealingOrElseQueue => { self.from_pending_block( best_block, - || queue.pending_transactions(best_block), + || queue.pending_transactions(best_block, best_block_timestamp), |sealing| sealing.transactions().iter().map(|t| t.clone().into()).collect() ) }, @@ -1325,7 +1325,7 @@ mod tests { // then assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(miner.pending_transactions().len(), 1); - assert_eq!(miner.ready_transactions(best_block).len(), 1); + assert_eq!(miner.ready_transactions(best_block, 0).len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1); assert_eq!(miner.pending_receipts(best_block).len(), 1); // This method will let us know if pending block was created (before calling that method) @@ -1345,7 +1345,7 @@ mod tests { // then assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(miner.pending_transactions().len(), 1); - assert_eq!(miner.ready_transactions(best_block).len(), 0); + assert_eq!(miner.ready_transactions(best_block, 0).len(), 0); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); assert_eq!(miner.pending_receipts(best_block).len(), 0); } @@ -1364,7 +1364,7 @@ mod tests { assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(miner.pending_transactions().len(), 1); assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); - assert_eq!(miner.ready_transactions(best_block).len(), 0); + assert_eq!(miner.ready_transactions(best_block, 0).len(), 0); assert_eq!(miner.pending_receipts(best_block).len(), 0); // This method will let us know if pending block was created (before calling that method) assert!(miner.prepare_work_sealing(&client)); diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 495dc3bba..d88a261f3 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -154,7 +154,7 @@ pub trait MinerService : Send + Sync { fn pending_transactions(&self) -> Vec; /// Get a list of all transactions that can go into the given block. - fn ready_transactions(&self, best_block: BlockNumber) -> Vec; + fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec; /// Get a list of all future transactions. fn future_transactions(&self) -> Vec; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 34305cfc9..ef7094a90 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -276,17 +276,17 @@ struct VerifiedTransaction { origin: TransactionOrigin, /// Insertion time insertion_time: QueuingInstant, - /// Delay until specifid block. - min_block: Option, + /// Delay until specified condition is met. + condition: Option, } impl VerifiedTransaction { - fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, min_block: Option) -> Self { + fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, condition: Option) -> Self { VerifiedTransaction { transaction: transaction, origin: origin, insertion_time: time, - min_block: min_block, + condition: condition, } } @@ -666,14 +666,14 @@ impl TransactionQueue { tx: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, - min_block: Option, + condition: Option, details_provider: &TransactionDetailsProvider, ) -> Result { if origin == TransactionOrigin::Local { let hash = tx.hash(); let cloned_tx = tx.clone(); - let result = self.add_internal(tx, origin, time, min_block, details_provider); + let result = self.add_internal(tx, origin, time, condition, details_provider); match result { Ok(TransactionImportResult::Current) => { self.local_transactions.mark_pending(hash); @@ -694,7 +694,7 @@ impl TransactionQueue { } result } else { - self.add_internal(tx, origin, time, min_block, details_provider) + self.add_internal(tx, origin, time, condition, details_provider) } } @@ -704,7 +704,7 @@ impl TransactionQueue { tx: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, - min_block: Option, + condition: Option, details_provider: &TransactionDetailsProvider, ) -> Result { if origin != TransactionOrigin::Local && tx.gas_price < self.minimal_gas_price { @@ -815,7 +815,7 @@ impl TransactionQueue { } tx.check_low_s()?; // No invalid transactions beyond this point. - let vtx = VerifiedTransaction::new(tx, origin, time, min_block); + let vtx = VerifiedTransaction::new(tx, origin, time, condition); let r = self.import_tx(vtx, client_account.nonce).map_err(Error::Transaction); assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); r @@ -1068,11 +1068,11 @@ impl TransactionQueue { /// Returns top transactions from the queue ordered by priority. pub fn top_transactions(&self) -> Vec { - self.top_transactions_at(BlockNumber::max_value()) + self.top_transactions_at(BlockNumber::max_value(), u64::max_value()) } - fn filter_pending_transaction(&self, best_block: BlockNumber, mut f: F) + fn filter_pending_transaction(&self, best_block: BlockNumber, best_timestamp: u64, mut f: F) where F: FnMut(&VerifiedTransaction) { let mut delayed = HashSet::new(); @@ -1082,7 +1082,12 @@ impl TransactionQueue { if delayed.contains(&sender) { continue; } - if tx.min_block.unwrap_or(0) > best_block { + let delay = match tx.condition { + Some(Condition::Number(n)) => n > best_block, + Some(Condition::Timestamp(t)) => t > best_timestamp, + None => false, + }; + if delay { delayed.insert(sender); continue; } @@ -1091,16 +1096,16 @@ impl TransactionQueue { } /// Returns top transactions from the queue ordered by priority. - pub fn top_transactions_at(&self, best_block: BlockNumber) -> Vec { + pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec { let mut r = Vec::new(); - self.filter_pending_transaction(best_block, |tx| r.push(tx.transaction.clone())); + self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(tx.transaction.clone())); r } /// Return all ready transactions. - pub fn pending_transactions(&self, best_block: BlockNumber) -> Vec { + pub fn pending_transactions(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec { let mut r = Vec::new(); - self.filter_pending_transaction(best_block, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.min_block))); + self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.condition.clone()))); r } @@ -1109,7 +1114,7 @@ impl TransactionQueue { self.future.by_priority .iter() .map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`")) - .map(|t| PendingTransaction { transaction: t.transaction.clone(), min_block: t.min_block }) + .map(|t| PendingTransaction { transaction: t.transaction.clone(), condition: t.condition.clone() }) .collect() } @@ -1382,7 +1387,7 @@ pub mod test { use super::{TransactionSet, TransactionOrder, VerifiedTransaction}; use miner::local_transactions::LocalTransactionsList; use client::TransactionImportResult; - use transaction::{SignedTransaction, Transaction, Action}; + use transaction::{SignedTransaction, Transaction, Action, Condition}; pub struct DummyTransactionDetailsProvider { account_details: AccountDetails, @@ -2178,15 +2183,15 @@ pub mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(1), &default_tx_provider()).unwrap(); + let res1 = txq.add(tx.clone(), TransactionOrigin::External, 0, Some(Condition::Number(1)), &default_tx_provider()).unwrap(); let res2 = txq.add(tx2.clone(), TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap(); // then assert_eq!(res1, TransactionImportResult::Current); assert_eq!(res2, TransactionImportResult::Current); - let top = txq.top_transactions_at(0); + let top = txq.top_transactions_at(0, 0); assert_eq!(top.len(), 0); - let top = txq.top_transactions_at(1); + let top = txq.top_transactions_at(1, 0); assert_eq!(top.len(), 2); } diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index be703d7be..abaf1cc3b 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -29,7 +29,7 @@ use spec::Spec; use views::BlockView; use util::stats::Histogram; use ethkey::{KeyPair, Secret}; -use transaction::{PendingTransaction, Transaction, Action}; +use transaction::{PendingTransaction, Transaction, Action, Condition}; use miner::MinerService; #[test] @@ -299,7 +299,7 @@ fn does_not_propagate_delayed_transactions() { action: Action::Call(Address::default()), value: 0.into(), data: Vec::new(), - }.sign(secret, None), Some(2)); + }.sign(secret, None), Some(Condition::Number(2))); let tx1 = PendingTransaction::new(Transaction { nonce: 1.into(), gas_price: 0.into(), diff --git a/ethcore/src/types/blockchain_info.rs b/ethcore/src/types/blockchain_info.rs index 48a1fde44..1f691bc42 100644 --- a/ethcore/src/types/blockchain_info.rs +++ b/ethcore/src/types/blockchain_info.rs @@ -34,6 +34,8 @@ pub struct BlockChainInfo { pub best_block_hash: H256, /// Best blockchain block number. pub best_block_number: BlockNumber, + /// Best blockchain block timestamp. + pub best_block_timestamp: u64, /// Best ancient block hash. pub ancient_block_hash: Option, /// Best ancient block number. diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index 8c1f07e85..bb117629b 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -52,6 +52,16 @@ impl Decodable for Action { } } +/// Transaction activation condition. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] +pub enum Condition { + /// Valid at this block number or later. + Number(BlockNumber), + /// Valid at this unix time or later. + Timestamp(u64), +} + /// A set of information describing an externally-originating message call /// or contract creation operation. #[derive(Default, Debug, Clone, PartialEq, Eq)] @@ -448,16 +458,16 @@ impl Deref for LocalizedTransaction { pub struct PendingTransaction { /// Signed transaction data. pub transaction: SignedTransaction, - /// To be activated at this block. `None` for immediately. - pub min_block: Option, + /// To be activated at this condition. `None` for immediately. + pub condition: Option, } impl PendingTransaction { /// Create a new pending transaction from signed transaction. - pub fn new(signed: SignedTransaction, min_block: Option) -> Self { + pub fn new(signed: SignedTransaction, condition: Option) -> Self { PendingTransaction { transaction: signed, - min_block: min_block, + condition: condition, } } } @@ -466,7 +476,7 @@ impl From for PendingTransaction { fn from(t: SignedTransaction) -> Self { PendingTransaction { transaction: t, - min_block: None, + condition: None, } } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index af521d56e..add5be129 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -16,6 +16,7 @@ use std::collections::{BTreeMap, HashMap}; use std::mem; +use std::path::PathBuf; use parking_lot::{Mutex, RwLock}; use crypto::KEY_ITERATIONS; @@ -164,8 +165,8 @@ impl SecretStore for EthStore { self.store.update(account_ref, old, safe_account) } - fn local_path(&self) -> String { - self.store.dir.path().map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| String::new()) + fn local_path(&self) -> PathBuf { + self.store.dir.path().cloned().unwrap_or_else(PathBuf::new) } fn list_geth_accounts(&self, testnet: bool) -> Vec
{ diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index c90974f42..7042f434a 100755 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . use std::hash::{Hash, Hasher}; +use std::path::PathBuf; use ethkey::{Address, Message, Signature, Secret, Public}; use Error; use json::Uuid; @@ -73,7 +74,7 @@ pub trait SecretStore: SimpleSecretStore { fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>; fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>; - fn local_path(&self) -> String; + fn local_path(&self) -> PathBuf; fn list_geth_accounts(&self, testnet: bool) -> Vec
; fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec
, testnet: bool) -> Result, Error>; } diff --git a/js/package.json b/js/package.json index c6e9b2827..9eb0cd804 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.3.58", + "version": "0.3.62", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", diff --git a/js/scripts/build-rpc-markdown.js b/js/scripts/build-rpc-markdown.js index a06f2ed49..2407cf208 100644 --- a/js/scripts/build-rpc-markdown.js +++ b/js/scripts/build-rpc-markdown.js @@ -16,11 +16,12 @@ import fs from 'fs'; import path from 'path'; -import chalk from 'chalk'; import { isPlainObject } from 'lodash'; +import { info, warn, error } from './helpers/log'; import { Dummy } from '../src/jsonrpc/helpers'; import interfaces from '../src/jsonrpc'; +import rustMethods from './helpers/parsed-rpc-traits'; const ROOT_DIR = path.join(__dirname, '../docs'); @@ -28,20 +29,13 @@ if (!fs.existsSync(ROOT_DIR)) { fs.mkdirSync(ROOT_DIR); } -// INFO Logging helper -function info (log) { - console.log(chalk.blue(`INFO:\t${log}`)); -} - -// WARN Logging helper -function warn (log) { - console.warn(chalk.yellow(`WARN:\t${log}`)); -} - -// ERROR Logging helper -function error (log) { - console.error(chalk.red(`ERROR:\t${log}`)); -} +Object.keys(rustMethods).forEach((group) => { + Object.keys(rustMethods[group]).forEach((method) => { + if (interfaces[group] == null || interfaces[group][method] == null) { + error(`${group}_${method} is defined in Rust traits, but not in js/src/jsonrpc/interfaces`); + } + }); +}); function printType (type) { return type.print || `\`${type.name}\``; @@ -291,7 +285,8 @@ Object.keys(interfaces).sort().forEach((group) => { Object.keys(spec).sort(methodComparator).forEach((iname) => { const method = spec[iname]; - const name = `${group.replace(/_.*$/, '')}_${iname}`; + const groupName = group.replace(/_.*$/, ''); + const name = `${groupName}_${iname}`; if (method.nodoc || method.deprecated) { info(`Skipping ${name}: ${method.nodoc || 'Deprecated'}`); @@ -299,6 +294,10 @@ Object.keys(interfaces).sort().forEach((group) => { return; } + if (rustMethods[groupName] == null || rustMethods[groupName][iname] == null) { + error(`${name} is defined in js/src/jsonrpc/interfaces, but not in Rust traits`); + } + const desc = method.desc; const params = buildParameters(method.params); const returns = `- ${formatType(method.returns)}`; diff --git a/js/scripts/helpers/log.js b/js/scripts/helpers/log.js new file mode 100644 index 000000000..dc4955649 --- /dev/null +++ b/js/scripts/helpers/log.js @@ -0,0 +1,32 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import chalk from 'chalk'; + +// INFO Logging helper +export function info (log) { + console.log(chalk.blue(`INFO:\t${log}`)); +} + +// WARN Logging helper +export function warn (log) { + console.warn(chalk.yellow(`WARN:\t${log}`)); +} + +// ERROR Logging helper +export function error (log) { + console.error(chalk.red(`ERROR:\t${log}`)); +} diff --git a/js/scripts/helpers/parsed-rpc-traits.js b/js/scripts/helpers/parsed-rpc-traits.js new file mode 100644 index 000000000..9f2d4ea8a --- /dev/null +++ b/js/scripts/helpers/parsed-rpc-traits.js @@ -0,0 +1,81 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import fs from 'fs'; +import path from 'path'; + +// ```js +// rustMethods['eth']['call'] === true +// ``` +const rustMethods = {}; + +export default rustMethods; + +// Get a list of JSON-RPC from Rust trait source code +function parseMethodsFromRust (source) { + // Matching the custom `rpc` attribute with it's doc comment + const attributePattern = /((?:\s*\/\/\/.*$)*)\s*#\[rpc\(([^)]+)\)]/gm; + const commentPattern = /\s*\/\/\/\s*/g; + const separatorPattern = /\s*,\s*/g; + const assignPattern = /([\S]+)\s*=\s*"([^"]*)"/; + const ignorePattern = /@(ignore|deprecated|unimplemented|alias)\b/i; + + const methods = []; + + source.toString().replace(attributePattern, (match, comment, props) => { + comment = comment.replace(commentPattern, '\n').trim(); + + // Skip deprecated methods + if (ignorePattern.test(comment)) { + return match; + } + + props.split(separatorPattern).forEach((prop) => { + const [, key, value] = prop.split(assignPattern) || []; + + if (key === 'name' && value != null) { + methods.push(value); + } + }); + + return match; + }); + + return methods; +} + +// Get a list of all JSON-RPC methods from all defined traits +function getMethodsFromRustTraits () { + const traitsDir = path.join(__dirname, '../../../rpc/src/v1/traits'); + + return fs.readdirSync(traitsDir) + .filter((name) => name !== 'mod.rs' && /\.rs$/.test(name)) + .map((name) => fs.readFileSync(path.join(traitsDir, name))) + .map(parseMethodsFromRust) + .reduce((a, b) => a.concat(b)); +} + +getMethodsFromRustTraits().sort().forEach((method) => { + const [group, name] = method.split('_'); + + // Skip methods with malformed names + if (group == null || name == null) { + return; + } + + rustMethods[group] = rustMethods[group] || {}; + rustMethods[group][name] = true; +}); diff --git a/js/src/api/subscriptions/eth.js b/js/src/api/subscriptions/eth.js index 506201e6b..8e56f335f 100644 --- a/js/src/api/subscriptions/eth.js +++ b/js/src/api/subscriptions/eth.js @@ -23,6 +23,7 @@ export default class Eth { this._started = false; this._lastBlock = new BigNumber(-1); + this._pollTimerId = null; } get isStarted () { @@ -37,7 +38,7 @@ export default class Eth { _blockNumber = () => { const nextTimeout = (timeout = 1000) => { - setTimeout(() => { + this._pollTimerId = setTimeout(() => { this._blockNumber(); }, timeout); }; @@ -57,6 +58,6 @@ export default class Eth { nextTimeout(); }) - .catch(nextTimeout); + .catch(() => nextTimeout()); } } diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index 4f621824b..1574dcacc 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -20,6 +20,9 @@ export default class Personal { this._api = api; this._updateSubscriptions = updateSubscriptions; this._started = false; + + this._lastDefaultAccount = '0x0'; + this._pollTimerId = null; } get isStarted () { @@ -37,12 +40,35 @@ export default class Personal { ]); } - _defaultAccount = () => { + // FIXME: Because of the different API instances, the "wait for valid changes" approach + // doesn't work. Since the defaultAccount is critical to operation, we poll in exactly + // same way we do in ../eth (ala same as eth_blockNumber) and update. This should be moved + // to pub-sub as it becomes available + _defaultAccount = (timerDisabled = false) => { + const nextTimeout = (timeout = 1000) => { + if (!timerDisabled) { + this._pollTimerId = setTimeout(() => { + this._defaultAccount(); + }, timeout); + } + }; + + if (!this._api.transport.isConnected) { + nextTimeout(500); + return; + } + return this._api.parity .defaultAccount() .then((defaultAccount) => { - this._updateSubscriptions('parity_defaultAccount', null, defaultAccount); - }); + if (this._lastDefaultAccount !== defaultAccount) { + this._lastDefaultAccount = defaultAccount; + this._updateSubscriptions('parity_defaultAccount', null, defaultAccount); + } + + nextTimeout(); + }) + .catch(() => nextTimeout()); } _listAccounts = () => { @@ -54,14 +80,20 @@ export default class Personal { } _accountsInfo = () => { - return Promise - .all([ - this._api.parity.accountsInfo(), - this._api.parity.allAccountsInfo() - ]) - .then(([info, allInfo]) => { + return this._api.parity + .accountsInfo() + .then((info) => { this._updateSubscriptions('parity_accountsInfo', null, info); - this._updateSubscriptions('parity_allAccountsInfo', null, allInfo); + + return this._api.parity + .allAccountsInfo() + .catch(() => { + // NOTE: This fails on non-secure APIs, swallow error + return {}; + }) + .then((allInfo) => { + this._updateSubscriptions('parity_allAccountsInfo', null, allInfo); + }); }); } @@ -89,7 +121,7 @@ export default class Personal { case 'parity_setDappsAddresses': case 'parity_setNewDappsWhitelist': - this._defaultAccount(); + this._defaultAccount(true); return; } }); diff --git a/js/src/api/subscriptions/personal.spec.js b/js/src/api/subscriptions/personal.spec.js index e4d683766..ac046d250 100644 --- a/js/src/api/subscriptions/personal.spec.js +++ b/js/src/api/subscriptions/personal.spec.js @@ -36,6 +36,9 @@ function stubApi (accounts, info) { return { _calls, + transport: { + isConnected: true + }, parity: { accountsInfo: () => { const stub = sinon.stub().resolves(info || TEST_INFO)(); diff --git a/js/src/contracts/abi/sms-verification.json b/js/src/contracts/abi/sms-verification.json index d6852b182..3eb5492a4 100644 --- a/js/src/contracts/abi/sms-verification.json +++ b/js/src/contracts/abi/sms-verification.json @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] \ No newline at end of file diff --git a/js/src/contracts/verification.js b/js/src/contracts/verification.js index 2d69fa8d6..8101565db 100644 --- a/js/src/contracts/verification.js +++ b/js/src/contracts/verification.js @@ -20,23 +20,23 @@ export const checkIfVerified = (contract, account) => { return contract.instance.certified.call({}, [account]); }; -export const checkIfRequested = (contract, account) => { +export const findLastRequested = (contract, account) => { let subId = null; let resolved = false; return new Promise((resolve, reject) => { contract .subscribe('Requested', { - fromBlock: 0, toBlock: 'pending' + fromBlock: 0, + toBlock: 'pending', + limit: 1, + topics: [account] }, (err, logs) => { if (err) { return reject(err); } - const e = logs.find((l) => { - return l.type === 'mined' && l.params.who && l.params.who.value === account; - }); - resolve(e ? e.transactionHash : false); + resolve(logs[0] || null); resolved = true; if (subId) { diff --git a/js/src/dapps/basiccoin/Application/application.js b/js/src/dapps/basiccoin/Application/application.js index 1277399f0..24c6202cc 100644 --- a/js/src/dapps/basiccoin/Application/application.js +++ b/js/src/dapps/basiccoin/Application/application.js @@ -45,7 +45,7 @@ export default class Application extends Component { } componentDidMount () { - this.attachInstance(); + return this.attachInstance(); } render () { @@ -80,12 +80,12 @@ export default class Application extends Component { } attachInstance () { - Promise + return Promise .all([ - attachInstances(), - api.parity.accountsInfo() + api.parity.accountsInfo(), + attachInstances() ]) - .then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => { + .then(([accountsInfo, { managerInstance, registryInstance, tokenregInstance }]) => { accountsInfo = accountsInfo || {}; this.setState({ loading: false, diff --git a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js index 7b122c830..666ac1a97 100644 --- a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js +++ b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js @@ -17,7 +17,6 @@ import React, { Component, PropTypes } from 'react'; import { api } from '../../parity'; -import AddressSelect from '../../AddressSelect'; import Container from '../../Container'; import styles from './deployment.css'; @@ -122,36 +121,13 @@ export default class Deployment extends Component { } renderForm () { - const { accounts } = this.context; const { baseText, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state; const hasError = !!(nameError || tlaError || totalSupplyError); const error = `${styles.input} ${styles.error}`; - const addresses = Object.keys(accounts); - - //
- // - // - //
- // register on network (fee: { globalFeeText }ETH) - //
- //
return (
-
- - -
- the owner account to deploy from -
-
{ - const fromAddress = event.target.value; - - this.setState({ fromAddress }); - } - onChangeName = (event) => { const name = event.target.value; const nameError = name && (name.length > 2) && (name.length < 32) @@ -271,7 +241,7 @@ export default class Deployment extends Component { onDeploy = () => { const { managerInstance, registryInstance, tokenregInstance } = this.context; - const { base, deployBusy, fromAddress, globalReg, globalFee, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state; + const { base, deployBusy, globalReg, globalFee, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state; const hasError = !!(nameError || tlaError || totalSupplyError); if (hasError || deployBusy) { @@ -281,14 +251,18 @@ export default class Deployment extends Component { const tokenreg = (globalReg ? tokenregInstance : registryInstance).address; const values = [base.mul(totalSupply), tla, name, tokenreg]; const options = { - from: fromAddress, value: globalReg ? globalFee : 0 }; this.setState({ deployBusy: true, deployState: 'Estimating gas for the transaction' }); - managerInstance - .deploy.estimateGas(options, values) + return api.parity + .defaultAccount() + .then((defaultAddress) => { + options.from = defaultAddress; + + return managerInstance.deploy.estimateGas(options, values); + }) .then((gas) => { this.setState({ deployState: 'Gas estimated, Posting transaction to the network' }); diff --git a/js/src/dapps/basiccoin/services.js b/js/src/dapps/basiccoin/services.js index 7c43cd87d..6853c8ac4 100644 --- a/js/src/dapps/basiccoin/services.js +++ b/js/src/dapps/basiccoin/services.js @@ -25,6 +25,8 @@ let registryInstance; const registries = {}; const subscriptions = {}; + +let defaultSubscriptionId; let nextSubscriptionId = 1000; let isTest = false; @@ -65,6 +67,20 @@ export function unsubscribeEvents (subscriptionId) { delete subscriptions[subscriptionId]; } +export function subscribeDefaultAddress (callback) { + return api + .subscribe('parity_defaultAccount', callback) + .then((subscriptionId) => { + defaultSubscriptionId = subscriptionId; + + return defaultSubscriptionId; + }); +} + +export function unsubscribeDefaultAddress () { + return api.unsubscribe(defaultSubscriptionId); +} + function pollEvents () { const loop = Object.values(subscriptions); const timeout = () => setTimeout(pollEvents, 1000); diff --git a/js/src/dapps/githubhint/Application/application.js b/js/src/dapps/githubhint/Application/application.js index fe2cf441c..f09f9c2fa 100644 --- a/js/src/dapps/githubhint/Application/application.js +++ b/js/src/dapps/githubhint/Application/application.js @@ -17,10 +17,9 @@ import React, { Component } from 'react'; import { api } from '../parity'; -import { attachInterface } from '../services'; +import { attachInterface, subscribeDefaultAddress, unsubscribeDefaultAddress } from '../services'; import Button from '../Button'; import Events from '../Events'; -import IdentityIcon from '../IdentityIcon'; import Loading from '../Loading'; import styles from './application.css'; @@ -32,7 +31,7 @@ let nextEventId = 0; export default class Application extends Component { state = { - fromAddress: null, + defaultAddress: null, loading: true, url: '', urlError: null, @@ -47,19 +46,32 @@ export default class Application extends Component { registerType: 'file', repo: '', repoError: null, + subscriptionId: null, events: {}, eventIds: [] } componentDidMount () { - attachInterface() - .then((state) => { - this.setState(state, () => { - this.setState({ loading: false }); - }); + return Promise + .all([ + attachInterface(), + subscribeDefaultAddress((error, defaultAddress) => { + if (!error) { + this.setState({ defaultAddress }); + } + }) + ]) + .then(([state]) => { + this.setState(Object.assign({}, state, { + loading: false + })); }); } + componentWillUnmount () { + return unsubscribeDefaultAddress(); + } + render () { const { loading } = this.state; @@ -75,12 +87,14 @@ export default class Application extends Component { } renderPage () { - const { fromAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state; + const { defaultAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state; let hashClass = null; if (contentHashError) { - hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning; + hashClass = contentHashOwner !== defaultAddress + ? styles.hashError + : styles.hashWarning; } else if (contentHash) { hashClass = styles.hashOk; } @@ -166,20 +180,13 @@ export default class Application extends Component { } renderButtons () { - const { accounts, fromAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state; - const account = accounts[fromAddress]; + const { defaultAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state; return (
-
- -
); @@ -294,11 +301,11 @@ export default class Application extends Component { } onClickRegister = () => { - const { commit, commitError, contentHashError, contentHashOwner, fromAddress, url, urlError, registerType, repo, repoError } = this.state; + const { defaultAddress, commit, commitError, contentHashError, contentHashOwner, url, urlError, registerType, repo, repoError } = this.state; // TODO: No errors are currently set, validation to be expanded and added for each // field (query is fast to pick up the issues, so not burning atm) - if ((contentHashError && contentHashOwner !== fromAddress) || repoError || urlError || commitError) { + if ((contentHashError && contentHashOwner !== defaultAddress) || repoError || urlError || commitError) { return; } @@ -368,13 +375,15 @@ export default class Application extends Component { } registerContent (contentRepo, contentCommit) { - const { contentHash, fromAddress, instance } = this.state; + const { defaultAddress, contentHash, instance } = this.state; - contentCommit = contentCommit.substr(0, 2) === '0x' ? contentCommit : `0x${contentCommit}`; + contentCommit = contentCommit.substr(0, 2) === '0x' + ? contentCommit + : `0x${contentCommit}`; const eventId = nextEventId++; const values = [contentHash, contentRepo, contentCommit]; - const options = { from: fromAddress }; + const options = { from: defaultAddress }; this.setState({ eventIds: [eventId].concat(this.state.eventIds), @@ -383,7 +392,7 @@ export default class Application extends Component { contentHash, contentRepo, contentCommit, - fromAddress, + defaultAddress, registerBusy: true, registerState: 'Estimating gas for the transaction', timestamp: new Date() @@ -421,11 +430,11 @@ export default class Application extends Component { } registerUrl (contentUrl) { - const { contentHash, fromAddress, instance } = this.state; + const { contentHash, defaultAddress, instance } = this.state; const eventId = nextEventId++; const values = [contentHash, contentUrl]; - const options = { from: fromAddress }; + const options = { from: defaultAddress }; this.setState({ eventIds: [eventId].concat(this.state.eventIds), @@ -433,7 +442,7 @@ export default class Application extends Component { [eventId]: { contentHash, contentUrl, - fromAddress, + defaultAddress, registerBusy: true, registerState: 'Estimating gas for the transaction', timestamp: new Date() @@ -470,25 +479,6 @@ export default class Application extends Component { ); } - onSelectFromAddress = () => { - const { accounts, fromAddress } = this.state; - const addresses = Object.keys(accounts); - let index = 0; - - addresses.forEach((address, _index) => { - if (address === fromAddress) { - index = _index; - } - }); - - index++; - if (index >= addresses.length) { - index = 0; - } - - this.setState({ fromAddress: addresses[index] }); - } - lookupHash (url) { const { instance } = this.state; diff --git a/js/src/dapps/githubhint/services.js b/js/src/dapps/githubhint/services.js index 97f866e5e..a4a4a6891 100644 --- a/js/src/dapps/githubhint/services.js +++ b/js/src/dapps/githubhint/services.js @@ -17,48 +17,44 @@ import * as abis from '~/contracts/abi'; import { api } from './parity'; +let defaultSubscriptionId; + export function attachInterface () { return api.parity .registryAddress() .then((registryAddress) => { console.log(`the registry was found at ${registryAddress}`); - const registry = api.newContract(abis.registry, registryAddress).instance; - - return Promise - .all([ - registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']), - api.parity.accountsInfo() - ]); + return api + .newContract(abis.registry, registryAddress).instance + .getAddress.call({}, [api.util.sha3('githubhint'), 'A']); }) - .then(([address, accountsInfo]) => { + .then((address) => { console.log(`githubhint was found at ${address}`); const contract = api.newContract(abis.githubhint, address); - const accounts = Object - .keys(accountsInfo) - .reduce((obj, address) => { - const account = accountsInfo[address]; - - return Object.assign(obj, { - [address]: { - address, - name: account.name - } - }); - }, {}); - const fromAddress = Object.keys(accounts)[0]; return { - accounts, address, - accountsInfo, contract, - instance: contract.instance, - fromAddress + instance: contract.instance }; }) .catch((error) => { console.error('attachInterface', error); }); } + +export function subscribeDefaultAddress (callback) { + return api + .subscribe('parity_defaultAccount', callback) + .then((subscriptionId) => { + defaultSubscriptionId = subscriptionId; + + return defaultSubscriptionId; + }); +} + +export function unsubscribeDefaultAddress () { + return api.unsubscribe(defaultSubscriptionId); +} diff --git a/js/src/dapps/githubhint/IdentityIcon/identityIcon.css b/js/src/dapps/localtx/IdentityIcon/identityIcon.css similarity index 100% rename from js/src/dapps/githubhint/IdentityIcon/identityIcon.css rename to js/src/dapps/localtx/IdentityIcon/identityIcon.css diff --git a/js/src/dapps/githubhint/IdentityIcon/identityIcon.js b/js/src/dapps/localtx/IdentityIcon/identityIcon.js similarity index 100% rename from js/src/dapps/githubhint/IdentityIcon/identityIcon.js rename to js/src/dapps/localtx/IdentityIcon/identityIcon.js diff --git a/js/src/dapps/githubhint/IdentityIcon/index.js b/js/src/dapps/localtx/IdentityIcon/index.js similarity index 100% rename from js/src/dapps/githubhint/IdentityIcon/index.js rename to js/src/dapps/localtx/IdentityIcon/index.js diff --git a/js/src/dapps/localtx/Transaction/transaction.js b/js/src/dapps/localtx/Transaction/transaction.js index 9bad58693..8bd545305 100644 --- a/js/src/dapps/localtx/Transaction/transaction.js +++ b/js/src/dapps/localtx/Transaction/transaction.js @@ -22,7 +22,7 @@ import { api } from '../parity'; import styles from './transaction.css'; -import IdentityIcon from '../../githubhint/IdentityIcon'; +import IdentityIcon from '../IdentityIcon'; class BaseTransaction extends Component { shortHash (hash) { diff --git a/js/src/dapps/registry/Lookup/actions.js b/js/src/dapps/registry/Lookup/actions.js index 890586eff..6f80fac0d 100644 --- a/js/src/dapps/registry/Lookup/actions.js +++ b/js/src/dapps/registry/Lookup/actions.js @@ -82,6 +82,7 @@ export const ownerLookup = (name) => (dispatch, getState) => { return; } + name = name.toLowerCase(); dispatch(ownerLookupStart(name)); return getOwner(contract, name) diff --git a/js/src/dapps/signaturereg/Application/application.js b/js/src/dapps/signaturereg/Application/application.js index c39814d27..21e36994e 100644 --- a/js/src/dapps/signaturereg/Application/application.js +++ b/js/src/dapps/signaturereg/Application/application.js @@ -30,7 +30,6 @@ export default class Application extends Component { state = { accounts: {}, address: null, - fromAddress: null, accountsInfo: {}, blockNumber: new BigNumber(0), contract: null, @@ -41,11 +40,9 @@ export default class Application extends Component { } componentDidMount () { - attachInterface() + return attachInterface() .then((state) => { - this.setState(state, () => { - this.setState({ loading: false }); - }); + this.setState(Object.assign({}, state, { loading: false })); return attachBlockNumber(state.instance, (state) => { this.setState(state); @@ -86,17 +83,14 @@ export default class Application extends Component { } renderImport () { - const { accounts, fromAddress, instance, showImport } = this.state; + const { instance, showImport } = this.state; if (showImport) { return ( ); } @@ -124,10 +118,4 @@ export default class Application extends Component { showImport: !this.state.showImport }); } - - setFromAddress = (fromAddress) => { - this.setState({ - fromAddress - }); - } } diff --git a/js/src/dapps/signaturereg/Import/import.js b/js/src/dapps/signaturereg/Import/import.js index 3d370315e..c1fcc3997 100644 --- a/js/src/dapps/signaturereg/Import/import.js +++ b/js/src/dapps/signaturereg/Import/import.js @@ -19,18 +19,14 @@ import React, { Component, PropTypes } from 'react'; import { api } from '../parity'; import { callRegister, postRegister } from '../services'; import Button from '../Button'; -import IdentityIcon from '../IdentityIcon'; import styles from './import.css'; export default class Import extends Component { static propTypes = { - accounts: PropTypes.object.isRequired, - fromAddress: PropTypes.string.isRequired, instance: PropTypes.object.isRequired, visible: PropTypes.bool.isRequired, - onClose: PropTypes.func.isRequired, - onSetFromAddress: PropTypes.func.isRequired + onClose: PropTypes.func.isRequired } state = { @@ -83,21 +79,12 @@ export default class Import extends Component { } renderRegister () { - const { accounts, fromAddress } = this.props; - - const account = accounts[fromAddress]; const count = this.countFunctions(); let buttons = null; if (count) { buttons = (
-
- -
@@ -197,15 +184,15 @@ export default class Import extends Component { } onRegister = () => { - const { instance, fromAddress, onClose } = this.props; + const { instance, onClose } = this.props; const { functions, fnstate } = this.state; - Promise + return Promise .all( functions .filter((fn) => !fn.constant) .filter((fn) => fnstate[fn.signature] === 'fntodo') - .map((fn) => postRegister(instance, fn.id, { from: fromAddress })) + .map((fn) => postRegister(instance, fn.id, {})) ) .then(() => { onClose(); @@ -214,23 +201,4 @@ export default class Import extends Component { console.error('onRegister', error); }); } - - onSelectFromAddress = () => { - const { accounts, fromAddress, onSetFromAddress } = this.props; - const addresses = Object.keys(accounts); - let index = 0; - - addresses.forEach((address, _index) => { - if (address === fromAddress) { - index = _index; - } - }); - - index++; - if (index >= addresses.length) { - index = 0; - } - - onSetFromAddress(addresses[index]); - } } diff --git a/js/src/dapps/signaturereg/services.js b/js/src/dapps/signaturereg/services.js index 229ea497d..d9c60da6b 100644 --- a/js/src/dapps/signaturereg/services.js +++ b/js/src/dapps/signaturereg/services.js @@ -166,8 +166,13 @@ export function callRegister (instance, id, options = {}) { } export function postRegister (instance, id, options = {}) { - return instance.register - .estimateGas(options, [id]) + return api.parity + .defaultAccount() + .then((defaultAddress) => { + options.from = defaultAddress; + + return instance.register.estimateGas(options, [id]); + }) .then((gas) => { options.gas = gas.mul(1.2).toFixed(0); console.log('postRegister', `gas estimated at ${gas.toFormat(0)}, setting to ${gas.mul(1.2).toFormat(0)}`); diff --git a/js/src/jsonrpc/index.spec.js b/js/src/jsonrpc/index.spec.js index 66988b664..af4afb9c9 100644 --- a/js/src/jsonrpc/index.spec.js +++ b/js/src/jsonrpc/index.spec.js @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import fs from 'fs'; -import path from 'path'; import interfaces from './'; import * as customTypes from './types'; @@ -27,91 +25,13 @@ function verifyType (obj) { } } -// Get a list of JSON-RPC from Rust trait source code -function parseMethodsFromRust (source) { - // Matching the custom `rpc` attribute with it's doc comment - const attributePattern = /((?:\s*\/\/\/.*$)*)\s*#\[rpc\(([^)]+)\)]/gm; - const commentPattern = /\s*\/\/\/\s*/g; - const separatorPattern = /\s*,\s*/g; - const assignPattern = /([\S]+)\s*=\s*"([^"]*)"/; - const ignorePattern = /@(ignore|deprecated|unimplemented|alias)\b/i; - - const methods = []; - - source.toString().replace(attributePattern, (match, comment, props) => { - comment = comment.replace(commentPattern, '\n').trim(); - - // Skip deprecated methods - if (ignorePattern.test(comment)) { - return match; - } - - props.split(separatorPattern).forEach((prop) => { - const [, key, value] = prop.split(assignPattern) || []; - - if (key === 'name' && value != null) { - methods.push(value); - } - }); - - return match; - }); - - return methods; -} - -// Get a list of all JSON-RPC methods from all defined traits -function getMethodsFromRustTraits () { - const traitsDir = path.join(__dirname, '../../../rpc/src/v1/traits'); - - return fs.readdirSync(traitsDir) - .filter((name) => name !== 'mod.rs' && /\.rs$/.test(name)) - .map((name) => fs.readFileSync(path.join(traitsDir, name))) - .map(parseMethodsFromRust) - .reduce((a, b) => a.concat(b)); -} - -const rustMethods = {}; - -getMethodsFromRustTraits().sort().forEach((method) => { - const [group, name] = method.split('_'); - - // Skip methods with malformed names - if (group == null || name == null) { - return; - } - - rustMethods[group] = rustMethods[group] || {}; - rustMethods[group][name] = true; -}); - describe('jsonrpc/interfaces', () => { - describe('Rust trait methods', () => { - Object.keys(rustMethods).forEach((group) => { - describe(group, () => { - Object.keys(rustMethods[group]).forEach((name) => { - describe(name, () => { - it('has a defined JS interface', () => { - expect(interfaces[group][name]).to.exist; - }); - }); - }); - }); - }); - }); - Object.keys(interfaces).forEach((group) => { describe(group, () => { Object.keys(interfaces[group]).forEach((name) => { const method = interfaces[group][name]; describe(name, () => { - if (!method.nodoc) { - it('is present in Rust codebase', () => { - expect(rustMethods[group][name]).to.exist; - }); - } - it('has the correct interface', () => { expect(method.desc).to.be.a('string'); expect(method.params).to.be.an('array'); diff --git a/js/src/modals/AddDapps/addDapps.css b/js/src/modals/AddDapps/addDapps.css index 857dfa015..8de8f7f80 100644 --- a/js/src/modals/AddDapps/addDapps.css +++ b/js/src/modals/AddDapps/addDapps.css @@ -15,15 +15,24 @@ /* along with Parity. If not, see . */ +.modal { + flex-direction: column; +} + +.container { + margin-top: 1.5em; + overflow-y: auto; +} + .description { margin-top: .5em !important; } .list { + margin-bottom: 1.5em; + .background { - background: rgba(255, 255, 255, 0.2); - margin: 0 -1.5em; - padding: 0.5em 1.5em; + padding: 0.5em 0; } .header { @@ -37,3 +46,26 @@ opacity: 0.75; } } + +.selectIcon { + position: absolute; + right: 0.5em; + top: 0.5em; +} + +.selected, +.unselected { + position: relative; +} + +.unselected { + background: rgba(0, 0, 0, 0.4) !important; + + .selectIcon { + opacity: 0.15; + } +} + +.selected { + background: rgba(255, 255, 255, 0.15) !important; +} diff --git a/js/src/modals/AddDapps/addDapps.js b/js/src/modals/AddDapps/addDapps.js index 593a9f74a..0a4d19af9 100644 --- a/js/src/modals/AddDapps/addDapps.js +++ b/js/src/modals/AddDapps/addDapps.js @@ -14,14 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { Checkbox } from 'material-ui'; -import { List, ListItem } from 'material-ui/List'; import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; -import { Modal, Button } from '~/ui'; -import { DoneIcon } from '~/ui/Icons'; +import { ContainerTitle, DappCard, Portal, SectionList } from '~/ui'; +import { CheckIcon } from '~/ui/Icons'; import styles from './addDapps.css'; @@ -39,71 +37,63 @@ export default class AddDapps extends Component { } return ( - } - key='done' - label={ - - } - onClick={ store.closeModal } - /> - ] } - compact - title={ - - } - visible + -
- { - this.renderList(store.sortedLocal, + , - - ) - } - { - this.renderList(store.sortedBuiltin, - , - - ) - } - { - this.renderList(store.sortedNetwork, - , - - ) - } - + } + /> +
+
+ { + this.renderList(store.sortedLocal, store.displayApps, + , + + ) + } + { + this.renderList(store.sortedBuiltin, store.displayApps, + , + + ) + } + { + this.renderList(store.sortedNetwork, store.displayApps, + , + + ) + } +
+ ); } - renderList (items, header, byline) { + renderList (items, visibleItems, header, byline) { if (!items || !items.length) { return null; } @@ -114,41 +104,40 @@ export default class AddDapps extends Component {
{ header }
{ byline }
- - { items.map(this.renderApp) } - +
); } renderApp = (app) => { const { store } = this.props; - const isHidden = !store.displayApps[app.id].visible; + const isVisible = store.displayApps[app.id].visible; - const onCheck = () => { - if (isHidden) { - store.showApp(app.id); - } else { + const onClick = () => { + if (isVisible) { store.hideApp(app.id); + } else { + store.showApp(app.id); } }; return ( - - } - primaryText={ app.name } - secondaryText={ -
- { app.description } -
- } - /> + onClick={ onClick } + > + + ); } } diff --git a/js/src/modals/AddDapps/addDapps.spec.js b/js/src/modals/AddDapps/addDapps.spec.js index 31af368e3..e629a5375 100644 --- a/js/src/modals/AddDapps/addDapps.spec.js +++ b/js/src/modals/AddDapps/addDapps.spec.js @@ -33,13 +33,13 @@ describe('modals/AddDapps', () => { it('does not render the modal with modalOpen = false', () => { expect( - renderShallow({ modalOpen: false }).find('Connect(Modal)') + renderShallow({ modalOpen: false }).find('Portal') ).to.have.length(0); }); it('does render the modal with modalOpen = true', () => { expect( - renderShallow({ modalOpen: true }).find('Connect(Modal)') + renderShallow({ modalOpen: true }).find('Portal') ).to.have.length(1); }); }); diff --git a/js/src/modals/Verification/GatherData/gatherData.css b/js/src/modals/Verification/GatherData/gatherData.css index e0c0f8b57..0f9ba3617 100644 --- a/js/src/modals/Verification/GatherData/gatherData.css +++ b/js/src/modals/Verification/GatherData/gatherData.css @@ -30,6 +30,10 @@ margin-left: .5em; } +.field { + margin-bottom: .5em; +} + .terms { line-height: 1.3; opacity: .7; diff --git a/js/src/modals/Verification/GatherData/gatherData.js b/js/src/modals/Verification/GatherData/gatherData.js index e6f21136c..ecd096be6 100644 --- a/js/src/modals/Verification/GatherData/gatherData.js +++ b/js/src/modals/Verification/GatherData/gatherData.js @@ -31,19 +31,22 @@ import emailTermsOfService from '~/3rdparty/email-verification/terms-of-service' import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works'; import styles from './gatherData.css'; +const boolOfError = PropTypes.oneOfType([ PropTypes.bool, PropTypes.instanceOf(Error) ]); + export default class GatherData extends Component { static propTypes = { fee: React.PropTypes.instanceOf(BigNumber), fields: PropTypes.array.isRequired, - hasRequested: nullableProptype(PropTypes.bool.isRequired), + accountHasRequested: nullableProptype(PropTypes.bool.isRequired), isServerRunning: nullableProptype(PropTypes.bool.isRequired), - isVerified: nullableProptype(PropTypes.bool.isRequired), + isAbleToRequest: nullableProptype(boolOfError.isRequired), + accountIsVerified: nullableProptype(PropTypes.bool.isRequired), method: PropTypes.string.isRequired, setConsentGiven: PropTypes.func.isRequired } render () { - const { method, isVerified } = this.props; + const { method, accountIsVerified } = this.props; const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService; const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks; @@ -55,6 +58,7 @@ export default class GatherData extends Component { { this.renderCertified() } { this.renderRequested() } { this.renderFields() } + { this.renderIfAbleToRequest() } } - disabled={ isVerified } + disabled={ accountIsVerified } onCheck={ this.consentOnChange } />
{ termsOfService }
@@ -145,27 +149,27 @@ export default class GatherData extends Component { } renderCertified () { - const { isVerified } = this.props; + const { accountIsVerified } = this.props; - if (isVerified) { + if (accountIsVerified) { return (

); - } else if (isVerified === false) { + } else if (accountIsVerified === false) { return (

@@ -175,7 +179,7 @@ export default class GatherData extends Component { return (

@@ -183,33 +187,33 @@ export default class GatherData extends Component { } renderRequested () { - const { isVerified, hasRequested } = this.props; + const { accountIsVerified, accountHasRequested } = this.props; // If the account is verified, don't show that it has requested verification. - if (isVerified) { + if (accountIsVerified) { return null; } - if (hasRequested) { + if (accountHasRequested) { return (

); - } else if (hasRequested === false) { + } else if (accountHasRequested === false) { return (

@@ -218,7 +222,7 @@ export default class GatherData extends Component { return (

@@ -226,7 +230,7 @@ export default class GatherData extends Component { } renderFields () { - const { isVerified, fields } = this.props; + const { accountIsVerified, fields } = this.props; const rendered = fields.map((field) => { const onChange = (_, v) => { @@ -236,11 +240,12 @@ export default class GatherData extends Component { return ( @@ -250,6 +255,36 @@ export default class GatherData extends Component { return (
{rendered}
); } + renderIfAbleToRequest () { + const { accountIsVerified, isAbleToRequest } = this.props; + + // If the account is verified, don't show a warning. + // If the client is able to send the request, don't show a warning + if (accountIsVerified || isAbleToRequest === true) { + return null; + } + + if (isAbleToRequest === null) { + return ( +

+ +

+ ); + } else if (isAbleToRequest) { + return ( +
+ +

+ { isAbleToRequest.message } +

+
+ ); + } + } + consentOnChange = (_, consentGiven) => { this.props.setConsentGiven(consentGiven); } diff --git a/js/src/modals/Verification/email-store.js b/js/src/modals/Verification/email-store.js index b69764926..d18d5fac9 100644 --- a/js/src/modals/Verification/email-store.js +++ b/js/src/modals/Verification/email-store.js @@ -16,6 +16,7 @@ import { observable, computed, action } from 'mobx'; import { sha3 } from '~/api/util/sha3'; +import { bytesToHex } from '~/api/util/format'; import EmailVerificationABI from '~/contracts/abi/email-verification.json'; import VerificationStore, { @@ -23,6 +24,8 @@ import VerificationStore, { } from './store'; import { isServerRunning, hasReceivedCode, postToServer } from '~/3rdparty/email-verification'; +const ZERO20 = '0x0000000000000000000000000000000000000000'; + // name in the `BadgeReg.sol` contract const EMAIL_VERIFICATION = 'emailverification'; @@ -44,9 +47,9 @@ export default class EmailVerificationStore extends VerificationStore { switch (this.step) { case LOADING: - return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null; + return this.contract && this.fee && this.accountIsVerified !== null && this.accountHasRequested !== null; case QUERY_DATA: - return this.isEmailValid && this.consentGiven; + return this.isEmailValid && this.consentGiven && this.isAbleToRequest === true; case QUERY_CODE: return this.requestTx && this.isCodeValid === true; case POSTED_CONFIRMATION: @@ -68,8 +71,53 @@ export default class EmailVerificationStore extends VerificationStore { return hasReceivedCode(this.email, this.account, this.isTestnet); } + // If the email has already been used for verification of another account, + // we prevent the user from wasting ETH to request another verification. + @action setIfAbleToRequest = () => { + const { isEmailValid } = this; + + if (!isEmailValid) { + this.isAbleToRequest = true; + return; + } + + const { contract, email } = this; + const emailHash = sha3.text(email); + + this.isAbleToRequest = null; + contract + .instance.reverse + .call({}, [ emailHash ]) + .then((address) => { + if (address === ZERO20) { + this.isAbleToRequest = true; + } else { + this.isAbleToRequest = new Error('Another account has been verified using this e-mail.'); + } + }) + .catch((err) => { + this.error = 'Failed to check if able to send request: ' + err.message; + }); + } + + // Determine the values relevant for checking if the last request contains + // the same data as the current one. requestValues = () => [ sha3.text(this.email) ] + shallSkipRequest = (currentValues) => { + const { accountHasRequested } = this; + const lastRequest = this.lastRequestValues; + + if (!accountHasRequested) { + return Promise.resolve(false); + } + // If the last email verification `request` for the selected address contains + // the same email as the current one, don't send another request to save ETH. + const skip = currentValues[0] === bytesToHex(lastRequest.emailHash.value); + + return Promise.resolve(skip); + } + @action setEmail = (email) => { this.email = email; } diff --git a/js/src/modals/Verification/sms-store.js b/js/src/modals/Verification/sms-store.js index ee57a29ec..25c07403c 100644 --- a/js/src/modals/Verification/sms-store.js +++ b/js/src/modals/Verification/sms-store.js @@ -43,7 +43,7 @@ export default class SMSVerificationStore extends VerificationStore { switch (this.step) { case LOADING: - return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null; + return this.contract && this.fee && this.accountIsVerified !== null && this.accountHasRequested !== null; case QUERY_DATA: return this.isNumberValid && this.consentGiven; case QUERY_CODE: @@ -67,6 +67,18 @@ export default class SMSVerificationStore extends VerificationStore { return hasReceivedCode(this.number, this.account, this.isTestnet); } + // SMS verification events don't contain the phone number, so we will have to + // send a new request every single time. See below. + @action setIfAbleToRequest = () => { + this.isAbleToRequest = true; + } + + // SMS verification `request` & `confirm` transactions and events don't contain the + // phone number, so we will have to send a new request every single time. This may + // cost the user more money, but given that it fails otherwise, it seems like a + // reasonable tradeoff. + shallSkipRequest = () => Promise.resolve(false) + @action setNumber = (number) => { this.number = number; } diff --git a/js/src/modals/Verification/store.js b/js/src/modals/Verification/store.js index a0eaba2ee..b652131d9 100644 --- a/js/src/modals/Verification/store.js +++ b/js/src/modals/Verification/store.js @@ -19,7 +19,7 @@ import { sha3 } from '~/api/util/sha3'; import Contract from '~/api/contract'; import Contracts from '~/contracts'; -import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification'; +import { checkIfVerified, findLastRequested, awaitPuzzle } from '~/contracts/verification'; import { checkIfTxFailed, waitForConfirmations } from '~/util/tx'; export const LOADING = 'fetching-contract'; @@ -38,8 +38,10 @@ export default class VerificationStore { @observable contract = null; @observable fee = null; - @observable isVerified = null; - @observable hasRequested = null; + @observable accountIsVerified = null; + @observable accountHasRequested = null; + @observable isAbleToRequest = null; + @observable lastRequestValues = null; @observable isServerRunning = null; @observable consentGiven = false; @observable requestTx = null; @@ -68,6 +70,14 @@ export default class VerificationStore { console.error('verification: ' + this.error); } }); + + autorun(() => { + if (this.step !== QUERY_DATA) { + return; + } + + this.setIfAbleToRequest(); + }); } @action load = () => { @@ -91,19 +101,20 @@ export default class VerificationStore { this.error = 'Failed to fetch the fee: ' + err.message; }); - const isVerified = checkIfVerified(contract, account) - .then((isVerified) => { - this.isVerified = isVerified; + const accountIsVerified = checkIfVerified(contract, account) + .then((accountIsVerified) => { + this.accountIsVerified = accountIsVerified; }) .catch((err) => { this.error = 'Failed to check if verified: ' + err.message; }); - const hasRequested = checkIfRequested(contract, account) - .then((txHash) => { - this.hasRequested = !!txHash; - if (txHash) { - this.requestTx = txHash; + const accountHasRequested = findLastRequested(contract, account) + .then((log) => { + this.accountHasRequested = !!log; + if (log) { + this.lastRequestValues = log.params; + this.requestTx = log.transactionHash; } }) .catch((err) => { @@ -111,7 +122,7 @@ export default class VerificationStore { }); Promise - .all([ isServerRunning, fee, isVerified, hasRequested ]) + .all([ isServerRunning, fee, accountIsVerified, accountHasRequested ]) .then(() => { this.step = QUERY_DATA; }); @@ -150,40 +161,41 @@ export default class VerificationStore { requestValues = () => [] @action sendRequest = () => { - const { api, account, contract, fee, hasRequested } = this; + const { api, account, contract, fee } = this; const request = contract.functions.find((fn) => fn.name === 'request'); const options = { from: account, value: fee.toString() }; const values = this.requestValues(); - let chain = Promise.resolve(); + this.shallSkipRequest(values) + .then((skipRequest) => { + if (skipRequest) { + return; + } - if (!hasRequested) { - this.step = POSTING_REQUEST; - chain = request.estimateGas(options, values) - .then((gas) => { - options.gas = gas.mul(1.2).toFixed(0); - return request.postTransaction(options, values); - }) - .then((handle) => { - // TODO: The "request rejected" error doesn't have any property to - // distinguish it from other errors, so we can't give a meaningful error here. - return api.pollMethod('parity_checkRequest', handle); - }) - .then((txHash) => { - this.requestTx = txHash; - return checkIfTxFailed(api, txHash, options.gas) - .then((hasFailed) => { - if (hasFailed) { - throw new Error('Transaction failed, all gas used up.'); - } - this.step = POSTED_REQUEST; - return waitForConfirmations(api, txHash, 1); - }); - }); - } - - chain + this.step = POSTING_REQUEST; + return request.estimateGas(options, values) + .then((gas) => { + options.gas = gas.mul(1.2).toFixed(0); + return request.postTransaction(options, values); + }) + .then((handle) => { + // The "request rejected" error doesn't have any property to distinguish + // it from other errors, so we can't give a meaningful error here. + return api.pollMethod('parity_checkRequest', handle); + }) + .then((txHash) => { + this.requestTx = txHash; + return checkIfTxFailed(api, txHash, options.gas) + .then((hasFailed) => { + if (hasFailed) { + throw new Error('Transaction failed, all gas used up.'); + } + this.step = POSTED_REQUEST; + return waitForConfirmations(api, txHash, 1); + }); + }); + }) .then(() => this.checkIfReceivedCode()) .then((hasReceived) => { if (hasReceived) { diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index 4e179eb43..37166b4af 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; import { observer } from 'mobx-react'; import { observable } from 'mobx'; @@ -206,7 +207,7 @@ class Verification extends Component { const { step, - isServerRunning, fee, isVerified, hasRequested, + isServerRunning, isAbleToRequest, fee, accountIsVerified, accountHasRequested, requestTx, isCodeValid, confirmationTx, setCode } = this.store; @@ -223,17 +224,37 @@ class Verification extends Component { if (method === 'sms') { fields.push({ key: 'number', - label: 'phone number in international format', - hint: 'the SMS will be sent to this number', + label: ( + + ), + hint: ( + + ), error: this.store.isNumberValid ? null : 'invalid number', onChange: this.store.setNumber }); } else if (method === 'email') { fields.push({ key: 'email', - label: 'email address', - hint: 'the code will be sent to this address', - error: this.store.isEmailValid ? null : 'invalid email', + label: ( + + ), + hint: ( + + ), + error: this.store.isEmailValid ? null : 'invalid e-mail', onChange: this.store.setEmail }); } @@ -241,10 +262,12 @@ class Verification extends Component { return ( ); diff --git a/js/src/modals/index.js b/js/src/modals/index.js index 7541615b6..d412b5296 100644 --- a/js/src/modals/index.js +++ b/js/src/modals/index.js @@ -16,10 +16,10 @@ import AddAddress from './AddAddress'; import AddContract from './AddContract'; -import AddDapps from './AddDapps'; import CreateAccount from './CreateAccount'; import CreateWallet from './CreateWallet'; import DappPermissions from './DappPermissions'; +import DappsVisible from './AddDapps'; import DeleteAccount from './DeleteAccount'; import DeployContract from './DeployContract'; import EditMeta from './EditMeta'; @@ -37,10 +37,10 @@ import WalletSettings from './WalletSettings'; export { AddAddress, AddContract, - AddDapps, CreateAccount, CreateWallet, DappPermissions, + DappsVisible, DeleteAccount, DeployContract, EditMeta, diff --git a/js/src/ui/Container/container.js b/js/src/ui/Container/container.js index 8bc5e3c32..695695871 100644 --- a/js/src/ui/Container/container.js +++ b/js/src/ui/Container/container.js @@ -29,15 +29,14 @@ export default class Container extends Component { className: PropTypes.string, compact: PropTypes.bool, light: PropTypes.bool, + onClick: PropTypes.func, style: PropTypes.object, tabIndex: PropTypes.number, title: nodeOrStringProptype() } render () { - const { children, className, compact, light, style, tabIndex } = this.props; - const classes = `${styles.container} ${light ? styles.light : ''} ${className}`; - + const { children, className, compact, light, onClick, style, tabIndex } = this.props; const props = {}; if (Number.isInteger(tabIndex)) { @@ -45,8 +44,27 @@ export default class Container extends Component { } return ( -
- +
+ { this.renderTitle() } { children } diff --git a/js/src/views/Dapps/Summary/summary.css b/js/src/ui/DappCard/dappCard.css similarity index 100% rename from js/src/views/Dapps/Summary/summary.css rename to js/src/ui/DappCard/dappCard.css diff --git a/js/src/views/Dapps/Summary/summary.js b/js/src/ui/DappCard/dappCard.js similarity index 57% rename from js/src/views/Dapps/Summary/summary.js rename to js/src/ui/DappCard/dappCard.js index cf7f225d0..4836e6a4f 100644 --- a/js/src/views/Dapps/Summary/summary.js +++ b/js/src/ui/DappCard/dappCard.js @@ -17,59 +17,80 @@ import React, { Component, PropTypes } from 'react'; import { Link } from 'react-router'; -import { Container, ContainerTitle, DappIcon, Tags } from '~/ui'; +import Container, { Title as ContainerTitle } from '~/ui/Container'; +import DappIcon from '~/ui/DappIcon'; +import Tags from '~/ui/Tags'; -import styles from './summary.css'; +import styles from './dappCard.css'; -export default class Summary extends Component { +export default class DappCard extends Component { static propTypes = { app: PropTypes.object.isRequired, - children: PropTypes.node - } + children: PropTypes.node, + className: PropTypes.string, + onClick: PropTypes.func, + showLink: PropTypes.bool, + showTags: PropTypes.bool + }; + + static defaultProps = { + showLink: false, + showTags: false + }; render () { - const { app } = this.props; + const { app, children, className, onClick, showLink, showTags } = this.props; if (!app) { return null; } - const link = this.renderLink(app); - return ( - + - +
{ app.author }, v{ app.version }
- { this.props.children } + { children }
); } renderLink (app) { - // Special case for web dapp - if (app.url === 'web') { - return ( - - { app.name } - - ); - } - return ( - + { app.name } ); diff --git a/js/src/views/Dapps/Summary/index.js b/js/src/ui/DappCard/index.js similarity index 95% rename from js/src/views/Dapps/Summary/index.js rename to js/src/ui/DappCard/index.js index e36c50678..eed38ca78 100644 --- a/js/src/views/Dapps/Summary/index.js +++ b/js/src/ui/DappCard/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './summary'; +export default from './dappCard'; diff --git a/js/src/ui/index.js b/js/src/ui/index.js index ce71bae02..0994d53d9 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -30,6 +30,7 @@ import Container, { Title as ContainerTitle } from './Container'; import ContextProvider from './ContextProvider'; import CopyToClipboard from './CopyToClipboard'; import CurrencySymbol from './CurrencySymbol'; +import DappCard from './DappCard'; import DappIcon from './DappIcon'; import Editor from './Editor'; import Errors from './Errors'; @@ -79,6 +80,7 @@ export { CopyToClipboard, CurrencySymbol, DappIcon, + DappCard, Editor, Errors, FEATURES, diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index 102906a23..fc196fad8 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -21,14 +21,13 @@ import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { AddDapps, DappPermissions } from '~/modals'; +import { DappPermissions, DappsVisible } from '~/modals'; import PermissionStore from '~/modals/DappPermissions/store'; -import { Actionbar, Button, Page } from '~/ui'; +import { Actionbar, Button, DappCard, Page } from '~/ui'; import { LockedIcon, VisibleIcon } from '~/ui/Icons'; import UrlButton from './UrlButton'; import DappsStore from './dappsStore'; -import Summary from './Summary'; import styles from './dapps.css'; @@ -82,8 +81,8 @@ class Dapps extends Component { return (
- + - +
); } diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 5ef3e8cc1..c97f9d938 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -192,7 +192,7 @@ pub fn sign_and_dispatch(client: &C, miner: &M, accounts: &AccountProvider { let network_id = client.signing_network_id(); - let min_block = filled.min_block.clone(); + let condition = filled.condition.clone(); let signed_transaction = sign_no_dispatch(client, miner, accounts, filled, password)?; let (signed_transaction, token) = match signed_transaction { @@ -201,7 +201,7 @@ pub fn sign_and_dispatch(client: &C, miner: &M, accounts: &AccountProvider }; trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id); - let pending_transaction = PendingTransaction::new(signed_transaction, min_block); + let pending_transaction = PendingTransaction::new(signed_transaction, condition.map(Into::into)); dispatch_transaction(&*client, &*miner, pending_transaction).map(|hash| { match token { Some(ref token) => WithToken::Yes(hash, token.clone()), @@ -222,7 +222,7 @@ pub fn fill_optional_fields(request: TransactionRequest, default_sender: A 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), - min_block: request.min_block, + condition: request.condition, } } diff --git a/rpc/src/v1/helpers/requests.rs b/rpc/src/v1/helpers/requests.rs index d7a737dc0..993a8c5cd 100644 --- a/rpc/src/v1/helpers/requests.rs +++ b/rpc/src/v1/helpers/requests.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . use util::{Address, U256, Bytes}; +use v1::types::TransactionCondition; /// Transaction request coming from RPC #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] @@ -33,8 +34,8 @@ pub struct TransactionRequest { pub data: Option, /// Transaction's nonce pub nonce: Option, - /// Delay until this block if specified. - pub min_block: Option, + /// Delay until this condition is met. + pub condition: Option, } /// Transaction request coming from RPC with default values filled in. @@ -56,8 +57,8 @@ pub struct FilledTransactionRequest { pub data: Bytes, /// Transaction's nonce pub nonce: Option, - /// Delay until this block if specified. - pub min_block: Option, + /// Delay until this condition is met. + pub condition: Option, } impl From for TransactionRequest { @@ -70,7 +71,7 @@ impl From for TransactionRequest { value: Some(r.value), data: Some(r.data), nonce: r.nonce, - min_block: r.min_block, + condition: r.condition, } } } diff --git a/rpc/src/v1/helpers/signing_queue.rs b/rpc/src/v1/helpers/signing_queue.rs index 428f17b83..f224dcf0c 100644 --- a/rpc/src/v1/helpers/signing_queue.rs +++ b/rpc/src/v1/helpers/signing_queue.rs @@ -349,7 +349,7 @@ mod test { value: 10_000_000.into(), data: vec![], nonce: None, - min_block: None, + condition: None, }) } diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs index 185f094e7..055ccebeb 100644 --- a/rpc/src/v1/impls/parity.rs +++ b/rpc/src/v1/impls/parity.rs @@ -130,7 +130,7 @@ impl Parity for ParityClient where .into_iter().collect::>(); let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?; - let other = store.addresses_info().expect("addresses_info always returns Ok; qed"); + let other = store.addresses_info(); Ok(info .into_iter() diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index 4348c2f42..05ac7e5e2 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -51,22 +51,27 @@ impl ParityAccountsClient where C: MiningBlockChainClient { } impl ParityAccounts for ParityAccountsClient where C: MiningBlockChainClient { - fn all_accounts_info(&self) -> Result>, Error> { + fn all_accounts_info(&self) -> Result>, Error> { self.active()?; let store = take_weak!(self.accounts); let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?; - let other = store.addresses_info().expect("addresses_info always returns Ok; qed"); + let other = store.addresses_info(); - Ok(info.into_iter().chain(other.into_iter()).map(|(a, v)| { - let mut m = map![ - "name".to_owned() => v.name, - "meta".to_owned() => v.meta - ]; - if let &Some(ref uuid) = &v.uuid { - m.insert("uuid".to_owned(), format!("{}", uuid)); - } - (format!("0x{}", a.hex()), m) - }).collect()) + Ok(info + .into_iter() + .chain(other.into_iter()) + .map(|(address, v)| { + let mut m = map![ + "name".to_owned() => v.name, + "meta".to_owned() => v.meta + ]; + if let &Some(ref uuid) = &v.uuid { + m.insert("uuid".to_owned(), format!("{}", uuid)); + } + (address.into(), m) + }) + .collect() + ) } fn new_account_from_phrase(&self, phrase: String, pass: String) -> Result { @@ -132,8 +137,7 @@ impl ParityAccounts for ParityAccountsClient where C: MiningBlock let store = take_weak!(self.accounts); let addr: Address = addr.into(); - store.remove_address(addr) - .expect("remove_address always returns Ok; qed"); + store.remove_address(addr); Ok(true) } @@ -143,8 +147,7 @@ impl ParityAccounts for ParityAccountsClient where C: MiningBlock let addr: Address = addr.into(); store.set_account_name(addr.clone(), name.clone()) - .or_else(|_| store.set_address_name(addr, name)) - .expect("set_address_name always returns Ok; qed"); + .unwrap_or_else(|_| store.set_address_name(addr, name)); Ok(true) } @@ -154,8 +157,7 @@ impl ParityAccounts for ParityAccountsClient where C: MiningBlock let addr: Address = addr.into(); store.set_account_meta(addr.clone(), meta.clone()) - .or_else(|_| store.set_address_meta(addr, meta)) - .expect("set_address_meta always returns Ok; qed"); + .unwrap_or_else(|_| store.set_address_meta(addr, meta)); Ok(true) } diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 315541c1f..a25a5bbc1 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -87,8 +87,8 @@ impl SignerClient where C: MiningBlockChainClient, if let Some(gas) = modification.gas { request.gas = gas.into(); } - if let Some(ref min_block) = modification.min_block { - request.min_block = min_block.as_ref().and_then(|b| b.to_min_block_num()); + if let Some(ref condition) = modification.condition { + request.condition = condition.clone().map(Into::into); } } let result = f(&*client, &*miner, &*accounts, payload); @@ -160,7 +160,7 @@ impl Signer for SignerClient where C: MiningBlockC // Dispatch if everything is ok if sender_matches && data_matches && value_matches && nonce_matches { - let pending_transaction = PendingTransaction::new(signed_transaction, request.min_block); + let pending_transaction = PendingTransaction::new(signed_transaction, request.condition.map(Into::into)); dispatch_transaction(&*client, &*miner, pending_transaction) .map(Into::into) .map(ConfirmationResponse::SendTransaction) diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 33c624896..82b776d00 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -212,7 +212,7 @@ impl MinerService for TestMinerService { self.local_transactions.lock().iter().map(|(hash, stats)| (*hash, stats.clone())).collect() } - fn ready_transactions(&self, _best_block: BlockNumber) -> Vec { + fn ready_transactions(&self, _best_block: BlockNumber, _best_timestamp: u64) -> Vec { self.pending_transactions.lock().values().cloned().map(Into::into).collect() } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index cc7beaf50..dc20ed9d8 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -365,6 +365,8 @@ fn rpc_eth_accounts() { let tester = EthTester::default(); let address = tester.accounts_provider.new_account("").unwrap(); tester.accounts_provider.set_new_dapps_whitelist(None).unwrap(); + tester.accounts_provider.set_address_name(1.into(), "1".into()); + tester.accounts_provider.set_address_name(10.into(), "10".into()); // with current policy it should return the account let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#; @@ -514,7 +516,7 @@ fn rpc_eth_pending_transaction_by_hash() { tester.miner.pending_transactions.lock().insert(H256::zero(), tx); } - let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","minBlock":null,"networkId":null,"nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"condition":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x1","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","networkId":null,"nonce":"0x0","publicKey":"0x7ae46da747962c2ee46825839c1ef9298e3bd2e70ca2938495c3693a485ec3eaa8f196327881090ff64cf4fbb0a48485d4f83098e189ed3b7a87d5941b59f789","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","standardV":"0x0","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"v":"0x1b","value":"0xa"},"id":1}"#; let request = r#"{ "jsonrpc": "2.0", "method": "eth_getTransactionByHash", @@ -830,12 +832,11 @@ fn rpc_eth_sign_transaction() { let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""tx":{"# + - r#""blockHash":null,"blockNumber":null,"creates":null,"# + + r#""blockHash":null,"blockNumber":null,"condition":null,"creates":null,"# + &format!("\"from\":\"0x{:?}\",", &address) + r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + &format!("\"hash\":\"0x{:?}\",", t.hash()) + r#""input":"0x","# + - r#""minBlock":null,"# + &format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) + r#""nonce":"0x1","# + &format!("\"publicKey\":\"0x{:?}\",", t.recover_public().unwrap()) + diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index 476e97229..be8cb3f91 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -120,10 +120,11 @@ fn should_be_able_to_set_meta() { fn rpc_parity_set_and_get_dapps_accounts() { // given let tester = setup(); + tester.accounts.set_address_name(10.into(), "10".into()); assert_eq!(tester.accounts.dapps_addresses("app1".into()).unwrap(), vec![]); // when - let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappsAddresses","params":["app1",["0x000000000000000000000000000000000000000a"]], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setDappsAddresses","params":["app1",["0x000000000000000000000000000000000000000a","0x0000000000000000000000000000000000000001"]], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index b1609cf8f..9762a8531 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -85,7 +85,7 @@ fn should_return_list_of_items_to_confirm() { value: U256::from(1), data: vec![], nonce: None, - min_block: None, + condition: None, })).unwrap(); tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into())).unwrap(); @@ -93,7 +93,7 @@ fn should_return_list_of_items_to_confirm() { let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let response = concat!( r#"{"jsonrpc":"2.0","result":["#, - r#"{"id":"0x1","payload":{"sendTransaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","minBlock":null,"nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, + r#"{"id":"0x1","payload":{"sendTransaction":{"condition":null,"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, r#"{"id":"0x2","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","data":"0x05"}}}"#, r#"],"id":1}"# ); @@ -116,7 +116,7 @@ fn should_reject_transaction_from_queue_without_dispatching() { value: U256::from(1), data: vec![], nonce: None, - min_block: None, + condition: None, })).unwrap(); assert_eq!(tester.signer.requests().len(), 1); @@ -143,7 +143,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { value: U256::from(1), data: vec![], nonce: None, - min_block: None, + condition: None, })).unwrap(); assert_eq!(tester.signer.requests().len(), 1); @@ -187,7 +187,7 @@ fn should_confirm_transaction_and_dispatch() { value: U256::from(1), data: vec![], nonce: None, - min_block: None, + condition: None, })).unwrap(); let t = Transaction { @@ -233,7 +233,7 @@ fn should_alter_the_sender_and_nonce() { value: U256::from(1), data: vec![], nonce: Some(10.into()), - min_block: None, + condition: None, })).unwrap(); let t = Transaction { @@ -283,7 +283,7 @@ fn should_confirm_transaction_with_token() { value: U256::from(1), data: vec![], nonce: None, - min_block: None, + condition: None, })).unwrap(); let t = Transaction { @@ -332,7 +332,7 @@ fn should_confirm_transaction_with_rlp() { value: U256::from(1), data: vec![], nonce: None, - min_block: None, + condition: None, })).unwrap(); let t = Transaction { @@ -380,7 +380,7 @@ fn should_return_error_when_sender_does_not_match() { value: U256::from(1), data: vec![], nonce: None, - min_block: None, + condition: None, })).unwrap(); let t = Transaction { diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index 7413acc21..0f77325dd 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -277,12 +277,11 @@ fn should_add_sign_transaction_to_the_queue() { let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + r#""raw":"0x"# + &rlp.to_hex() + r#"","# + r#""tx":{"# + - r#""blockHash":null,"blockNumber":null,"creates":null,"# + + r#""blockHash":null,"blockNumber":null,"condition":null,"creates":null,"# + &format!("\"from\":\"0x{:?}\",", &address) + r#""gas":"0x76c0","gasPrice":"0x9184e72a000","# + &format!("\"hash\":\"0x{:?}\",", t.hash()) + r#""input":"0x","# + - r#""minBlock":null,"# + &format!("\"networkId\":{},", t.network_id().map_or("null".to_owned(), |n| format!("{}", n))) + r#""nonce":"0x1","# + &format!("\"publicKey\":\"0x{:?}\",", t.public_key()) + diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index 1df707dc2..595a33740 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -25,7 +25,7 @@ build_rpc_trait! { pub trait ParityAccounts { /// Returns accounts information. #[rpc(name = "parity_allAccountsInfo")] - fn all_accounts_info(&self) -> Result>, Error>; + fn all_accounts_info(&self) -> Result>, Error>; /// Creates new account from the given phrase using standard brainwallet mechanism. /// Second parameter is password for the new account. diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index b020ac6e1..d9848a0ac 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -139,7 +139,7 @@ mod tests { fn test_serialize_block_transactions() { let t = BlockTransactions::Full(vec![Transaction::default()]); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","minBlock":null}]"#); + assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","condition":null}]"#); let t = BlockTransactions::Hashes(vec![H256::default().into()]); let serialized = serde_json::to_string(&t).unwrap(); diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index 0c36fdff9..63d4819f3 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -21,7 +21,7 @@ use serde::{Serialize, Serializer}; use util::log::Colour; use util::bytes::ToPretty; -use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, BlockNumber}; +use v1::types::{U256, TransactionRequest, RichRawTransaction, H160, H256, H520, Bytes, TransactionCondition}; use v1::helpers; /// Confirmation waiting in a queue @@ -196,9 +196,8 @@ pub struct TransactionModification { pub gas_price: Option, /// Modified gas pub gas: Option, - /// Modified min block - #[serde(rename="minBlock")] - pub min_block: Option>, + /// Modified transaction condition. + pub condition: Option>, } /// Represents two possible return values. @@ -240,7 +239,7 @@ impl Serialize for Either where mod tests { use std::str::FromStr; use serde_json; - use v1::types::{U256, H256, BlockNumber}; + use v1::types::{U256, H256, TransactionCondition}; use v1::helpers; use super::*; @@ -274,13 +273,13 @@ mod tests { value: 100_000.into(), data: vec![1, 2, 3], nonce: Some(1.into()), - min_block: None, + condition: None, }), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","minBlock":null}}}"#; + let expected = r#"{"id":"0xf","payload":{"sendTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -300,13 +299,13 @@ mod tests { value: 100_000.into(), data: vec![1, 2, 3], nonce: Some(1.into()), - min_block: None, + condition: None, }), }; // when let res = serde_json::to_string(&ConfirmationRequest::from(request)); - let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","minBlock":null}}}"#; + let expected = r#"{"id":"0xf","payload":{"signTransaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x186a0","data":"0x010203","nonce":"0x1","condition":null}}}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); @@ -336,7 +335,7 @@ mod tests { let s1 = r#"{ "sender": "0x000000000000000000000000000000000000000a", "gasPrice":"0xba43b7400", - "minBlock":"0x42" + "condition": { "block": 66 } }"#; let s2 = r#"{"gas": "0x1233"}"#; let s3 = r#"{}"#; @@ -351,19 +350,19 @@ mod tests { sender: Some(10.into()), gas_price: Some(U256::from_str("0ba43b7400").unwrap()), gas: None, - min_block: Some(Some(BlockNumber::Num(0x42))), + condition: Some(Some(TransactionCondition::Number(0x42))), }); assert_eq!(res2, TransactionModification { sender: None, gas_price: None, gas: Some(U256::from_str("1233").unwrap()), - min_block: None, + condition: None, }); assert_eq!(res3, TransactionModification { sender: None, gas_price: None, gas: None, - min_block: None, + condition: None, }); } diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 3f84bffaf..a823a0104 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -27,6 +27,7 @@ mod log; mod sync; mod transaction; mod transaction_request; +mod transaction_condition; mod receipt; mod rpc_settings; mod trace; @@ -55,6 +56,7 @@ pub use self::sync::{ }; pub use self::transaction::{Transaction, RichRawTransaction, LocalTransactionStatus}; pub use self::transaction_request::TransactionRequest; +pub use self::transaction_condition::TransactionCondition; pub use self::receipt::Receipt; pub use self::rpc_settings::RpcSettings; pub use self::trace::{LocalizedTrace, TraceResults}; diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index f47b23667..106392fe1 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -19,7 +19,7 @@ use ethcore::miner; use ethcore::contract_address; use ethcore::transaction::{LocalizedTransaction, Action, PendingTransaction, SignedTransaction}; use v1::helpers::errors; -use v1::types::{Bytes, H160, H256, U256, H512, BlockNumber}; +use v1::types::{Bytes, H160, H256, U256, H512, TransactionCondition}; /// Transaction #[derive(Debug, Default, Clone, PartialEq, Serialize)] @@ -70,8 +70,7 @@ pub struct Transaction { /// The S field of the signature. pub s: U256, /// Transaction activates at specified block. - #[serde(rename="minBlock")] - pub min_block: Option, + pub condition: Option, } /// Local Transaction Status @@ -190,7 +189,7 @@ impl From for Transaction { v: t.original_v().into(), r: signature.r().into(), s: signature.s().into(), - min_block: None, + condition: None, } } } @@ -224,7 +223,7 @@ impl From for Transaction { v: t.original_v().into(), r: signature.r().into(), s: signature.s().into(), - min_block: None, + condition: None, } } } @@ -232,7 +231,7 @@ impl From for Transaction { impl From for Transaction { fn from(t: PendingTransaction) -> Transaction { let mut r = Transaction::from(t.transaction); - r.min_block = t.min_block.map(|b| BlockNumber::Num(b)); + r.condition = t.condition.map(|b| b.into()); r } } @@ -261,7 +260,7 @@ mod tests { fn test_transaction_serialize() { let t = Transaction::default(); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","minBlock":null}"#); + assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"networkId":null,"standardV":"0x0","v":"0x0","r":"0x0","s":"0x0","condition":null}"#); } #[test] diff --git a/rpc/src/v1/types/transaction_condition.rs b/rpc/src/v1/types/transaction_condition.rs new file mode 100644 index 000000000..2f530f686 --- /dev/null +++ b/rpc/src/v1/types/transaction_condition.rs @@ -0,0 +1,67 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use ethcore; + +/// Represents condition on minimum block number or block timestamp. +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub enum TransactionCondition { + /// Valid at this minimum block number. + #[serde(rename="block")] + Number(u64), + /// Valid at given unix time. + #[serde(rename="time")] + Timestamp(u64), +} + +impl Into for TransactionCondition { + fn into(self) -> ethcore::transaction::Condition { + match self { + TransactionCondition::Number(n) => ethcore::transaction::Condition::Number(n), + TransactionCondition::Timestamp(n) => ethcore::transaction::Condition::Timestamp(n), + } + } +} + +impl From for TransactionCondition { + fn from(condition: ethcore::transaction::Condition) -> Self { + match condition { + ethcore::transaction::Condition::Number(n) => TransactionCondition::Number(n), + ethcore::transaction::Condition::Timestamp(n) => TransactionCondition::Timestamp(n), + } + } +} + +#[cfg(test)] +mod tests { + use ethcore; + use super::*; + use serde_json; + + #[test] + fn condition_deserialization() { + let s = r#"[{ "block": 51 }, { "time": 10 }]"#; + let deserialized: Vec = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized, vec![TransactionCondition::Number(51), TransactionCondition::Timestamp(10)]) + } + + #[test] + fn condition_into() { + assert_eq!(ethcore::transaction::Condition::Number(100), TransactionCondition::Number(100).into()); + assert_eq!(ethcore::transaction::Condition::Timestamp(100), TransactionCondition::Timestamp(100).into()); + } +} + diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 9434983fa..5839a0de8 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -16,7 +16,7 @@ //! `TransactionRequest` type -use v1::types::{Bytes, H160, U256, BlockNumber}; +use v1::types::{Bytes, H160, U256, TransactionCondition}; use v1::helpers; use util::log::Colour; @@ -41,9 +41,8 @@ pub struct TransactionRequest { pub data: Option, /// Transaction's nonce pub nonce: Option, - /// Delay until this block if specified. - #[serde(rename="minBlock")] - pub min_block: Option, + /// Delay until this block condition. + pub condition: Option, } pub fn format_ether(i: U256) -> String { @@ -93,7 +92,7 @@ impl From for TransactionRequest { value: r.value.map(Into::into), data: r.data.map(Into::into), nonce: r.nonce.map(Into::into), - min_block: r.min_block.map(|b| BlockNumber::Num(b)), + condition: r.condition.map(Into::into), } } } @@ -108,7 +107,7 @@ impl From for TransactionRequest { value: Some(r.value.into()), data: Some(r.data.into()), nonce: r.nonce.map(Into::into), - min_block: r.min_block.map(|b| BlockNumber::Num(b)), + condition: r.condition.map(Into::into), } } } @@ -123,7 +122,7 @@ impl Into for TransactionRequest { value: self.value.map(Into::into), data: self.data.map(Into::into), nonce: self.nonce.map(Into::into), - min_block: self.min_block.and_then(|b| b.to_min_block_num()), + condition: self.condition.map(Into::into), } } } @@ -134,7 +133,7 @@ mod tests { use std::str::FromStr; use rustc_serialize::hex::FromHex; use serde_json; - use v1::types::{U256, H160, BlockNumber}; + use v1::types::{U256, H160, TransactionCondition}; use super::*; #[test] @@ -147,7 +146,7 @@ mod tests { "value":"0x3", "data":"0x123456", "nonce":"0x4", - "minBlock":"0x13" + "condition": { "block": 19 } }"#; let deserialized: TransactionRequest = serde_json::from_str(s).unwrap(); @@ -159,7 +158,7 @@ mod tests { value: Some(U256::from(3)), data: Some(vec![0x12, 0x34, 0x56].into()), nonce: Some(U256::from(4)), - min_block: Some(BlockNumber::Num(0x13)), + condition: Some(TransactionCondition::Number(0x13)), }); } @@ -183,7 +182,7 @@ mod tests { value: Some(U256::from_str("9184e72a").unwrap()), data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()), nonce: None, - min_block: None, + condition: None, }); } @@ -200,7 +199,7 @@ mod tests { value: None, data: None, nonce: None, - min_block: None, + condition: None, }); } @@ -224,7 +223,7 @@ mod tests { value: None, data: Some(vec![0x85, 0x95, 0xba, 0xb1].into()), nonce: None, - min_block: None, + condition: None, }); } diff --git a/rpc_client/src/signer_client.rs b/rpc_client/src/signer_client.rs index bff6be275..956a28208 100644 --- a/rpc_client/src/signer_client.rs +++ b/rpc_client/src/signer_client.rs @@ -1,5 +1,5 @@ use client::{Rpc, RpcError}; -use rpc::v1::types::{ConfirmationRequest, TransactionModification, U256, BlockNumber}; +use rpc::v1::types::{ConfirmationRequest, TransactionModification, U256, TransactionCondition}; use serde_json::{Value as JsonValue, to_value}; use std::path::PathBuf; use futures::{BoxFuture, Canceled}; @@ -22,13 +22,13 @@ impl SignerRpc { id: U256, new_gas: Option, new_gas_price: Option, - new_min_block: Option>, + new_condition: Option>, pwd: &str ) -> BoxFuture, Canceled> { self.rpc.request("signer_confirmRequest", vec![ to_value(&format!("{:#x}", id)), - to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, min_block: new_min_block }), + to_value(&TransactionModification { sender: None, gas_price: new_gas_price, gas: new_gas, condition: new_condition }), to_value(&pwd), ]) } diff --git a/scripts/deb-build.sh b/scripts/deb-build.sh index 28d15bf3a..682cbcbbf 100644 --- a/scripts/deb-build.sh +++ b/scripts/deb-build.sh @@ -25,7 +25,7 @@ echo "Homepage: https://ethcore.io" >> $control echo "Vcs-Git: git://github.com/ethcore/parity.git" >> $control echo "Vcs-Browser: https://github.com/ethcore/parity" >> $control echo "Architecture: $1" >> $control -echo "Depends: libssl1.0.0" >> $control +echo "Depends: libssl1.0.0 (>=1.0.0)" >> $control echo "Description: Ethereum network client by Ethcore" >> $control #build .deb package diff --git a/util/fetch/Cargo.toml b/util/fetch/Cargo.toml index 308816ed7..96c1317f2 100644 --- a/util/fetch/Cargo.toml +++ b/util/fetch/Cargo.toml @@ -9,8 +9,9 @@ authors = ["Parity Technologies "] [dependencies] futures = "0.1" futures-cpupool = "0.1" +parking_lot = "0.3" log = "0.3" -reqwest = "0.2" +reqwest = "0.4" mime = "0.2" clippy = { version = "0.0.90", optional = true} diff --git a/util/fetch/src/client.rs b/util/fetch/src/client.rs index 7bc2b211d..09fe4741b 100644 --- a/util/fetch/src/client.rs +++ b/util/fetch/src/client.rs @@ -16,13 +16,14 @@ //! Fetching -use std::{io, fmt}; +use std::{io, fmt, time}; use std::sync::Arc; use std::sync::atomic::{self, AtomicBool}; use futures::{self, BoxFuture, Future}; use futures_cpupool::{CpuPool, CpuFuture}; use mime::{self, Mime}; +use parking_lot::RwLock; use reqwest; #[derive(Default, Debug, Clone)] @@ -73,24 +74,52 @@ pub trait Fetch: Clone + Send + Sync + 'static { fn close(self) where Self: Sized {} } -#[derive(Clone)] +const CLIENT_TIMEOUT_SECONDS: u64 = 5; + pub struct Client { - client: Arc, + client: RwLock<(time::Instant, Arc)>, pool: CpuPool, limit: Option, } +impl Clone for Client { + fn clone(&self) -> Self { + let (ref time, ref client) = *self.client.read(); + Client { + client: RwLock::new((time.clone(), client.clone())), + pool: self.pool.clone(), + limit: self.limit.clone(), + } + } +} + impl Client { - fn with_limit(limit: Option) -> Result { + fn new_client() -> Result, Error> { let mut client = reqwest::Client::new()?; client.redirect(reqwest::RedirectPolicy::limited(5)); + Ok(Arc::new(client)) + } + fn with_limit(limit: Option) -> Result { Ok(Client { - client: Arc::new(client), + client: RwLock::new((time::Instant::now(), Self::new_client()?)), pool: CpuPool::new(4), limit: limit, }) } + + fn client(&self) -> Result, Error> { + { + let (ref time, ref client) = *self.client.read(); + if time.elapsed() < time::Duration::from_secs(CLIENT_TIMEOUT_SECONDS) { + return Ok(client.clone()); + } + } + + let client = Self::new_client()?; + *self.client.write() = (time::Instant::now(), client.clone()); + Ok(client) + } } impl Fetch for Client { @@ -112,12 +141,19 @@ impl Fetch for Client { fn fetch_with_abort(&self, url: &str, abort: Abort) -> Self::Result { debug!(target: "fetch", "Fetching from: {:?}", url); - self.pool.spawn(FetchTask { - url: url.into(), - client: self.client.clone(), - limit: self.limit, - abort: abort, - }) + match self.client() { + Ok(client) => { + self.pool.spawn(FetchTask { + url: url.into(), + client: client, + limit: self.limit, + abort: abort, + }) + }, + Err(err) => { + self.pool.spawn(futures::future::err(err)) + }, + } } } diff --git a/util/fetch/src/lib.rs b/util/fetch/src/lib.rs index a013a0bcb..34091f4bc 100644 --- a/util/fetch/src/lib.rs +++ b/util/fetch/src/lib.rs @@ -21,6 +21,7 @@ extern crate log; extern crate futures; extern crate futures_cpupool; +extern crate parking_lot; extern crate reqwest; pub extern crate mime;