Merge branch 'cht-td' into lightrpc

This commit is contained in:
Robert Habermeier 2017-02-04 17:14:47 +01:00
commit 484b93abdc
92 changed files with 1383 additions and 776 deletions

114
Cargo.lock generated
View File

@ -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,16 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "elastic-array"
version = "0.6.0"
@ -515,6 +530,7 @@ dependencies = [
"ethcore-network 1.6.0",
"ethcore-util 1.6.0",
"futures 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0",
@ -650,7 +666,7 @@ dependencies = [
"ethcore-bloom-journal 0.1.0",
"ethcore-devtools 1.6.0",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -715,7 +731,7 @@ dependencies = [
"ethcore-util 1.6.0",
"ethcrypto 0.1.0",
"ethkey 0.2.0",
"itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -779,7 +795,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]]
@ -896,6 +913,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"
@ -941,14 +987,22 @@ dependencies = [
[[package]]
name = "itertools"
version = "0.4.13"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
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"
@ -1509,7 +1563,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)",
]
@ -1707,15 +1761,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)",
]
@ -1901,6 +1955,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"
@ -1933,11 +1992,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)",
]
@ -2397,6 +2469,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"
@ -2425,6 +2498,8 @@ 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"
"checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "<none>"
@ -2441,12 +2516,15 @@ 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>"
@ -2521,7 +2599,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>"
@ -2540,10 +2618,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"

View File

@ -22,6 +22,7 @@ time = "0.1"
smallvec = "0.3.1"
futures = "0.1"
rand = "0.3"
itertools = "0.5"
[features]
default = []

View File

