Merge branch 'block_header_rpc' into on-demand-priority

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

5
Cargo.lock generated
View File

@ -637,6 +637,8 @@ name = "ethcore-secretstore"
version = "1.0.0"
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)",
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,22 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#![allow(unused_mut, unused_variables, unused_imports)]
//! Secret store ACL storage contract.
// TODO: testing.
include!(concat!(env!("OUT_DIR"), "/secretstore_acl_storage.rs"));

View File

@ -1627,10 +1627,12 @@ impl ::client::ProvingBlockChainClient for Client {
}
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<Vec<DBValue>> {
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());

View File

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

View File

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

View File

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

View File

@ -0,0 +1,19 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default function () {
// empty file included while building parity.js (don't include local keygen)
}

View File

@ -14,31 +14,35 @@
// You should have received a copy of the GNU General Public License
// 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);
}

View File

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

View File

@ -0,0 +1,61 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
// Allow a web worker in the browser, with a fallback for Node.js
const hasWebWorkers = typeof Worker !== 'undefined';
const KeyWorker = hasWebWorkers ? require('worker-loader!./worker')
: require('./worker').KeyWorker;
class WorkerContainer {
busy = false;
_worker = new KeyWorker();
action (action, payload) {
if (this.busy) {
throw new Error('Cannot issue an action on a busy worker!');
}
this.busy = true;
return new Promise((resolve, reject) => {
this._worker.postMessage({ action, payload });
this._worker.onmessage = ({ data }) => {
this.busy = false;
resolve(data);
};
});
}
}
class WorkerPool {
pool = [];
getWorker () {
let container = this.pool.find((container) => !container.busy);
if (container) {
return container;
}
container = new WorkerContainer();
this.pool.push(container);
return container;
}
}
export default new WorkerPool();

View File

@ -19,7 +19,7 @@ import accounts from './accounts';
import transactions from './transactions';
import { 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]) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -67,7 +67,7 @@ impl IoHandler<ClientIoMessage> for QueueCull {
let (sync, on_demand, txq) = (self.sync.clone(), self.on_demand.clone(), self.txq.clone());
let 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)));

View File

