Merge branch 'block_header_rpc' into on-demand-priority

This commit is contained in:
Robert Habermeier 2017-04-06 17:59:55 +02:00
commit cf75a19e8d
47 changed files with 960 additions and 367 deletions

5
Cargo.lock generated
View File

@ -637,6 +637,8 @@ name = "ethcore-secretstore"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.7.0",
"ethcore-devtools 1.7.0", "ethcore-devtools 1.7.0",
"ethcore-ipc 1.7.0", "ethcore-ipc 1.7.0",
"ethcore-ipc-codegen 1.7.0", "ethcore-ipc-codegen 1.7.0",
@ -648,6 +650,7 @@ dependencies = [
"futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"native-contracts 0.1.0",
"parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.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-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1754,7 +1757,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" version = "1.4.0"
source = "git+https://github.com/paritytech/js-precompiled.git#6028c355854797a5938c26f5d2b2faf10d8833d7" source = "git+https://github.com/paritytech/js-precompiled.git#9bfc6f3dfca2c337c53084bedcc65c2b526927a1"
dependencies = [ dependencies = [
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -1,6 +1,8 @@
# [Parity](https://ethcore.io/parity.html) # [Parity](https://ethcore.io/parity.html)
### Fast, light, and robust Ethereum implementation ### Fast, light, and robust Ethereum implementation
### [Download latest release](https://github.com/paritytech/parity/releases)
[![build status](https://gitlab.ethcore.io/parity/parity/badges/master/build.svg)](https://gitlab.ethcore.io/parity/parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![GPLv3][license-image]][license-url] [![build status](https://gitlab.ethcore.io/parity/parity/badges/master/build.svg)](https://gitlab.ethcore.io/parity/parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![GPLv3][license-image]][license-url]
### Join the chat! ### Join the chat!
@ -22,7 +24,6 @@ Be sure to check out [our wiki][wiki-url] for more information.
[doc-url]: https://paritytech.github.io/parity/ethcore/index.html [doc-url]: https://paritytech.github.io/parity/ethcore/index.html
[wiki-url]: https://github.com/paritytech/parity/wiki [wiki-url]: https://github.com/paritytech/parity/wiki
**Parity requires Rust version 1.15.0 to build**
---- ----
@ -45,14 +46,14 @@ of RPC APIs.
If you run into an issue while using parity, feel free to file one in this repository If you run into an issue while using parity, feel free to file one in this repository
or hop on our [gitter chat room][gitter-url] to ask a question. We are glad to help! or hop on our [gitter chat room][gitter-url] to ask a question. We are glad to help!
Parity's current release is 1.5. You can download it at https://parity.io or follow the instructions Parity's current release is 1.6. You can download it at https://github.com/paritytech/parity/releases or follow the instructions
below to build from source. below to build from source.
---- ----
## Build dependencies ## Build dependencies
Parity is fully compatible with Stable Rust. **Parity requires Rust version 1.16.0 to build**
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this: We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this:
@ -80,7 +81,7 @@ Once you have rustup, install parity or download and build from source
---- ----
## Quick install ## Quick build and install
```bash ```bash
cargo install --git https://github.com/paritytech/parity.git parity cargo install --git https://github.com/paritytech/parity.git parity

View File

@ -119,7 +119,7 @@ impl Decodable for Entry {
} }
fn cht_key(number: u64) -> String { fn cht_key(number: u64) -> String {
format!("canonical_{}", number) format!("{:08x}_canonical", number)
} }
fn era_key(number: u64) -> String { fn era_key(number: u64) -> String {

View File

@ -83,6 +83,13 @@ pub trait LightChainClient: Send + Sync {
/// Get the signing network ID. /// Get the signing network ID.
fn signing_network_id(&self) -> Option<u64>; fn signing_network_id(&self) -> Option<u64>;
/// Get environment info for execution at a given block.
/// Fails if that block's header is not stored.
fn env_info(&self, id: BlockId) -> Option<EnvInfo>;
/// Get a handle to the consensus engine.
fn engine(&self) -> &Arc<Engine>;
/// Query whether a block is known. /// Query whether a block is known.
fn is_known(&self, hash: &H256) -> bool; fn is_known(&self, hash: &H256) -> bool;
@ -348,6 +355,14 @@ impl LightChainClient for Client {
Client::signing_network_id(self) Client::signing_network_id(self)
} }
fn env_info(&self, id: BlockId) -> Option<EnvInfo> {
Client::env_info(self, id)
}
fn engine(&self) -> &Arc<Engine> {
Client::engine(self)
}
fn is_known(&self, hash: &H256) -> bool { fn is_known(&self, hash: &H256) -> bool {
self.status(hash) == BlockStatus::InChain self.status(hash) == BlockStatus::InChain
} }

View File

@ -509,6 +509,9 @@ impl TransactionProof {
pub fn check_response(&self, _: &Mutex<::cache::Cache>, state_items: &[DBValue]) -> Result<super::ExecutionResult, Error> { pub fn check_response(&self, _: &Mutex<::cache::Cache>, state_items: &[DBValue]) -> Result<super::ExecutionResult, Error> {
let root = self.header.state_root(); let root = self.header.state_root();
let mut env_info = self.env_info.clone();
env_info.gas_limit = self.tx.gas.clone();
let proved_execution = state::check_proof( let proved_execution = state::check_proof(
state_items, state_items,
root, root,

View File

@ -23,6 +23,7 @@ use std::io::Write;
// TODO: `include!` these from files where they're pretty-printed? // TODO: `include!` these from files where they're pretty-printed?
const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#; const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#;
const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#; const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#;
const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#;
fn build_file(name: &str, abi: &str, filename: &str) { fn build_file(name: &str, abi: &str, filename: &str) {
let code = ::native_contract_generator::generate_module(name, abi).unwrap(); let code = ::native_contract_generator::generate_module(name, abi).unwrap();
@ -37,4 +38,5 @@ fn build_file(name: &str, abi: &str, filename: &str) {
fn main() { fn main() {
build_file("Registry", REGISTRY_ABI, "registry.rs"); build_file("Registry", REGISTRY_ABI, "registry.rs");
build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs"); build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs");
build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs");
} }

View File

@ -25,6 +25,8 @@ extern crate ethcore_util as util;
mod registry; mod registry;
mod service_transaction; mod service_transaction;
mod secretstore_acl_storage;
pub use self::registry::Registry; pub use self::registry::Registry;
pub use self::service_transaction::ServiceTransactionChecker; pub use self::service_transaction::ServiceTransactionChecker;
pub use self::secretstore_acl_storage::SecretStoreAclStorage;

View File

@ -0,0 +1,22 @@
// 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/>.
#![allow(unused_mut, unused_variables, unused_imports)]
//! Secret store ACL storage contract.
// TODO: testing.
include!(concat!(env!("OUT_DIR"), "/secretstore_acl_storage.rs"));

View File

@ -1627,10 +1627,12 @@ impl ::client::ProvingBlockChainClient for Client {
} }
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<Vec<DBValue>> { fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<Vec<DBValue>> {
let (state, env_info) = match (self.state_at(id), self.env_info(id)) { let (state, mut env_info) = match (self.state_at(id), self.env_info(id)) {
(Some(s), Some(e)) => (s, e), (Some(s), Some(e)) => (s, e),
_ => return None, _ => return None,
}; };
env_info.gas_limit = transaction.gas.clone();
let mut jdb = self.state_db.lock().journal_db().boxed_clone(); let mut jdb = self.state_db.lock().journal_db().boxed_clone();
let backend = state::backend::Proving::new(jdb.as_hashdb_mut()); let backend = state::backend::Proving::new(jdb.as_hashdb_mut());

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "1.7.43", "version": "1.7.46",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",
@ -176,7 +176,7 @@
"geopattern": "1.2.3", "geopattern": "1.2.3",
"isomorphic-fetch": "2.2.1", "isomorphic-fetch": "2.2.1",
"js-sha3": "0.5.5", "js-sha3": "0.5.5",
"keythereum": "0.4.3", "keythereum": "0.4.6",
"lodash": "4.17.2", "lodash": "4.17.2",
"loglevel": "1.4.1", "loglevel": "1.4.1",
"marked": "0.3.6", "marked": "0.3.6",

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { keythereum } from '../ethkey'; import { createKeyObject, decryptPrivateKey } from '../ethkey';
export default class Account { export default class Account {
constructor (persist, data) { constructor (persist, data) {
@ -31,12 +31,14 @@ export default class Account {
} }
isValidPassword (password) { isValidPassword (password) {
try { return decryptPrivateKey(this._keyObject, password)
keythereum.recover(Buffer.from(password), this._keyObject); .then((privateKey) => {
return true; if (!privateKey) {
} catch (e) { return false;
return false; }
}
return true;
});
} }
get address () { get address () {
@ -68,21 +70,23 @@ export default class Account {
} }
decryptPrivateKey (password) { decryptPrivateKey (password) {
return keythereum.recover(Buffer.from(password), this._keyObject); return decryptPrivateKey(this._keyObject, password);
}
changePassword (key, password) {
return createKeyObject(key, password).then((keyObject) => {
this._keyObject = keyObject;
this._persist();
});
} }
static fromPrivateKey (persist, key, password) { static fromPrivateKey (persist, key, password) {
const iv = keythereum.crypto.randomBytes(16); return createKeyObject(key, password).then((keyObject) => {
const salt = keythereum.crypto.randomBytes(32); const account = new Account(persist, { keyObject });
// Keythereum will fail if `password` is an empty string return account;
password = Buffer.from(password); });
const keyObject = keythereum.dump(password, key, salt, iv);
const account = new Account(persist, { keyObject });
return account;
} }
toJSON () { toJSON () {

View File

@ -38,14 +38,23 @@ export default class Accounts {
create (secret, password) { create (secret, password) {
const privateKey = Buffer.from(secret.slice(2), 'hex'); const privateKey = Buffer.from(secret.slice(2), 'hex');
const account = Account.fromPrivateKey(this.persist, privateKey, password);
this._store.push(account); return Account
this.lastAddress = account.address; .fromPrivateKey(this.persist, privateKey, password)
.then((account) => {
const { address } = account;
this.persist(); if (this._store.find((account) => account.address === address)) {
throw new Error(`Account ${address} already exists!`);
}
return account.address; this._store.push(account);
this.lastAddress = address;
this.persist();
return account.address;
});
} }
set lastAddress (value) { set lastAddress (value) {
@ -73,28 +82,41 @@ export default class Accounts {
remove (address, password) { remove (address, password) {
address = address.toLowerCase(); address = address.toLowerCase();
const account = this.get(address);
if (!account) {
return false;
}
return account
.isValidPassword(password)
.then((isValid) => {
if (!isValid) {
return false;
}
if (address === this.lastAddress) {
this.lastAddress = NULL_ADDRESS;
}
this.removeUnsafe(address);
return true;
});
}
removeUnsafe (address) {
address = address.toLowerCase();
const index = this._store.findIndex((account) => account.address === address); const index = this._store.findIndex((account) => account.address === address);
if (index === -1) { if (index === -1) {
return false; return;
}
const account = this._store[index];
if (!account.isValidPassword(password)) {
console.log('invalid password');
return false;
}
if (address === this.lastAddress) {
this.lastAddress = NULL_ADDRESS;
} }
this._store.splice(index, 1); this._store.splice(index, 1);
this.persist(); this.persist();
return true;
} }
mapArray (mapper) { mapArray (mapper) {

View File

@ -0,0 +1,19 @@
// 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/>.
export default function () {
// empty file included while building parity.js (don't include local keygen)
}

View File

@ -14,31 +14,35 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
// Allow a web worker in the browser, with a fallback for Node.js import workerPool from './workerPool';
const hasWebWorkers = typeof Worker !== 'undefined';
const KeyWorker = hasWebWorkers ? require('worker-loader!./worker')
: require('./worker').KeyWorker;
// Local accounts should never be used outside of the browser export function createKeyObject (key, password) {
export let keythereum = null; return workerPool.getWorker().action('createKeyObject', { key, password })
.then((obj) => JSON.parse(obj));
}
if (hasWebWorkers) { export function decryptPrivateKey (keyObject, password) {
require('keythereum/dist/keythereum'); return workerPool
.getWorker()
.action('decryptPrivateKey', { keyObject, password })
.then((privateKey) => {
if (privateKey) {
return Buffer.from(privateKey);
}
keythereum = window.keythereum; return null;
});
} }
export function phraseToAddress (phrase) { export function phraseToAddress (phrase) {
return phraseToWallet(phrase).then((wallet) => wallet.address); return phraseToWallet(phrase)
.then((wallet) => wallet.address);
} }
export function phraseToWallet (phrase) { export function phraseToWallet (phrase) {
return new Promise((resolve, reject) => { return workerPool.getWorker().action('phraseToWallet', phrase);
const worker = new KeyWorker(); }
worker.postMessage(phrase); export function verifySecret (secret) {
worker.onmessage = ({ data }) => { return workerPool.getWorker().action('verifySecret', secret);
resolve(data);
};
});
} }

View File

@ -14,58 +14,104 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { keccak_256 as keccak256 } from 'js-sha3';
import secp256k1 from 'secp256k1/js'; import secp256k1 from 'secp256k1/js';
import { keccak_256 as keccak256 } from 'js-sha3';
import { bytesToHex } from '~/api/util/format';
const isWorker = typeof self !== 'undefined';
// Stay compatible between environments // Stay compatible between environments
if (typeof self !== 'object') { if (!isWorker) {
const scope = typeof global === 'undefined' ? window : global; const scope = typeof global === 'undefined' ? window : global;
scope.self = scope; scope.self = scope;
} }
function bytesToHex (bytes) { // keythereum should never be used outside of the browser
return '0x' + Array.from(bytes).map(n => ('0' + n.toString(16)).slice(-2)).join(''); let keythereum = null;
if (isWorker) {
require('keythereum/dist/keythereum');
keythereum = self.keythereum;
} }
// Logic ported from /ethkey/src/brain.rs function route ({ action, payload }) {
function phraseToWallet (phrase) { if (action in actions) {
let secret = keccak256.array(phrase); return actions[action](payload);
for (let i = 0; i < 16384; i++) {
secret = keccak256.array(secret);
} }
while (true) { return null;
secret = keccak256.array(secret); }
const secretBuf = Buffer.from(secret); const actions = {
phraseToWallet (phrase) {
let secret = keccak256.array(phrase);
if (secp256k1.privateKeyVerify(secretBuf)) { for (let i = 0; i < 16384; i++) {
// No compression, slice out last 64 bytes secret = keccak256.array(secret);
const publicBuf = secp256k1.publicKeyCreate(secretBuf, false).slice(-64); }
const address = keccak256.array(publicBuf).slice(12);
if (address[0] !== 0) { while (true) {
continue; secret = keccak256.array(secret);
const secretBuf = Buffer.from(secret);
if (secp256k1.privateKeyVerify(secretBuf)) {
// No compression, slice out last 64 bytes
const publicBuf = secp256k1.publicKeyCreate(secretBuf, false).slice(-64);
const address = keccak256.array(publicBuf).slice(12);
if (address[0] !== 0) {
continue;
}
const wallet = {
secret: bytesToHex(secretBuf),
public: bytesToHex(publicBuf),
address: bytesToHex(address)
};
return wallet;
} }
}
},
const wallet = { verifySecret (secret) {
secret: bytesToHex(secretBuf), const key = Buffer.from(secret.slice(2), 'hex');
public: bytesToHex(publicBuf),
address: bytesToHex(address)
};
return wallet; return secp256k1.privateKeyVerify(key);
},
createKeyObject ({ key, password }) {
key = Buffer.from(key);
password = Buffer.from(password);
const iv = keythereum.crypto.randomBytes(16);
const salt = keythereum.crypto.randomBytes(32);
const keyObject = keythereum.dump(password, key, salt, iv);
return JSON.stringify(keyObject);
},
decryptPrivateKey ({ keyObject, password }) {
password = Buffer.from(password);
try {
const key = keythereum.recover(password, keyObject);
// Convert to array to safely send from the worker
return Array.from(key);
} catch (e) {
return null;
} }
} }
} };
self.onmessage = function ({ data }) { self.onmessage = function ({ data }) {
const wallet = phraseToWallet(data); const result = route(data);
postMessage(wallet); postMessage(result);
close();
}; };
// Emulate a web worker in Node.js // Emulate a web worker in Node.js
@ -73,9 +119,9 @@ class KeyWorker {
postMessage (data) { postMessage (data) {
// Force async // Force async
setTimeout(() => { setTimeout(() => {
const wallet = phraseToWallet(data); const result = route(data);
this.onmessage({ data: wallet }); this.onmessage({ data: result });
}, 0); }, 0);
} }

View File

@ -0,0 +1,61 @@
// 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/>.
// Allow a web worker in the browser, with a fallback for Node.js
const hasWebWorkers = typeof Worker !== 'undefined';
const KeyWorker = hasWebWorkers ? require('worker-loader!./worker')
: require('./worker').KeyWorker;
class WorkerContainer {
busy = false;
_worker = new KeyWorker();
action (action, payload) {
if (this.busy) {
throw new Error('Cannot issue an action on a busy worker!');
}
this.busy = true;
return new Promise((resolve, reject) => {
this._worker.postMessage({ action, payload });
this._worker.onmessage = ({ data }) => {
this.busy = false;
resolve(data);
};
});
}
}
class WorkerPool {
pool = [];
getWorker () {
let container = this.pool.find((container) => !container.busy);
if (container) {
return container;
}
container = new WorkerContainer();
this.pool.push(container);
return container;
}
}
export default new WorkerPool();

View File

@ -19,7 +19,7 @@ import accounts from './accounts';
import transactions from './transactions'; import transactions from './transactions';
import { Middleware } from '../transport'; import { Middleware } from '../transport';
import { inNumber16 } from '../format/input'; import { inNumber16 } from '../format/input';
import { phraseToWallet, phraseToAddress } from './ethkey'; import { phraseToWallet, phraseToAddress, verifySecret } from './ethkey';
import { randomPhrase } from '@parity/wordlist'; import { randomPhrase } from '@parity/wordlist';
export default class LocalAccountsMiddleware extends Middleware { export default class LocalAccountsMiddleware extends Middleware {
@ -57,6 +57,22 @@ export default class LocalAccountsMiddleware extends Middleware {
}); });
}); });
register('parity_changePassword', ([address, oldPassword, newPassword]) => {
const account = accounts.get(address);
return account
.decryptPrivateKey(oldPassword)
.then((privateKey) => {
if (!privateKey) {
return false;
}
account.changePassword(privateKey, newPassword);
return true;
});
});
register('parity_checkRequest', ([id]) => { register('parity_checkRequest', ([id]) => {
return transactions.hash(id) || Promise.resolve(null); return transactions.hash(id) || Promise.resolve(null);
}); });
@ -84,6 +100,17 @@ export default class LocalAccountsMiddleware extends Middleware {
}); });
}); });
register('parity_newAccountFromSecret', ([secret, password]) => {
return verifySecret(secret)
.then((isValid) => {
if (!isValid) {
throw new Error('Invalid secret key');
}
return accounts.create(secret, password);
});
});
register('parity_setAccountMeta', ([address, meta]) => { register('parity_setAccountMeta', ([address, meta]) => {
accounts.get(address).meta = meta; accounts.get(address).meta = meta;
@ -127,6 +154,12 @@ export default class LocalAccountsMiddleware extends Middleware {
return accounts.remove(address, password); return accounts.remove(address, password);
}); });
register('parity_testPassword', ([address, password]) => {
const account = accounts.get(address);
return account.isValidPassword(password);
});
register('signer_confirmRequest', ([id, modify, password]) => { register('signer_confirmRequest', ([id, modify, password]) => {
const { const {
gasPrice, gasPrice,
@ -137,30 +170,33 @@ export default class LocalAccountsMiddleware extends Middleware {
data data
} = Object.assign(transactions.get(id), modify); } = Object.assign(transactions.get(id), modify);
return this const account = accounts.get(from);
.rpcRequest('parity_nextNonce', [from])
.then((nonce) => {
const tx = new EthereumTx({
nonce,
to,
data,
gasLimit: inNumber16(gasLimit),
gasPrice: inNumber16(gasPrice),
value: inNumber16(value)
});
const account = accounts.get(from);
tx.sign(account.decryptPrivateKey(password)); return Promise.all([
this.rpcRequest('parity_nextNonce', [from]),
const serializedTx = `0x${tx.serialize().toString('hex')}`; account.decryptPrivateKey(password)
])
return this.rpcRequest('eth_sendRawTransaction', [serializedTx]); .then(([nonce, privateKey]) => {
}) const tx = new EthereumTx({
.then((hash) => { nonce,
transactions.confirm(id, hash); to,
data,
return {}; gasLimit: inNumber16(gasLimit),
gasPrice: inNumber16(gasPrice),
value: inNumber16(value)
}); });
tx.sign(privateKey);
const serializedTx = `0x${tx.serialize().toString('hex')}`;
return this.rpcRequest('eth_sendRawTransaction', [serializedTx]);
})
.then((hash) => {
transactions.confirm(id, hash);
return {};
});
}); });
register('signer_rejectRequest', ([id]) => { register('signer_rejectRequest', ([id]) => {

View File

@ -80,12 +80,16 @@ export default class JsonRpcBase extends EventEmitter {
const res = middleware.handle(method, params); const res = middleware.handle(method, params);
if (res != null) { if (res != null) {
const result = this._wrapSuccessResult(res); // If `res` isn't a promise, we need to wrap it
const json = this.encode(method, params); return Promise.resolve(res)
.then((res) => {
const result = this._wrapSuccessResult(res);
const json = this.encode(method, params);
Logging.send(method, params, { json, result }); Logging.send(method, params, { json, result });
return res; return res;
});
} }
} }

View File

@ -17,7 +17,7 @@
import { range } from 'lodash'; import { range } from 'lodash';
export function bytesToHex (bytes) { export function bytesToHex (bytes) {
return '0x' + bytes.map((b) => ('0' + b.toString(16)).slice(-2)).join(''); return '0x' + Buffer.from(bytes).toString('hex');
} }
export function cleanupValue (value, type) { export function cleanupValue (value, type) {

View File

@ -23,6 +23,7 @@ import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
import { Form, Input, IdentityIcon } from '~/ui'; import { Form, Input, IdentityIcon } from '~/ui';
import PasswordStrength from '~/ui/Form/PasswordStrength'; import PasswordStrength from '~/ui/Form/PasswordStrength';
import { RefreshIcon } from '~/ui/Icons'; import { RefreshIcon } from '~/ui/Icons';
import Loading from '~/ui/Loading';
import ChangeVault from '../ChangeVault'; import ChangeVault from '../ChangeVault';
import styles from '../createAccount.css'; import styles from '../createAccount.css';
@ -170,7 +171,9 @@ export default class CreateAccount extends Component {
const { accounts } = this.state; const { accounts } = this.state;
if (!accounts) { if (!accounts) {
return null; return (
<Loading className={ styles.selector } size={ 1 } />
);
} }
const identities = Object const identities = Object
@ -205,6 +208,14 @@ export default class CreateAccount extends Component {
createIdentities = () => { createIdentities = () => {
const { createStore } = this.props; const { createStore } = this.props;
this.setState({
accounts: null,
selectedAddress: ''
});
createStore.setAddress('');
createStore.setPhrase('');
return createStore return createStore
.createIdentities() .createIdentities()
.then((accounts) => { .then((accounts) => {

View File

@ -58,12 +58,12 @@ describe('modals/CreateAccount/NewAccount', () => {
return instance.componentWillMount(); return instance.componentWillMount();
}); });
it('creates initial accounts', () => { it('resets the accounts', () => {
expect(Object.keys(instance.state.accounts).length).to.equal(7); expect(instance.state.accounts).to.be.null;
}); });
it('sets the initial selected value', () => { it('resets the initial selected value', () => {
expect(instance.state.selectedAddress).to.equal(Object.keys(instance.state.accounts)[0]); expect(instance.state.selectedAddress).to.equal('');
}); });
}); });
}); });

View File

@ -69,7 +69,7 @@ export default class Store {
return !(this.nameError || this.walletFileError); return !(this.nameError || this.walletFileError);
case 'fromNew': case 'fromNew':
return !(this.nameError || this.passwordRepeatError); return !(this.nameError || this.passwordRepeatError) && this.hasAddress;
case 'fromPhrase': case 'fromPhrase':
return !(this.nameError || this.passwordRepeatError); return !(this.nameError || this.passwordRepeatError);
@ -85,6 +85,10 @@ export default class Store {
} }
} }
@computed get hasAddress () {
return !!(this.address);
}
@computed get passwordRepeatError () { @computed get passwordRepeatError () {
return this.password === this.passwordRepeat return this.password === this.passwordRepeat
? null ? null

View File

@ -329,6 +329,7 @@ describe('modals/CreateAccount/Store', () => {
describe('createType === fromNew', () => { describe('createType === fromNew', () => {
beforeEach(() => { beforeEach(() => {
store.setCreateType('fromNew'); store.setCreateType('fromNew');
store.setAddress('0x0000000000000000000000000000000000000000');
}); });
it('returns true on no errors', () => { it('returns true on no errors', () => {
@ -337,11 +338,13 @@ describe('modals/CreateAccount/Store', () => {
it('returns false on nameError', () => { it('returns false on nameError', () => {
store.setName(''); store.setName('');
expect(store.canCreate).to.be.false; expect(store.canCreate).to.be.false;
}); });
it('returns false on passwordRepeatError', () => { it('returns false on passwordRepeatError', () => {
store.setPassword('testing'); store.setPassword('testing');
expect(store.canCreate).to.be.false; expect(store.canCreate).to.be.false;
}); });
}); });

View File

@ -92,9 +92,9 @@ export function generateQr (from, tx, hash, rlp) {
account: from.substr(2), account: from.substr(2),
hash: hash.substr(2), hash: hash.substr(2),
details: { details: {
gasPrice: inNumber10(inHex(tx.gasPrice.toString('hex'))), gasPrice: inNumber10(inHex(tx.gasPrice.toString('hex') || '0')),
gas: inNumber10(inHex(tx.gasLimit.toString('hex'))), gas: inNumber10(inHex(tx.gasLimit.toString('hex') || '0')),
nonce: inNumber10(inHex(tx.nonce.toString('hex'))), nonce: inNumber10(inHex(tx.nonce.toString('hex') || '0')),
to: inAddress(tx.to.toString('hex')), to: inAddress(tx.to.toString('hex')),
value: inHex(tx.value.toString('hex') || '0') value: inHex(tx.value.toString('hex') || '0')
} }

View File

@ -24,9 +24,11 @@ const ENV = process.env.NODE_ENV || 'development';
const isProd = ENV === 'production'; const isProd = ENV === 'production';
const LIBRARY = process.env.LIBRARY; const LIBRARY = process.env.LIBRARY;
if (!LIBRARY) { if (!LIBRARY) {
process.exit(-1); process.exit(-1);
} }
const SRC = LIBRARY.toLowerCase(); const SRC = LIBRARY.toLowerCase();
const OUTPUT_PATH = path.join(__dirname, '../.npmjs', SRC); const OUTPUT_PATH = path.join(__dirname, '../.npmjs', SRC);
@ -63,12 +65,18 @@ module.exports = {
'babel-loader?cacheDirectory=true' 'babel-loader?cacheDirectory=true'
], ],
exclude: /node_modules/ exclude: /node_modules/
},
{
test: /\.js$/,
include: /node_modules\/(ethereumjs-tx|@parity\/wordlist)/,
use: 'babel-loader'
} }
] ]
}, },
resolve: { resolve: {
alias: { alias: {
'secp256k1/js': path.resolve(__dirname, '../src/api/local/ethkey/dummy.js'),
'~': path.resolve(__dirname, '../src') '~': path.resolve(__dirname, '../src')
}, },
modules: [ modules: [
@ -85,15 +93,12 @@ module.exports = {
to: 'package.json', to: 'package.json',
transform: function (content, path) { transform: function (content, path) {
const json = JSON.parse(content.toString()); const json = JSON.parse(content.toString());
json.version = packageJson.version;
// Add tests dependencies to Dev Deps
json.devDependencies.chai = packageJson.devDependencies.chai; json.devDependencies.chai = packageJson.devDependencies.chai;
json.devDependencies.mocha = packageJson.devDependencies.mocha; json.devDependencies.mocha = packageJson.devDependencies.mocha;
json.devDependencies.nock = packageJson.devDependencies.nock; json.devDependencies.nock = packageJson.devDependencies.nock;
// Add test script
json.scripts.test = 'mocha \'test/*.spec.js\''; json.scripts.test = 'mocha \'test/*.spec.js\'';
json.version = packageJson.version;
return new Buffer(JSON.stringify(json, null, ' '), 'utf-8'); return new Buffer(JSON.stringify(json, null, ' '), 'utf-8');
} }

View File

@ -67,7 +67,7 @@ impl IoHandler<ClientIoMessage> for QueueCull {
let (sync, on_demand, txq) = (self.sync.clone(), self.on_demand.clone(), self.txq.clone()); let (sync, on_demand, txq) = (self.sync.clone(), self.on_demand.clone(), self.txq.clone());
let best_header = self.client.best_block_header(); let best_header = self.client.best_block_header();
let start_nonce = self.client.engine().account_start_nonce; let start_nonce = self.client.engine().account_start_nonce();
info!(target: "cull", "Attempting to cull queued transactions from {} senders.", senders.len()); info!(target: "cull", "Attempting to cull queued transactions from {} senders.", senders.len());
self.remote.spawn_with_timeout(move || { self.remote.spawn_with_timeout(move || {
@ -77,7 +77,7 @@ impl IoHandler<ClientIoMessage> for QueueCull {
.map(|&address| request::Account { header: best_header.clone(), address: address }) .map(|&address| request::Account { header: best_header.clone(), address: address })
.map(move |request| { .map(move |request| {
on_demand.account(ctx, request) on_demand.account(ctx, request)
.map(move |maybe_acc| maybe_acc.map_or(start_nonce, |acc.nonce|)) .map(move |maybe_acc| maybe_acc.map_or(start_nonce, |acc| acc.nonce))
}) })
.zip(senders.iter()) .zip(senders.iter())
.map(|(fut, &addr)| fut.map(move |nonce| (addr, nonce))); .map(|(fut, &addr)| fut.map(move |nonce| (addr, nonce)));

View File

@ -648,7 +648,9 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
let signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?; let signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?;
// secret store key server // secret store key server
let secretstore_deps = secretstore::Dependencies { }; let secretstore_deps = secretstore::Dependencies {
client: client.clone(),
};
let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps); let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps);
// the ipfs server // the ipfs server

View File

@ -14,7 +14,9 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use dir::default_data_path; use dir::default_data_path;
use ethcore::client::Client;
use helpers::replace_home; use helpers::replace_home;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -30,10 +32,10 @@ pub struct Configuration {
pub data_path: String, pub data_path: String,
} }
#[derive(Debug, PartialEq, Clone)]
/// Secret store dependencies /// Secret store dependencies
pub struct Dependencies { pub struct Dependencies {
// the only dependency will be BlockChainClient /// Blockchain client.
pub client: Arc<Client>,
} }
#[cfg(not(feature = "secretstore"))] #[cfg(not(feature = "secretstore"))]
@ -64,7 +66,7 @@ mod server {
impl KeyServer { impl KeyServer {
/// Create new key server /// Create new key server
pub fn new(conf: Configuration, _deps: Dependencies) -> Result<Self, String> { pub fn new(conf: Configuration, deps: Dependencies) -> Result<Self, String> {
let key_pairs = vec![ let key_pairs = vec![
ethkey::KeyPair::from_secret("6c26a76e9b31048d170873a791401c7e799a11f0cefc0171cc31a49800967509".parse().unwrap()).unwrap(), ethkey::KeyPair::from_secret("6c26a76e9b31048d170873a791401c7e799a11f0cefc0171cc31a49800967509".parse().unwrap()).unwrap(),
ethkey::KeyPair::from_secret("7e94018b3731afdb3b4e6f4c3e179475640166da12e1d1b0c7d80729b1a5b452".parse().unwrap()).unwrap(), ethkey::KeyPair::from_secret("7e94018b3731afdb3b4e6f4c3e179475640166da12e1d1b0c7d80729b1a5b452".parse().unwrap()).unwrap(),
@ -96,7 +98,7 @@ mod server {
} }
}; };
let key_server = ethcore_secretstore::start(conf) let key_server = ethcore_secretstore::start(deps.client, conf)
.map_err(Into::<String>::into)?; .map_err(Into::<String>::into)?;
Ok(KeyServer { Ok(KeyServer {

View File

@ -269,7 +269,7 @@ impl LightDispatcher {
match nonce_future { match nonce_future {
Some(x) => Some(x) =>
x.map(|acc| acc.map_or(account_start_nonce, |acc| acc.nonce)) x.map(move |acc| acc.map_or(account_start_nonce, |acc| acc.nonce))
.map_err(|_| errors::no_light_peers()) .map_err(|_| errors::no_light_peers())
.boxed(), .boxed(),
None => future::err(errors::network_disabled()).boxed() None => future::err(errors::network_disabled()).boxed()

View File

@ -346,3 +346,8 @@ pub fn deprecated<T: Into<Option<String>>>(message: T) -> Error {
data: message.into().map(Value::String), data: message.into().map(Value::String),
} }
} }
// on-demand sender cancelled.
pub fn on_demand_cancel(_cancel: ::futures::sync::oneshot::Canceled) -> Error {
internal("on-demand sender cancelled", "")
}

View File

@ -0,0 +1,212 @@
// 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/>.
//! Helpers for fetching blockchain data either from the light client or the network.
use std::sync::Arc;
use ethcore::basic_account::BasicAccount;
use ethcore::encoded;
use ethcore::executed::{Executed, ExecutionError};
use ethcore::ids::BlockId;
use ethcore::transaction::{Action, Transaction as EthTransaction};
use futures::{future, Future, BoxFuture};
use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use light::cache::Cache;
use light::client::LightChainClient;
use light::cht;
use light::on_demand::{OnDemand, request};
use ethsync::LightSync;
use util::{Address, Mutex, Uint, U256};
use v1::helpers::{CallRequest as CRequest, errors, dispatch};
use v1::types::{BlockNumber, CallRequest};
/// Helper for fetching blockchain data either from the light client or the network
/// as necessary.
pub struct LightFetch {
/// The light client.
pub client: Arc<LightChainClient>,
/// The on-demand request service.
pub on_demand: Arc<OnDemand>,
/// Handle to the network.
pub sync: Arc<LightSync>,
/// The light data cache.
pub cache: Arc<Mutex<Cache>>,
}
/// Type alias for convenience.
pub type ExecutionResult = Result<Executed, ExecutionError>;
impl LightFetch {
/// Get a block header from the on demand service or client, or error.
pub fn header(&self, id: BlockId) -> BoxFuture<encoded::Header, Error> {
if let Some(h) = self.client.block_header(id) {
return future::ok(h).boxed()
}
let maybe_future = match id {
BlockId::Number(n) => {
let cht_root = cht::block_to_cht_number(n).and_then(|cn| self.client.cht_root(cn as usize));
match cht_root {
None => return future::err(errors::unknown_block()).boxed(),
Some(root) => {
let req = request::HeaderProof::new(n, root)
.expect("only fails for 0; client always stores genesis; client already queried; qed");
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.sync.with_context(|ctx| {
let fut = self.on_demand.hash_by_number(ctx, req)
.map(request::HeaderByHash)
.map_err(errors::on_demand_cancel);
fut.and_then(move |req| {
match sync.with_context(|ctx| on_demand.header_by_hash(ctx, req)) {
Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
})
}
}
}
BlockId::Hash(h) => {
self.sync.with_context(|ctx|
self.on_demand.header_by_hash(ctx, request::HeaderByHash(h))
.then(|res| future::done(match res {
Ok(h) => Ok(h),
Err(e) => Err(errors::on_demand_cancel(e)),
}))
.boxed()
)
}
_ => None, // latest, earliest, and pending will have all already returned.
};
match maybe_future {
Some(recv) => recv,
None => future::err(errors::network_disabled()).boxed()
}
}
/// helper for getting account info at a given block.
/// `None` indicates the account doesn't exist at the given block.
pub fn account(&self, address: Address, id: BlockId) -> BoxFuture<Option<BasicAccount>, Error> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.header(id).and_then(move |header| {
let maybe_fut = sync.with_context(|ctx| on_demand.account(ctx, request::Account {
header: header,
address: address,
}));
match maybe_fut {
Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
/// helper for getting proved execution.
pub fn proved_execution(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<ExecutionResult, Error> {
const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]);
let (sync, on_demand, client) = (self.sync.clone(), self.on_demand.clone(), self.client.clone());
let req: CRequest = req.into();
let id = num.0.into();
let from = req.from.unwrap_or(Address::zero());
let nonce_fut = match req.nonce {
Some(nonce) => future::ok(Some(nonce)).boxed(),
None => self.account(from, id).map(|acc| acc.map(|a| a.nonce)).boxed(),
};
let gas_price_fut = match req.gas_price {
Some(price) => future::ok(price).boxed(),
None => dispatch::fetch_gas_price_corpus(
self.sync.clone(),
self.client.clone(),
self.on_demand.clone(),
self.cache.clone(),
).map(|corp| match corp.median() {
Some(median) => *median,
None => DEFAULT_GAS_PRICE,
}).boxed()
};
// if nonce resolves, this should too since it'll be in the LRU-cache.
let header_fut = self.header(id);
// fetch missing transaction fields from the network.
nonce_fut.join(gas_price_fut).and_then(move |(nonce, gas_price)| {
let action = req.to.map_or(Action::Create, Action::Call);
let gas = req.gas.unwrap_or(U256::from(10_000_000)); // better gas amount?
let value = req.value.unwrap_or_else(U256::zero);
let data = req.data.map_or_else(Vec::new, |d| d.to_vec());
future::done(match nonce {
Some(n) => Ok(EthTransaction {
nonce: n,
action: action,
gas: gas,
gas_price: gas_price,
value: value,
data: data,
}.fake_sign(from)),
None => Err(errors::unknown_block()),
})
}).join(header_fut).and_then(move |(tx, hdr)| {
// then request proved execution.
// TODO: get last-hashes from network.
let env_info = match client.env_info(id) {
Some(env_info) => env_info,
_ => return future::err(errors::unknown_block()).boxed(),
};
let request = request::TransactionProof {
tx: tx,
header: hdr,
env_info: env_info,
engine: client.engine().clone(),
};
let proved_future = sync.with_context(move |ctx| {
on_demand.transaction_proof(ctx, request).map_err(errors::on_demand_cancel).boxed()
});
match proved_future {
Some(fut) => fut.boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
/// get a block itself. fails on unknown block ID.
pub fn block(&self, id: BlockId) -> BoxFuture<encoded::Block, Error> {
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
self.header(id).map(request::Body::new).and_then(move |req| {
match sync.with_context(move |ctx| on_demand.block(ctx, req)) {
Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
}

View File

@ -21,6 +21,7 @@ pub mod accounts;
pub mod block_import; pub mod block_import;
pub mod dispatch; pub mod dispatch;
pub mod fake_sign; pub mod fake_sign;
pub mod light_fetch;
pub mod informant; pub mod informant;
pub mod oneshot; pub mod oneshot;

View File

@ -146,7 +146,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
(Some(block), Some(total_difficulty)) => { (Some(block), Some(total_difficulty)) => {
let view = block.header_view(); let view = block.header_view();
Ok(Some(RichBlock { Ok(Some(RichBlock {
block: Block { inner: Block {
hash: Some(view.sha3().into()), hash: Some(view.sha3().into()),
size: Some(block.rlp().as_raw().len().into()), size: Some(block.rlp().as_raw().len().into()),
parent_hash: view.parent_hash().into(), parent_hash: view.parent_hash().into(),
@ -202,7 +202,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
.map(Into::into); .map(Into::into);
let block = RichBlock { let block = RichBlock {
block: Block { inner: Block {
hash: Some(uncle.hash().into()), hash: Some(uncle.hash().into()),
size: size, size: size,
parent_hash: uncle.parent_hash().clone().into(), parent_hash: uncle.parent_hash().clone().into(),

View File

@ -48,6 +48,7 @@ use v1::impls::eth_filter::Filterable;
use v1::helpers::{CallRequest as CRequest, errors, limit_logs, dispatch}; use v1::helpers::{CallRequest as CRequest, errors, limit_logs, dispatch};
use v1::helpers::{PollFilter, PollManager}; use v1::helpers::{PollFilter, PollManager};
use v1::helpers::block_import::is_major_importing; use v1::helpers::block_import::is_major_importing;
use v1::helpers::light_fetch::LightFetch;
use v1::traits::Eth; use v1::traits::Eth;
use v1::types::{ use v1::types::{
RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo,
@ -84,12 +85,6 @@ impl Clone for EthClient {
} }
} }
// helper for internal error: on demand sender cancelled.
fn err_premature_cancel(_cancel: oneshot::Canceled) -> Error {
errors::internal("on-demand sender prematurely cancelled", "")
}
type ExecutionResult = Result<Executed, ExecutionError>;
impl EthClient { impl EthClient {
/// Create a new `EthClient` with a handle to the light sync instance, client, /// Create a new `EthClient` with a handle to the light sync instance, client,
@ -113,158 +108,15 @@ impl EthClient {
} }
} }
/// Get a block header from the on demand service or client, or error. /// Create a light data fetcher instance.
fn header(&self, id: BlockId) -> BoxFuture<encoded::Header, Error> { fn fetcher(&self) -> LightFetch {
if let Some(h) = self.client.block_header(id) { LightFetch {
return future::ok(h).boxed() client: self.client.clone(),
on_demand: self.on_demand.clone(),
sync: self.sync.clone(),
cache: self.cache.clone(),
} }
let maybe_future = match id {
BlockId::Number(n) => {
let cht_root = cht::block_to_cht_number(n).and_then(|cn| self.client.cht_root(cn as usize));
match cht_root {
None => return future::err(errors::unknown_block()).boxed(),
Some(root) => {
let req = request::HeaderProof::new(n, root)
.expect("only fails for 0; client always stores genesis; client already queried; qed");
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.sync.with_context(|ctx| {
let fut = self.on_demand.hash_by_number(ctx, req)
.map(request::HeaderByHash)
.map_err(err_premature_cancel);
fut.and_then(move |req| {
match sync.with_context(|ctx| on_demand.header_by_hash(ctx, req)) {
Some(fut) => fut.map_err(err_premature_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
})
}
}
}
BlockId::Hash(h) => {
self.sync.with_context(|ctx|
self.on_demand.header_by_hash(ctx, request::HeaderByHash(h))
.then(|res| future::done(match res {
Ok(h) => Ok(h),
Err(e) => Err(err_premature_cancel(e)),
}))
.boxed()
)
}
_ => None, // latest, earliest, and pending will have all already returned.
};
match maybe_future {
Some(recv) => recv,
None => future::err(errors::network_disabled()).boxed()
}
}
// helper for getting account info at a given block.
// `None` indicates the account doesn't exist at the given block.
fn account(&self, address: Address, id: BlockId) -> BoxFuture<Option<BasicAccount>, Error> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.header(id).and_then(move |header| {
let maybe_fut = sync.with_context(|ctx| on_demand.account(ctx, request::Account {
header: header,
address: address,
}));
match maybe_fut {
Some(fut) => fut.map_err(err_premature_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
// helper for getting proved execution.
fn proved_execution(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<ExecutionResult, Error> {
const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]);
let (sync, on_demand, client) = (self.sync.clone(), self.on_demand.clone(), self.client.clone());
let req: CRequest = req.into();
let id = num.0.into();
let from = req.from.unwrap_or(Address::zero());
let nonce_fut = match req.nonce {
Some(nonce) => future::ok(Some(nonce)).boxed(),
None => self.account(from, id).map(|acc| acc.map(|a| a.nonce)).boxed(),
};
let gas_price_fut = match req.gas_price {
Some(price) => future::ok(price).boxed(),
None => dispatch::fetch_gas_price_corpus(
self.sync.clone(),
self.client.clone(),
self.on_demand.clone(),
self.cache.clone(),
).map(|corp| match corp.median() {
Some(median) => *median,
None => DEFAULT_GAS_PRICE,
}).boxed()
};
// if nonce resolves, this should too since it'll be in the LRU-cache.
let header_fut = self.header(id);
// fetch missing transaction fields from the network.
nonce_fut.join(gas_price_fut).and_then(move |(nonce, gas_price)| {
let action = req.to.map_or(Action::Create, Action::Call);
let gas = req.gas.unwrap_or(U256::from(10_000_000)); // better gas amount?
let value = req.value.unwrap_or_else(U256::zero);
let data = req.data.map_or_else(Vec::new, |d| d.to_vec());
future::done(match nonce {
Some(n) => Ok(EthTransaction {
nonce: n,
action: action,
gas: gas,
gas_price: gas_price,
value: value,
data: data,
}.fake_sign(from)),
None => Err(errors::unknown_block()),
})
}).join(header_fut).and_then(move |(tx, hdr)| {
// then request proved execution.
// TODO: get last-hashes from network.
let env_info = match client.env_info(id) {
Some(env_info) => env_info,
_ => return future::err(errors::unknown_block()).boxed(),
};
let request = request::TransactionProof {
tx: tx,
header: hdr,
env_info: env_info,
engine: client.engine().clone(),
};
let proved_future = sync.with_context(move |ctx| {
on_demand.transaction_proof(ctx, request).map_err(err_premature_cancel).boxed()
});
match proved_future {
Some(fut) => fut.boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
// get a block itself. fails on unknown block ID.
fn block(&self, id: BlockId) -> BoxFuture<encoded::Block, Error> {
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
self.header(id).map(request::Body::new).and_then(move |req| {
match sync.with_context(move |ctx| on_demand.block(ctx, req)) {
Some(fut) => fut.map_err(err_premature_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
} }
// get a "rich" block structure. Fails on unknown block. // get a "rich" block structure. Fails on unknown block.
@ -277,7 +129,7 @@ impl EthClient {
let header = block.decode_header(); let header = block.decode_header();
let extra_info = engine.extra_info(&header); let extra_info = engine.extra_info(&header);
RichBlock { RichBlock {
block: Block { inner: Block {
hash: Some(header.hash().into()), hash: Some(header.hash().into()),
size: Some(block.rlp().as_raw().len().into()), size: Some(block.rlp().as_raw().len().into()),
parent_hash: header.parent_hash().clone().into(), parent_hash: header.parent_hash().clone().into(),
@ -307,7 +159,7 @@ impl EthClient {
}; };
// get the block itself. // get the block itself.
self.block(id).and_then(move |block| { self.fetcher().block(id).and_then(move |block| {
// then fetch the total difficulty (this is much easier after getting the block). // then fetch the total difficulty (this is much easier after getting the block).
match client.score(id) { match client.score(id) {
Some(score) => future::ok(fill_rich(block, Some(score))).boxed(), Some(score) => future::ok(fill_rich(block, Some(score))).boxed(),
@ -344,7 +196,7 @@ impl EthClient {
}; };
fill_rich(block, score) fill_rich(block, score)
}).map_err(err_premature_cancel).boxed(), }).map_err(errors::on_demand_cancel).boxed(),
None => return future::err(errors::network_disabled()).boxed(), None => return future::err(errors::network_disabled()).boxed(),
} }
} }
@ -415,7 +267,7 @@ impl Eth for EthClient {
} }
fn balance(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> { fn balance(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
self.account(address.into(), num.0.into()) self.fetcher().account(address.into(), num.0.into())
.map(|acc| acc.map_or(0.into(), |a| a.balance).into()).boxed() .map(|acc| acc.map_or(0.into(), |a| a.balance).into()).boxed()
} }
@ -432,20 +284,20 @@ impl Eth for EthClient {
} }
fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> { fn transaction_count(&self, address: RpcH160, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
self.account(address.into(), num.0.into()) self.fetcher().account(address.into(), num.0.into())
.map(|acc| acc.map_or(0.into(), |a| a.nonce).into()).boxed() .map(|acc| acc.map_or(0.into(), |a| a.nonce).into()).boxed()
} }
fn block_transaction_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> { fn block_transaction_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.header(BlockId::Hash(hash.into())).and_then(move |hdr| { self.fetcher().header(BlockId::Hash(hash.into())).and_then(move |hdr| {
if hdr.transactions_root() == SHA3_NULL_RLP { if hdr.transactions_root() == SHA3_NULL_RLP {
future::ok(Some(U256::from(0).into())).boxed() future::ok(Some(U256::from(0).into())).boxed()
} else { } else {
sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr)))
.map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into()))) .map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into())))
.map(|x| x.map_err(err_premature_cancel).boxed()) .map(|x| x.map_err(errors::on_demand_cancel).boxed())
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) .unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
} }
}).boxed() }).boxed()
@ -454,13 +306,13 @@ impl Eth for EthClient {
fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> { fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.header(num.into()).and_then(move |hdr| { self.fetcher().header(num.into()).and_then(move |hdr| {
if hdr.transactions_root() == SHA3_NULL_RLP { if hdr.transactions_root() == SHA3_NULL_RLP {
future::ok(Some(U256::from(0).into())).boxed() future::ok(Some(U256::from(0).into())).boxed()
} else { } else {
sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr)))
.map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into()))) .map(|x| x.map(|b| Some(U256::from(b.transactions_count()).into())))
.map(|x| x.map_err(err_premature_cancel).boxed()) .map(|x| x.map_err(errors::on_demand_cancel).boxed())
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) .unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
} }
}).boxed() }).boxed()
@ -469,13 +321,13 @@ impl Eth for EthClient {
fn block_uncles_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> { fn block_uncles_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.header(BlockId::Hash(hash.into())).and_then(move |hdr| { self.fetcher().header(BlockId::Hash(hash.into())).and_then(move |hdr| {
if hdr.uncles_hash() == SHA3_EMPTY_LIST_RLP { if hdr.uncles_hash() == SHA3_EMPTY_LIST_RLP {
future::ok(Some(U256::from(0).into())).boxed() future::ok(Some(U256::from(0).into())).boxed()
} else { } else {
sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr)))
.map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into()))) .map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into())))
.map(|x| x.map_err(err_premature_cancel).boxed()) .map(|x| x.map_err(errors::on_demand_cancel).boxed())
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) .unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
} }
}).boxed() }).boxed()
@ -484,13 +336,13 @@ impl Eth for EthClient {
fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> { fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone()); let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.header(num.into()).and_then(move |hdr| { self.fetcher().header(num.into()).and_then(move |hdr| {
if hdr.uncles_hash() == SHA3_EMPTY_LIST_RLP { if hdr.uncles_hash() == SHA3_EMPTY_LIST_RLP {
future::ok(Some(U256::from(0).into())).boxed() future::ok(Some(U256::from(0).into())).boxed()
} else { } else {
sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr))) sync.with_context(|ctx| on_demand.block(ctx, request::Body::new(hdr)))
.map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into()))) .map(|x| x.map(|b| Some(U256::from(b.uncles_count()).into())))
.map(|x| x.map_err(err_premature_cancel).boxed()) .map(|x| x.map_err(errors::on_demand_cancel).boxed())
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed()) .unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
} }
}).boxed() }).boxed()
@ -525,7 +377,7 @@ impl Eth for EthClient {
} }
fn call(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<Bytes, Error> { fn call(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<Bytes, Error> {
self.proved_execution(req, num).and_then(|res| { self.fetcher().proved_execution(req, num).and_then(|res| {
match res { match res {
Ok(exec) => Ok(exec.output.into()), Ok(exec) => Ok(exec.output.into()),
Err(e) => Err(errors::execution(e)), Err(e) => Err(errors::execution(e)),
@ -535,7 +387,7 @@ impl Eth for EthClient {
fn estimate_gas(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> { fn estimate_gas(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<RpcU256, Error> {
// TODO: binary chop for more accurate estimates. // TODO: binary chop for more accurate estimates.
self.proved_execution(req, num).and_then(|res| { self.fetcher().proved_execution(req, num).and_then(|res| {
match res { match res {
Ok(exec) => Ok((exec.refunded + exec.gas_used).into()), Ok(exec) => Ok((exec.refunded + exec.gas_used).into()),
Err(e) => Err(errors::execution(e)), Err(e) => Err(errors::execution(e)),
@ -662,7 +514,7 @@ impl Filterable for EthClient {
future::ok(matches) future::ok(matches)
}) // and then collect them into a vector. }) // and then collect them into a vector.
.map(|matches| matches.into_iter().map(|(_, v)| v).collect()) .map(|matches| matches.into_iter().map(|(_, v)| v).collect())
.map_err(err_premature_cancel) .map_err(errors::on_demand_cancel)
}); });
match maybe_future { match maybe_future {

View File

@ -32,6 +32,7 @@ use jsonrpc_core::Error;
use jsonrpc_macros::Trailing; use jsonrpc_macros::Trailing;
use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::dispatch::{LightDispatcher, DEFAULT_MAC}; use v1::helpers::dispatch::{LightDispatcher, DEFAULT_MAC};
use v1::helpers::light_fetch::LightFetch;
use v1::metadata::Metadata; use v1::metadata::Metadata;
use v1::traits::Parity; use v1::traits::Parity;
use v1::types::{ use v1::types::{
@ -40,7 +41,7 @@ use v1::types::{
TransactionStats, LocalTransactionStatus, TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo, BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, DappId, ChainStatus, OperationsInfo, DappId, ChainStatus,
AccountInfo, HwAccountInfo, AccountInfo, HwAccountInfo, Header, RichHeader,
}; };
/// Parity implementation for light client. /// Parity implementation for light client.
@ -75,6 +76,16 @@ impl ParityClient {
dapps_port: dapps_port, dapps_port: dapps_port,
} }
} }
/// Create a light blockchain data fetcher.
fn fetcher(&self) -> LightFetch {
LightFetch {
client: self.light_dispatch.client.clone(),
on_demand: self.light_dispatch.on_demand.clone(),
sync: self.light_dispatch.sync.clone(),
cache: self.light_dispatch.cache.clone(),
}
}
} }
impl Parity for ParityClient { impl Parity for ParityClient {
@ -342,4 +353,38 @@ impl Parity for ParityClient {
capability: Capability::Light, capability: Capability::Light,
}) })
} }
fn block_header(&self, number: Trailing<BlockNumber>) -> BoxFuture<RichHeader, Error> {
use ethcore::encoded;
let engine = self.light_dispatch.client.engine().clone();
let from_encoded = move |encoded: encoded::Header| {
let header = encoded.decode();
let extra_info = engine.extra_info(&header);
RichHeader {
inner: Header {
hash: Some(header.hash().into()),
size: Some(encoded.rlp().as_raw().len().into()),
parent_hash: header.parent_hash().clone().into(),
uncles_hash: header.uncles_hash().clone().into(),
author: header.author().clone().into(),
miner: header.author().clone().into(),
state_root: header.state_root().clone().into(),
transactions_root: header.transactions_root().clone().into(),
receipts_root: header.receipts_root().clone().into(),
number: Some(header.number().into()),
gas_used: header.gas_used().clone().into(),
gas_limit: header.gas_limit().clone().into(),
logs_bloom: header.log_bloom().clone().into(),
timestamp: header.timestamp().into(),
difficulty: header.difficulty().clone().into(),
seal_fields: header.seal().iter().cloned().map(Into::into).collect(),
extra_data: Bytes::new(header.extra_data().clone()),
},
extra_info: extra_info,
}
};
self.fetcher().header(number.0.into()).map(from_encoded).boxed()
}
} }

View File

@ -28,6 +28,7 @@ use crypto::ecies;
use ethkey::{Brain, Generator}; use ethkey::{Brain, Generator};
use ethstore::random_phrase; use ethstore::random_phrase;
use ethsync::{SyncProvider, ManageNetwork}; use ethsync::{SyncProvider, ManageNetwork};
use ethcore::ids::BlockId;
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use ethcore::client::{MiningBlockChainClient}; use ethcore::client::{MiningBlockChainClient};
use ethcore::mode::Mode; use ethcore::mode::Mode;
@ -47,7 +48,7 @@ use v1::types::{
TransactionStats, LocalTransactionStatus, TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo, BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, DappId, ChainStatus, OperationsInfo, DappId, ChainStatus,
AccountInfo, HwAccountInfo, AccountInfo, HwAccountInfo, Header, RichHeader
}; };
/// Parity implementation. /// Parity implementation.
@ -393,4 +394,38 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
capability: Capability::Full, capability: Capability::Full,
}) })
} }
fn block_header(&self, number: Trailing<BlockNumber>) -> BoxFuture<RichHeader, Error> {
const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched earlier), extra_info is always available if object exists; qed";
let client = take_weakf!(self.client);
let id: BlockId = number.0.into();
let encoded = match client.block_header(id.clone()) {
Some(encoded) => encoded,
None => return future::err(errors::unknown_block()).boxed(),
};
future::ok(RichHeader {
inner: Header {
hash: Some(encoded.hash().into()),
size: Some(encoded.rlp().as_raw().len().into()),
parent_hash: encoded.parent_hash().into(),
uncles_hash: encoded.uncles_hash().into(),
author: encoded.author().into(),
miner: encoded.author().into(),
state_root: encoded.state_root().into(),
transactions_root: encoded.transactions_root().into(),
receipts_root: encoded.receipts_root().into(),
number: Some(encoded.number().into()),
gas_used: encoded.gas_used().into(),
gas_limit: encoded.gas_limit().into(),
logs_bloom: encoded.log_bloom().into(),
timestamp: encoded.timestamp().into(),
difficulty: encoded.difficulty().into(),
seal_fields: encoded.seal().into_iter().map(Into::into).collect(),
extra_data: Bytes::new(encoded.extra_data()),
},
extra_info: client.block_extra_info(id).expect(EXTRA_INFO_PROOF),
}).boxed()
}
} }

View File

@ -28,7 +28,7 @@ use v1::types::{
TransactionStats, LocalTransactionStatus, TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo, BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, DappId, ChainStatus, OperationsInfo, DappId, ChainStatus,
AccountInfo, HwAccountInfo, AccountInfo, HwAccountInfo, RichHeader,
}; };
build_rpc_trait! { build_rpc_trait! {
@ -198,5 +198,10 @@ build_rpc_trait! {
/// Get node kind info. /// Get node kind info.
#[rpc(name = "parity_nodeKind")] #[rpc(name = "parity_nodeKind")]
fn node_kind(&self) -> Result<::v1::types::NodeKind, Error>; fn node_kind(&self) -> Result<::v1::types::NodeKind, Error>;
/// Get block header.
/// Same as `eth_getBlockByNumber` but without uncles and transactions.
#[rpc(async, name = "parity_getBlockHeaderByNumber")]
fn block_header(&self, Trailing<BlockNumber>) -> BoxFuture<RichHeader, Error>;
} }
} }

View File

@ -96,34 +96,90 @@ pub struct Block {
pub size: Option<U256>, pub size: Option<U256>,
} }
/// Block representation with additional info /// Block header representation.
#[derive(Debug, Serialize)]
pub struct Header {
/// Hash of the block
pub hash: Option<H256>,
/// Hash of the parent
#[serde(rename="parentHash")]
pub parent_hash: H256,
/// Hash of the uncles
#[serde(rename="sha3Uncles")]
pub uncles_hash: H256,
/// Authors address
pub author: H160,
// TODO: get rid of this one
/// ?
pub miner: H160,
/// State root hash
#[serde(rename="stateRoot")]
pub state_root: H256,
/// Transactions root hash
#[serde(rename="transactionsRoot")]
pub transactions_root: H256,
/// Transactions receipts root hash
#[serde(rename="receiptsRoot")]
pub receipts_root: H256,
/// Block number
pub number: Option<U256>,
/// Gas Used
#[serde(rename="gasUsed")]
pub gas_used: U256,
/// Gas Limit
#[serde(rename="gasLimit")]
pub gas_limit: U256,
/// Extra data
#[serde(rename="extraData")]
pub extra_data: Bytes,
/// Logs bloom
#[serde(rename="logsBloom")]
pub logs_bloom: H2048,
/// Timestamp
pub timestamp: U256,
/// Difficulty
pub difficulty: U256,
/// Seal fields
#[serde(rename="sealFields")]
pub seal_fields: Vec<Bytes>,
/// Size in bytes
pub size: Option<U256>,
}
/// Block representation with additional info.
pub type RichBlock = Rich<Block>;
/// Header representation with additional info.
pub type RichHeader = Rich<Header>;
/// Value representation with additional info
#[derive(Debug)] #[derive(Debug)]
pub struct RichBlock { pub struct Rich<T> {
/// Standard block /// Standard value.
pub block: Block, pub inner: T,
/// Engine-specific fields with additional description. /// Engine-specific fields with additional description.
/// Should be included directly to serialized block object. /// Should be included directly to serialized block object.
// TODO [ToDr] #[serde(skip_serializing)] // TODO [ToDr] #[serde(skip_serializing)]
pub extra_info: BTreeMap<String, String>, pub extra_info: BTreeMap<String, String>,
} }
impl Deref for RichBlock { impl<T> Deref for Rich<T> {
type Target = Block; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.block &self.inner
} }
} }
impl Serialize for RichBlock { impl<T: Serialize> Serialize for Rich<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
use serde_json::{to_value, Value}; use serde_json::{to_value, Value};
let serialized = (to_value(&self.block), to_value(&self.extra_info)); let serialized = (to_value(&self.inner), to_value(&self.extra_info));
if let (Ok(Value::Object(mut block)), Ok(Value::Object(extras))) = serialized { if let (Ok(Value::Object(mut value)), Ok(Value::Object(extras))) = serialized {
// join two objects // join two objects
block.extend(extras); value.extend(extras);
// and serialize // and serialize
block.serialize(serializer) value.serialize(serializer)
} else { } else {
Err(S::Error::custom("Unserializable structures.")) Err(S::Error::custom("Unserializable structures."))
} }
@ -135,7 +191,7 @@ mod tests {
use std::collections::BTreeMap; use std::collections::BTreeMap;
use serde_json; use serde_json;
use v1::types::{Transaction, H64, H160, H256, H2048, Bytes, U256}; use v1::types::{Transaction, H64, H160, H256, H2048, Bytes, U256};
use super::{Block, RichBlock, BlockTransactions}; use super::{Block, RichBlock, BlockTransactions, Header, RichHeader};
#[test] #[test]
fn test_serialize_block_transactions() { fn test_serialize_block_transactions() {
@ -174,7 +230,7 @@ mod tests {
}; };
let serialized_block = serde_json::to_string(&block).unwrap(); let serialized_block = serde_json::to_string(&block).unwrap();
let rich_block = RichBlock { let rich_block = RichBlock {
block: block, inner: block,
extra_info: map![ extra_info: map![
"mixHash".into() => format!("0x{:?}", H256::default()), "mixHash".into() => format!("0x{:?}", H256::default()),
"nonce".into() => format!("0x{:?}", H64::default()) "nonce".into() => format!("0x{:?}", H64::default())
@ -212,7 +268,7 @@ mod tests {
}; };
let serialized_block = serde_json::to_string(&block).unwrap(); let serialized_block = serde_json::to_string(&block).unwrap();
let rich_block = RichBlock { let rich_block = RichBlock {
block: block, inner: block,
extra_info: map![ extra_info: map![
"mixHash".into() => format!("0x{:?}", H256::default()), "mixHash".into() => format!("0x{:?}", H256::default()),
"nonce".into() => format!("0x{:?}", H64::default()) "nonce".into() => format!("0x{:?}", H64::default())
@ -223,4 +279,39 @@ mod tests {
assert_eq!(serialized_block, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","totalDifficulty":"0x0","sealFields":["0x","0x"],"uncles":[],"transactions":[],"size":null}"#); assert_eq!(serialized_block, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","totalDifficulty":"0x0","sealFields":["0x","0x"],"uncles":[],"transactions":[],"size":null}"#);
assert_eq!(serialized_rich_block, r#"{"author":"0x0000000000000000000000000000000000000000","difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sealFields":["0x","0x"],"sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","size":null,"stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","totalDifficulty":"0x0","transactions":[],"transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","uncles":[]}"#); assert_eq!(serialized_rich_block, r#"{"author":"0x0000000000000000000000000000000000000000","difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sealFields":["0x","0x"],"sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","size":null,"stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","totalDifficulty":"0x0","transactions":[],"transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","uncles":[]}"#);
} }
#[test]
fn test_serialize_header() {
let header = Header {
hash: Some(H256::default()),
parent_hash: H256::default(),
uncles_hash: H256::default(),
author: H160::default(),
miner: H160::default(),
state_root: H256::default(),
transactions_root: H256::default(),
receipts_root: H256::default(),
number: Some(U256::default()),
gas_used: U256::default(),
gas_limit: U256::default(),
extra_data: Bytes::default(),
logs_bloom: H2048::default(),
timestamp: U256::default(),
difficulty: U256::default(),
seal_fields: vec![Bytes::default(), Bytes::default()],
size: Some(69.into()),
};
let serialized_header = serde_json::to_string(&header).unwrap();
let rich_header = RichHeader {
inner: header,
extra_info: map![
"mixHash".into() => format!("0x{:?}", H256::default()),
"nonce".into() => format!("0x{:?}", H64::default())
],
};
let serialized_rich_header = serde_json::to_string(&rich_header).unwrap();
assert_eq!(serialized_header, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","sealFields":["0x","0x"],"size":"0x45"}"#);
assert_eq!(serialized_rich_header, r#"{"author":"0x0000000000000000000000000000000000000000","difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sealFields":["0x","0x"],"sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","size":"0x45","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000"}"#);
}
} }

View File

@ -45,7 +45,7 @@ mod work;
pub use self::account_info::{AccountInfo, HwAccountInfo}; pub use self::account_info::{AccountInfo, HwAccountInfo};
pub use self::bytes::Bytes; pub use self::bytes::Bytes;
pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich};
pub use self::block_number::BlockNumber; pub use self::block_number::BlockNumber;
pub use self::call_request::CallRequest; pub use self::call_request::CallRequest;
pub use self::confirmations::{ pub use self::confirmations::{

View File

@ -32,7 +32,6 @@ $HOME/.cargo,\
$HOME/.multirust,\ $HOME/.multirust,\
rocksdb,\ rocksdb,\
secp256k1,\ secp256k1,\
src/tests,\
util/json-tests,\ util/json-tests,\
util/src/network/tests,\ util/src/network/tests,\
ethcore/src/evm/tests,\ ethcore/src/evm/tests,\

View File

@ -24,12 +24,15 @@ tokio-core = "0.1"
tokio-service = "0.1" tokio-service = "0.1"
tokio-proto = "0.1" tokio-proto = "0.1"
url = "1.0" url = "1.0"
ethabi = "1.0.0"
ethcore = { path = "../ethcore" }
ethcore-devtools = { path = "../devtools" } ethcore-devtools = { path = "../devtools" }
ethcore-util = { path = "../util" } ethcore-util = { path = "../util" }
ethcore-ipc = { path = "../ipc/rpc" } ethcore-ipc = { path = "../ipc/rpc" }
ethcore-ipc-nano = { path = "../ipc/nano" } ethcore-ipc-nano = { path = "../ipc/nano" }
ethcrypto = { path = "../ethcrypto" } ethcrypto = { path = "../ethcrypto" }
ethkey = { path = "../ethkey" } ethkey = { path = "../ethkey" }
native-contracts = { path = "../ethcore/native_contracts" }
[profile.release] [profile.release]
debug = true debug = true

View File

@ -14,38 +14,92 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::{HashMap, HashSet}; use std::sync::Arc;
use parking_lot::RwLock; use futures::{future, Future};
use parking_lot::Mutex;
use ethkey::public_to_address;
use ethcore::client::{Client, BlockChainClient, BlockId};
use native_contracts::SecretStoreAclStorage;
use types::all::{Error, DocumentAddress, Public}; use types::all::{Error, DocumentAddress, Public};
const ACL_CHECKER_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_acl_checker";
/// ACL storage of Secret Store /// ACL storage of Secret Store
pub trait AclStorage: Send + Sync { pub trait AclStorage: Send + Sync {
/// Check if requestor with `public` key can access document with hash `document` /// Check if requestor with `public` key can access document with hash `document`
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error>; fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error>;
} }
/// Dummy ACL storage implementation /// On-chain ACL storage implementation.
#[derive(Default, Debug)] pub struct OnChainAclStorage {
pub struct DummyAclStorage { /// Blockchain client.
prohibited: RwLock<HashMap<Public, HashSet<DocumentAddress>>>, client: Arc<Client>,
/// On-chain contract.
contract: Mutex<Option<SecretStoreAclStorage>>,
} }
impl DummyAclStorage { impl OnChainAclStorage {
#[cfg(test)] pub fn new(client: Arc<Client>) -> Self {
/// Prohibit given requestor access to given document OnChainAclStorage {
pub fn prohibit(&self, public: Public, document: DocumentAddress) { client: client,
self.prohibited.write() contract: Mutex::new(None),
.entry(public) }
.or_insert_with(Default::default)
.insert(document);
} }
} }
impl AclStorage for DummyAclStorage { impl AclStorage for OnChainAclStorage {
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error> { fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error> {
Ok(self.prohibited.read() let mut contract = self.contract.lock();
.get(public) if !contract.is_some() {
.map(|docs| !docs.contains(document)) *contract = self.client.registry_address(ACL_CHECKER_CONTRACT_REGISTRY_NAME.to_owned())
.unwrap_or(true)) .and_then(|contract_addr| {
trace!(target: "secretstore", "Configuring for ACL checker contract from {}", contract_addr);
Some(SecretStoreAclStorage::new(contract_addr))
})
}
if let Some(ref contract) = *contract {
let address = public_to_address(&public);
let do_call = |a, d| future::done(self.client.call_contract(BlockId::Latest, a, d));
contract.check_permissions(do_call, address, document.clone())
.map_err(|err| Error::Internal(err))
.wait()
} else {
Err(Error::Internal("ACL checker contract is not configured".to_owned()))
}
}
}
#[cfg(test)]
pub mod tests {
use std::collections::{HashMap, HashSet};
use parking_lot::RwLock;
use types::all::{Error, DocumentAddress, Public};
use super::AclStorage;
#[derive(Default, Debug)]
/// Dummy ACL storage implementation
pub struct DummyAclStorage {
prohibited: RwLock<HashMap<Public, HashSet<DocumentAddress>>>,
}
impl DummyAclStorage {
#[cfg(test)]
/// Prohibit given requestor access to given document
pub fn prohibit(&self, public: Public, document: DocumentAddress) {
self.prohibited.write()
.entry(public)
.or_insert_with(Default::default)
.insert(document);
}
}
impl AclStorage for DummyAclStorage {
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error> {
Ok(self.prohibited.read()
.get(public)
.map(|docs| !docs.contains(document))
.unwrap_or(true))
}
} }
} }

View File

@ -147,7 +147,7 @@ mod tests {
use std::sync::Arc; use std::sync::Arc;
use ethcrypto; use ethcrypto;
use ethkey::{self, Random, Generator}; use ethkey::{self, Random, Generator};
use acl_storage::DummyAclStorage; use acl_storage::tests::DummyAclStorage;
use key_storage::tests::DummyKeyStorage; use key_storage::tests::DummyKeyStorage;
use types::all::{ClusterConfiguration, NodeAddress, EncryptionConfiguration, DocumentEncryptedKey, DocumentKey}; use types::all::{ClusterConfiguration, NodeAddress, EncryptionConfiguration, DocumentEncryptedKey, DocumentKey};
use super::super::{RequestSignature, DocumentAddress}; use super::super::{RequestSignature, DocumentAddress};

View File

@ -220,7 +220,7 @@ impl SessionImpl {
self.completed.notify_all(); self.completed.notify_all();
}, },
// we can not decrypt data // we can not decrypt data
SessionState::Failed => (), SessionState::Failed => self.completed.notify_all(),
// cannot reach other states // cannot reach other states
_ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"), _ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"),
} }
@ -285,7 +285,10 @@ impl SessionImpl {
SessionState::WaitingForPartialDecryption => SessionState::WaitingForPartialDecryption =>
SessionImpl::start_waiting_for_partial_decryption(self.node().clone(), self.id.clone(), self.access_key.clone(), &self.cluster, &self.encrypted_data, &mut *data), SessionImpl::start_waiting_for_partial_decryption(self.node().clone(), self.id.clone(), self.access_key.clone(), &self.cluster, &self.encrypted_data, &mut *data),
// we can not have enough nodes for decryption // we can not have enough nodes for decryption
SessionState::Failed => Ok(()), SessionState::Failed => {
self.completed.notify_all();
Ok(())
},
// cannot reach other states // cannot reach other states
_ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"), _ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"),
} }
@ -480,6 +483,7 @@ fn process_initialization_response(encrypted_data: &DocumentKeyShare, data: &mut
// check if we still can receive enough confirmations to do a decryption? // check if we still can receive enough confirmations to do a decryption?
if encrypted_data.id_numbers.len() - data.rejected_nodes.len() < encrypted_data.threshold + 1 { if encrypted_data.id_numbers.len() - data.rejected_nodes.len() < encrypted_data.threshold + 1 {
data.decrypted_secret = Some(Err(Error::AccessDenied));
data.state = SessionState::Failed; data.state = SessionState::Failed;
} }
}, },
@ -503,7 +507,7 @@ fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use super::super::super::acl_storage::DummyAclStorage; use super::super::super::acl_storage::tests::DummyAclStorage;
use ethkey::{self, Random, Generator, Public, Secret}; use ethkey::{self, Random, Generator, Public, Secret};
use key_server_cluster::{NodeId, DocumentKeyShare, SessionId, Error}; use key_server_cluster::{NodeId, DocumentKeyShare, SessionId, Error};
use key_server_cluster::cluster::tests::DummyCluster; use key_server_cluster::cluster::tests::DummyCluster;

View File

@ -21,7 +21,7 @@ use ethcrypto;
use super::types::all::DocumentAddress; use super::types::all::DocumentAddress;
pub use super::types::all::{NodeId, EncryptionConfiguration}; pub use super::types::all::{NodeId, EncryptionConfiguration};
pub use super::acl_storage::{AclStorage, DummyAclStorage}; pub use super::acl_storage::AclStorage;
pub use super::key_storage::{KeyStorage, DocumentKeyShare}; pub use super::key_storage::{KeyStorage, DocumentKeyShare};
pub use super::serialization::{SerializableSignature, SerializableH256, SerializableSecret, SerializablePublic}; pub use super::serialization::{SerializableSignature, SerializableH256, SerializableSecret, SerializablePublic};
pub use self::cluster::{ClusterCore, ClusterConfiguration, ClusterClient}; pub use self::cluster::{ClusterCore, ClusterConfiguration, ClusterClient};
@ -30,6 +30,8 @@ pub use self::decryption_session::Session as DecryptionSession;
#[cfg(test)] #[cfg(test)]
pub use super::key_storage::tests::DummyKeyStorage; pub use super::key_storage::tests::DummyKeyStorage;
#[cfg(test)]
pub use super::acl_storage::tests::DummyAclStorage;
pub type SessionId = DocumentAddress; pub type SessionId = DocumentAddress;
@ -72,6 +74,8 @@ pub enum Error {
Serde(String), Serde(String),
/// Key storage error. /// Key storage error.
KeyStorage(String), KeyStorage(String),
/// Acl storage error.
AccessDenied,
} }
impl From<ethkey::Error> for Error { impl From<ethkey::Error> for Error {
@ -110,6 +114,7 @@ impl fmt::Display for Error {
Error::Io(ref e) => write!(f, "i/o error {}", e), Error::Io(ref e) => write!(f, "i/o error {}", e),
Error::Serde(ref e) => write!(f, "serde error {}", e), Error::Serde(ref e) => write!(f, "serde error {}", e),
Error::KeyStorage(ref e) => write!(f, "key storage error {}", e), Error::KeyStorage(ref e) => write!(f, "key storage error {}", e),
Error::AccessDenied => write!(f, "Access denied"),
} }
} }
} }

View File

@ -32,11 +32,14 @@ extern crate tokio_service;
extern crate tokio_proto; extern crate tokio_proto;
extern crate url; extern crate url;
extern crate ethabi;
extern crate ethcore;
extern crate ethcore_devtools as devtools; extern crate ethcore_devtools as devtools;
extern crate ethcore_util as util; extern crate ethcore_util as util;
extern crate ethcore_ipc as ipc; extern crate ethcore_ipc as ipc;
extern crate ethcrypto; extern crate ethcrypto;
extern crate ethkey; extern crate ethkey;
extern crate native_contracts;
mod key_server_cluster; mod key_server_cluster;
mod types; mod types;
@ -52,15 +55,18 @@ mod key_server;
mod key_storage; mod key_storage;
mod serialization; mod serialization;
use std::sync::Arc;
use ethcore::client::Client;
pub use types::all::{DocumentAddress, DocumentKey, DocumentEncryptedKey, RequestSignature, Public, pub use types::all::{DocumentAddress, DocumentKey, DocumentEncryptedKey, RequestSignature, Public,
Error, NodeAddress, ServiceConfiguration, ClusterConfiguration, EncryptionConfiguration}; Error, NodeAddress, ServiceConfiguration, ClusterConfiguration, EncryptionConfiguration};
pub use traits::{KeyServer}; pub use traits::{KeyServer};
/// Start new key server instance /// Start new key server instance
pub fn start(config: ServiceConfiguration) -> Result<Box<KeyServer>, Error> { pub fn start(client: Arc<Client>, config: ServiceConfiguration) -> Result<Box<KeyServer>, Error> {
use std::sync::Arc; use std::sync::Arc;
let acl_storage = Arc::new(acl_storage::DummyAclStorage::default()); let acl_storage = Arc::new(acl_storage::OnChainAclStorage::new(client));
let key_storage = Arc::new(key_storage::PersistentKeyStorage::new(&config)?); let key_storage = Arc::new(key_storage::PersistentKeyStorage::new(&config)?);
let key_server = key_server::KeyServerImpl::new(&config.cluster_config, acl_storage, key_storage)?; let key_server = key_server::KeyServerImpl::new(&config.cluster_config, acl_storage, key_storage)?;
let listener = http_listener::KeyServerHttpListener::start(config, key_server)?; let listener = http_listener::KeyServerHttpListener::start(config, key_server)?;

View File

@ -119,7 +119,10 @@ impl From<ethkey::Error> for Error {
impl From<key_server_cluster::Error> for Error { impl From<key_server_cluster::Error> for Error {
fn from(err: key_server_cluster::Error) -> Self { fn from(err: key_server_cluster::Error) -> Self {
Error::Internal(err.into()) match err {
key_server_cluster::Error::AccessDenied => Error::AccessDenied,
_ => Error::Internal(err.into()),
}
} }
} }