@ -12,10 +12,130 @@
// GNU General Public License for more details.
//! Canonical hash trie definitions and helper functions.
//!
//! Each CHT is a trie mapping block numbers to canonical hashes and total difficulty.
//! One is generated for every `SIZE` blocks, allowing us to discard those blocks in
//! favor the the trie root. When the "ancient" blocks need to be accessed, we simply
//! request an inclusion proof of a specific block number against the trie with the
//! root has. A correct proof implies that the claimed block is identical to the one
//! we discarded.
use ethcore::ids::BlockId;
use util::{Bytes, H256, U256, HashDB, MemoryDB};
use util::trie::{self, TrieMut, TrieDBMut, Trie, TrieDB, Recorder};
use rlp::{Stream, RlpStream};
// encode a key.
macro_rules! key {
($num: expr) => { ::rlp::encode(&$num) }
}
macro_rules! val {
($hash: expr, $td: expr) => {{
let mut stream = RlpStream::new_list(2);
stream.append(&$hash).append(&$td);
stream.drain()
}}
}
/// The size of each CHT.
pub const SIZE: u64 = 2048;
/// A canonical hash trie. This is generic over any database it can query.
/// See module docs for more details.
#[derive(Debug, Clone)]
pub struct CHT<DB: HashDB> {
db: DB,
root: H256, // the root of this CHT.
number: u64,
}
impl<DB: HashDB> CHT<DB> {
/// Query the root of the CHT.
pub fn root(&self) -> H256 { self.root }
/// Query the number of the CHT.
pub fn number(&self) -> u64 { self.number }
/// Generate an inclusion proof for the entry at a specific block.
/// Nodes before level `from_level` will be omitted.
/// Returns an error on an incomplete trie, and `Ok(None)` on an unprovable request.
pub fn prove(&self, num: u64, from_level: u32) -> trie::Result<Option<Vec<Bytes>>> {
if block_to_cht_number(num) != Some(self.number) { return Ok(None) }
let mut recorder = Recorder::with_depth(from_level);
let t = TrieDB::new(&self.db, &self.root)?;
t.get_with(&key!(num), &mut recorder)?;
Ok(Some(recorder.drain().into_iter().map(|x| x.data).collect()))
}
}
/// Block information necessary to build a CHT.
pub struct BlockInfo {
/// The block's hash.
pub hash: H256,
/// The block's parent's hash.
pub parent_hash: H256,
/// The block's total difficulty.
pub total_difficulty: U256,
}
/// Build an in-memory CHT from a closure which provides necessary information
/// about blocks. If the fetcher ever fails to provide the info, the CHT
/// will not be generated.
pub fn build<F>(cht_num: u64, mut fetcher: F) -> Option<CHT<MemoryDB>>
where F: FnMut(BlockId) -> Option<BlockInfo>
{
let mut db = MemoryDB::new();
// start from the last block by number and work backwards.
let last_num = start_number(cht_num + 1) - 1;
let mut id = BlockId::Number(last_num);
let mut root = H256::default();
{
let mut t = TrieDBMut::new(&mut db, &mut root);
for blk_num in (0..SIZE).map(|n| last_num - n) {
let info = match fetcher(id) {
Some(info) => info,
None => return None,
};
id = BlockId::Hash(info.parent_hash);
t.insert(&key!(blk_num), &val!(info.hash, info.total_difficulty))
.expect("fresh in-memory database is infallible; qed");
}
}
Some(CHT {
db: db,
root: root,
number: cht_num,
})
}
/// Compute a CHT root from an iterator of (hash, td) pairs. Fails if shorter than
/// SIZE items. The items are assumed to proceed sequentially from `start_number(cht_num)`.
/// Discards the trie's nodes.
pub fn compute_root<I>(cht_num: u64, iterable: I) -> Option<H256>
where I: IntoIterator<Item=(H256, U256)>
{
let mut v = Vec::with_capacity(SIZE as usize);
let start_num = start_number(cht_num) as usize;
for (i, (h, td)) in iterable.into_iter().take(SIZE as usize).enumerate() {
v.push((key!(i + start_num).to_vec(), val!(h, td).to_vec()))
}
if v.len() == SIZE as usize {
Some(::util::triehash::trie_root(v))
} else {
None
}
}
/// Convert a block number to a CHT number.
/// Returns `None` for `block_num` == 0, `Some` otherwise.
pub fn block_to_cht_number(block_num: u64) -> Option<u64> {
@ -37,6 +157,12 @@ pub fn start_number(cht_num: u64) -> u64 {
#[cfg(test)]
mod tests {
#[test]
fn size_is_lt_usize() {
// to ensure safe casting on the target platform.
assert!(::cht::SIZE < usize::max_value() as u64)
}
#[test]
fn block_to_cht_number() {
assert!(::cht::block_to_cht_number(0).is_none());

View File

@ -173,26 +173,34 @@ impl HeaderChain {
// produce next CHT root if it's time.
let earliest_era = *candidates.keys().next().expect("at least one era just created; qed");
if earliest_era + HISTORY + cht::SIZE <= number {
let mut values = Vec::with_capacity(cht::SIZE as usize);
{
let cht_num = cht::block_to_cht_number(earliest_era)
.expect("fails only for number == 0; genesis never imported; qed");
debug_assert_eq!(cht_num as usize, self.cht_roots.lock().len());
let mut headers = self.headers.write();
for i in (0..cht::SIZE).map(|x| x + earliest_era) {
let cht_root = {
let mut i = earliest_era;
// iterable function which removes the candidates as it goes
// along. this will only be called until the CHT is complete.
let iter = || {
let era_entry = candidates.remove(&i)
.expect("all eras are sequential with no gaps; qed");
i += 1;
for ancient in &era_entry.candidates {
headers.remove(&ancient.hash);
}
values.push((
::rlp::encode(&i).to_vec(),
::rlp::encode(&era_entry.canonical_hash).to_vec(),
));
}
}
let canon = &era_entry.candidates[0];
(canon.hash, canon.total_difficulty)
};
cht::compute_root(cht_num, ::itertools::repeat_call(iter))
.expect("fails only when too few items; this is checked; qed")
};
let cht_root = ::util::triehash::trie_root(values);
debug!(target: "chain", "Produced CHT {} root: {:?}", (earliest_era - 1) % cht::SIZE, cht_root);
debug!(target: "chain", "Produced CHT {} root: {:?}", cht_num, cht_root);
self.cht_roots.lock().push(cht_root);
}

View File

@ -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),

View File

@ -68,6 +68,7 @@ extern crate smallvec;
extern crate time;
extern crate futures;
extern crate rand;
extern crate itertools;
#[cfg(feature = "ipc")]
extern crate ethcore_ipc as ipc;

View File

@ -23,6 +23,8 @@ use ethcore::transaction::PendingTransaction;
use ethcore::ids::BlockId;
use ethcore::encoded;
use cht::{self, BlockInfo};
use util::{Bytes, H256};
use request;
@ -227,48 +229,54 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
}
fn header_proof(&self, req: request::HeaderProof) -> Option<(encoded::Header, Vec<Bytes>)> {
use util::MemoryDB;
use util::trie::{Trie, TrieMut, TrieDB, TrieDBMut, Recorder};
if Some(req.cht_number) != ::cht::block_to_cht_number(req.block_number) {
if Some(req.cht_number) != cht::block_to_cht_number(req.block_number) {
debug!(target: "les_provider", "Requested CHT number mismatch with block number.");
return None;
}
let mut memdb = MemoryDB::new();
let mut root = H256::default();
let mut needed_hdr = None;
{
let mut t = TrieDBMut::new(&mut memdb, &mut root);
let start_num = ::cht::start_number(req.cht_number);
for i in (0..::cht::SIZE).map(|x| x + start_num) {
match self.block_header(BlockId::Number(i)) {
None => return None,
Some(hdr) => {
t.insert(
&*::rlp::encode(&i),
&*::rlp::encode(&hdr.hash()),
).expect("fresh in-memory database is infallible; qed");
if i == req.block_number { needed_hdr = Some(hdr) }
// build the CHT, caching the requested header as we pass through it.
let cht = {
let block_info = |id| {
let hdr = self.block_header(id);
let td = self.block_total_difficulty(id);
match (hdr, td) {
(Some(hdr), Some(td)) => {
let info = BlockInfo {
hash: hdr.hash(),
parent_hash: hdr.parent_hash(),
total_difficulty: td,
};
if hdr.number() == req.block_number {
needed_hdr = Some(hdr);
}
Some(info)
}
_ => None,
}
};
match cht::build(req.cht_number, block_info) {
Some(cht) => cht,
None => return None, // incomplete CHT.
}
};
let needed_hdr = needed_hdr.expect("`needed_hdr` always set in loop, number checked before; qed");
let mut recorder = Recorder::with_depth(req.from_level);
let t = TrieDB::new(&memdb, &root)
.expect("Same DB and root as just produced by TrieDBMut; qed");
if let Err(e) = t.get_with(&*::rlp::encode(&req.block_number), &mut recorder) {
// prove our result.
match cht.prove(req.block_number, req.from_level) {
Ok(Some(proof)) => Some((needed_hdr, proof)),
Ok(None) => None,
Err(e) => {
debug!(target: "les_provider", "Error looking up number in freshly-created CHT: {}", e);
return None;
None
}
}
// TODO: cache calculated CHT if possible.
let proof = recorder.drain().into_iter().map(|x| x.data).collect();
Some((needed_hdr, proof))
}
fn ready_transactions(&self) -> Vec<PendingTransaction> {

View File

@ -9,7 +9,11 @@
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
]
}
},
"timeoutPropose": 10000,
"timeoutPrevote": 10000,
"timeoutPrecommit": 10000,
"timeoutCommit": 10000
}
}
},

View File

@ -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()]);

View File

@ -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()])
]);

