Merge branch 'master' into cht-td
This commit is contained in:
commit
02142e3a57
96
Cargo.lock
generated
96
Cargo.lock
generated
@ -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)" = "<none>"
|
||||
"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)" = "<none>"
|
||||
"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)" = "<none>"
|
||||
"checksum jsonrpc-http-server 7.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||
"checksum jsonrpc-ipc-server 1.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>"
|
||||
@ -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)" = "<none>"
|
||||
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
||||
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
|
||||
@ -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"
|
||||
|
@ -214,6 +214,7 @@ impl HeaderChain {
|
||||
pub fn get_header(&self, id: BlockId) -> Option<Bytes> {
|
||||
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 }
|
||||
|
@ -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),
|
||||
|
@ -9,7 +9,11 @@
|
||||
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
|
||||
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
|
||||
]
|
||||
}
|
||||
},
|
||||
"timeoutPropose": 10000,
|
||||
"timeoutPrevote": 10000,
|
||||
"timeoutPrecommit": 10000,
|
||||
"timeoutCommit": 10000
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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<SecretStore>) -> 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<Address>) -> 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<HashMap<Address, AccountMeta>, Error> {
|
||||
Ok(self.address_book.read().get())
|
||||
/// Removes addresses that are neither accounts nor in address book.
|
||||
fn filter_addresses(&self, addresses: Vec<Address>) -> Result<Vec<Address>, Error> {
|
||||
let valid = self.addresses_info().into_iter()
|
||||
.map(|(address, _)| address)
|
||||
.chain(self.accounts()?)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
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<Address, AccountMeta> {
|
||||
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()]);
|
||||
|
@ -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<K: hash::Hash + Eq, V> ops::DerefMut for DiskMap<K, V> {
|
||||
}
|
||||
|
||||
impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
||||
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<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
||||
}
|
||||
|
||||
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()])
|
||||
]);
|
||||
|
@ -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
|
||||
|
@ -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()),
|
||||
|
@ -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.
|
||||
|
@ -1406,7 +1406,11 @@ impl BlockChainClient for Client {
|
||||
}
|
||||
|
||||
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
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) {
|
||||
|
@ -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<PendingTransaction> {
|
||||
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<u64> { None }
|
||||
|
@ -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()))?;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,9 @@ fn set_timeout<S: Sync + Send + Clone>(io: &IoContext<S>, timeout: Duration) {
|
||||
|
||||
impl <S> IoHandler<S> for TransitionHandler<S> where S: Sync + Send + Clone + 'static {
|
||||
fn initialize(&self, io: &IoContext<S>) {
|
||||
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.
|
||||
|
@ -118,13 +118,13 @@ impl <M: Message + Default + Encodable + Debug> VoteCollector<M> {
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
@ -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<UnverifiedTransaction>,
|
||||
default_origin: TransactionOrigin,
|
||||
min_block: Option<BlockNumber>,
|
||||
condition: Option<TransactionCondition>,
|
||||
transaction_queue: &mut BanningTransactionQueue,
|
||||
) -> Vec<Result<TransactionImportResult, Error>> {
|
||||
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<PendingTransaction> {
|
||||
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<H256, LocalTransactionStatus> {
|
||||
@ -942,14 +942,14 @@ impl MinerService for Miner {
|
||||
self.transaction_queue.lock().future_transactions()
|
||||
}
|
||||
|
||||
fn ready_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction> {
|
||||
fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec<PendingTransaction> {
|
||||
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));
|
||||
|
@ -154,7 +154,7 @@ pub trait MinerService : Send + Sync {
|
||||
fn pending_transactions(&self) -> Vec<PendingTransaction>;
|
||||
|
||||
/// Get a list of all transactions that can go into the given block.
|
||||
fn ready_transactions(&self, best_block: BlockNumber) -> Vec<PendingTransaction>;
|
||||
fn ready_transactions(&self, best_block: BlockNumber, best_block_timestamp: u64) -> Vec<PendingTransaction>;
|
||||
|
||||
/// Get a list of all future transactions.
|
||||
fn future_transactions(&self) -> Vec<PendingTransaction>;
|
||||
|
@ -276,17 +276,17 @@ struct VerifiedTransaction {
|
||||
origin: TransactionOrigin,
|
||||
/// Insertion time
|
||||
insertion_time: QueuingInstant,
|
||||
/// Delay until specifid block.
|
||||
min_block: Option<BlockNumber>,
|
||||
/// Delay until specified condition is met.
|
||||
condition: Option<Condition>,
|
||||
}
|
||||
|
||||
impl VerifiedTransaction {
|
||||
fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, min_block: Option<BlockNumber>) -> Self {
|
||||
fn new(transaction: SignedTransaction, origin: TransactionOrigin, time: QueuingInstant, condition: Option<Condition>) -> 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<BlockNumber>,
|
||||
condition: Option<Condition>,
|
||||
details_provider: &TransactionDetailsProvider,
|
||||
) -> Result<TransactionImportResult, Error> {
|
||||
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<BlockNumber>,
|
||||
condition: Option<Condition>,
|
||||
details_provider: &TransactionDetailsProvider,
|
||||
) -> Result<TransactionImportResult, Error> {
|
||||
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<SignedTransaction> {
|
||||
self.top_transactions_at(BlockNumber::max_value())
|
||||
self.top_transactions_at(BlockNumber::max_value(), u64::max_value())
|
||||
|
||||
}
|
||||
|
||||
fn filter_pending_transaction<F>(&self, best_block: BlockNumber, mut f: F)
|
||||
fn filter_pending_transaction<F>(&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<SignedTransaction> {
|
||||
pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<SignedTransaction> {
|
||||
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<PendingTransaction> {
|
||||
pub fn pending_transactions(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<PendingTransaction> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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<H256>,
|
||||
/// Best ancient block number.
|
||||
|
@ -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<BlockNumber>,
|
||||
/// To be activated at this condition. `None` for immediately.
|
||||
pub condition: Option<Condition>,
|
||||
}
|
||||
|
||||
impl PendingTransaction {
|
||||
/// Create a new pending transaction from signed transaction.
|
||||
pub fn new(signed: SignedTransaction, min_block: Option<BlockNumber>) -> Self {
|
||||
pub fn new(signed: SignedTransaction, condition: Option<Condition>) -> Self {
|
||||
PendingTransaction {
|
||||
transaction: signed,
|
||||
min_block: min_block,
|
||||
condition: condition,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -466,7 +476,7 @@ impl From<SignedTransaction> for PendingTransaction {
|
||||
fn from(t: SignedTransaction) -> Self {
|
||||
PendingTransaction {
|
||||
transaction: t,
|
||||
min_block: None,
|
||||
condition: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Address> {
|
||||
|
@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<Address>;
|
||||
fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec<Address>, testnet: bool) -> Result<Vec<StoreAccountRef>, Error>;
|
||||
}
|
||||
|
@ -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 <admin@parity.io>",
|
||||
|
@ -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)}`;
|
||||
|
32
js/scripts/helpers/log.js
Normal file
32
js/scripts/helpers/log.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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}`));
|
||||
}
|
81
js/scripts/helpers/parsed-rpc-traits.js
Normal file
81
js/scripts/helpers/parsed-rpc-traits.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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;
|
||||
});
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
@ -36,6 +36,9 @@ function stubApi (accounts, info) {
|
||||
|
||||
return {
|
||||
_calls,
|
||||
transport: {
|
||||
isConnected: true
|
||||
},
|
||||
parity: {
|
||||
accountsInfo: () => {
|
||||
const stub = sinon.stub().resolves(info || TEST_INFO)();
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
// <div className={ styles.input }>
|
||||
// <label>global registration</label>
|
||||
// <select onChange={ this.onChangeRegistrar }>
|
||||
// <option value='no'>No, only for me</option>
|
||||
// <option value='yes'>Yes, for everybody</option>
|
||||
// </select>
|
||||
// <div className={ styles.hint }>
|
||||
// register on network (fee: { globalFeeText }ETH)
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div className={ styles.form }>
|
||||
<div className={ styles.input }>
|
||||
<label>deployment account</label>
|
||||
<AddressSelect
|
||||
addresses={ addresses }
|
||||
onChange={ this.onChangeFrom }
|
||||
/>
|
||||
<div className={ styles.hint }>
|
||||
the owner account to deploy from
|
||||
</div>
|
||||
</div>
|
||||
<div className={ nameError ? error : styles.input }>
|
||||
<label>token name</label>
|
||||
<input
|
||||
@ -206,12 +182,6 @@ export default class Deployment extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
onChangeFrom = (event) => {
|
||||
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' });
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
<div className={ styles.buttons }>
|
||||
<div className={ styles.addressSelect }>
|
||||
<Button invert onClick={ this.onSelectFromAddress }>
|
||||
<IdentityIcon address={ account.address } />
|
||||
<div>{ account.name || account.address }</div>
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
onClick={ this.onClickRegister }
|
||||
disabled={ (contentHashError && contentHashOwner !== fromAddress) || urlError || repoError || commitError }
|
||||
disabled={ (contentHashError && contentHashOwner !== defaultAddress) || urlError || repoError || commitError }
|
||||
>register url</Button>
|
||||
</div>
|
||||
);
|
||||
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -82,6 +82,7 @@ export const ownerLookup = (name) => (dispatch, getState) => {
|
||||
return;
|
||||
}
|
||||
|
||||
name = name.toLowerCase();
|
||||
dispatch(ownerLookupStart(name));
|
||||
|
||||
return getOwner(contract, name)
|
||||
|
@ -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 (
|
||||
<Import
|
||||
accounts={ accounts }
|
||||
fromAddress={ fromAddress }
|
||||
instance={ instance }
|
||||
visible={ showImport }
|
||||
onClose={ this.toggleImport }
|
||||
onSetFromAddress={ this.setFromAddress }
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -124,10 +118,4 @@ export default class Application extends Component {
|
||||
showImport: !this.state.showImport
|
||||
});
|
||||
}
|
||||
|
||||
setFromAddress = (fromAddress) => {
|
||||
this.setState({
|
||||
fromAddress
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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 = (
|
||||
<div className={ styles.buttonrow }>
|
||||
<div className={ styles.addressSelect }>
|
||||
<Button invert onClick={ this.onSelectFromAddress }>
|
||||
<IdentityIcon address={ account.address } />
|
||||
<div>{ account.name || account.address }</div>
|
||||
</Button>
|
||||
</div>
|
||||
<Button onClick={ this.onRegister }>
|
||||
register functions
|
||||
</Button>
|
||||
@ -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]);
|
||||
}
|
||||
}
|
||||
|
@ -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)}`);
|
||||
|
@ -14,8 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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');
|
||||
|
@ -15,15 +15,24 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.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;
|
||||
}
|
||||
|
@ -14,14 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 (
|
||||
<Modal
|
||||
actions={ [
|
||||
<Button
|
||||
icon={ <DoneIcon /> }
|
||||
key='done'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='dapps.add.button.done'
|
||||
defaultMessage='Done'
|
||||
/>
|
||||
}
|
||||
onClick={ store.closeModal }
|
||||
/>
|
||||
] }
|
||||
compact
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='dapps.add.label'
|
||||
defaultMessage='visible applications'
|
||||
/>
|
||||
}
|
||||
visible
|
||||
<Portal
|
||||
className={ styles.modal }
|
||||
onClose={ store.closeModal }
|
||||
open
|
||||
>
|
||||
<div className={ styles.warning } />
|
||||
{
|
||||
this.renderList(store.sortedLocal,
|
||||
<ContainerTitle
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='dapps.add.local.label'
|
||||
defaultMessage='Applications locally available'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='dapps.add.local.desc'
|
||||
defaultMessage='All applications installed locally on the machine by the user for access by the Parity client.'
|
||||
id='dapps.add.label'
|
||||
defaultMessage='visible applications'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderList(store.sortedBuiltin,
|
||||
<FormattedMessage
|
||||
id='dapps.add.builtin.label'
|
||||
defaultMessage='Applications bundled with Parity'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='dapps.add.builtin.desc'
|
||||
defaultMessage='Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderList(store.sortedNetwork,
|
||||
<FormattedMessage
|
||||
id='dapps.add.network.label'
|
||||
defaultMessage='Applications on the global network'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='dapps.add.network.desc'
|
||||
defaultMessage='These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Modal>
|
||||
}
|
||||
/>
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.warning } />
|
||||
{
|
||||
this.renderList(store.sortedLocal, store.displayApps,
|
||||
<FormattedMessage
|
||||
id='dapps.add.local.label'
|
||||
defaultMessage='Applications locally available'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='dapps.add.local.desc'
|
||||
defaultMessage='All applications installed locally on the machine by the user for access by the Parity client.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderList(store.sortedBuiltin, store.displayApps,
|
||||
<FormattedMessage
|
||||
id='dapps.add.builtin.label'
|
||||
defaultMessage='Applications bundled with Parity'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='dapps.add.builtin.desc'
|
||||
defaultMessage='Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderList(store.sortedNetwork, store.displayApps,
|
||||
<FormattedMessage
|
||||
id='dapps.add.network.label'
|
||||
defaultMessage='Applications on the global network'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='dapps.add.network.desc'
|
||||
defaultMessage='These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
<div className={ styles.header }>{ header }</div>
|
||||
<div className={ styles.byline }>{ byline }</div>
|
||||
</div>
|
||||
<List>
|
||||
{ items.map(this.renderApp) }
|
||||
</List>
|
||||
<SectionList
|
||||
items={ items }
|
||||
noStretch
|
||||
renderItem={ this.renderApp }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<ListItem
|
||||
<DappCard
|
||||
app={ app }
|
||||
className={
|
||||
isVisible
|
||||
? styles.selected
|
||||
: styles.unselected
|
||||
}
|
||||
key={ app.id }
|
||||
leftCheckbox={
|
||||
<Checkbox
|
||||
checked={ !isHidden }
|
||||
onCheck={ onCheck }
|
||||
/>
|
||||
}
|
||||
primaryText={ app.name }
|
||||
secondaryText={
|
||||
<div className={ styles.description }>
|
||||
{ app.description }
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
onClick={ onClick }
|
||||
>
|
||||
<CheckIcon className={ styles.selectIcon } />
|
||||
</DappCard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -30,6 +30,10 @@
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.terms {
|
||||
line-height: 1.3;
|
||||
opacity: .7;
|
||||
|
@ -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() }
|
||||
<Checkbox
|
||||
className={ styles.spacing }
|
||||
label={
|
||||
@ -63,7 +67,7 @@ export default class GatherData extends Component {
|
||||
defaultMessage='I agree to the terms and conditions below.'
|
||||
/>
|
||||
}
|
||||
disabled={ isVerified }
|
||||
disabled={ accountIsVerified }
|
||||
onCheck={ this.consentOnChange }
|
||||
/>
|
||||
<div className={ styles.terms }>{ termsOfService }</div>
|
||||
@ -145,27 +149,27 @@ export default class GatherData extends Component {
|
||||
}
|
||||
|
||||
renderCertified () {
|
||||
const { isVerified } = this.props;
|
||||
const { accountIsVerified } = this.props;
|
||||
|
||||
if (isVerified) {
|
||||
if (accountIsVerified) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<ErrorIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isVerified.true'
|
||||
id='ui.verification.gatherData.accountIsVerified.true'
|
||||
defaultMessage='Your account is already verified.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if (isVerified === false) {
|
||||
} else if (accountIsVerified === false) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<SuccessIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isVerified.false'
|
||||
id='ui.verification.gatherData.accountIsVerified.false'
|
||||
defaultMessage='Your account is not verified yet.'
|
||||
/>
|
||||
</p>
|
||||
@ -175,7 +179,7 @@ export default class GatherData extends Component {
|
||||
return (
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isVerified.pending'
|
||||
id='ui.verification.gatherData.accountIsVerified.pending'
|
||||
defaultMessage='Checking if your account is verified…'
|
||||
/>
|
||||
</p>
|
||||
@ -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 (
|
||||
<div className={ styles.container }>
|
||||
<InfoIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.hasRequested.true'
|
||||
defaultMessage='You already requested verification.'
|
||||
id='ui.verification.gatherData.accountHasRequested.true'
|
||||
defaultMessage='You already requested verification from this account.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if (hasRequested === false) {
|
||||
} else if (accountHasRequested === false) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<SuccessIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.hasRequested.false'
|
||||
defaultMessage='You did not request verification yet.'
|
||||
id='ui.verification.gatherData.accountHasRequested.false'
|
||||
defaultMessage='You did not request verification from this account yet.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
@ -218,7 +222,7 @@ export default class GatherData extends Component {
|
||||
return (
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.hasRequested.pending'
|
||||
id='ui.verification.gatherData.accountHasRequested.pending'
|
||||
defaultMessage='Checking if you requested verification…'
|
||||
/>
|
||||
</p>
|
||||
@ -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 (
|
||||
<Input
|
||||
className={ styles.field }
|
||||
key={ field.key }
|
||||
label={ field.label }
|
||||
hint={ field.hint }
|
||||
error={ field.error }
|
||||
disabled={ isVerified }
|
||||
disabled={ accountIsVerified }
|
||||
onChange={ onChange }
|
||||
onSubmit={ onSubmit }
|
||||
/>
|
||||
@ -250,6 +255,36 @@ export default class GatherData extends Component {
|
||||
return (<div>{rendered}</div>);
|
||||
}
|
||||
|
||||
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 (
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isAbleToRequest.pending'
|
||||
defaultMessage='Validating your input…'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
} else if (isAbleToRequest) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<ErrorIcon />
|
||||
<p className={ styles.message }>
|
||||
{ isAbleToRequest.message }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
consentOnChange = (_, consentGiven) => {
|
||||
this.props.setConsentGiven(consentGiven);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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: (
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.phoneNumber.label'
|
||||
defaultMessage='phone number in international format'
|
||||
/>
|
||||
),
|
||||
hint: (
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.phoneNumber.hint'
|
||||
defaultMessage='the SMS will be sent to this number'
|
||||
/>
|
||||
),
|
||||
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: (
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.email.label'
|
||||
defaultMessage='e-mail address'
|
||||
/>
|
||||
),
|
||||
hint: (
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.email.hint'
|
||||
defaultMessage='the code will be sent to this address'
|
||||
/>
|
||||
),
|
||||
error: this.store.isEmailValid ? null : 'invalid e-mail',
|
||||
onChange: this.store.setEmail
|
||||
});
|
||||
}
|
||||
@ -241,10 +262,12 @@ class Verification extends Component {
|
||||
return (
|
||||
<GatherData
|
||||
fee={ fee }
|
||||
hasRequested={ hasRequested }
|
||||
accountHasRequested={ accountHasRequested }
|
||||
isServerRunning={ isServerRunning }
|
||||
isVerified={ isVerified }
|
||||
method={ method } fields={ fields }
|
||||
isAbleToRequest={ isAbleToRequest }
|
||||
accountIsVerified={ accountIsVerified }
|
||||
method={ method }
|
||||
fields={ fields }
|
||||
setConsentGiven={ setConsentGiven }
|
||||
/>
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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 (
|
||||
<div className={ classes } style={ style } { ...props }>
|
||||
<Card className={ compact ? styles.compact : styles.padded }>
|
||||
<div
|
||||
className={
|
||||
[
|
||||
styles.container,
|
||||
light
|
||||
? styles.light
|
||||
: '',
|
||||
className
|
||||
].join(' ')
|
||||
}
|
||||
style={ style }
|
||||
{ ...props }
|
||||
>
|
||||
<Card
|
||||
className={
|
||||
compact
|
||||
? styles.compact
|
||||
: styles.padded
|
||||
}
|
||||
onClick={ onClick }
|
||||
>
|
||||
{ this.renderTitle() }
|
||||
{ children }
|
||||
</Card>
|
||||
|
@ -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 (
|
||||
<Container className={ styles.container }>
|
||||
<Container
|
||||
className={
|
||||
[styles.container, className].join(' ')
|
||||
}
|
||||
onClick={ onClick }
|
||||
>
|
||||
<DappIcon
|
||||
app={ app }
|
||||
className={ styles.image }
|
||||
/>
|
||||
<Tags tags={ [app.type] } />
|
||||
<Tags
|
||||
tags={
|
||||
showTags
|
||||
? [app.type]
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<div className={ styles.description }>
|
||||
<ContainerTitle
|
||||
className={ styles.title }
|
||||
title={ link }
|
||||
title={
|
||||
showLink
|
||||
? this.renderLink(app)
|
||||
: app.name
|
||||
}
|
||||
byline={ app.description }
|
||||
/>
|
||||
<div className={ styles.author }>
|
||||
{ app.author }, v{ app.version }
|
||||
</div>
|
||||
{ this.props.children }
|
||||
{ children }
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
renderLink (app) {
|
||||
// Special case for web dapp
|
||||
if (app.url === 'web') {
|
||||
return (
|
||||
<Link to={ `/web` }>
|
||||
{ app.name }
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={ `/app/${app.id}` }>
|
||||
<Link
|
||||
to={
|
||||
app.url === 'web'
|
||||
? '/web'
|
||||
: `/app/${app.id}`
|
||||
}
|
||||
>
|
||||
{ app.name }
|
||||
</Link>
|
||||
);
|
@ -14,4 +14,4 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './summary';
|
||||
export default from './dappCard';
|
@ -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,
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<AddDapps store={ this.store } />
|
||||
<DappPermissions store={ this.permissionStore } />
|
||||
<DappsVisible store={ this.store } />
|
||||
<Actionbar
|
||||
className={ styles.toolbar }
|
||||
title={
|
||||
@ -146,7 +145,11 @@ class Dapps extends Component {
|
||||
className={ styles.item }
|
||||
key={ app.id }
|
||||
>
|
||||
<Summary app={ app } />
|
||||
<DappCard
|
||||
app={ app }
|
||||
showLink
|
||||
showTags
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ pub fn sign_and_dispatch<C, M>(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<C, M>(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<C, M>(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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<Bytes>,
|
||||
/// Transaction's nonce
|
||||
pub nonce: Option<U256>,
|
||||
/// Delay until this block if specified.
|
||||
pub min_block: Option<u64>,
|
||||
/// Delay until this condition is met.
|
||||
pub condition: Option<TransactionCondition>,
|
||||
}
|
||||
|
||||
/// 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<U256>,
|
||||
/// Delay until this block if specified.
|
||||
pub min_block: Option<u64>,
|
||||
/// Delay until this condition is met.
|
||||
pub condition: Option<TransactionCondition>,
|
||||
}
|
||||
|
||||
impl From<FilledTransactionRequest> for TransactionRequest {
|
||||
@ -70,7 +71,7 @@ impl From<FilledTransactionRequest> for TransactionRequest {
|
||||
value: Some(r.value),
|
||||
data: Some(r.data),
|
||||
nonce: r.nonce,
|
||||
min_block: r.min_block,
|
||||
condition: r.condition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -349,7 +349,7 @@ mod test {
|
||||
value: 10_000_000.into(),
|
||||
data: vec![],
|
||||
nonce: None,
|
||||
min_block: None,
|
||||
condition: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -130,7 +130,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
||||
.into_iter().collect::<HashSet<_>>();
|
||||
|
||||
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()
|
||||
|
@ -51,22 +51,27 @@ impl<C> ParityAccountsClient<C> where C: MiningBlockChainClient {
|
||||
}
|
||||
|
||||
impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlockChainClient {
|
||||
fn all_accounts_info(&self) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error> {
|
||||
fn all_accounts_info(&self) -> Result<BTreeMap<RpcH160, BTreeMap<String, String>>, 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<RpcH160, Error> {
|
||||
@ -132,8 +137,7 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> 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<C: 'static> ParityAccounts for ParityAccountsClient<C> 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<C: 'static> ParityAccounts for ParityAccountsClient<C> 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)
|
||||
}
|
||||
|
||||
|
@ -87,8 +87,8 @@ impl<C: 'static, M: 'static> SignerClient<C, M> 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<C: 'static, M: 'static> Signer for SignerClient<C, M> 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)
|
||||
|
@ -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<PendingTransaction> {
|
||||
fn ready_transactions(&self, _best_block: BlockNumber, _best_timestamp: u64) -> Vec<PendingTransaction> {
|
||||
self.pending_transactions.lock().values().cloned().map(Into::into).collect()
|
||||
}
|
||||
|
||||
|
@ -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()) +
|
||||
|
@ -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()));
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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()) +
|
||||
|
@ -25,7 +25,7 @@ build_rpc_trait! {
|
||||
pub trait ParityAccounts {
|
||||
/// Returns accounts information.
|
||||
#[rpc(name = "parity_allAccountsInfo")]
|
||||
fn all_accounts_info(&self) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error>;
|
||||
fn all_accounts_info(&self) -> Result<BTreeMap<H160, BTreeMap<String, String>>, Error>;
|
||||
|
||||
/// Creates new account from the given phrase using standard brainwallet mechanism.
|
||||
/// Second parameter is password for the new account.
|
||||
|
@ -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();
|
||||
|
@ -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<U256>,
|
||||
/// Modified gas
|
||||
pub gas: Option<U256>,
|
||||
/// Modified min block
|
||||
#[serde(rename="minBlock")]
|
||||
pub min_block: Option<Option<BlockNumber>>,
|
||||
/// Modified transaction condition.
|
||||
pub condition: Option<Option<TransactionCondition>>,
|
||||
}
|
||||
|
||||
/// Represents two possible return values.
|
||||
@ -240,7 +239,7 @@ impl<A, B> Serialize for Either<A, B> 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,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
|
@ -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<BlockNumber>,
|
||||
pub condition: Option<TransactionCondition>,
|
||||
}
|
||||
|
||||
/// Local Transaction Status
|
||||
@ -190,7 +189,7 @@ impl From<LocalizedTransaction> 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<SignedTransaction> 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<SignedTransaction> for Transaction {
|
||||
impl From<PendingTransaction> 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]
|
||||
|
67
rpc/src/v1/types/transaction_condition.rs
Normal file
67
rpc/src/v1/types/transaction_condition.rs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<ethcore::transaction::Condition> 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<ethcore::transaction::Condition> 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<TransactionCondition> = 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());
|
||||
}
|
||||
}
|
||||
|
@ -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<Bytes>,
|
||||
/// Transaction's nonce
|
||||
pub nonce: Option<U256>,
|
||||
/// Delay until this block if specified.
|
||||
#[serde(rename="minBlock")]
|
||||
pub min_block: Option<BlockNumber>,
|
||||
/// Delay until this block condition.
|
||||
pub condition: Option<TransactionCondition>,
|
||||
}
|
||||
|
||||
pub fn format_ether(i: U256) -> String {
|
||||
@ -93,7 +92,7 @@ impl From<helpers::TransactionRequest> 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<helpers::FilledTransactionRequest> 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<helpers::TransactionRequest> 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,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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<U256>,
|
||||
new_gas_price: Option<U256>,
|
||||
new_min_block: Option<Option<BlockNumber>>,
|
||||
new_condition: Option<Option<TransactionCondition>>,
|
||||
pwd: &str
|
||||
) -> BoxFuture<Result<U256, RpcError>, 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),
|
||||
])
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -9,8 +9,9 @@ authors = ["Parity Technologies <admin@parity.io>"]
|
||||
[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}
|
||||
|
||||
|
@ -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<reqwest::Client>,
|
||||
client: RwLock<(time::Instant, Arc<reqwest::Client>)>,
|
||||
pool: CpuPool,
|
||||
limit: Option<usize>,
|
||||
}
|
||||
|
||||
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<usize>) -> Result<Self, Error> {
|
||||
fn new_client() -> Result<Arc<reqwest::Client>, Error> {
|
||||
let mut client = reqwest::Client::new()?;
|
||||
client.redirect(reqwest::RedirectPolicy::limited(5));
|
||||
Ok(Arc::new(client))
|
||||
}
|
||||
|
||||
fn with_limit(limit: Option<usize>) -> Result<Self, Error> {
|
||||
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<Arc<reqwest::Client>, 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))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user