@ -648,7 +648,9 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
let signer_server = signer::start(cmd.signer_conf.clone(), signing_queue, signer_deps)?;
// 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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,212 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Helpers for fetching blockchain data either from the light client or the network.
use std::sync::Arc;
use ethcore::basic_account::BasicAccount;
use ethcore::encoded;
use ethcore::executed::{Executed, ExecutionError};
use ethcore::ids::BlockId;
use ethcore::transaction::{Action, Transaction as EthTransaction};
use futures::{future, Future, BoxFuture};
use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use light::cache::Cache;
use light::client::LightChainClient;
use light::cht;
use light::on_demand::{OnDemand, request};
use ethsync::LightSync;
use util::{Address, Mutex, Uint, U256};
use v1::helpers::{CallRequest as CRequest, errors, dispatch};
use v1::types::{BlockNumber, CallRequest};
/// Helper for fetching blockchain data either from the light client or the network
/// as necessary.
pub struct LightFetch {
/// The light client.
pub client: Arc<LightChainClient>,
/// The on-demand request service.
pub on_demand: Arc<OnDemand>,
/// Handle to the network.
pub sync: Arc<LightSync>,
/// The light data cache.
pub cache: Arc<Mutex<Cache>>,
}
/// Type alias for convenience.
pub type ExecutionResult = Result<Executed, ExecutionError>;
impl LightFetch {
/// Get a block header from the on demand service or client, or error.
pub fn header(&self, id: BlockId) -> BoxFuture<encoded::Header, Error> {
if let Some(h) = self.client.block_header(id) {
return future::ok(h).boxed()
}
let maybe_future = match id {
BlockId::Number(n) => {
let cht_root = cht::block_to_cht_number(n).and_then(|cn| self.client.cht_root(cn as usize));
match cht_root {
None => return future::err(errors::unknown_block()).boxed(),
Some(root) => {
let req = request::HeaderProof::new(n, root)
.expect("only fails for 0; client always stores genesis; client already queried; qed");
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.sync.with_context(|ctx| {
let fut = self.on_demand.hash_by_number(ctx, req)
.map(request::HeaderByHash)
.map_err(errors::on_demand_cancel);
fut.and_then(move |req| {
match sync.with_context(|ctx| on_demand.header_by_hash(ctx, req)) {
Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
})
}
}
}
BlockId::Hash(h) => {
self.sync.with_context(|ctx|
self.on_demand.header_by_hash(ctx, request::HeaderByHash(h))
.then(|res| future::done(match res {
Ok(h) => Ok(h),
Err(e) => Err(errors::on_demand_cancel(e)),
}))
.boxed()
)
}
_ => None, // latest, earliest, and pending will have all already returned.
};
match maybe_future {
Some(recv) => recv,
None => future::err(errors::network_disabled()).boxed()
}
}
/// helper for getting account info at a given block.
/// `None` indicates the account doesn't exist at the given block.
pub fn account(&self, address: Address, id: BlockId) -> BoxFuture<Option<BasicAccount>, Error> {
let (sync, on_demand) = (self.sync.clone(), self.on_demand.clone());
self.header(id).and_then(move |header| {
let maybe_fut = sync.with_context(|ctx| on_demand.account(ctx, request::Account {
header: header,
address: address,
}));
match maybe_fut {
Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
/// helper for getting proved execution.
pub fn proved_execution(&self, req: CallRequest, num: Trailing<BlockNumber>) -> BoxFuture<ExecutionResult, Error> {
const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]);
let (sync, on_demand, client) = (self.sync.clone(), self.on_demand.clone(), self.client.clone());
let req: CRequest = req.into();
let id = num.0.into();
let from = req.from.unwrap_or(Address::zero());
let nonce_fut = match req.nonce {
Some(nonce) => future::ok(Some(nonce)).boxed(),
None => self.account(from, id).map(|acc| acc.map(|a| a.nonce)).boxed(),
};
let gas_price_fut = match req.gas_price {
Some(price) => future::ok(price).boxed(),
None => dispatch::fetch_gas_price_corpus(
self.sync.clone(),
self.client.clone(),
self.on_demand.clone(),
self.cache.clone(),
).map(|corp| match corp.median() {
Some(median) => *median,
None => DEFAULT_GAS_PRICE,
}).boxed()
};
// if nonce resolves, this should too since it'll be in the LRU-cache.
let header_fut = self.header(id);
// fetch missing transaction fields from the network.
nonce_fut.join(gas_price_fut).and_then(move |(nonce, gas_price)| {
let action = req.to.map_or(Action::Create, Action::Call);
let gas = req.gas.unwrap_or(U256::from(10_000_000)); // better gas amount?
let value = req.value.unwrap_or_else(U256::zero);
let data = req.data.map_or_else(Vec::new, |d| d.to_vec());
future::done(match nonce {
Some(n) => Ok(EthTransaction {
nonce: n,
action: action,
gas: gas,
gas_price: gas_price,
value: value,
data: data,
}.fake_sign(from)),
None => Err(errors::unknown_block()),
})
}).join(header_fut).and_then(move |(tx, hdr)| {
// then request proved execution.
// TODO: get last-hashes from network.
let env_info = match client.env_info(id) {
Some(env_info) => env_info,
_ => return future::err(errors::unknown_block()).boxed(),
};
let request = request::TransactionProof {
tx: tx,
header: hdr,
env_info: env_info,
engine: client.engine().clone(),
};
let proved_future = sync.with_context(move |ctx| {
on_demand.transaction_proof(ctx, request).map_err(errors::on_demand_cancel).boxed()
});
match proved_future {
Some(fut) => fut.boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
/// get a block itself. fails on unknown block ID.
pub fn block(&self, id: BlockId) -> BoxFuture<encoded::Block, Error> {
let (on_demand, sync) = (self.on_demand.clone(), self.sync.clone());
self.header(id).map(request::Body::new).and_then(move |req| {
match sync.with_context(move |ctx| on_demand.block(ctx, req)) {
Some(fut) => fut.map_err(errors::on_demand_cancel).boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}).boxed()
}
}

View File

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

View File

@ -146,7 +146,7 @@ impl<C, SN: ?Sized, S: ?Sized, M, EM> EthClient<C, SN, S, M, EM> where
(Some(block), Some(total_difficulty)) => {
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(),

View File

@ -48,6 +48,7 @@ use v1::impls::eth_filter::Filterable;
use v1::helpers::{CallRequest as CRequest, errors, limit_logs, dispatch};
use v1::helpers::{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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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