View File

@ -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

View File

@ -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()),

View File

@ -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.

View File

@ -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) {

View File

@ -552,7 +552,7 @@ impl BlockChainClient for TestBlockChainClient {
let mut adding = false;
let mut blocks = Vec::new();
for (_, hash) in numbers_read.iter().sort_by(|tuple1, tuple2| tuple1.0.cmp(tuple2.0)) {
for (_, hash) in numbers_read.iter().sorted_by(|tuple1, tuple2| tuple1.0.cmp(tuple2.0)) {
if hash == to {
if adding {
blocks.push(hash.clone());
@ -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 }

View File

@ -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()))?;
}

View File

@ -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();
}
}

View File

@ -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.

View File

@ -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
}
}

View File

@ -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));

View File

@ -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>;

View File

@ -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);
}

View File

@ -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(),

View File

@ -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.

View File

@ -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,
}
}
}

View File

@ -18,7 +18,7 @@ tiny-keccak = "1.0"
docopt = { version = "0.6", optional = true }
time = "0.1.34"
lazy_static = "0.2"
itertools = "0.4"
itertools = "0.5"
parking_lot = "0.3"
ethcrypto = { path = "../ethcrypto" }
ethcore-util = { path = "../util" }

View File

@ -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> {

View File

@ -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>;
}

View File

@ -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>",

View File

@ -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
View 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}`));
}

View 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;
});

View File

@ -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());
}
}

View File

@ -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) => {
if (this._lastDefaultAccount !== defaultAccount) {
this._lastDefaultAccount = defaultAccount;
this._updateSubscriptions('parity_defaultAccount', null, defaultAccount);
});
}
nextTimeout();
})
.catch(() => nextTimeout());
}
_listAccounts = () => {
@ -54,15 +80,21 @@ 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);
return this._api.parity
.allAccountsInfo()
.catch(() => {
// NOTE: This fails on non-secure APIs, swallow error
return {};
})
.then((allInfo) => {
this._updateSubscriptions('parity_allAccountsInfo', null, allInfo);
});
});
}
_loggingSubscribe () {
@ -89,7 +121,7 @@ export default class Personal {
case 'parity_setDappsAddresses':
case 'parity_setNewDappsWhitelist':
this._defaultAccount();
this._defaultAccount(true);
return;
}
});

View File

@ -36,6 +36,9 @@ function stubApi (accounts, info) {
return {
_calls,
transport: {
isConnected: true
},
parity: {
accountsInfo: () => {
const stub = sinon.stub().resolves(info || TEST_INFO)();

View File

@ -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) {

View File

@ -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,

View File

@ -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' });

View File

@ -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);

View File

@ -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;

View File

@ -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);
}

View File

@ -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) {

View File

@ -82,6 +82,7 @@ export const ownerLookup = (name) => (dispatch, getState) => {
return;
}
name = name.toLowerCase();
dispatch(ownerLookupStart(name));
return getOwner(contract, name)

View File

@ -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
});
}
}

View File

@ -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]);
}
}

View File

@ -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)}`);

