Merge branch 'block_header_rpc' into on-demand-priority
This commit is contained in:
commit
cf75a19e8d
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -637,6 +637,8 @@ name = "ethcore-secretstore"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"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-ipc 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)",
|
||||
"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)",
|
||||
"native-contracts 0.1.0",
|
||||
"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)",
|
||||
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -1754,7 +1757,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "parity-ui-precompiled"
|
||||
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 = [
|
||||
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -1,6 +1,8 @@
|
||||
# [Parity](https://ethcore.io/parity.html)
|
||||
### 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]
|
||||
|
||||
### 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
|
||||
[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
|
||||
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.
|
||||
|
||||
----
|
||||
|
||||
## 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:
|
||||
|
||||
@ -80,7 +81,7 @@ Once you have rustup, install parity or download and build from source
|
||||
|
||||
----
|
||||
|
||||
## Quick install
|
||||
## Quick build and install
|
||||
|
||||
```bash
|
||||
cargo install --git https://github.com/paritytech/parity.git parity
|
||||
|
@ -119,7 +119,7 @@ impl Decodable for Entry {
|
||||
}
|
||||
|
||||
fn cht_key(number: u64) -> String {
|
||||
format!("canonical_{}", number)
|
||||
format!("{:08x}_canonical", number)
|
||||
}
|
||||
|
||||
fn era_key(number: u64) -> String {
|
||||
|
@ -83,6 +83,13 @@ pub trait LightChainClient: Send + Sync {
|
||||
/// Get the signing network ID.
|
||||
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.
|
||||
fn is_known(&self, hash: &H256) -> bool;
|
||||
|
||||
@ -348,6 +355,14 @@ impl LightChainClient for Client {
|
||||
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 {
|
||||
self.status(hash) == BlockStatus::InChain
|
||||
}
|
||||
|
@ -509,6 +509,9 @@ impl TransactionProof {
|
||||
pub fn check_response(&self, _: &Mutex<::cache::Cache>, state_items: &[DBValue]) -> Result<super::ExecutionResult, Error> {
|
||||
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(
|
||||
state_items,
|
||||
root,
|
||||
|
@ -23,6 +23,7 @@ use std::io::Write;
|
||||
// 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 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) {
|
||||
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() {
|
||||
build_file("Registry", REGISTRY_ABI, "registry.rs");
|
||||
build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs");
|
||||
build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs");
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ extern crate ethcore_util as util;
|
||||
|
||||
mod registry;
|
||||
mod service_transaction;
|
||||
mod secretstore_acl_storage;
|
||||
|
||||
pub use self::registry::Registry;
|
||||
pub use self::service_transaction::ServiceTransactionChecker;
|
||||
pub use self::secretstore_acl_storage::SecretStoreAclStorage;
|
||||
|
22
ethcore/native_contracts/src/secretstore_acl_storage.rs
Normal file
22
ethcore/native_contracts/src/secretstore_acl_storage.rs
Normal 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"));
|
@ -395,7 +395,7 @@ impl Client {
|
||||
if header.number() < self.engine().params().validate_receipts_transition && header.receipts_root() != locked_block.block().header().receipts_root() {
|
||||
locked_block = locked_block.strip_receipts();
|
||||
}
|
||||
|
||||
|
||||
// Final Verification
|
||||
if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) {
|
||||
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
|
||||
@ -1627,10 +1627,12 @@ impl ::client::ProvingBlockChainClient for Client {
|
||||
}
|
||||
|
||||
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),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
env_info.gas_limit = transaction.gas.clone();
|
||||
let mut jdb = self.state_db.lock().journal_db().boxed_clone();
|
||||
let backend = state::backend::Proving::new(jdb.as_hashdb_mut());
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parity.js",
|
||||
"version": "1.7.43",
|
||||
"version": "1.7.46",
|
||||
"main": "release/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
@ -176,7 +176,7 @@
|
||||
"geopattern": "1.2.3",
|
||||
"isomorphic-fetch": "2.2.1",
|
||||
"js-sha3": "0.5.5",
|
||||
"keythereum": "0.4.3",
|
||||
"keythereum": "0.4.6",
|
||||
"lodash": "4.17.2",
|
||||
"loglevel": "1.4.1",
|
||||
"marked": "0.3.6",
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { keythereum } from '../ethkey';
|
||||
import { createKeyObject, decryptPrivateKey } from '../ethkey';
|
||||
|
||||
export default class Account {
|
||||
constructor (persist, data) {
|
||||
@ -31,12 +31,14 @@ export default class Account {
|
||||
}
|
||||
|
||||
isValidPassword (password) {
|
||||
try {
|
||||
keythereum.recover(Buffer.from(password), this._keyObject);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return decryptPrivateKey(this._keyObject, password)
|
||||
.then((privateKey) => {
|
||||
if (!privateKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
get address () {
|
||||
@ -68,21 +70,23 @@ export default class Account {
|
||||
}
|
||||
|
||||
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) {
|
||||
const iv = keythereum.crypto.randomBytes(16);
|
||||
const salt = keythereum.crypto.randomBytes(32);
|
||||
return createKeyObject(key, password).then((keyObject) => {
|
||||
const account = new Account(persist, { keyObject });
|
||||
|
||||
// Keythereum will fail if `password` is an empty string
|
||||
password = Buffer.from(password);
|
||||
|
||||
const keyObject = keythereum.dump(password, key, salt, iv);
|
||||
|
||||
const account = new Account(persist, { keyObject });
|
||||
|
||||
return account;
|
||||
return account;
|
||||
});
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
|
@ -38,14 +38,23 @@ export default class Accounts {
|
||||
|
||||
create (secret, password) {
|
||||
const privateKey = Buffer.from(secret.slice(2), 'hex');
|
||||
const account = Account.fromPrivateKey(this.persist, privateKey, password);
|
||||
|
||||
this._store.push(account);
|
||||
this.lastAddress = account.address;
|
||||
return Account
|
||||
.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) {
|
||||
@ -73,28 +82,41 @@ export default class Accounts {
|
||||
remove (address, password) {
|
||||
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);
|
||||
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const account = this._store[index];
|
||||
|
||||
if (!account.isValidPassword(password)) {
|
||||
console.log('invalid password');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address === this.lastAddress) {
|
||||
this.lastAddress = NULL_ADDRESS;
|
||||
return;
|
||||
}
|
||||
|
||||
this._store.splice(index, 1);
|
||||
|
||||
this.persist();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
mapArray (mapper) {
|
||||
|
19
js/src/api/local/ethkey/dummy.js
Normal file
19
js/src/api/local/ethkey/dummy.js
Normal 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)
|
||||
}
|
@ -14,31 +14,35 @@
|
||||
// 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;
|
||||
import workerPool from './workerPool';
|
||||
|
||||
// Local accounts should never be used outside of the browser
|
||||
export let keythereum = null;
|
||||
export function createKeyObject (key, password) {
|
||||
return workerPool.getWorker().action('createKeyObject', { key, password })
|
||||
.then((obj) => JSON.parse(obj));
|
||||
}
|
||||
|
||||
if (hasWebWorkers) {
|
||||
require('keythereum/dist/keythereum');
|
||||
export function decryptPrivateKey (keyObject, password) {
|
||||
return workerPool
|
||||
.getWorker()
|
||||
.action('decryptPrivateKey', { keyObject, password })
|
||||
.then((privateKey) => {
|
||||
if (privateKey) {
|
||||
return Buffer.from(privateKey);
|
||||
}
|
||||
|
||||
keythereum = window.keythereum;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
export function phraseToAddress (phrase) {
|
||||
return phraseToWallet(phrase).then((wallet) => wallet.address);
|
||||
return phraseToWallet(phrase)
|
||||
.then((wallet) => wallet.address);
|
||||
}
|
||||
|
||||
export function phraseToWallet (phrase) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new KeyWorker();
|
||||
|
||||
worker.postMessage(phrase);
|
||||
worker.onmessage = ({ data }) => {
|
||||
resolve(data);
|
||||
};
|
||||
});
|
||||
return workerPool.getWorker().action('phraseToWallet', phrase);
|
||||
}
|
||||
|
||||
export function verifySecret (secret) {
|
||||
return workerPool.getWorker().action('verifySecret', secret);
|
||||
}
|
||||
|
@ -14,58 +14,104 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 { keccak_256 as keccak256 } from 'js-sha3';
|
||||
import { bytesToHex } from '~/api/util/format';
|
||||
|
||||
const isWorker = typeof self !== 'undefined';
|
||||
|
||||
// Stay compatible between environments
|
||||
if (typeof self !== 'object') {
|
||||
if (!isWorker) {
|
||||
const scope = typeof global === 'undefined' ? window : global;
|
||||
|
||||
scope.self = scope;
|
||||
}
|
||||
|
||||
function bytesToHex (bytes) {
|
||||
return '0x' + Array.from(bytes).map(n => ('0' + n.toString(16)).slice(-2)).join('');
|
||||
// keythereum should never be used outside of the browser
|
||||
let keythereum = null;
|
||||
|
||||
if (isWorker) {
|
||||
require('keythereum/dist/keythereum');
|
||||
|
||||
keythereum = self.keythereum;
|
||||
}
|
||||
|
||||
// Logic ported from /ethkey/src/brain.rs
|
||||
function phraseToWallet (phrase) {
|
||||
let secret = keccak256.array(phrase);
|
||||
|
||||
for (let i = 0; i < 16384; i++) {
|
||||
secret = keccak256.array(secret);
|
||||
function route ({ action, payload }) {
|
||||
if (action in actions) {
|
||||
return actions[action](payload);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
secret = keccak256.array(secret);
|
||||
return null;
|
||||
}
|
||||
|
||||
const secretBuf = Buffer.from(secret);
|
||||
const actions = {
|
||||
phraseToWallet (phrase) {
|
||||
let secret = keccak256.array(phrase);
|
||||
|
||||
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);
|
||||
for (let i = 0; i < 16384; i++) {
|
||||
secret = keccak256.array(secret);
|
||||
}
|
||||
|
||||
if (address[0] !== 0) {
|
||||
continue;
|
||||
while (true) {
|
||||
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 = {
|
||||
secret: bytesToHex(secretBuf),
|
||||
public: bytesToHex(publicBuf),
|
||||
address: bytesToHex(address)
|
||||
};
|
||||
verifySecret (secret) {
|
||||
const key = Buffer.from(secret.slice(2), 'hex');
|
||||
|
||||
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 }) {
|
||||
const wallet = phraseToWallet(data);
|
||||
const result = route(data);
|
||||
|
||||
postMessage(wallet);
|
||||
close();
|
||||
postMessage(result);
|
||||
};
|
||||
|
||||
// Emulate a web worker in Node.js
|
||||
@ -73,9 +119,9 @@ class KeyWorker {
|
||||
postMessage (data) {
|
||||
// Force async
|
||||
setTimeout(() => {
|
||||
const wallet = phraseToWallet(data);
|
||||
const result = route(data);
|
||||
|
||||
this.onmessage({ data: wallet });
|
||||
this.onmessage({ data: result });
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
61
js/src/api/local/ethkey/workerPool.js
Normal file
61
js/src/api/local/ethkey/workerPool.js
Normal 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();
|
@ -19,7 +19,7 @@ import accounts from './accounts';
|
||||
import transactions from './transactions';
|
||||
import { Middleware } from '../transport';
|
||||
import { inNumber16 } from '../format/input';
|
||||
import { phraseToWallet, phraseToAddress } from './ethkey';
|
||||
import { phraseToWallet, phraseToAddress, verifySecret } from './ethkey';
|
||||
import { randomPhrase } from '@parity/wordlist';
|
||||
|
||||
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]) => {
|
||||
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]) => {
|
||||
accounts.get(address).meta = meta;
|
||||
|
||||
@ -127,6 +154,12 @@ export default class LocalAccountsMiddleware extends Middleware {
|
||||
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]) => {
|
||||
const {
|
||||
gasPrice,
|
||||
@ -137,30 +170,33 @@ export default class LocalAccountsMiddleware extends Middleware {
|
||||
data
|
||||
} = Object.assign(transactions.get(id), modify);
|
||||
|
||||
return this
|
||||
.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);
|
||||
const account = accounts.get(from);
|
||||
|
||||
tx.sign(account.decryptPrivateKey(password));
|
||||
|
||||
const serializedTx = `0x${tx.serialize().toString('hex')}`;
|
||||
|
||||
return this.rpcRequest('eth_sendRawTransaction', [serializedTx]);
|
||||
})
|
||||
.then((hash) => {
|
||||
transactions.confirm(id, hash);
|
||||
|
||||
return {};
|
||||
return Promise.all([
|
||||
this.rpcRequest('parity_nextNonce', [from]),
|
||||
account.decryptPrivateKey(password)
|
||||
])
|
||||
.then(([nonce, privateKey]) => {
|
||||
const tx = new EthereumTx({
|
||||
nonce,
|
||||
to,
|
||||
data,
|
||||
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]) => {
|
||||
|
@ -80,12 +80,16 @@ export default class JsonRpcBase extends EventEmitter {
|
||||
const res = middleware.handle(method, params);
|
||||
|
||||
if (res != null) {
|
||||
const result = this._wrapSuccessResult(res);
|
||||
const json = this.encode(method, params);
|
||||
// If `res` isn't a promise, we need to wrap it
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
import { range } from 'lodash';
|
||||
|
||||
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) {
|
||||
|
@ -23,6 +23,7 @@ import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
||||
import { Form, Input, IdentityIcon } from '~/ui';
|
||||
import PasswordStrength from '~/ui/Form/PasswordStrength';
|
||||
import { RefreshIcon } from '~/ui/Icons';
|
||||
import Loading from '~/ui/Loading';
|
||||
|
||||
import ChangeVault from '../ChangeVault';
|
||||
import styles from '../createAccount.css';
|
||||
@ -170,7 +171,9 @@ export default class CreateAccount extends Component {
|
||||
const { accounts } = this.state;
|
||||
|
||||
if (!accounts) {
|
||||
return null;
|
||||
return (
|
||||
<Loading className={ styles.selector } size={ 1 } />
|
||||
);
|
||||
}
|
||||
|
||||
const identities = Object
|
||||
@ -205,6 +208,14 @@ export default class CreateAccount extends Component {
|
||||
createIdentities = () => {
|
||||
const { createStore } = this.props;
|
||||
|
||||
this.setState({
|
||||
accounts: null,
|
||||
selectedAddress: ''
|
||||
});
|
||||
|
||||
createStore.setAddress('');
|
||||
createStore.setPhrase('');
|
||||
|
||||
return createStore
|
||||
.createIdentities()
|
||||
.then((accounts) => {
|
||||
|
@ -58,12 +58,12 @@ describe('modals/CreateAccount/NewAccount', () => {
|
||||
return instance.componentWillMount();
|
||||
});
|
||||
|
||||
it('creates initial accounts', () => {
|
||||
expect(Object.keys(instance.state.accounts).length).to.equal(7);
|
||||
it('resets the accounts', () => {
|
||||
expect(instance.state.accounts).to.be.null;
|
||||
});
|
||||
|
||||
it('sets the initial selected value', () => {
|
||||
expect(instance.state.selectedAddress).to.equal(Object.keys(instance.state.accounts)[0]);
|
||||
it('resets the initial selected value', () => {
|
||||
expect(instance.state.selectedAddress).to.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -69,7 +69,7 @@ export default class Store {
|
||||
return !(this.nameError || this.walletFileError);
|
||||
|
||||
case 'fromNew':
|
||||
return !(this.nameError || this.passwordRepeatError);
|
||||
return !(this.nameError || this.passwordRepeatError) && this.hasAddress;
|
||||
|
||||
case 'fromPhrase':
|
||||
return !(this.nameError || this.passwordRepeatError);
|
||||
@ -85,6 +85,10 @@ export default class Store {
|
||||
}
|
||||
}
|
||||
|
||||
@computed get hasAddress () {
|
||||
return !!(this.address);
|
||||
}
|
||||
|
||||
@computed get passwordRepeatError () {
|
||||
return this.password === this.passwordRepeat
|
||||
? null
|
||||
|
@ -329,6 +329,7 @@ describe('modals/CreateAccount/Store', () => {
|
||||
describe('createType === fromNew', () => {
|
||||
beforeEach(() => {
|
||||
store.setCreateType('fromNew');
|
||||
store.setAddress('0x0000000000000000000000000000000000000000');
|
||||
});
|
||||
|
||||
it('returns true on no errors', () => {
|
||||
@ -337,11 +338,13 @@ describe('modals/CreateAccount/Store', () => {
|
||||
|
||||
it('returns false on nameError', () => {
|
||||
store.setName('');
|
||||
|
||||
expect(store.canCreate).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false on passwordRepeatError', () => {
|
||||
store.setPassword('testing');
|
||||
|
||||
expect(store.canCreate).to.be.false;
|
||||
});
|
||||
});
|
||||
|
@ -92,9 +92,9 @@ export function generateQr (from, tx, hash, rlp) {
|
||||
account: from.substr(2),
|
||||
hash: hash.substr(2),
|
||||
details: {
|
||||
gasPrice: inNumber10(inHex(tx.gasPrice.toString('hex'))),
|
||||
gas: inNumber10(inHex(tx.gasLimit.toString('hex'))),
|
||||
nonce: inNumber10(inHex(tx.nonce.toString('hex'))),
|
||||
gasPrice: inNumber10(inHex(tx.gasPrice.toString('hex') || '0')),
|
||||
gas: inNumber10(inHex(tx.gasLimit.toString('hex') || '0')),
|
||||
nonce: inNumber10(inHex(tx.nonce.toString('hex') || '0')),
|
||||
to: inAddress(tx.to.toString('hex')),
|
||||
value: inHex(tx.value.toString('hex') || '0')
|
||||
}
|
||||
|
@ -24,9 +24,11 @@ const ENV = process.env.NODE_ENV || 'development';
|
||||
const isProd = ENV === 'production';
|
||||
|
||||
const LIBRARY = process.env.LIBRARY;
|
||||
|
||||
if (!LIBRARY) {
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
const SRC = LIBRARY.toLowerCase();
|
||||
const OUTPUT_PATH = path.join(__dirname, '../.npmjs', SRC);
|
||||
|
||||
@ -63,12 +65,18 @@ module.exports = {
|
||||
'babel-loader?cacheDirectory=true'
|
||||
],
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
include: /node_modules\/(ethereumjs-tx|@parity\/wordlist)/,
|
||||
use: 'babel-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'secp256k1/js': path.resolve(__dirname, '../src/api/local/ethkey/dummy.js'),
|
||||
'~': path.resolve(__dirname, '../src')
|
||||
},
|
||||
modules: [
|
||||
@ -85,15 +93,12 @@ module.exports = {
|
||||
to: 'package.json',
|
||||
transform: function (content, path) {
|
||||
const json = JSON.parse(content.toString());
|
||||
json.version = packageJson.version;
|
||||
|
||||
// Add tests dependencies to Dev Deps
|
||||
json.devDependencies.chai = packageJson.devDependencies.chai;
|
||||
json.devDependencies.mocha = packageJson.devDependencies.mocha;
|
||||
json.devDependencies.nock = packageJson.devDependencies.nock;
|
||||
|
||||
// Add test script
|
||||
json.scripts.test = 'mocha \'test/*.spec.js\'';
|
||||
json.version = packageJson.version;
|
||||
|
||||
return new Buffer(JSON.stringify(json, null, ' '), 'utf-8');
|
||||
}
|
||||
|
@ -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 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());
|
||||
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(move |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())
|
||||
.map(|(fut, &addr)| fut.map(move |nonce| (addr, nonce)));
|
||||
|
@ -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)?;
|
||||
|
||||
// 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);
|
||||
|
||||
// the ipfs server
|
||||
|
@ -14,7 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use dir::default_data_path;
|
||||
use ethcore::client::Client;
|
||||
use helpers::replace_home;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@ -30,10 +32,10 @@ pub struct Configuration {
|
||||
pub data_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
/// Secret store dependencies
|
||||
pub struct Dependencies {
|
||||
// the only dependency will be BlockChainClient
|
||||
/// Blockchain client.
|
||||
pub client: Arc<Client>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "secretstore"))]
|
||||
@ -64,7 +66,7 @@ mod server {
|
||||
|
||||
impl KeyServer {
|
||||
/// 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![
|
||||
ethkey::KeyPair::from_secret("6c26a76e9b31048d170873a791401c7e799a11f0cefc0171cc31a49800967509".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)?;
|
||||
|
||||
Ok(KeyServer {
|
||||
|
@ -269,7 +269,7 @@ impl LightDispatcher {
|
||||
|
||||
match nonce_future {
|
||||
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())
|
||||
.boxed(),
|
||||
None => future::err(errors::network_disabled()).boxed()
|
||||
|
@ -346,3 +346,8 @@ pub fn deprecated<T: Into<Option<String>>>(message: T) -> Error {
|
||||
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", "")
|
||||
}
|
||||
|
212
rpc/src/v1/helpers/light_fetch.rs
Normal file
212
rpc/src/v1/helpers/light_fetch.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ pub mod accounts;
|
||||
pub mod block_import;
|
||||
pub mod dispatch;
|
||||
pub mod fake_sign;
|
||||
pub mod light_fetch;
|
||||
pub mod informant;
|
||||
pub mod oneshot;
|
||||
|
||||
|
@ -146,7 +146,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
|
||||
(Some(block), Some(total_difficulty)) => {
|
||||
let view = block.header_view();
|
||||
Ok(Some(RichBlock {
|
||||
block: Block {
|
||||
inner: Block {
|
||||
hash: Some(view.sha3().into()),
|
||||
size: Some(block.rlp().as_raw().len().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);
|
||||
|
||||
let block = RichBlock {
|
||||
block: Block {
|
||||
inner: Block {
|
||||
hash: Some(uncle.hash().into()),
|
||||
size: size,
|
||||
parent_hash: uncle.parent_hash().clone().into(),
|
||||
|
@ -48,6 +48,7 @@ use v1::impls::eth_filter::Filterable;
|
||||
use v1::helpers::{CallRequest as CRequest, errors, limit_logs, dispatch};
|
||||
use v1::helpers::{PollFilter, PollManager};
|
||||
use v1::helpers::block_import::is_major_importing;
|
||||
use v1::helpers::light_fetch::LightFetch;
|
||||
use v1::traits::Eth;
|
||||
use v1::types::{
|
||||
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 {
|
||||
/// 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.
|
||||
fn header(&self, id: BlockId) -> BoxFuture<encoded::Header, Error> {
|
||||
if let Some(h) = self.client.block_header(id) {
|
||||
return future::ok(h).boxed()
|
||||
/// Create a light data fetcher instance.
|
||||
fn fetcher(&self) -> LightFetch {
|
||||
LightFetch {
|
||||
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.
|
||||
@ -277,7 +129,7 @@ impl EthClient {
|
||||
let header = block.decode_header();
|
||||
let extra_info = engine.extra_info(&header);
|
||||
RichBlock {
|
||||
block: Block {
|
||||
inner: Block {
|
||||
hash: Some(header.hash().into()),
|
||||
size: Some(block.rlp().as_raw().len().into()),
|
||||
parent_hash: header.parent_hash().clone().into(),
|
||||
@ -307,7 +159,7 @@ impl EthClient {
|
||||
};
|
||||
|
||||
// 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).
|
||||
match client.score(id) {
|
||||
Some(score) => future::ok(fill_rich(block, Some(score))).boxed(),
|
||||
@ -344,7 +196,7 @@ impl EthClient {
|
||||
};
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
@ -415,7 +267,7 @@ impl Eth for EthClient {
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@ -432,20 +284,20 @@ impl Eth for EthClient {
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fn block_transaction_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> {
|
||||
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 {
|
||||
future::ok(Some(U256::from(0).into())).boxed()
|
||||
} else {
|
||||
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_err(err_premature_cancel).boxed())
|
||||
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
|
||||
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
|
||||
}
|
||||
}).boxed()
|
||||
@ -454,13 +306,13 @@ impl Eth for EthClient {
|
||||
fn block_transaction_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> {
|
||||
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 {
|
||||
future::ok(Some(U256::from(0).into())).boxed()
|
||||
} else {
|
||||
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_err(err_premature_cancel).boxed())
|
||||
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
|
||||
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
|
||||
}
|
||||
}).boxed()
|
||||
@ -469,13 +321,13 @@ impl Eth for EthClient {
|
||||
fn block_uncles_count_by_hash(&self, hash: RpcH256) -> BoxFuture<Option<RpcU256>, Error> {
|
||||
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 {
|
||||
future::ok(Some(U256::from(0).into())).boxed()
|
||||
} else {
|
||||
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_err(err_premature_cancel).boxed())
|
||||
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
|
||||
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
|
||||
}
|
||||
}).boxed()
|
||||
@ -484,13 +336,13 @@ impl Eth for EthClient {
|
||||
fn block_uncles_count_by_number(&self, num: BlockNumber) -> BoxFuture<Option<RpcU256>, Error> {
|
||||
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 {
|
||||
future::ok(Some(U256::from(0).into())).boxed()
|
||||
} else {
|
||||
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_err(err_premature_cancel).boxed())
|
||||
.map(|x| x.map_err(errors::on_demand_cancel).boxed())
|
||||
.unwrap_or_else(|| future::err(errors::network_disabled()).boxed())
|
||||
}
|
||||
}).boxed()
|
||||
@ -525,7 +377,7 @@ impl Eth for EthClient {
|
||||
}
|
||||
|
||||
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 {
|
||||
Ok(exec) => Ok(exec.output.into()),
|
||||
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> {
|
||||
// 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 {
|
||||
Ok(exec) => Ok((exec.refunded + exec.gas_used).into()),
|
||||
Err(e) => Err(errors::execution(e)),
|
||||
@ -662,7 +514,7 @@ impl Filterable for EthClient {
|
||||
future::ok(matches)
|
||||
}) // and then collect them into a vector.
|
||||
.map(|matches| matches.into_iter().map(|(_, v)| v).collect())
|
||||
.map_err(err_premature_cancel)
|
||||
.map_err(errors::on_demand_cancel)
|
||||
});
|
||||
|
||||
match maybe_future {
|
||||
|
@ -32,6 +32,7 @@ use jsonrpc_core::Error;
|
||||
use jsonrpc_macros::Trailing;
|
||||
use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings};
|
||||
use v1::helpers::dispatch::{LightDispatcher, DEFAULT_MAC};
|
||||
use v1::helpers::light_fetch::LightFetch;
|
||||
use v1::metadata::Metadata;
|
||||
use v1::traits::Parity;
|
||||
use v1::types::{
|
||||
@ -40,7 +41,7 @@ use v1::types::{
|
||||
TransactionStats, LocalTransactionStatus,
|
||||
BlockNumber, ConsensusCapability, VersionInfo,
|
||||
OperationsInfo, DappId, ChainStatus,
|
||||
AccountInfo, HwAccountInfo,
|
||||
AccountInfo, HwAccountInfo, Header, RichHeader,
|
||||
};
|
||||
|
||||
/// Parity implementation for light client.
|
||||
@ -75,6 +76,16 @@ impl ParityClient {
|
||||
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 {
|
||||
@ -342,4 +353,38 @@ impl Parity for ParityClient {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ use crypto::ecies;
|
||||
use ethkey::{Brain, Generator};
|
||||
use ethstore::random_phrase;
|
||||
use ethsync::{SyncProvider, ManageNetwork};
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::miner::MinerService;
|
||||
use ethcore::client::{MiningBlockChainClient};
|
||||
use ethcore::mode::Mode;
|
||||
@ -47,7 +48,7 @@ use v1::types::{
|
||||
TransactionStats, LocalTransactionStatus,
|
||||
BlockNumber, ConsensusCapability, VersionInfo,
|
||||
OperationsInfo, DappId, ChainStatus,
|
||||
AccountInfo, HwAccountInfo,
|
||||
AccountInfo, HwAccountInfo, Header, RichHeader
|
||||
};
|
||||
|
||||
/// Parity implementation.
|
||||
@ -393,4 +394,38 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ use v1::types::{
|
||||
TransactionStats, LocalTransactionStatus,
|
||||
BlockNumber, ConsensusCapability, VersionInfo,
|
||||
OperationsInfo, DappId, ChainStatus,
|
||||
AccountInfo, HwAccountInfo,
|
||||
AccountInfo, HwAccountInfo, RichHeader,
|
||||
};
|
||||
|
||||
build_rpc_trait! {
|
||||
@ -198,5 +198,10 @@ build_rpc_trait! {
|
||||
/// Get node kind info.
|
||||
#[rpc(name = "parity_nodeKind")]
|
||||
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>;
|
||||
}
|
||||
}
|
||||
|
@ -96,34 +96,90 @@ pub struct Block {
|
||||
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)]
|
||||
pub struct RichBlock {
|
||||
/// Standard block
|
||||
pub block: Block,
|
||||
pub struct Rich<T> {
|
||||
/// Standard value.
|
||||
pub inner: T,
|
||||
/// Engine-specific fields with additional description.
|
||||
/// Should be included directly to serialized block object.
|
||||
// TODO [ToDr] #[serde(skip_serializing)]
|
||||
pub extra_info: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl Deref for RichBlock {
|
||||
type Target = Block;
|
||||
impl<T> Deref for Rich<T> {
|
||||
type Target = T;
|
||||
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 {
|
||||
use serde_json::{to_value, Value};
|
||||
|
||||
let serialized = (to_value(&self.block), to_value(&self.extra_info));
|
||||
if let (Ok(Value::Object(mut block)), Ok(Value::Object(extras))) = serialized {
|
||||
let serialized = (to_value(&self.inner), to_value(&self.extra_info));
|
||||
if let (Ok(Value::Object(mut value)), Ok(Value::Object(extras))) = serialized {
|
||||
// join two objects
|
||||
block.extend(extras);
|
||||
value.extend(extras);
|
||||
// and serialize
|
||||
block.serialize(serializer)
|
||||
value.serialize(serializer)
|
||||
} else {
|
||||
Err(S::Error::custom("Unserializable structures."))
|
||||
}
|
||||
@ -135,7 +191,7 @@ mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
use serde_json;
|
||||
use v1::types::{Transaction, H64, H160, H256, H2048, Bytes, U256};
|
||||
use super::{Block, RichBlock, BlockTransactions};
|
||||
use super::{Block, RichBlock, BlockTransactions, Header, RichHeader};
|
||||
|
||||
#[test]
|
||||
fn test_serialize_block_transactions() {
|
||||
@ -174,7 +230,7 @@ mod tests {
|
||||
};
|
||||
let serialized_block = serde_json::to_string(&block).unwrap();
|
||||
let rich_block = RichBlock {
|
||||
block: block,
|
||||
inner: block,
|
||||
extra_info: map![
|
||||
"mixHash".into() => format!("0x{:?}", H256::default()),
|
||||
"nonce".into() => format!("0x{:?}", H64::default())
|
||||
@ -212,7 +268,7 @@ mod tests {
|
||||
};
|
||||
let serialized_block = serde_json::to_string(&block).unwrap();
|
||||
let rich_block = RichBlock {
|
||||
block: block,
|
||||
inner: block,
|
||||
extra_info: map![
|
||||
"mixHash".into() => format!("0x{:?}", H256::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_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"}"#);
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ mod work;
|
||||
|
||||
pub use self::account_info::{AccountInfo, HwAccountInfo};
|
||||
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::call_request::CallRequest;
|
||||
pub use self::confirmations::{
|
||||
|
@ -32,7 +32,6 @@ $HOME/.cargo,\
|
||||
$HOME/.multirust,\
|
||||
rocksdb,\
|
||||
secp256k1,\
|
||||
src/tests,\
|
||||
util/json-tests,\
|
||||
util/src/network/tests,\
|
||||
ethcore/src/evm/tests,\
|
||||
|
@ -24,12 +24,15 @@ tokio-core = "0.1"
|
||||
tokio-service = "0.1"
|
||||
tokio-proto = "0.1"
|
||||
url = "1.0"
|
||||
ethabi = "1.0.0"
|
||||
ethcore = { path = "../ethcore" }
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
ethcore-util = { path = "../util" }
|
||||
ethcore-ipc = { path = "../ipc/rpc" }
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
ethcrypto = { path = "../ethcrypto" }
|
||||
ethkey = { path = "../ethkey" }
|
||||
native-contracts = { path = "../ethcore/native_contracts" }
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
@ -14,38 +14,92 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
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};
|
||||
|
||||
const ACL_CHECKER_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_acl_checker";
|
||||
|
||||
/// ACL storage of Secret Store
|
||||
pub trait AclStorage: Send + Sync {
|
||||
/// Check if requestor with `public` key can access document with hash `document`
|
||||
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error>;
|
||||
}
|
||||
|
||||
/// Dummy ACL storage implementation
|
||||
#[derive(Default, Debug)]
|
||||
pub struct DummyAclStorage {
|
||||
prohibited: RwLock<HashMap<Public, HashSet<DocumentAddress>>>,
|
||||
/// On-chain ACL storage implementation.
|
||||
pub struct OnChainAclStorage {
|
||||
/// Blockchain client.
|
||||
client: Arc<Client>,
|
||||
/// On-chain contract.
|
||||
contract: Mutex<Option<SecretStoreAclStorage>>,
|
||||
}
|
||||
|
||||
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 OnChainAclStorage {
|
||||
pub fn new(client: Arc<Client>) -> Self {
|
||||
OnChainAclStorage {
|
||||
client: client,
|
||||
contract: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AclStorage for DummyAclStorage {
|
||||
impl AclStorage for OnChainAclStorage {
|
||||
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error> {
|
||||
Ok(self.prohibited.read()
|
||||
.get(public)
|
||||
.map(|docs| !docs.contains(document))
|
||||
.unwrap_or(true))
|
||||
let mut contract = self.contract.lock();
|
||||
if !contract.is_some() {
|
||||
*contract = self.client.registry_address(ACL_CHECKER_CONTRACT_REGISTRY_NAME.to_owned())
|
||||
.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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use ethcrypto;
|
||||
use ethkey::{self, Random, Generator};
|
||||
use acl_storage::DummyAclStorage;
|
||||
use acl_storage::tests::DummyAclStorage;
|
||||
use key_storage::tests::DummyKeyStorage;
|
||||
use types::all::{ClusterConfiguration, NodeAddress, EncryptionConfiguration, DocumentEncryptedKey, DocumentKey};
|
||||
use super::super::{RequestSignature, DocumentAddress};
|
||||
|
@ -220,7 +220,7 @@ impl SessionImpl {
|
||||
self.completed.notify_all();
|
||||
},
|
||||
// we can not decrypt data
|
||||
SessionState::Failed => (),
|
||||
SessionState::Failed => self.completed.notify_all(),
|
||||
// cannot reach other states
|
||||
_ => 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 =>
|
||||
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
|
||||
SessionState::Failed => Ok(()),
|
||||
SessionState::Failed => {
|
||||
self.completed.notify_all();
|
||||
Ok(())
|
||||
},
|
||||
// cannot reach other states
|
||||
_ => 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?
|
||||
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;
|
||||
}
|
||||
},
|
||||
@ -503,7 +507,7 @@ fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
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 key_server_cluster::{NodeId, DocumentKeyShare, SessionId, Error};
|
||||
use key_server_cluster::cluster::tests::DummyCluster;
|
||||
|
@ -21,7 +21,7 @@ use ethcrypto;
|
||||
use super::types::all::DocumentAddress;
|
||||
|
||||
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::serialization::{SerializableSignature, SerializableH256, SerializableSecret, SerializablePublic};
|
||||
pub use self::cluster::{ClusterCore, ClusterConfiguration, ClusterClient};
|
||||
@ -30,6 +30,8 @@ pub use self::decryption_session::Session as DecryptionSession;
|
||||
|
||||
#[cfg(test)]
|
||||
pub use super::key_storage::tests::DummyKeyStorage;
|
||||
#[cfg(test)]
|
||||
pub use super::acl_storage::tests::DummyAclStorage;
|
||||
|
||||
pub type SessionId = DocumentAddress;
|
||||
|
||||
@ -72,6 +74,8 @@ pub enum Error {
|
||||
Serde(String),
|
||||
/// Key storage error.
|
||||
KeyStorage(String),
|
||||
/// Acl storage error.
|
||||
AccessDenied,
|
||||
}
|
||||
|
||||
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::Serde(ref e) => write!(f, "serde error {}", e),
|
||||
Error::KeyStorage(ref e) => write!(f, "key storage error {}", e),
|
||||
Error::AccessDenied => write!(f, "Access denied"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,11 +32,14 @@ extern crate tokio_service;
|
||||
extern crate tokio_proto;
|
||||
extern crate url;
|
||||
|
||||
extern crate ethabi;
|
||||
extern crate ethcore;
|
||||
extern crate ethcore_devtools as devtools;
|
||||
extern crate ethcore_util as util;
|
||||
extern crate ethcore_ipc as ipc;
|
||||
extern crate ethcrypto;
|
||||
extern crate ethkey;
|
||||
extern crate native_contracts;
|
||||
|
||||
mod key_server_cluster;
|
||||
mod types;
|
||||
@ -52,15 +55,18 @@ mod key_server;
|
||||
mod key_storage;
|
||||
mod serialization;
|
||||
|
||||
use std::sync::Arc;
|
||||
use ethcore::client::Client;
|
||||
|
||||
pub use types::all::{DocumentAddress, DocumentKey, DocumentEncryptedKey, RequestSignature, Public,
|
||||
Error, NodeAddress, ServiceConfiguration, ClusterConfiguration, EncryptionConfiguration};
|
||||
pub use traits::{KeyServer};
|
||||
|
||||
/// 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;
|
||||
|
||||
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_server = key_server::KeyServerImpl::new(&config.cluster_config, acl_storage, key_storage)?;
|
||||
let listener = http_listener::KeyServerHttpListener::start(config, key_server)?;
|
||||
|
@ -119,7 +119,10 @@ impl From<ethkey::Error> for Error {
|
||||
|
||||
impl From<key_server_cluster::Error> for Error {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user