Merge branch 'master' of github.com:ethcore/parity into fixed_hash_util

This commit is contained in:
debris 2016-08-03 16:31:13 +02:00
commit cad7125acc
64 changed files with 896 additions and 672 deletions

14
Cargo.lock generated
View File

@ -279,7 +279,6 @@ dependencies = [
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -346,7 +345,6 @@ dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -354,7 +352,6 @@ name = "ethcore-ipc-nano"
version = "1.3.0"
dependencies = [
"ethcore-ipc 1.3.0",
"jsonrpc-core 2.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
]
@ -521,7 +518,6 @@ dependencies = [
"parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syntex 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -928,7 +924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "parity-dapps"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -942,7 +938,7 @@ dependencies = [
[[package]]
name = "parity-dapps-home"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)",
]
@ -950,7 +946,7 @@ dependencies = [
[[package]]
name = "parity-dapps-signer"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)",
]
@ -958,7 +954,7 @@ dependencies = [
[[package]]
name = "parity-dapps-status"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)",
]
@ -966,7 +962,7 @@ dependencies = [
[[package]]
name = "parity-dapps-wallet"
version = "0.6.0"
source = "git+https://github.com/ethcore/parity-ui.git#7120546d08d4d9eb648e255c04935002223d362f"
source = "git+https://github.com/ethcore/parity-ui.git#697e860dedc45003909602a002e7743478ab173a"
dependencies = [
"parity-dapps 0.6.0 (git+https://github.com/ethcore/parity-ui.git)",
]

View File

@ -57,8 +57,8 @@ default = ["ui", "use-precompiled-js"]
ui = ["dapps", "ethcore-signer/ui"]
use-precompiled-js = ["ethcore-dapps/use-precompiled-js", "ethcore-signer/use-precompiled-js"]
dapps = ["ethcore-dapps"]
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"]
ipc = ["ethcore/ipc"]
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"]
json-tests = ["ethcore/json-tests"]
[[bin]]

View File

@ -25,12 +25,6 @@ use std::mem;
use ipc::binary::BinaryConvertError;
use std::collections::{VecDeque, HashMap, BTreeMap};
impl From<String> for Error {
fn from(s: String) -> Error {
Error::RocksDb(s)
}
}
enum WriteCacheEntry {
Remove,
Write(Vec<u8>),

View File

@ -31,8 +31,8 @@ pub struct KeyValue {
pub value: Vec<u8>,
}
#[derive(Debug, Binary)]
pub enum Error {
#[derive(Debug, Binary)]
pub enum Error {
AlreadyOpen,
IsClosed,
RocksDb(String),
@ -41,6 +41,12 @@ pub struct KeyValue {
UncommitedTransactions,
}
impl From<String> for Error {
fn from(s: String) -> Error {
Error::RocksDb(s)
}
}
/// Database configuration
#[derive(Binary)]
pub struct DatabaseConfig {
@ -68,7 +74,7 @@ impl DatabaseConfig {
}
}
pub trait DatabaseService : Sized {
pub trait DatabaseService : Sized {
/// Opens database in the specified path
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error>;

View File

@ -8,7 +8,6 @@ authors = ["Ethcore <admin@ethcore.io>"]
build = "build.rs"
[build-dependencies]
syntex = "*"
"ethcore-ipc-codegen" = { path = "../ipc/codegen" }
[dependencies]
@ -35,7 +34,6 @@ ethcore-ipc = { path = "../ipc/rpc" }
ethstore = { path = "../ethstore" }
ethcore-ipc-nano = { path = "../ipc/nano" }
[dependencies.hyper]
git = "https://github.com/ethcore/hyper"
default-features = false

View File

@ -14,48 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate syntex;
extern crate ethcore_ipc_codegen as codegen;
use std::env;
use std::path::Path;
extern crate ethcore_ipc_codegen;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
// serialization pass
{
let src = Path::new("src/types/mod.rs.in");
let dst = Path::new(&out_dir).join("types.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
// blockchain client interface
{
let src = Path::new("src/client/traits.rs");
let intermediate = Path::new(&out_dir).join("traits.intermediate.rs.in");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &intermediate).unwrap();
let dst = Path::new(&out_dir).join("traits.ipc.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &intermediate, &dst).unwrap();
}
// chain notify interface
{
let src = Path::new("src/client/chain_notify.rs");
let intermediate = Path::new(&out_dir).join("chain_notify.intermediate.rs.in");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &intermediate).unwrap();
let dst = Path::new(&out_dir).join("chain_notify.ipc.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &intermediate, &dst).unwrap();
}
ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap();
ethcore_ipc_codegen::derive_ipc("src/client/traits.rs").unwrap();
ethcore_ipc_codegen::derive_ipc("src/client/chain_notify.rs").unwrap();
}

View File

@ -665,8 +665,8 @@ impl BlockChain {
let mut write_hashes = self.pending_block_hashes.write();
let mut write_txs = self.pending_transaction_addresses.write();
batch.extend_with_cache(DB_COL_EXTRA, &mut *write_hashes, update.block_hashes, CacheUpdatePolicy::Remove);
batch.extend_with_cache(DB_COL_EXTRA, &mut *write_txs, update.transactions_addresses, CacheUpdatePolicy::Remove);
batch.extend_with_cache(DB_COL_EXTRA, &mut *write_hashes, update.block_hashes, CacheUpdatePolicy::Overwrite);
batch.extend_with_cache(DB_COL_EXTRA, &mut *write_txs, update.transactions_addresses, CacheUpdatePolicy::Overwrite);
}
}
@ -1111,6 +1111,7 @@ mod tests {
let ir3b = bc.insert_block(&batch, &b3b, vec![]);
bc.commit();
db.write(batch).unwrap();
assert_eq!(bc.block_hash(3).unwrap(), b3b_hash);
let batch = db.transaction();
let ir3a = bc.insert_block(&batch, &b3a, vec![]);
bc.commit();

View File

@ -178,15 +178,15 @@ impl Client {
db_config.compaction = config.db_compaction.compaction_profile();
db_config.wal = config.db_wal;
let db = Arc::new(Database::open(&db_config, &path.to_str().unwrap()).expect("Error opening database"));
let db = Arc::new(try!(Database::open(&db_config, &path.to_str().unwrap()).map_err(ClientError::Database)));
let chain = Arc::new(BlockChain::new(config.blockchain, &gb, db.clone()));
let tracedb = Arc::new(try!(TraceDB::new(config.tracing, db.clone(), chain.clone())));
let mut state_db = journaldb::new(db.clone(), config.pruning, DB_COL_STATE);
if state_db.is_empty() && spec.ensure_db_good(state_db.as_hashdb_mut()) {
let batch = DBTransaction::new(&db);
state_db.commit(&batch, 0, &spec.genesis_header().hash(), None).expect("Error commiting genesis state to state DB");
db.write(batch).expect("Error writing genesis state to state DB");
try!(state_db.commit(&batch, 0, &spec.genesis_header().hash(), None));
try!(db.write(batch).map_err(ClientError::Database));
}
if !chain.block_header(&chain.best_block_hash()).map_or(true, |h| state_db.contains(h.state_root())) {

View File

@ -1,4 +1,5 @@
use trace::Error as TraceError;
use util::UtilError;
use std::fmt::{Display, Formatter, Error as FmtError};
/// Client configuration errors.
@ -6,6 +7,10 @@ use std::fmt::{Display, Formatter, Error as FmtError};
pub enum Error {
/// TraceDB configuration error.
Trace(TraceError),
/// Database error
Database(String),
/// Util error
Util(UtilError),
}
impl From<TraceError> for Error {
@ -14,10 +19,18 @@ impl From<TraceError> for Error {
}
}
impl From<UtilError> for Error {
fn from(err: UtilError) -> Self {
Error::Util(err)
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match *self {
Error::Trace(ref err) => write!(f, "{}", err)
Error::Trace(ref err) => write!(f, "{}", err),
Error::Util(ref err) => write!(f, "{}", err),
Error::Database(ref s) => write!(f, "Database error: {}", s),
}
}
}

View File

@ -40,13 +40,13 @@ pub use self::traits::{BlockChainClient, MiningBlockChainClient, RemoteClient};
mod traits {
#![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/traits.ipc.rs"));
include!(concat!(env!("OUT_DIR"), "/traits.rs"));
}
pub mod chain_notify {
//! Chain notify interface
#![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/chain_notify.ipc.rs"));
include!(concat!(env!("OUT_DIR"), "/chain_notify.rs"));
}

View File

@ -257,7 +257,7 @@ pub fn get_temp_journal_db() -> GuardedTempResult<Box<JournalDB>> {
}
impl MiningBlockChainClient for TestBlockChainClient {
fn prepare_open_block(&self, _author: Address, _gas_range_target: (U256, U256), _extra_data: Bytes) -> OpenBlock {
fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock {
let engine = &self.spec.engine;
let genesis_header = self.spec.genesis_header();
let mut db_result = get_temp_journal_db();
@ -265,7 +265,7 @@ impl MiningBlockChainClient for TestBlockChainClient {
self.spec.ensure_db_good(db.as_hashdb_mut());
let last_hashes = vec![genesis_header.hash()];
OpenBlock::new(
let mut open_block = OpenBlock::new(
engine.deref(),
self.vm_factory(),
Default::default(),
@ -273,10 +273,13 @@ impl MiningBlockChainClient for TestBlockChainClient {
db,
&genesis_header,
last_hashes,
Address::zero(),
(3141562.into(), 31415620.into()),
vec![]
).expect("Opening block for tests will not fail.")
author,
gas_range_target,
extra_data
).expect("Opening block for tests will not fail.");
// TODO [todr] Override timestamp for predictability (set_timestamp_now kind of sucks)
open_block.set_timestamp(10_000_000);
open_block
}
fn vm_factory(&self) -> &EvmFactory {

View File

@ -16,7 +16,8 @@
use std::collections::HashMap;
use std::sync::Arc;
use util::{RwLock, U256, H256};
use std::time::{Instant, Duration};
use util::{Mutex, U256, H256};
/// External miner interface.
pub trait ExternalMinerService: Send + Sync {
@ -25,50 +26,50 @@ pub trait ExternalMinerService: Send + Sync {
/// Total hashrate.
fn hashrate(&self) -> U256;
/// Returns true if external miner is mining.
fn is_mining(&self) -> bool;
}
/// External Miner.
pub struct ExternalMiner {
hashrates: Arc<RwLock<HashMap<H256, U256>>>,
hashrates: Arc<Mutex<HashMap<H256, (Instant, U256)>>>,
}
impl Default for ExternalMiner {
fn default() -> Self {
ExternalMiner {
hashrates: Arc::new(RwLock::new(HashMap::new())),
hashrates: Arc::new(Mutex::new(HashMap::new())),
}
}
}
impl ExternalMiner {
/// Creates new external miner with prefilled hashrates.
pub fn new(hashrates: Arc<RwLock<HashMap<H256, U256>>>) -> Self {
pub fn new(hashrates: Arc<Mutex<HashMap<H256, (Instant, U256)>>>) -> Self {
ExternalMiner {
hashrates: hashrates
hashrates: hashrates,
}
}
}
const ENTRY_TIMEOUT: u64 = 2;
impl ExternalMinerService for ExternalMiner {
fn submit_hashrate(&self, hashrate: U256, id: H256) {
self.hashrates.write().insert(id, hashrate);
self.hashrates.lock().insert(id, (Instant::now() + Duration::from_secs(ENTRY_TIMEOUT), hashrate));
}
fn hashrate(&self) -> U256 {
self.hashrates.read().iter().fold(U256::from(0), |sum, (_, v)| sum + *v)
}
fn is_mining(&self) -> bool {
!self.hashrates.read().is_empty()
let mut hashrates = self.hashrates.lock();
let h = hashrates.drain().filter(|&(_, (t, _))| t > Instant::now()).collect();
*hashrates = h;
hashrates.iter().fold(U256::from(0), |sum, (_, &(_, v))| sum + v)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::sleep;
use std::time::Duration;
use util::{H256, U256};
fn miner() -> ExternalMiner {
@ -76,16 +77,18 @@ mod tests {
}
#[test]
fn should_return_that_is_mining_if_there_is_at_least_one_entry() {
fn it_should_forget_old_hashrates() {
// given
let m = miner();
assert_eq!(m.is_mining(), false);
assert_eq!(m.hashrate(), U256::from(0));
m.submit_hashrate(U256::from(10), H256::from(1));
assert_eq!(m.hashrate(), U256::from(10));
// when
m.submit_hashrate(U256::from(10), H256::from(1));
sleep(Duration::from_secs(3));
// then
assert_eq!(m.is_mining(), true);
assert_eq!(m.hashrate(), U256::from(0));
}
#[test]

View File

@ -780,6 +780,10 @@ impl MinerService for Miner {
}
}
fn is_sealing(&self) -> bool {
self.sealing_work.lock().queue.is_in_use()
}
fn map_sealing_work<F, T>(&self, chain: &MiningBlockChainClient, f: F) -> Option<T> where F: FnOnce(&ClosedBlock) -> T {
trace!(target: "miner", "map_sealing_work: entering");
self.enable_and_prepare_sealing(chain);

View File

@ -150,6 +150,9 @@ pub trait MinerService : Send + Sync {
/// Returns highest transaction nonce for given address.
fn last_nonce(&self, address: &Address) -> Option<U256>;
/// Is it currently sealing?
fn is_sealing(&self) -> bool;
/// Suggested gas price.
fn sensible_gas_price(&self) -> U256 { 20000000000u64.into() }

View File

@ -17,4 +17,4 @@
//! Types used in the public api
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/types.rs"));
include!(concat!(env!("OUT_DIR"), "/mod.rs.in"));

View File

@ -138,7 +138,7 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
let ok = store.change_password(&address, &old_pwd, &new_pwd).is_ok();
Ok(format!("{}", ok))
} else if args.cmd_list {
let accounts = store.accounts();
let accounts = try!(store.accounts());
Ok(format_accounts(&accounts))
} else if args.cmd_import {
let src = try!(key_dir(&args.flag_src));

View File

@ -68,7 +68,7 @@ pub mod aes {
use rcrypto::blockmodes::{CtrMode, CbcDecryptor, PkcsPadding};
use rcrypto::aessafe::{AesSafe128Encryptor, AesSafe128Decryptor};
use rcrypto::symmetriccipher::{Encryptor, Decryptor, SymmetricCipherError};
use rcrypto::buffer::{RefReadBuffer, RefWriteBuffer};
use rcrypto::buffer::{RefReadBuffer, RefWriteBuffer, WriteBuffer};
/// Encrypt a message
pub fn encrypt(k: &[u8], iv: &[u8], plain: &[u8], dest: &mut [u8]) {
@ -83,10 +83,12 @@ pub mod aes {
}
/// Decrypt a message using cbc mode
pub fn decrypt_cbc(k: &[u8], iv: &[u8], encrypted: &[u8], dest: &mut [u8]) -> Result<(), SymmetricCipherError> {
pub fn decrypt_cbc(k: &[u8], iv: &[u8], encrypted: &[u8], dest: &mut [u8]) -> Result<usize, SymmetricCipherError> {
let mut encryptor = CbcDecryptor::new(AesSafe128Decryptor::new(k), PkcsPadding, iv.to_vec());
try!(encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut RefWriteBuffer::new(dest), true));
Ok(())
let len = dest.len();
let mut buffer = RefWriteBuffer::new(dest);
try!(encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut buffer, true));
Ok(len - buffer.remaining())
}
}

View File

@ -109,4 +109,3 @@ macro_rules! impl_hash {
impl_hash!(H128, 16);
impl_hash!(H160, 20);
impl_hash!(H256, 32);
impl_hash!(H768, 96);

View File

@ -11,10 +11,10 @@ mod version;
pub use self::cipher::{Cipher, CipherSer, CipherSerParams, Aes128Ctr};
pub use self::crypto::Crypto;
pub use self::error::Error;
pub use self::hash::{H128, H160, H256, H768};
pub use self::hash::{H128, H160, H256};
pub use self::id::UUID;
pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams};
pub use self::key_file::KeyFile;
pub use self::presale::PresaleWallet;
pub use self::presale::{PresaleWallet, Encseed};
pub use self::version::Version;

View File

@ -1,10 +1,34 @@
use std::io::Read;
use std::ops::Deref;
use serde_json;
use super::{H160, H768};
use serde::{Deserialize, Deserializer, Error};
use rustc_serialize::hex::FromHex;
use super::{H160};
#[derive(Debug, PartialEq)]
pub struct Encseed(Vec<u8>);
impl Deref for Encseed {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Deserialize for Encseed {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer
{
let s = try!(String::deserialize(deserializer));
let data = try!(s.from_hex().map_err(|e| Error::custom(format!("Invalid hex value {}", e))));
Ok(Encseed(data))
}
}
#[derive(Debug, PartialEq, Deserialize)]
pub struct PresaleWallet {
pub encseed: H768,
pub encseed: Encseed,
#[serde(rename = "ethaddr")]
pub address: H160,
}
@ -19,7 +43,8 @@ impl PresaleWallet {
mod tests {
use std::str::FromStr;
use serde_json;
use json::{PresaleWallet, H160, H768};
use rustc_serialize::hex::FromHex;
use json::{PresaleWallet, H160, Encseed};
#[test]
fn presale_wallet() {
@ -32,7 +57,27 @@ mod tests {
} "#;
let expected = PresaleWallet {
encseed: H768::from_str("137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066").unwrap(),
encseed: Encseed("137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066".from_hex().unwrap()),
address: H160::from_str("ede84640d1a1d3e06902048e67aa7db8d52c2ce1").unwrap(),
};
let wallet: PresaleWallet = serde_json::from_str(json).unwrap();
assert_eq!(expected, wallet);
}
#[test]
fn long_presale_wallet() {
let json = r#"
{
"encseed":
"137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0d",
"ethaddr": "ede84640d1a1d3e06902048e67aa7db8d52c2ce1",
"email": "123@gmail.com",
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
} "#;
let expected = PresaleWallet {
encseed: Encseed("137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0dcf0d".from_hex().unwrap()),
address: H160::from_str("ede84640d1a1d3e06902048e67aa7db8d52c2ce1").unwrap(),
};

View File

@ -10,7 +10,7 @@ use {crypto, Error};
pub struct PresaleWallet {
iv: [u8; 16],
ciphertext: [u8; 80],
ciphertext: Vec<u8>,
address: Address,
}
@ -19,8 +19,8 @@ impl From<json::PresaleWallet> for PresaleWallet {
let mut iv = [0u8; 16];
iv.copy_from_slice(&wallet.encseed[..16]);
let mut ciphertext = [0u8; 80];
ciphertext.copy_from_slice(&wallet.encseed[16..]);
let mut ciphertext = vec![];
ciphertext.extend_from_slice(&wallet.encseed[16..]);
PresaleWallet {
iv: iv,
@ -42,10 +42,11 @@ impl PresaleWallet {
let mut derived_key = vec![0u8; 16];
pbkdf2(&mut h_mac, password.as_bytes(), 2000, &mut derived_key);
let mut key = [0u8; 64];
try!(crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword));
let mut key = vec![0; self.ciphertext.len()];
let len = try!(crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword));
let unpadded = &key[..len];
let secret = Secret::from(key.keccak256());
let secret = Secret::from(unpadded.keccak256());
if let Ok(kp) = KeyPair::from_secret(secret) {
if kp.address() == self.address {
return Ok(kp)

View File

@ -95,3 +95,58 @@ pub fn register(reg: &mut rustc_plugin::Registry) {
reg.register_attribute("ipc".to_owned(), AttributeType::Normal);
}
#[derive(Debug)]
pub enum Error { InvalidFileName, ExpandFailure }
pub fn derive_ipc(src_path: &str) -> Result<(), Error> {
use std::env;
use std::path::{Path, PathBuf};
let out_dir = env::var_os("OUT_DIR").unwrap();
let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned()));
let mut intermediate_file_name = file_name.clone();
intermediate_file_name.push_str(".rpc.in");
let intermediate_path = Path::new(&out_dir).join(&intermediate_file_name);
let final_path = Path::new(&out_dir).join(&file_name);
{
let mut registry = syntex::Registry::new();
register(&mut registry);
if let Err(_) = registry.expand("", &Path::new(src_path), &intermediate_path) {
// will be reported by compiler
return Err(Error::ExpandFailure)
}
}
{
let mut registry = syntex::Registry::new();
register(&mut registry);
if let Err(_) = registry.expand("", &intermediate_path, &final_path) {
// will be reported by compiler
return Err(Error::ExpandFailure)
}
}
Ok(())
}
pub fn derive_binary(src_path: &str) -> Result<(), Error> {
use std::env;
use std::path::{Path, PathBuf};
let out_dir = env::var_os("OUT_DIR").unwrap();
let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned()));
let final_path = Path::new(&out_dir).join(&file_name);
let mut registry = syntex::Registry::new();
register(&mut registry);
if let Err(_) = registry.expand("", &Path::new(src_path), &final_path) {
// will be reported by compiler
return Err(Error::ExpandFailure)
}
Ok(())
}

View File

@ -15,5 +15,4 @@ semver = "0.2"
log = "0.3"
[build-dependencies]
syntex = "*"
ethcore-ipc-codegen = { path = "../codegen" }

View File

@ -14,30 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate syntex;
extern crate ethcore_ipc_codegen as codegen;
use std::env;
use std::path::Path;
extern crate ethcore_ipc_codegen;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
// ipc pass
{
let src = Path::new("src/service.rs.in");
let dst = Path::new(&out_dir).join("hypervisor_service_ipc.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
// serialization pass
{
let src = Path::new(&out_dir).join("hypervisor_service_ipc.rs");
let dst = Path::new(&out_dir).join("hypervisor_service_cg.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
ethcore_ipc_codegen::derive_ipc("src/service.rs.in").unwrap();
}

View File

@ -17,4 +17,4 @@
//! Parity interprocess hypervisor IPC service
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/hypervisor_service_cg.rs"));
include!(concat!(env!("OUT_DIR"), "/service.rs.in"));

View File

@ -7,7 +7,6 @@ license = "GPL-3.0"
[features]
[dependencies]
jsonrpc-core = "2.0"
ethcore-ipc = { path = "../rpc" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
log = "0.3"

View File

@ -19,14 +19,11 @@
extern crate ethcore_ipc as ipc;
extern crate nanomsg;
#[macro_use] extern crate log;
extern crate jsonrpc_core;
use jsonrpc_core::IoHandler;
pub use ipc::{WithSocket, IpcInterface, IpcConfig};
pub use nanomsg::Socket as NanoSocket;
use std::sync::*;
use std::sync::atomic::*;
use nanomsg::{Socket, Protocol, Error, Endpoint, PollRequest, PollFd, PollInOut};
use std::ops::Deref;
@ -218,149 +215,14 @@ impl<S: ?Sized> Worker<S> where S: IpcInterface {
}
}
/// Error in handling JSON RPC request
pub enum IoHandlerError {
BadRequest,
HandlerError,
}
/// Worker to handle JSON RPC requests
pub struct IoHandlerWorker {
handler: Arc<IoHandler>,
socket: Socket,
_endpoint: Endpoint,
poll: Vec<PollFd>,
buf: Vec<u8>,
}
/// IPC server for json-rpc handler (single thread)
pub struct IoHandlerServer {
is_stopping: Arc<AtomicBool>,
is_stopped: Arc<AtomicBool>,
handler: Arc<IoHandler>,
socket_addr: String,
}
impl IoHandlerServer {
/// New IPC server for JSON RPC `handler` and ipc socket address `socket_addr`
pub fn new(handler: &Arc<IoHandler>, socket_addr: &str) -> IoHandlerServer {
IoHandlerServer {
handler: handler.clone(),
is_stopping: Arc::new(AtomicBool::new(false)),
is_stopped: Arc::new(AtomicBool::new(true)),
socket_addr: socket_addr.to_owned(),
}
}
/// IPC Server starts (non-blocking, in seprate thread)
pub fn start(&self) -> Result<(), SocketError> {
let mut worker = try!(IoHandlerWorker::new(&self.handler, &self.socket_addr));
self.is_stopping.store(false, Ordering::Relaxed);
let worker_is_stopping = self.is_stopping.clone();
let worker_is_stopped = self.is_stopped.clone();
::std::thread::spawn(move || {
worker_is_stopped.store(false, Ordering::Relaxed);
while !worker_is_stopping.load(Ordering::Relaxed) {
worker.poll()
}
worker_is_stopped.store(true, Ordering::Relaxed);
});
Ok(())
}
/// IPC server stop (func will wait until effective stop)
pub fn stop(&self) {
self.is_stopping.store(true, Ordering::Relaxed);
while !self.is_stopped.load(Ordering::Relaxed) {
std::thread::sleep(std::time::Duration::from_millis(50));
}
}
}
impl Drop for IoHandlerServer {
fn drop(&mut self) {
self.stop()
}
}
impl IoHandlerWorker {
pub fn new(handler: &Arc<IoHandler>, socket_addr: &str) -> Result<IoHandlerWorker, SocketError> {
let mut socket = try!(Socket::new(Protocol::Rep).map_err(|e| {
warn!(target: "ipc", "Failed to create ipc socket: {:?}", e);
SocketError::RequestLink
}));
let endpoint = try!(socket.bind(socket_addr).map_err(|e| {
warn!(target: "ipc", "Failed to bind socket to address '{}': {:?}", socket_addr, e);
SocketError::RequestLink
}));
let poll = vec![socket.new_pollfd(PollInOut::In)];
Ok(IoHandlerWorker {
handler: handler.clone(),
socket: socket,
_endpoint: endpoint,
poll: poll,
buf: Vec::with_capacity(1024),
})
}
pub fn poll(&mut self) {
let mut request = PollRequest::new(&mut self.poll[..]);
let _result_guard = Socket::poll(&mut request, POLL_TIMEOUT);
let fd = request.get_fds()[0]; // guaranteed to exist and be the only one
// because contains only immutable socket field as a member
if !fd.can_read() {
return;
}
unsafe { self.buf.set_len(0); }
match self.socket.nb_read_to_end(&mut self.buf) {
Ok(0) => {
warn!(target: "ipc", "RPC empty message received");
return;
},
Ok(_) => {
let rpc_msg = match String::from_utf8(self.buf.clone()) {
Ok(val) => val,
Err(e) => {
warn!(target: "ipc", "RPC decoding error (utf-8): {:?}", e);
return;
}
};
let response: Option<String> = self.handler.handle_request(&rpc_msg);
if let Some(response_str) = response {
let response_bytes = response_str.into_bytes();
if let Err(e) = self.socket.nb_write(&response_bytes) {
warn!(target: "ipc", "Failed to write response: {:?}", e);
}
}
},
Err(Error::TryAgain) => {
// no data
},
Err(x) => {
warn!(target: "ipc", "Error polling connections {:?}", x);
panic!("IPC RPC fatal error");
},
}
}
}
#[cfg(test)]
mod service_tests {
use super::{Worker, IoHandlerServer};
use super::Worker;
use ipc::*;
use std::io::{Read, Write};
use std::sync::{Arc, RwLock};
use nanomsg::{Socket, Protocol, Endpoint};
use jsonrpc_core;
use jsonrpc_core::{IoHandler, Value, Params, MethodCommand};
struct TestInvoke {
method_num: u16,
@ -400,15 +262,6 @@ mod service_tests {
(socket, endpoint)
}
fn dummy_request(addr: &str, buf: &[u8]) -> Vec<u8> {
let mut socket = Socket::new(Protocol::Req).unwrap();
let _endpoint = socket.connect(addr).unwrap();
socket.write(buf).unwrap();
let mut buf = Vec::new();
socket.read_to_end(&mut buf).unwrap();
buf
}
#[test]
fn can_create_worker() {
let worker = Worker::<DummyService>::new(&Arc::new(DummyService::new()));
@ -462,29 +315,4 @@ mod service_tests {
assert_eq!(0, worker.service.methods_stack.read().unwrap()[0].method_num);
assert_eq!(vec![0u8; 1024*1024-2], worker.service.methods_stack.read().unwrap()[0].params);
}
#[test]
fn test_jsonrpc_handler() {
let url = "ipc:///tmp/parity-test50.ipc";
struct SayHello;
impl MethodCommand for SayHello {
fn execute(&self, _params: Params) -> Result<Value, jsonrpc_core::Error> {
Ok(Value::String("hello".to_string()))
}
}
let io = Arc::new(IoHandler::new());
io.add_method("say_hello", SayHello);
let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":"hello","id":1}"#;
let server = IoHandlerServer::new(&io, url);
server.start().unwrap();
assert_eq!(String::from_utf8(dummy_request(url, request.as_bytes())).unwrap(), response.to_string());
server.stop();
}
}

View File

@ -16,4 +16,4 @@
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/binary.rs"));
include!(concat!(env!("OUT_DIR"), "/binary.rs.in"));

View File

@ -14,76 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate syntex;
extern crate ethcore_ipc_codegen as codegen;
use std::env;
use std::path::Path;
use std::process::exit;
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
// rpc pass
if {
let src = Path::new("nested.rs.in");
let dst = Path::new(&out_dir).join("nested_ipc.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).is_ok()
}
// serialization pass
{
let src = Path::new(&out_dir).join("nested_ipc.rs");
let dst = Path::new(&out_dir).join("nested_cg.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
// rpc pass
if {
let src = Path::new("service.rs.in");
let dst = Path::new(&out_dir).join("service_ipc.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).is_ok()
}
// serialization pass
{
let src = Path::new(&out_dir).join("service_ipc.rs");
let dst = Path::new(&out_dir).join("service_cg.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
// rpc pass
if {
let src = Path::new("with_attrs.rs.in");
let dst = Path::new(&out_dir).join("with_attrs_ipc.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).is_ok()
}
// serialization pass
{
let src = Path::new(&out_dir).join("with_attrs_ipc.rs");
let dst = Path::new(&out_dir).join("with_attrs_cg.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &dst).unwrap();
}
// rpc pass
{
let src = Path::new("binary.rs.in");
let dst = Path::new(&out_dir).join("binary.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
if let Err(err_msg) = registry.expand("", &src, &dst) {
println!("error: {}", err_msg);
exit(1);
}
}
codegen::derive_ipc("nested.rs.in").unwrap();
codegen::derive_ipc("service.rs.in").unwrap();
codegen::derive_ipc("with_attrs.rs.in").unwrap();
codegen::derive_binary("binary.rs.in").unwrap();
}

View File

@ -15,4 +15,4 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/nested_cg.rs"));
include!(concat!(env!("OUT_DIR"), "/nested.rs.in"));

View File

@ -15,4 +15,4 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/service_cg.rs"));
include!(concat!(env!("OUT_DIR"), "/service.rs.in"));

View File

@ -15,4 +15,4 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/with_attrs_cg.rs"));
include!(concat!(env!("OUT_DIR"), "/with_attrs.rs.in"));

View File

@ -74,6 +74,9 @@ Account Options:
[default: 8180].
--signer-path PATH Specify directory where Signer UIs tokens should
be stored. [default: $HOME/.parity/signer]
--signer-no-validation Disable Origin and Host headers validation for
Trusted Signer. WARNING: INSECURE. Used only for
development.
Networking Options:
--no-network Disable p2p networking.
@ -337,6 +340,7 @@ pub struct Args {
pub flag_no_signer: bool,
pub flag_signer_port: u16,
pub flag_signer_path: String,
pub flag_signer_no_validation: bool,
pub flag_force_sealing: bool,
pub flag_reseal_on_txs: String,
pub flag_reseal_min_period: u64,

View File

@ -303,6 +303,7 @@ impl Configuration {
enabled: self.signer_enabled(),
port: self.args.flag_signer_port,
signer_path: self.directories().signer,
skip_origin_validation: self.args.flag_signer_no_validation,
}
}
@ -789,6 +790,19 @@ mod tests {
assert_eq!(conf0.signer_enabled(), false);
}
#[test]
fn should_parse_signer_allow_all_flag() {
// given
// when
let conf0 = parse(&["parity", "--signer-no-validation"]);
let conf1 = parse(&["parity"]);
// then
assert_eq!(conf0.args.flag_signer_no_validation, true);
assert_eq!(conf1.args.flag_signer_no_validation, false);
}
#[test]
fn should_not_bail_on_empty_line_in_reserved_peers() {
let temp = RandomTempPath::new();

View File

@ -102,7 +102,7 @@ pub fn to_address(s: Option<String>) -> Result<Address, String> {
pub fn to_addresses(s: &Option<String>) -> Result<Vec<Address>, String> {
match *s {
Some(ref adds) if adds.is_empty() => adds.split(',')
Some(ref adds) if !adds.is_empty() => adds.split(',')
.map(|a| clean_0x(a).parse().map_err(|_| format!("Invalid address: {:?}", a)))
.collect(),
_ => Ok(Vec::new()),
@ -299,7 +299,7 @@ mod tests {
use util::{U256};
use ethcore::client::{Mode, BlockID};
use ethcore::miner::PendingSet;
use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_price, geth_ipc_path, to_bootnodes};
use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_addresses, to_price, geth_ipc_path, to_bootnodes};
#[test]
fn test_to_duration() {
@ -370,6 +370,18 @@ mod tests {
assert_eq!(to_address(None).unwrap(), Default::default());
}
#[test]
fn test_to_addresses() {
let addresses = to_addresses(&Some("0xD9A111feda3f362f55Ef1744347CDC8Dd9964a41,D9A111feda3f362f55Ef1744347CDC8Dd9964a42".into())).unwrap();
assert_eq!(
addresses,
vec![
"D9A111feda3f362f55Ef1744347CDC8Dd9964a41".parse().unwrap(),
"D9A111feda3f362f55Ef1744347CDC8Dd9964a42".parse().unwrap(),
]
);
}
#[test]
#[cfg_attr(feature = "dev", allow(float_cmp))]
fn test_to_price() {

View File

@ -104,8 +104,8 @@ pub struct Dependencies {
pub external_miner: Arc<ExternalMiner>,
pub logger: Arc<RotatingLogger>,
pub settings: Arc<NetworkSettings>,
pub allow_pending_receipt_query: bool,
pub net_service: Arc<ManageNetwork>,
pub geth_compatibility: bool,
}
fn to_modules(apis: &[Api]) -> BTreeMap<String, String> {
@ -163,7 +163,10 @@ pub fn setup_rpc<T: Extendable>(server: T, deps: Arc<Dependencies>, apis: ApiSet
&deps.secret_store,
&deps.miner,
&deps.external_miner,
deps.allow_pending_receipt_query
EthClientOptions {
allow_pending_receipt_query: !deps.geth_compatibility,
send_block_number_in_get_work: !deps.geth_compatibility,
}
);
server.add_delegate(client.to_delegate());

View File

@ -211,8 +211,8 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> {
external_miner: external_miner.clone(),
logger: logger.clone(),
settings: Arc::new(cmd.net_settings.clone()),
allow_pending_receipt_query: !cmd.geth_compatibility,
net_service: manage_network.clone()
net_service: manage_network.clone(),
geth_compatibility: cmd.geth_compatibility,
});
let dependencies = rpc::Dependencies {
@ -311,7 +311,7 @@ fn prepare_account_provider(dirs: &Directories, cfg: AccountsConfig) -> Result<A
for a in cfg.unlocked_accounts {
if passwords.iter().find(|p| account_service.unlock_account_permanently(a, (*p).clone()).is_ok()).is_none() {
return Err(format!("No password given to unlock account {}. Pass the password using `--password`.", a));
return Err(format!("No password found to unlock account {}. Make sure valid password is present in files passed using `--password`.", a));
}
}

View File

@ -32,6 +32,7 @@ pub struct Configuration {
pub enabled: bool,
pub port: u16,
pub signer_path: String,
pub skip_origin_validation: bool,
}
impl Default for Configuration {
@ -40,6 +41,7 @@ impl Default for Configuration {
enabled: true,
port: 8180,
signer_path: replace_home("$HOME/.parity/signer"),
skip_origin_validation: false,
}
}
}
@ -89,6 +91,11 @@ fn do_start(conf: Configuration, deps: Dependencies) -> Result<SignerServer, Str
deps.apis.signer_queue.clone(),
codes_path(conf.signer_path),
);
if conf.skip_origin_validation {
warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Trusted Signer with no origin validation."));
info!("If you do not intend this, exit now.");
}
let server = server.skip_origin_validation(conf.skip_origin_validation);
let server = rpc_apis::setup_rpc(server, deps.apis, rpc_apis::ApiSet::SafeContext);
server.start(addr)
};

View File

@ -21,5 +21,5 @@ mod signing_queue;
pub use self::poll_manager::PollManager;
pub use self::poll_filter::PollFilter;
pub use self::requests::{TransactionRequest, TransactionConfirmation, CallRequest};
pub use self::requests::{TransactionRequest, FilledTransactionRequest, ConfirmationRequest, ConfirmationPayload, CallRequest};
pub use self::signing_queue::{ConfirmationsQueue, ConfirmationPromise, ConfirmationResult, SigningQueue, QueueEvent};

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/>.
use util::{Address, U256};
use util::{Address, U256, Bytes, H256};
/// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
@ -30,18 +30,42 @@ pub struct TransactionRequest {
/// Value of transaction in wei
pub value: Option<U256>,
/// Additional data sent with transaction
pub data: Option<Vec<u8>>,
pub data: Option<Bytes>,
/// Transaction's nonce
pub nonce: Option<U256>,
}
/// Transaction confirmation waiting in a queue
/// Transaction request coming from RPC with default values filled in.
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
pub struct TransactionConfirmation {
/// Id of this confirmation
pub id: U256,
/// TransactionRequest
pub transaction: TransactionRequest,
pub struct FilledTransactionRequest {
/// Sender
pub from: Address,
/// Recipient
pub to: Option<Address>,
/// Gas Price
pub gas_price: U256,
/// Gas
pub gas: U256,
/// Value of transaction in wei
pub value: U256,
/// Additional data sent with transaction
pub data: Bytes,
/// Transaction's nonce
pub nonce: Option<U256>,
}
impl From<FilledTransactionRequest> for TransactionRequest {
fn from(r: FilledTransactionRequest) -> Self {
TransactionRequest {
from: r.from,
to: r.to,
gas_price: Some(r.gas_price),
gas: Some(r.gas),
value: Some(r.value),
data: Some(r.data),
nonce: r.nonce,
}
}
}
/// Call request
@ -62,3 +86,21 @@ pub struct CallRequest {
/// Nonce
pub nonce: Option<U256>,
}
/// Confirmation object
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ConfirmationRequest {
/// Id of this confirmation
pub id: U256,
/// Payload to confirm
pub payload: ConfirmationPayload,
}
/// Payload to confirm in Trusted Signer
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ConfirmationPayload {
/// Transaction
Transaction(FilledTransactionRequest),
/// Sign request
Sign(Address, H256),
}

View File

@ -17,10 +17,10 @@
use std::thread;
use std::time::{Instant, Duration};
use std::sync::{mpsc, Arc};
use std::collections::HashMap;
use std::collections::BTreeMap;
use jsonrpc_core;
use util::{Mutex, RwLock, U256};
use v1::helpers::{TransactionRequest, TransactionConfirmation};
use v1::helpers::{ConfirmationRequest, ConfirmationPayload};
/// Result that can be returned from JSON RPC.
pub type RpcResult = Result<jsonrpc_core::Value, jsonrpc_core::Error>;
@ -54,41 +54,41 @@ pub type QueueEventReceiver = mpsc::Receiver<QueueEvent>;
pub trait SigningQueue: Send + Sync {
/// Add new request to the queue.
/// Returns a `ConfirmationPromise` that can be used to await for resolution of given request.
fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise;
fn add_request(&self, request: ConfirmationPayload) -> ConfirmationPromise;
/// Removes a request from the queue.
/// Notifies possible token holders that transaction was rejected.
fn request_rejected(&self, id: U256) -> Option<TransactionConfirmation>;
/// Notifies possible token holders that request was rejected.
fn request_rejected(&self, id: U256) -> Option<ConfirmationRequest>;
/// Removes a request from the queue.
/// Notifies possible token holders that transaction was confirmed and given hash was assigned.
fn request_confirmed(&self, id: U256, result: RpcResult) -> Option<TransactionConfirmation>;
/// Notifies possible token holders that request was confirmed and given hash was assigned.
fn request_confirmed(&self, id: U256, result: RpcResult) -> Option<ConfirmationRequest>;
/// Returns a request if it is contained in the queue.
fn peek(&self, id: &U256) -> Option<TransactionConfirmation>;
fn peek(&self, id: &U256) -> Option<ConfirmationRequest>;
/// Return copy of all the requests in the queue.
fn requests(&self) -> Vec<TransactionConfirmation>;
fn requests(&self) -> Vec<ConfirmationRequest>;
/// Returns number of transactions awaiting confirmation.
/// Returns number of requests awaiting confirmation.
fn len(&self) -> usize;
/// Returns true if there are no transactions awaiting confirmation.
/// Returns true if there are no requests awaiting confirmation.
fn is_empty(&self) -> bool;
}
#[derive(Debug, Clone, PartialEq)]
/// Result of a pending transaction.
/// Result of a pending confirmation request.
pub enum ConfirmationResult {
/// The transaction has not yet been confirmed nor rejected.
/// The request has not yet been confirmed nor rejected.
Waiting,
/// The transaction has been rejected.
/// The request has been rejected.
Rejected,
/// The transaction has been confirmed.
/// The request has been confirmed.
Confirmed(RpcResult),
}
/// Time you need to confirm the transaction in UI.
/// Time you need to confirm the request in UI.
/// This is the amount of time token holder will wait before
/// returning `None`.
/// Unless we have a multi-threaded RPC this will lock
@ -100,12 +100,14 @@ const QUEUE_TIMEOUT_DURATION_SEC : u64 = 20;
pub struct ConfirmationToken {
result: Arc<Mutex<ConfirmationResult>>,
handle: thread::Thread,
request: TransactionConfirmation,
request: ConfirmationRequest,
timeout: Duration,
}
pub struct ConfirmationPromise {
id: U256,
result: Arc<Mutex<ConfirmationResult>>,
timeout: Duration,
}
impl ConfirmationToken {
@ -121,6 +123,7 @@ impl ConfirmationToken {
ConfirmationPromise {
id: self.request.id,
result: self.result.clone(),
timeout: self.timeout,
}
}
}
@ -134,8 +137,7 @@ impl ConfirmationPromise {
/// Returns `None` if transaction was rejected or timeout reached.
/// Returns `Some(result)` if transaction was confirmed.
pub fn wait_with_timeout(&self) -> Option<RpcResult> {
let timeout = Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC);
let res = self.wait_until(Instant::now() + timeout);
let res = self.wait_until(Instant::now() + self.timeout);
match res {
ConfirmationResult::Confirmed(h) => Some(h),
ConfirmationResult::Rejected | ConfirmationResult::Waiting => None,
@ -146,11 +148,11 @@ impl ConfirmationPromise {
pub fn result(&self) -> ConfirmationResult { self.wait_until(Instant::now()) }
/// Blocks current thread and awaits for
/// resolution of the transaction (rejected / confirmed)
/// Returns `None` if transaction was rejected or timeout reached.
/// Returns `Some(result)` if transaction was confirmed.
/// resolution of the request (rejected / confirmed)
/// Returns `None` if request was rejected or timeout reached.
/// Returns `Some(result)` if request was confirmed.
pub fn wait_until(&self, deadline: Instant) -> ConfirmationResult {
trace!(target: "own_tx", "Signer: Awaiting transaction confirmation... ({:?}).", self.id);
trace!(target: "own_tx", "Signer: Awaiting confirmation... ({:?}).", self.id);
loop {
let now = Instant::now();
// Check the result...
@ -166,12 +168,13 @@ impl ConfirmationPromise {
}
}
/// Queue for all unconfirmed transactions.
/// Queue for all unconfirmed requests.
pub struct ConfirmationsQueue {
id: Mutex<U256>,
queue: RwLock<HashMap<U256, ConfirmationToken>>,
queue: RwLock<BTreeMap<U256, ConfirmationToken>>,
sender: Mutex<mpsc::Sender<QueueEvent>>,
receiver: Mutex<Option<mpsc::Receiver<QueueEvent>>>,
timeout: Duration,
}
impl Default for ConfirmationsQueue {
@ -180,14 +183,23 @@ impl Default for ConfirmationsQueue {
ConfirmationsQueue {
id: Mutex::new(U256::from(0)),
queue: RwLock::new(HashMap::new()),
queue: RwLock::new(BTreeMap::new()),
sender: Mutex::new(send),
receiver: Mutex::new(Some(recv)),
timeout: Duration::from_secs(QUEUE_TIMEOUT_DURATION_SEC),
}
}
}
impl ConfirmationsQueue {
#[cfg(test)]
/// Creates new confirmations queue with specified timeout
pub fn with_timeout(timeout: Duration) -> Self {
let mut queue = Self::default();
queue.timeout = timeout;
queue
}
/// Blocks the thread and starts listening for notifications regarding all actions in the queue.
/// For each event, `listener` callback will be invoked.
/// This method can be used only once (only single consumer of events can exist).
@ -221,9 +233,9 @@ impl ConfirmationsQueue {
let _ = self.sender.lock().send(message);
}
/// Removes transaction from this queue and notifies `ConfirmationPromise` holders about the result.
/// Removes requests from this queue and notifies `ConfirmationPromise` holders about the result.
/// Notifies also a receiver about that event.
fn remove(&self, id: U256, result: Option<RpcResult>) -> Option<TransactionConfirmation> {
fn remove(&self, id: U256, result: Option<RpcResult>) -> Option<ConfirmationRequest> {
let token = self.queue.write().remove(&id);
if let Some(token) = token {
@ -248,7 +260,7 @@ impl Drop for ConfirmationsQueue {
}
impl SigningQueue for ConfirmationsQueue {
fn add_request(&self, transaction: TransactionRequest) -> ConfirmationPromise {
fn add_request(&self, request: ConfirmationPayload) -> ConfirmationPromise {
// Increment id
let id = {
let mut last_id = self.id.lock();
@ -257,16 +269,19 @@ impl SigningQueue for ConfirmationsQueue {
};
// Add request to queue
let res = {
debug!(target: "own_tx", "Signer: New entry ({:?}) in confirmation queue.", id);
trace!(target: "own_tx", "Signer: ({:?}) : {:?}", id, request);
let mut queue = self.queue.write();
queue.insert(id, ConfirmationToken {
result: Arc::new(Mutex::new(ConfirmationResult::Waiting)),
handle: thread::current(),
request: TransactionConfirmation {
request: ConfirmationRequest {
id: id,
transaction: transaction,
payload: request,
},
timeout: self.timeout,
});
debug!(target: "own_tx", "Signer: New transaction ({:?}) in confirmation queue.", id);
queue.get(&id).map(|token| token.as_promise()).expect("Token was just inserted.")
};
// Notify listeners
@ -275,21 +290,21 @@ impl SigningQueue for ConfirmationsQueue {
}
fn peek(&self, id: &U256) -> Option<TransactionConfirmation> {
fn peek(&self, id: &U256) -> Option<ConfirmationRequest> {
self.queue.read().get(id).map(|token| token.request.clone())
}
fn request_rejected(&self, id: U256) -> Option<TransactionConfirmation> {
debug!(target: "own_tx", "Signer: Transaction rejected ({:?}).", id);
fn request_rejected(&self, id: U256) -> Option<ConfirmationRequest> {
debug!(target: "own_tx", "Signer: Request rejected ({:?}).", id);
self.remove(id, None)
}
fn request_confirmed(&self, id: U256, result: RpcResult) -> Option<TransactionConfirmation> {
fn request_confirmed(&self, id: U256, result: RpcResult) -> Option<ConfirmationRequest> {
debug!(target: "own_tx", "Signer: Transaction confirmed ({:?}).", id);
self.remove(id, Some(result))
}
fn requests(&self) -> Vec<TransactionConfirmation> {
fn requests(&self) -> Vec<ConfirmationRequest> {
let queue = self.queue.read();
queue.values().map(|token| token.request.clone()).collect()
}
@ -312,20 +327,20 @@ mod test {
use std::thread;
use std::sync::Arc;
use util::{Address, U256, H256, Mutex};
use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, TransactionRequest};
use v1::helpers::{SigningQueue, ConfirmationsQueue, QueueEvent, FilledTransactionRequest, ConfirmationPayload};
use v1::types::H256 as NH256;
use jsonrpc_core::to_value;
fn request() -> TransactionRequest {
TransactionRequest {
fn request() -> ConfirmationPayload {
ConfirmationPayload::Transaction(FilledTransactionRequest {
from: Address::from(1),
to: Some(Address::from(2)),
gas_price: None,
gas: None,
value: Some(U256::from(10_000_000)),
data: None,
gas_price: 0.into(),
gas: 10_000.into(),
value: 10_000_000.into(),
data: vec![],
nonce: None,
}
})
}
#[test]
@ -391,6 +406,6 @@ mod test {
assert_eq!(all.len(), 1);
let el = all.get(0).unwrap();
assert_eq!(el.id, U256::from(1));
assert_eq!(el.transaction, request);
assert_eq!(el.payload, request);
}
}

View File

@ -47,6 +47,23 @@ use v1::helpers::CallRequest as CRequest;
use v1::impls::{default_gas_price, dispatch_transaction, error_codes};
use serde;
/// Eth RPC options
pub struct EthClientOptions {
/// Returns receipt from pending blocks
pub allow_pending_receipt_query: bool,
/// Send additional block number when asking for work
pub send_block_number_in_get_work: bool,
}
impl Default for EthClientOptions {
fn default() -> Self {
EthClientOptions {
allow_pending_receipt_query: true,
send_block_number_in_get_work: true,
}
}
}
/// Eth rpc implementation.
pub struct EthClient<C, S: ?Sized, M, EM> where
C: MiningBlockChainClient,
@ -60,7 +77,7 @@ pub struct EthClient<C, S: ?Sized, M, EM> where
miner: Weak<M>,
external_miner: Arc<EM>,
seed_compute: Mutex<SeedHashCompute>,
allow_pending_receipt_query: bool,
options: EthClientOptions,
}
impl<C, S: ?Sized, M, EM> EthClient<C, S, M, EM> where
@ -70,7 +87,7 @@ impl<C, S: ?Sized, M, EM> EthClient<C, S, M, EM> where
EM: ExternalMinerService {
/// Creates new EthClient.
pub fn new(client: &Arc<C>, sync: &Arc<S>, accounts: &Arc<AccountProvider>, miner: &Arc<M>, em: &Arc<EM>, allow_pending_receipt_query: bool)
pub fn new(client: &Arc<C>, sync: &Arc<S>, accounts: &Arc<AccountProvider>, miner: &Arc<M>, em: &Arc<EM>, options: EthClientOptions)
-> EthClient<C, S, M, EM> {
EthClient {
client: Arc::downgrade(client),
@ -79,7 +96,7 @@ impl<C, S: ?Sized, M, EM> EthClient<C, S, M, EM> where
accounts: Arc::downgrade(accounts),
external_miner: em.clone(),
seed_compute: Mutex::new(SeedHashCompute::new()),
allow_pending_receipt_query: allow_pending_receipt_query,
options: options,
}
}
@ -316,7 +333,7 @@ impl<C, S: ?Sized, M, EM> Eth for EthClient<C, S, M, EM> where
fn is_mining(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
match params {
Params::None => to_value(&self.external_miner.is_mining()),
Params::None => to_value(&(take_weak!(self.miner).is_sealing())),
_ => Err(Error::invalid_params())
}
}
@ -496,7 +513,7 @@ impl<C, S: ?Sized, M, EM> Eth for EthClient<C, S, M, EM> where
let miner = take_weak!(self.miner);
let hash: H256 = hash.into();
match miner.pending_receipts().get(&hash) {
Some(receipt) if self.allow_pending_receipt_query => to_value(&Receipt::from(receipt.clone())),
Some(receipt) if self.options.allow_pending_receipt_query => to_value(&Receipt::from(receipt.clone())),
_ => {
let client = take_weak!(self.client);
let receipt = client.transaction_receipt(TransactionID::Hash(hash));
@ -582,8 +599,13 @@ impl<C, S: ?Sized, M, EM> Eth for EthClient<C, S, M, EM> where
let pow_hash = b.hash();
let target = Ethash::difficulty_to_boundary(b.block().header().difficulty());
let seed_hash = self.seed_compute.lock().get_seedhash(b.block().header().number());
let block_number = RpcU256::from(b.block().header().number());
to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target), block_number))
if self.options.send_block_number_in_get_work {
let block_number = RpcU256::from(b.block().header().number());
to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target), block_number))
} else {
to_value(&(RpcH256::from(pow_hash), RpcH256::from(seed_hash), RpcH256::from(target)))
}
}).unwrap_or(Err(Error::internal_error())) // no work found.
},
_ => Err(Error::invalid_params())

View File

@ -23,24 +23,21 @@ use ethcore::client::MiningBlockChainClient;
use util::{U256, Address, H256, Mutex};
use transient_hashmap::TransientHashMap;
use ethcore::account_provider::AccountProvider;
use v1::helpers::{SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, TransactionRequest as TRequest};
use v1::helpers::{SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationsQueue, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest};
use v1::traits::EthSigning;
use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, U256 as RpcU256};
use v1::impls::{default_gas_price, sign_and_dispatch, transaction_rejected_error};
use v1::impls::{default_gas_price, sign_and_dispatch, transaction_rejected_error, signer_disabled_error};
fn fill_optional_fields<C, M>(request: &mut TRequest, client: &C, miner: &M)
fn fill_optional_fields<C, M>(request: TRequest, client: &C, miner: &M) -> FilledRequest
where C: MiningBlockChainClient, M: MinerService {
if request.value.is_none() {
request.value = Some(U256::from(0));
}
if request.gas.is_none() {
request.gas = Some(miner.sensible_gas_limit());
}
if request.gas_price.is_none() {
request.gas_price = Some(default_gas_price(client, miner));
}
if request.data.is_none() {
request.data = Some(Vec::new());
FilledRequest {
from: request.from,
to: request.to,
nonce: request.nonce,
gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)),
gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()),
value: request.value.unwrap_or_else(|| 0.into()),
data: request.data.unwrap_or_else(Vec::new),
}
}
@ -74,10 +71,26 @@ impl<C, M> EthSigningQueueClient<C, M> where C: MiningBlockChainClient, M: Miner
Ok(())
}
fn dispatch<F: FnOnce(ConfirmationPromise) -> Result<Value, Error>>(&self, params: Params, f: F) -> Result<Value, Error> {
fn dispatch_sign<F: FnOnce(ConfirmationPromise) -> Result<Value, Error>>(&self, params: Params, f: F) -> Result<Value, Error> {
from_params::<(RpcH160, RpcH256)>(params).and_then(|(address, msg)| {
let address: Address = address.into();
let msg: H256 = msg.into();
let accounts = take_weak!(self.accounts);
if accounts.is_unlocked(address) {
return to_value(&accounts.sign(address, msg).ok().map_or_else(RpcH520::default, Into::into));
}
let queue = take_weak!(self.queue);
let promise = queue.add_request(ConfirmationPayload::Sign(address, msg));
f(promise)
})
}
fn dispatch_transaction<F: FnOnce(ConfirmationPromise) -> Result<Value, Error>>(&self, params: Params, f: F) -> Result<Value, Error> {
from_params::<(TransactionRequest, )>(params)
.and_then(|(request, )| {
let mut request: TRequest = request.into();
let request: TRequest = request.into();
let accounts = take_weak!(self.accounts);
let (client, miner) = (take_weak!(self.client), take_weak!(self.miner));
@ -87,8 +100,8 @@ impl<C, M> EthSigningQueueClient<C, M> where C: MiningBlockChainClient, M: Miner
}
let queue = take_weak!(self.queue);
fill_optional_fields(&mut request, &*client, &*miner);
let promise = queue.add_request(request);
let request = fill_optional_fields(request, &*client, &*miner);
let promise = queue.add_request(ConfirmationPayload::Transaction(request));
f(promise)
})
}
@ -98,23 +111,32 @@ impl<C, M> EthSigning for EthSigningQueueClient<C, M>
where C: MiningBlockChainClient + 'static, M: MinerService + 'static
{
fn sign(&self, _params: Params) -> Result<Value, Error> {
fn sign(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
warn!("Invoking eth_sign is not yet supported with signer enabled.");
// TODO [ToDr] Implement sign when rest of the signing queue is ready.
rpc_unimplemented!()
self.dispatch_sign(params, |promise| {
promise.wait_with_timeout().unwrap_or_else(|| to_value(&RpcH520::default()))
})
}
fn post_sign(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
self.dispatch_sign(params, |promise| {
let id = promise.id();
self.pending.lock().insert(id, promise);
to_value(&RpcU256::from(id))
})
}
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
self.dispatch(params, |promise| {
self.dispatch_transaction(params, |promise| {
promise.wait_with_timeout().unwrap_or_else(|| to_value(&RpcH256::default()))
})
}
fn post_transaction(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
self.dispatch(params, |promise| {
self.dispatch_transaction(params, |promise| {
let id = promise.id();
self.pending.lock().insert(id, promise);
to_value(&RpcU256::from(id))
@ -193,13 +215,18 @@ impl<C, M> EthSigning for EthSigningUnsafeClient<C, M> where
})
}
fn post_sign(&self, _: Params) -> Result<Value, Error> {
// We don't support this in non-signer mode.
Err(signer_disabled_error())
}
fn post_transaction(&self, _: Params) -> Result<Value, Error> {
// We don't support this in non-signer mode.
Err(Error::invalid_params())
Err(signer_disabled_error())
}
fn check_transaction(&self, _: Params) -> Result<Value, Error> {
// We don't support this in non-signer mode.
Err(Error::invalid_params())
Err(signer_disabled_error())
}
}

View File

@ -27,7 +27,7 @@ use ethcore::miner::MinerService;
use v1::traits::Ethcore;
use v1::types::{Bytes, U256};
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use v1::impls::error_codes;
use v1::impls::signer_disabled_error;
/// Ethcore implementation.
pub struct EthcoreClient<C, M> where
@ -152,11 +152,7 @@ impl<C, M> Ethcore for EthcoreClient<C, M> where M: MinerService + 'static, C: M
fn unsigned_transactions_count(&self, _params: Params) -> Result<Value, Error> {
try!(self.active());
match self.confirmations_queue {
None => Err(Error {
code: ErrorCode::ServerError(error_codes::SIGNER_DISABLED),
message: "Trusted Signer is disabled. This API is not available.".into(),
data: None
}),
None => Err(signer_disabled_error()),
Some(ref queue) => to_value(&queue.len()),
}
}

View File

@ -42,7 +42,7 @@ mod traces;
mod rpc;
pub use self::web3::Web3Client;
pub use self::eth::EthClient;
pub use self::eth::{EthClient, EthClientOptions};
pub use self::eth_filter::EthFilterClient;
pub use self::eth_signing::{EthSigningUnsafeClient, EthSigningQueueClient};
pub use self::net::NetClient;
@ -54,7 +54,7 @@ pub use self::traces::TracesClient;
pub use self::rpc::RpcClient;
use v1::helpers::TransactionRequest;
use v1::types::H256 as NH256;
use v1::types::{H256 as RpcH256, H520 as RpcH520};
use ethcore::error::Error as EthcoreError;
use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient;
@ -80,7 +80,7 @@ mod error_codes {
fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result<Value, Error>
where C: MiningBlockChainClient, M: MinerService {
let hash = NH256::from(signed_transaction.hash());
let hash = RpcH256::from(signed_transaction.hash());
let import = miner.import_own_transaction(client, signed_transaction);
@ -89,6 +89,12 @@ fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedT
.and_then(|_| to_value(&hash))
}
fn signature_with_password(accounts: &AccountProvider, address: Address, hash: H256, pass: String) -> Result<Value, Error> {
accounts.sign_with_password(address, pass, hash)
.map_err(password_error)
.and_then(|hash| to_value(&RpcH520::from(hash)))
}
fn prepare_transaction<C, M>(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService {
Transaction {
nonce: request.nonce
@ -105,9 +111,10 @@ fn prepare_transaction<C, M>(client: &C, miner: &M, request: TransactionRequest)
}
}
fn unlock_sign_and_dispatch<C, M>(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address, password: String) -> Result<Value, Error>
fn unlock_sign_and_dispatch<C, M>(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, password: String) -> Result<Value, Error>
where C: MiningBlockChainClient, M: MinerService {
let address = request.from;
let signed_transaction = {
let t = prepare_transaction(client, miner, request);
let hash = t.hash();
@ -140,6 +147,14 @@ fn default_gas_price<C, M>(client: &C, miner: &M) -> U256 where C: MiningBlockCh
.unwrap_or_else(|_| miner.sensible_gas_price())
}
fn signer_disabled_error() -> Error {
Error {
code: ErrorCode::ServerError(error_codes::SIGNER_DISABLED),
message: "Trusted Signer is disabled. This API is not available.".into(),
data: None
}
}
fn signing_error(error: AccountError) -> Error {
Error {
code: ErrorCode::ServerError(error_codes::ACCOUNT_LOCKED),

View File

@ -105,10 +105,9 @@ impl<C: 'static, M: 'static> Personal for PersonalClient<C, M> where C: MiningBl
from_params::<(TransactionRequest, String)>(params)
.and_then(|(request, password)| {
let request: TRequest = request.into();
let sender = request.from;
let accounts = take_weak!(self.accounts);
unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, sender, password)
unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, password)
})
}

View File

@ -22,9 +22,9 @@ use ethcore::account_provider::AccountProvider;
use ethcore::client::MiningBlockChainClient;
use ethcore::miner::MinerService;
use v1::traits::PersonalSigner;
use v1::types::{TransactionModification, TransactionConfirmation, U256};
use v1::impls::unlock_sign_and_dispatch;
use v1::helpers::{SigningQueue, ConfirmationsQueue};
use v1::types::{TransactionModification, ConfirmationRequest, U256};
use v1::impls::{unlock_sign_and_dispatch, signature_with_password};
use v1::helpers::{SigningQueue, ConfirmationsQueue, ConfirmationPayload};
/// Transactions confirmation (personal) rpc implementation.
pub struct SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
@ -55,14 +55,16 @@ impl<C: 'static, M: 'static> SignerClient<C, M> where C: MiningBlockChainClient,
impl<C: 'static, M: 'static> PersonalSigner for SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
fn transactions_to_confirm(&self, _params: Params) -> Result<Value, Error> {
fn requests_to_confirm(&self, _params: Params) -> Result<Value, Error> {
try!(self.active());
let queue = take_weak!(self.queue);
to_value(&queue.requests().into_iter().map(From::from).collect::<Vec<TransactionConfirmation>>())
to_value(&queue.requests().into_iter().map(From::from).collect::<Vec<ConfirmationRequest>>())
}
fn confirm_transaction(&self, params: Params) -> Result<Value, Error> {
fn confirm_request(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
// TODO [ToDr] TransactionModification is redundant for some calls
// might be better to replace it in future
from_params::<(U256, TransactionModification, String)>(params).and_then(
|(id, modification, pass)| {
let id = id.into();
@ -70,17 +72,23 @@ impl<C: 'static, M: 'static> PersonalSigner for SignerClient<C, M> where C: Mini
let queue = take_weak!(self.queue);
let client = take_weak!(self.client);
let miner = take_weak!(self.miner);
queue.peek(&id).map(|confirmation| {
let mut request = confirmation.transaction;
// apply modification
if let Some(gas_price) = modification.gas_price {
request.gas_price = Some(gas_price.into());
}
let sender = request.from;
let result = unlock_sign_and_dispatch(&*client, &*miner, request, &*accounts, sender, pass);
if let Ok(ref hash) = result {
queue.request_confirmed(id, Ok(hash.clone()));
queue.peek(&id).map(|confirmation| {
let result = match confirmation.payload {
ConfirmationPayload::Transaction(mut request) => {
// apply modification
if let Some(gas_price) = modification.gas_price {
request.gas_price = gas_price.into();
}
unlock_sign_and_dispatch(&*client, &*miner, request.into(), &*accounts, pass)
},
ConfirmationPayload::Sign(address, hash) => {
signature_with_password(&*accounts, address, hash, pass)
}
};
if let Ok(ref response) = result {
queue.request_confirmed(id, Ok(response.clone()));
}
result
}).unwrap_or_else(|| Err(Error::invalid_params()))
@ -88,7 +96,7 @@ impl<C: 'static, M: 'static> PersonalSigner for SignerClient<C, M> where C: Mini
)
}
fn reject_transaction(&self, params: Params) -> Result<Value, Error> {
fn reject_request(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
from_params::<(U256, )>(params).and_then(
|(id, )| {

View File

@ -121,7 +121,7 @@ impl EthTester {
&account_provider,
&miner_service,
&external_miner,
true
Default::default(),
);
let eth_sign = EthSigningUnsafeClient::new(
&client,

View File

@ -181,8 +181,9 @@ impl MinerService for TestMinerService {
unimplemented!();
}
fn map_sealing_work<F, T>(&self, _chain: &MiningBlockChainClient, _f: F) -> Option<T> where F: FnOnce(&ClosedBlock) -> T {
None
fn map_sealing_work<F, T>(&self, chain: &MiningBlockChainClient, f: F) -> Option<T> where F: FnOnce(&ClosedBlock) -> T {
let open_block = chain.prepare_open_block(self.author(), *self.gas_range_target.write(), self.extra_data());
Some(f(&open_block.close()))
}
fn transaction(&self, hash: &H256) -> Option<SignedTransaction> {
@ -205,6 +206,10 @@ impl MinerService for TestMinerService {
self.last_nonces.read().get(address).cloned()
}
fn is_sealing(&self) -> bool {
false
}
/// Submit `seal` as a valid solution for the header of `pow_hash`.
/// Will check the seal, but not actually insert the block into the chain.
fn submit_seal(&self, _chain: &MiningBlockChainClient, _pow_hash: H256, _seal: Vec<Bytes>) -> Result<(), Error> {

View File

@ -17,10 +17,11 @@
use std::str::FromStr;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Instant, Duration};
use jsonrpc_core::IoHandler;
use util::hash::{Address, H256, FixedHash};
use util::numbers::{Uint, U256};
use util::RwLock;
use util::Mutex;
use ethcore::account_provider::AccountProvider;
use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionID};
use ethcore::log_entry::{LocalizedLogEntry, LogEntry};
@ -28,7 +29,7 @@ use ethcore::receipt::LocalizedReceipt;
use ethcore::transaction::{Transaction, Action};
use ethcore::miner::{ExternalMiner, MinerService};
use ethsync::SyncState;
use v1::{Eth, EthClient, EthSigning, EthSigningUnsafeClient};
use v1::{Eth, EthClient, EthClientOptions, EthSigning, EthSigningUnsafeClient};
use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService};
use rustc_serialize::hex::ToHex;
@ -57,19 +58,25 @@ struct EthTester {
pub sync: Arc<TestSyncProvider>,
pub accounts_provider: Arc<AccountProvider>,
pub miner: Arc<TestMinerService>,
hashrates: Arc<RwLock<HashMap<H256, U256>>>,
hashrates: Arc<Mutex<HashMap<H256, (Instant, U256)>>>,
pub io: IoHandler,
}
impl Default for EthTester {
fn default() -> Self {
Self::new_with_options(Default::default())
}
}
impl EthTester {
pub fn new_with_options(options: EthClientOptions) -> Self {
let client = blockchain_client();
let sync = sync_provider();
let ap = accounts_provider();
let miner = miner_service();
let hashrates = Arc::new(RwLock::new(HashMap::new()));
let hashrates = Arc::new(Mutex::new(HashMap::new()));
let external_miner = Arc::new(ExternalMiner::new(hashrates.clone()));
let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner, true).to_delegate();
let eth = EthClient::new(&client, &sync, &ap, &miner, &external_miner, options).to_delegate();
let sign = EthSigningUnsafeClient::new(&client, &ap, &miner).to_delegate();
let io = IoHandler::new();
io.add_delegate(eth);
@ -133,9 +140,9 @@ fn rpc_eth_syncing() {
#[test]
fn rpc_eth_hashrate() {
let tester = EthTester::default();
tester.hashrates.write().insert(H256::from(0), U256::from(0xfffa));
tester.hashrates.write().insert(H256::from(0), U256::from(0xfffb));
tester.hashrates.write().insert(H256::from(1), U256::from(0x1));
tester.hashrates.lock().insert(H256::from(0), (Instant::now() + Duration::from_secs(2), U256::from(0xfffa)));
tester.hashrates.lock().insert(H256::from(0), (Instant::now() + Duration::from_secs(2), U256::from(0xfffb)));
tester.hashrates.lock().insert(H256::from(1), (Instant::now() + Duration::from_secs(2), U256::from(0x1)));
let request = r#"{"jsonrpc": "2.0", "method": "eth_hashrate", "params": [], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":"0xfffc","id":1}"#;
@ -158,8 +165,8 @@ fn rpc_eth_submit_hashrate() {
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request(request), Some(response.to_owned()));
assert_eq!(tester.hashrates.read().get(&H256::from("0x59daa26581d0acd1fce254fb7e85952f4c09d0915afd33d3886cd914bc7d283c")).cloned(),
Some(U256::from(0x500_000)));
assert_eq!(tester.hashrates.lock().get(&H256::from("0x59daa26581d0acd1fce254fb7e85952f4c09d0915afd33d3886cd914bc7d283c")).cloned().unwrap().1,
U256::from(0x500_000));
}
#[test]
@ -210,16 +217,11 @@ fn rpc_eth_author() {
#[test]
fn rpc_eth_mining() {
let tester = EthTester::default();
tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap());
let request = r#"{"jsonrpc": "2.0", "method": "eth_mining", "params": [], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":false,"id":1}"#;
assert_eq!(tester.io.handle_request(request), Some(response.to_owned()));
tester.hashrates.write().insert(H256::from(1), U256::from(0x1));
let request = r#"{"jsonrpc": "2.0", "method": "eth_mining", "params": [], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request(request), Some(response.to_owned()));
}
#[test]
@ -794,15 +796,26 @@ fn returns_no_work_if_cant_mine() {
}
#[test]
fn returns_error_if_can_mine_and_no_closed_block() {
use ethsync::{SyncState};
fn returns_correct_work_package() {
let eth_tester = EthTester::default();
eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap());
eth_tester.sync.status.write().state = SyncState::Idle;
let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":null},"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":["0x3bbe93f74e7b97ae00784aeff8819c5cb600dd87e8b282a5d3446f3f871f0347","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000800000000000000000000000000000000000000000000000000000000000","0x01"],"id":1}"#;
assert_eq!(eth_tester.io.handle_request(request), Some(response.to_owned()));
}
#[test]
fn should_not_return_block_number() {
let eth_tester = EthTester::new_with_options(EthClientOptions {
allow_pending_receipt_query: true,
send_block_number_in_get_work: false,
});
eth_tester.miner.set_author(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap());
let request = r#"{"jsonrpc": "2.0", "method": "eth_getWork", "params": [], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":["0x3bbe93f74e7b97ae00784aeff8819c5cb600dd87e8b282a5d3446f3f871f0347","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000800000000000000000000000000000000000000000000000000000000000"],"id":1}"#;
assert_eq!(eth_tester.io.handle_request(request), Some(response.to_owned()));
}

View File

@ -16,13 +16,14 @@
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use jsonrpc_core::IoHandler;
use v1::impls::EthSigningQueueClient;
use v1::traits::EthSigning;
use v1::helpers::{ConfirmationsQueue, SigningQueue};
use v1::tests::helpers::TestMinerService;
use util::{Address, FixedHash};
use util::numbers::{Uint, U256};
use util::numbers::{Uint, U256, H256};
use ethcore::account_provider::AccountProvider;
use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action};
@ -37,7 +38,7 @@ struct EthSigningTester {
impl Default for EthSigningTester {
fn default() -> Self {
let queue = Arc::new(ConfirmationsQueue::default());
let queue = Arc::new(ConfirmationsQueue::with_timeout(Duration::from_millis(1)));
let client = Arc::new(TestBlockChainClient::default());
let miner = Arc::new(TestMinerService::default());
let accounts = Arc::new(AccountProvider::transient_provider());
@ -58,6 +59,78 @@ fn eth_signing() -> EthSigningTester {
EthSigningTester::default()
}
#[test]
fn should_add_sign_to_queue() {
// given
let tester = eth_signing();
let address = Address::random();
assert_eq!(tester.queue.requests().len(), 0);
// when
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_sign",
"params": [
""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"0x0000000000000000000000000000000000000000000000000000000000000005"
],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);
}
#[test]
fn should_post_sign_to_queue() {
// given
let tester = eth_signing();
let address = Address::random();
assert_eq!(tester.queue.requests().len(), 0);
// when
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_postSign",
"params": [
""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
"0x0000000000000000000000000000000000000000000000000000000000000005"
],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x01","id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);
}
#[test]
fn should_sign_if_account_is_unlocked() {
// given
let tester = eth_signing();
let hash: H256 = 5.into();
let acc = tester.accounts.new_account("test").unwrap();
tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap();
let signature = tester.accounts.sign(acc, hash).unwrap();
// when
let request = r#"{
"jsonrpc": "2.0",
"method": "eth_sign",
"params": [
""#.to_owned() + format!("0x{:?}", acc).as_ref() + r#"",
""# + format!("0x{:?}", hash).as_ref() + r#""
],
"id": 1
}"#;
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", signature).as_ref() + r#"","id":1}"#;
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 0);
}
#[test]
fn should_add_transaction_to_queue() {
@ -81,7 +154,6 @@ fn should_add_transaction_to_queue() {
}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);

View File

@ -23,7 +23,7 @@ use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action};
use v1::{SignerClient, PersonalSigner};
use v1::tests::helpers::TestMinerService;
use v1::helpers::{SigningQueue, ConfirmationsQueue, TransactionRequest};
use v1::helpers::{SigningQueue, ConfirmationsQueue, FilledTransactionRequest, ConfirmationPayload};
struct PersonalSignerTester {
queue: Arc<ConfirmationsQueue>,
@ -68,22 +68,28 @@ fn signer_tester() -> PersonalSignerTester {
#[test]
fn should_return_list_of_transactions_in_queue() {
fn should_return_list_of_items_to_confirm() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest {
from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
gas_price: U256::from(10_000),
gas: U256::from(10_000_000),
value: U256::from(1),
data: vec![],
nonce: None,
});
}));
tester.queue.add_request(ConfirmationPayload::Sign(1.into(), 5.into()));
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_transactionsToConfirm","params":[],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":[{"id":"0x01","transaction":{"data":null,"from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}],"id":1}"#;
let request = r#"{"jsonrpc":"2.0","method":"personal_requestsToConfirm","params":[],"id":1}"#;
let response = concat!(
r#"{"jsonrpc":"2.0","result":["#,
r#"{"id":"0x01","payload":{"transaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x01"}}},"#,
r#"{"id":"0x02","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","hash":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#,
r#"],"id":1}"#
);
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
@ -94,19 +100,19 @@ fn should_return_list_of_transactions_in_queue() {
fn should_reject_transaction_from_queue_without_dispatching() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest {
from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
gas_price: U256::from(10_000),
gas: U256::from(10_000_000),
value: U256::from(1),
data: vec![],
nonce: None,
});
}));
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_rejectTransaction","params":["0x01"],"id":1}"#;
let request = r#"{"jsonrpc":"2.0","method":"personal_rejectRequest","params":["0x01"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
// then
@ -119,19 +125,35 @@ fn should_reject_transaction_from_queue_without_dispatching() {
fn should_not_remove_transaction_if_password_is_invalid() {
// given
let tester = signer_tester();
tester.queue.add_request(TransactionRequest {
tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest {
from: Address::from(1),
to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
gas_price: U256::from(10_000),
gas: U256::from(10_000_000),
value: U256::from(1),
data: vec![],
nonce: None,
});
}));
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_confirmTransaction","params":["0x01",{},"xxx"],"id":1}"#;
let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x01",{},"xxx"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#;
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);
}
#[test]
fn should_not_remove_sign_if_password_is_invalid() {
// given
let tester = signer_tester();
tester.queue.add_request(ConfirmationPayload::Sign(0.into(), 5.into()));
assert_eq!(tester.queue.requests().len(), 1);
// when
let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x01",{},"xxx"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#;
// then
@ -145,15 +167,15 @@ fn should_confirm_transaction_and_dispatch() {
let tester = signer_tester();
let address = tester.accounts.new_account("test").unwrap();
let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
tester.queue.add_request(TransactionRequest {
tester.queue.add_request(ConfirmationPayload::Transaction(FilledTransactionRequest {
from: address,
to: Some(recipient),
gas_price: Some(U256::from(10_000)),
gas: Some(U256::from(10_000_000)),
value: Some(U256::from(1)),
data: None,
gas_price: U256::from(10_000),
gas: U256::from(10_000_000),
value: U256::from(1),
data: vec![],
nonce: None,
});
}));
let t = Transaction {
nonce: U256::zero(),
@ -172,7 +194,7 @@ fn should_confirm_transaction_and_dispatch() {
// when
let request = r#"{
"jsonrpc":"2.0",
"method":"personal_confirmTransaction",
"method":"personal_confirmRequest",
"params":["0x01", {"gasPrice":"0x1000"}, "test"],
"id":1
}"#;

View File

@ -206,6 +206,10 @@ pub trait EthSigning: Sized + Send + Sync + 'static {
/// Signs the data with given address signature.
fn sign(&self, _: Params) -> Result<Value, Error>;
/// Posts sign request asynchronously.
/// Will return a confirmation ID for later use with check_transaction.
fn post_sign(&self, _: Params) -> Result<Value, Error>;
/// Sends transaction; will block for 20s to try to return the
/// transaction hash.
/// If it cannot yet be signed, it will return a transaction ID for
@ -226,6 +230,7 @@ pub trait EthSigning: Sized + Send + Sync + 'static {
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("eth_sign", EthSigning::sign);
delegate.add_method("eth_postSign", EthSigning::post_sign);
delegate.add_method("eth_sendTransaction", EthSigning::send_transaction);
delegate.add_method("eth_postTransaction", EthSigning::post_transaction);
delegate.add_method("eth_checkTransaction", EthSigning::check_transaction);

View File

@ -61,24 +61,24 @@ pub trait Personal: Sized + Send + Sync + 'static {
}
}
/// Personal extension for transactions confirmations rpc interface.
/// Personal extension for confirmations rpc interface.
pub trait PersonalSigner: Sized + Send + Sync + 'static {
/// Returns a list of transactions to confirm.
fn transactions_to_confirm(&self, _: Params) -> Result<Value, Error>;
/// Returns a list of items to confirm.
fn requests_to_confirm(&self, _: Params) -> Result<Value, Error>;
/// Confirm and send a specific transaction.
fn confirm_transaction(&self, _: Params) -> Result<Value, Error>;
/// Confirm specific request.
fn confirm_request(&self, _: Params) -> Result<Value, Error>;
/// Reject the transaction request.
fn reject_transaction(&self, _: Params) -> Result<Value, Error>;
/// Reject the confirmation request.
fn reject_request(&self, _: Params) -> Result<Value, Error>;
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("personal_transactionsToConfirm", PersonalSigner::transactions_to_confirm);
delegate.add_method("personal_confirmTransaction", PersonalSigner::confirm_transaction);
delegate.add_method("personal_rejectTransaction", PersonalSigner::reject_transaction);
delegate.add_method("personal_requestsToConfirm", PersonalSigner::requests_to_confirm);
delegate.add_method("personal_confirmRequest", PersonalSigner::confirm_request);
delegate.add_method("personal_rejectRequest", PersonalSigner::reject_request);
delegate
}
}

View File

@ -0,0 +1,150 @@
// Copyright 2015, 2016 Ethcore (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/>.
//! Types used in Confirmations queue (Trusted Signer)
use v1::types::{U256, TransactionRequest, H160, H256};
use v1::helpers;
/// Confirmation waiting in a queue
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub struct ConfirmationRequest {
/// Id of this confirmation
pub id: U256,
/// Payload
pub payload: ConfirmationPayload,
}
impl From<helpers::ConfirmationRequest> for ConfirmationRequest {
fn from(c: helpers::ConfirmationRequest) -> Self {
ConfirmationRequest {
id: c.id.into(),
payload: c.payload.into(),
}
}
}
/// Sign request
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub struct SignRequest {
/// Address
pub address: H160,
/// Hash to sign
pub hash: H256,
}
/// Confirmation payload, i.e. the thing to be confirmed
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub enum ConfirmationPayload {
/// Transaction
#[serde(rename="transaction")]
Transaction(TransactionRequest),
/// Signature
#[serde(rename="sign")]
Sign(SignRequest),
}
impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
fn from(c: helpers::ConfirmationPayload) -> Self {
match c {
helpers::ConfirmationPayload::Transaction(t) => ConfirmationPayload::Transaction(t.into()),
helpers::ConfirmationPayload::Sign(address, hash) => ConfirmationPayload::Sign(SignRequest {
address: address.into(),
hash: hash.into(),
}),
}
}
}
/// Possible modifications to the confirmed transaction sent by `Trusted Signer`
#[derive(Debug, PartialEq, Deserialize)]
pub struct TransactionModification {
/// Modified gas price
#[serde(rename="gasPrice")]
pub gas_price: Option<U256>,
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use serde_json;
use v1::types::U256;
use v1::helpers;
use super::*;
#[test]
fn should_serialize_sign_confirmation() {
// given
let request = helpers::ConfirmationRequest {
id: 15.into(),
payload: helpers::ConfirmationPayload::Sign(1.into(), 5.into()),
};
// when
let res = serde_json::to_string(&ConfirmationRequest::from(request));
let expected = r#"{"id":"0x0f","payload":{"sign":{"address":"0x0000000000000000000000000000000000000001","hash":"0x0000000000000000000000000000000000000000000000000000000000000005"}}}"#;
// then
assert_eq!(res.unwrap(), expected.to_owned());
}
#[test]
fn should_serialize_transaction_confirmation() {
// given
let request = helpers::ConfirmationRequest {
id: 15.into(),
payload: helpers::ConfirmationPayload::Transaction(helpers::FilledTransactionRequest {
from: 0.into(),
to: None,
gas: 15_000.into(),
gas_price: 10_000.into(),
value: 100_000.into(),
data: vec![1, 2, 3],
nonce: Some(1.into()),
}),
};
// when
let res = serde_json::to_string(&ConfirmationRequest::from(request));
let expected = r#"{"id":"0x0f","payload":{"transaction":{"from":"0x0000000000000000000000000000000000000000","to":null,"gasPrice":"0x2710","gas":"0x3a98","value":"0x0186a0","data":"0x010203","nonce":"0x01"}}}"#;
// then
assert_eq!(res.unwrap(), expected.to_owned());
}
#[test]
fn should_deserialize_modification() {
// given
let s1 = r#"{
"gasPrice":"0x0ba43b7400"
}"#;
let s2 = r#"{}"#;
// when
let res1: TransactionModification = serde_json::from_str(s1).unwrap();
let res2: TransactionModification = serde_json::from_str(s2).unwrap();
// then
assert_eq!(res1, TransactionModification {
gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
});
assert_eq!(res2, TransactionModification {
gas_price: None,
});
}
}

View File

@ -17,6 +17,8 @@
mod bytes;
mod block;
mod block_number;
mod call_request;
mod confirmations;
mod filter;
mod hash;
mod index;
@ -24,7 +26,6 @@ mod log;
mod sync;
mod transaction;
mod transaction_request;
mod call_request;
mod receipt;
mod trace;
mod trace_filter;
@ -33,14 +34,15 @@ mod uint;
pub use self::bytes::Bytes;
pub use self::block::{Block, BlockTransactions};
pub use self::block_number::BlockNumber;
pub use self::call_request::CallRequest;
pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, TransactionModification};
pub use self::filter::Filter;
pub use self::hash::{H64, H160, H256, H520, H2048};
pub use self::index::Index;
pub use self::log::Log;
pub use self::sync::{SyncStatus, SyncInfo};
pub use self::transaction::Transaction;
pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification};
pub use self::call_request::CallRequest;
pub use self::transaction_request::TransactionRequest;
pub use self::receipt::Receipt;
pub use self::trace::{LocalizedTrace, TraceResults};
pub use self::trace_filter::TraceFilter;

View File

@ -17,7 +17,7 @@
//! `TransactionRequest` type
use v1::types::{Bytes, H160, U256};
use v1::helpers::{TransactionRequest as Request, TransactionConfirmation as Confirmation};
use v1::helpers;
/// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
@ -39,8 +39,8 @@ pub struct TransactionRequest {
pub nonce: Option<U256>,
}
impl From<Request> for TransactionRequest {
fn from(r: Request) -> Self {
impl From<helpers::TransactionRequest> for TransactionRequest {
fn from(r: helpers::TransactionRequest) -> Self {
TransactionRequest {
from: r.from.into(),
to: r.to.map(Into::into),
@ -53,9 +53,23 @@ impl From<Request> for TransactionRequest {
}
}
impl Into<Request> for TransactionRequest {
fn into(self) -> Request {
Request {
impl From<helpers::FilledTransactionRequest> for TransactionRequest {
fn from(r: helpers::FilledTransactionRequest) -> Self {
TransactionRequest {
from: r.from.into(),
to: r.to.map(Into::into),
gas_price: Some(r.gas_price.into()),
gas: Some(r.gas.into()),
value: Some(r.value.into()),
data: Some(r.data.into()),
nonce: r.nonce.map(Into::into),
}
}
}
impl Into<helpers::TransactionRequest> for TransactionRequest {
fn into(self) -> helpers::TransactionRequest {
helpers::TransactionRequest {
from: self.from.into(),
to: self.to.map(Into::into),
gas_price: self.gas_price.map(Into::into),
@ -67,32 +81,6 @@ impl Into<Request> for TransactionRequest {
}
}
/// Transaction confirmation waiting in a queue
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize)]
pub struct TransactionConfirmation {
/// Id of this confirmation
pub id: U256,
/// TransactionRequest
pub transaction: TransactionRequest,
}
impl From<Confirmation> for TransactionConfirmation {
fn from(c: Confirmation) -> Self {
TransactionConfirmation {
id: c.id.into(),
transaction: c.transaction.into(),
}
}
}
/// Possible modifications to the confirmed transaction sent by `SignerUI`
#[derive(Debug, PartialEq, Deserialize)]
pub struct TransactionModification {
/// Modified gas price
#[serde(rename="gasPrice")]
pub gas_price: Option<U256>,
}
#[cfg(test)]
mod tests {
@ -188,7 +176,6 @@ mod tests {
});
}
#[test]
fn transaction_request_deserialize_error() {
let s = r#"{
@ -203,26 +190,5 @@ mod tests {
assert!(deserialized.is_err(), "Should be error because to is empty");
}
#[test]
fn should_deserialize_modification() {
// given
let s1 = r#"{
"gasPrice":"0x0ba43b7400"
}"#;
let s2 = r#"{}"#;
// when
let res1: TransactionModification = serde_json::from_str(s1).unwrap();
let res2: TransactionModification = serde_json::from_str(s2).unwrap();
// then
assert_eq!(res1, TransactionModification {
gas_price: Some(U256::from_str("0ba43b7400").unwrap()),
});
assert_eq!(res2, TransactionModification {
gas_price: None,
});
}
}

View File

@ -53,6 +53,7 @@ pub struct ServerBuilder {
queue: Arc<ConfirmationsQueue>,
handler: Arc<IoHandler>,
authcodes_path: PathBuf,
skip_origin_validation: bool,
}
impl Extendable for ServerBuilder {
@ -68,13 +69,21 @@ impl ServerBuilder {
queue: queue,
handler: Arc::new(IoHandler::new()),
authcodes_path: authcodes_path,
skip_origin_validation: false,
}
}
/// If set to `true` server will not verify Origin of incoming requests.
/// Not recommended. Use only for development.
pub fn skip_origin_validation(mut self, skip: bool) -> Self {
self.skip_origin_validation = skip;
self
}
/// Starts a new `WebSocket` server in separate thread.
/// Returns a `Server` handle which closes the server when droped.
pub fn start(self, addr: SocketAddr) -> Result<Server, ServerError> {
Server::start(addr, self.handler, self.queue, self.authcodes_path)
Server::start(addr, self.handler, self.queue, self.authcodes_path, self.skip_origin_validation)
}
}
@ -89,10 +98,10 @@ pub struct Server {
impl Server {
/// Starts a new `WebSocket` server in separate thread.
/// Returns a `Server` handle which closes the server when droped.
fn start(addr: SocketAddr, handler: Arc<IoHandler>, queue: Arc<ConfirmationsQueue>, authcodes_path: PathBuf) -> Result<Server, ServerError> {
fn start(addr: SocketAddr, handler: Arc<IoHandler>, queue: Arc<ConfirmationsQueue>, authcodes_path: PathBuf, skip_origin_validation: bool) -> Result<Server, ServerError> {
let config = {
let mut config = ws::Settings::default();
// It's also used for handling min-sysui requests (browser can make many of them in paralel)
// accept only handshakes beginning with GET
config.method_strict = true;
// Was shutting down server when suspending on linux:
config.shutdown_on_interrupt = false;
@ -101,7 +110,9 @@ impl Server {
// Create WebSocket
let origin = format!("{}", addr);
let ws = try!(ws::Builder::new().with_settings(config).build(session::Factory::new(handler, origin, authcodes_path)));
let ws = try!(ws::Builder::new().with_settings(config).build(
session::Factory::new(handler, origin, authcodes_path, skip_origin_validation)
));
let panic_handler = PanicHandler::new_in_arc();
let ph = panic_handler.clone();

View File

@ -96,6 +96,7 @@ fn add_headers(mut response: ws::Response, mime: &str) -> ws::Response {
pub struct Session {
out: ws::Sender,
skip_origin_validation: bool,
self_origin: String,
authcodes_path: PathBuf,
handler: Arc<IoHandler>,
@ -107,9 +108,11 @@ impl ws::Handler for Session {
let host = req.header("host").or_else(|| req.header("Host")).map(|x| &x[..]);
// Check request origin and host header.
if !origin_is_allowed(&self.self_origin, origin) && !(origin.is_none() && origin_is_allowed(&self.self_origin, host)) {
warn!(target: "signer", "Blocked connection to Signer API from untrusted origin.");
return Ok(ws::Response::forbidden(format!("You are not allowed to access system ui. Use: http://{}", self.self_origin)));
if !self.skip_origin_validation {
if !origin_is_allowed(&self.self_origin, origin) && !(origin.is_none() && origin_is_allowed(&self.self_origin, host)) {
warn!(target: "signer", "Blocked connection to Signer API from untrusted origin.");
return Ok(ws::Response::forbidden(format!("You are not allowed to access system ui. Use: http://{}", self.self_origin)));
}
}
// Detect if it's a websocket request.
@ -150,14 +153,16 @@ impl ws::Handler for Session {
pub struct Factory {
handler: Arc<IoHandler>,
skip_origin_validation: bool,
self_origin: String,
authcodes_path: PathBuf,
}
impl Factory {
pub fn new(handler: Arc<IoHandler>, self_origin: String, authcodes_path: PathBuf) -> Self {
pub fn new(handler: Arc<IoHandler>, self_origin: String, authcodes_path: PathBuf, skip_origin_validation: bool) -> Self {
Factory {
handler: handler,
skip_origin_validation: skip_origin_validation,
self_origin: self_origin,
authcodes_path: authcodes_path,
}
@ -171,6 +176,7 @@ impl ws::Factory for Factory {
Session {
out: sender,
handler: self.handler.clone(),
skip_origin_validation: self.skip_origin_validation,
self_origin: self.self_origin.clone(),
authcodes_path: self.authcodes_path.clone(),
}

View File

@ -9,7 +9,6 @@ build = "build.rs"
[lib]
[build-dependencies]
syntex = "0.33"
ethcore-ipc-codegen = { path = "../ipc/codegen" }
[dependencies]

View File

@ -14,26 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate syntex;
extern crate ethcore_ipc_codegen as codegen;
use std::env;
use std::path::Path;
extern crate ethcore_ipc_codegen;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
// sync interface
{
let src = Path::new("src/api.rs");
let intermediate = Path::new(&out_dir).join("api.intermediate.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &src, &intermediate).unwrap();
let dst = Path::new(&out_dir).join("api.ipc.rs");
let mut registry = syntex::Registry::new();
codegen::register(&mut registry);
registry.expand("", &intermediate, &dst).unwrap();
}
ethcore_ipc_codegen::derive_ipc("src/api.rs").unwrap();
}

View File

@ -84,7 +84,7 @@ mod tests;
mod api {
#![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/api.ipc.rs"));
include!(concat!(env!("OUT_DIR"), "/api.rs"));
}
pub use api::{EthSync, SyncProvider, SyncClient, NetworkManagerClient, ManageNetwork, SyncConfig,

View File

@ -36,7 +36,7 @@ use std::fmt;
use std::slice;
use std::ops::{Deref, DerefMut};
/// Slie pretty print helper
/// Slice pretty print helper
pub struct PrettySlice<'a> (&'a [u8]);
impl<'a> fmt::Debug for PrettySlice<'a> {

View File

@ -71,6 +71,9 @@ impl<T> UsingQueue<T> where T: Clone {
self.pending = Some(b);
}
/// Is there anything in the queue currently?
pub fn is_in_use(&self) -> bool { self.in_use.len() > 0 }
/// Clears everything; the queue is entirely reset.
pub fn reset(&mut self) {
self.pending = None;