View File

@ -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');

View File

@ -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;
}

View File

@ -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,32 +37,23 @@ 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
<Portal
className={ styles.modal }
onClose={ store.closeModal }
open
>
<ContainerTitle
title={
<FormattedMessage
id='dapps.add.label'
defaultMessage='visible applications'
/>
}
visible
>
/>
<div className={ styles.container }>
<div className={ styles.warning } />
{
this.renderList(store.sortedLocal,
this.renderList(store.sortedLocal, store.displayApps,
<FormattedMessage
id='dapps.add.local.label'
defaultMessage='Applications locally available'
@ -76,7 +65,7 @@ export default class AddDapps extends Component {
)
}
{
this.renderList(store.sortedBuiltin,
this.renderList(store.sortedBuiltin, store.displayApps,
<FormattedMessage
id='dapps.add.builtin.label'
defaultMessage='Applications bundled with Parity'
@ -88,7 +77,7 @@ export default class AddDapps extends Component {
)
}
{
this.renderList(store.sortedNetwork,
this.renderList(store.sortedNetwork, store.displayApps,
<FormattedMessage
id='dapps.add.network.label'
defaultMessage='Applications on the global network'
@ -99,11 +88,12 @@ export default class AddDapps extends Component {
/>
)
}
</Modal>
</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>
);
}
}

View File

@ -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);
});
});

View File

@ -30,6 +30,10 @@
margin-left: .5em;
}
.field {
margin-bottom: .5em;
}
.terms {
line-height: 1.3;
opacity: .7;

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,24 +161,27 @@ 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)
return 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.
// 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) => {
@ -181,9 +195,7 @@ export default class VerificationStore {
return waitForConfirmations(api, txHash, 1);
});
});
}
chain
})
.then(() => this.checkIfReceivedCode())
.then((hasReceived) => {
if (hasReceived) {

View File

@ -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 }
/>
);

View File

@ -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,

View File

@ -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>

View File

@ -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>
);
<Link
to={
app.url === 'web'
? '/web'
: `/app/${app.id}`
}
return (
<Link to={ `/app/${app.id}` }>
>
{ app.name }
</Link>
);

View File

@ -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';

View File

@ -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,

View File

@ -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>
);
}

View File

@ -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,
}
}

View File

@ -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,
}
}
}

View File

@ -349,7 +349,7 @@ mod test {
value: 10_000_000.into(),
data: vec![],
nonce: None,
min_block: None,
condition: None,
})
}

View File

@ -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()

View File

@ -51,13 +51,16 @@ 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)| {
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
@ -65,8 +68,10 @@ impl<C: 'static> ParityAccounts for ParityAccountsClient<C> where C: MiningBlock
if let &Some(ref uuid) = &v.uuid {
m.insert("uuid".to_owned(), format!("{}", uuid));
}
(format!("0x{}", a.hex()), m)
}).collect())
(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)
}

View File

@ -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)

View File

@ -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()
}

View File

@ -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()) +

View File

@ -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()));

View File

@ -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 {

View File

@ -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()) +

View File

@ -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.

View File

@ -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();

View File

@ -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,
});
}

View File

@ -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};

View File

@ -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]

View 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());
}
}

View File

@ -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,
});
}

View File

@ -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),
])
}

View File

@ -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

View File

@ -21,7 +21,7 @@ rust-crypto = "0.2.34"
elastic-array = { git = "https://github.com/ethcore/elastic-array" }
rlp = { path = "rlp" }
heapsize = { version = "0.3", features = ["unstable"] }
itertools = "0.4"
itertools = "0.5"
sha3 = { path = "sha3" }
clippy = { version = "0.0.103", optional = true}
ethcore-devtools = { path = "../devtools" }

View File

@ -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}

View File

@ -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);
match self.client() {
Ok(client) => {
self.pool.spawn(FetchTask {
url: url.into(),
client: self.client.clone(),
client: client,
limit: self.limit,
abort: abort,
})
},
Err(err) => {
self.pool.spawn(futures::future::err(err))
},
}
}
}

View File

@ -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;

View File

@ -77,7 +77,9 @@ pub fn ordered_trie_root<I>(input: I) -> H256
/// assert_eq!(trie_root(v), H256::from_str(root).unwrap());
/// }
/// ```
pub fn trie_root(input: Vec<(Vec<u8>, Vec<u8>)>) -> H256 {
pub fn trie_root<I>(input: I) -> H256
where I: IntoIterator<Item=(Vec<u8>, Vec<u8>)>
{
let gen_input = input
// first put elements into btree to sort them and to remove duplicates
.into_iter()