From a132fefcc7d3d99556aec7ec42ff4b471db4ee1b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 2 Jun 2016 13:50:50 +0200 Subject: [PATCH 1/8] Transaction tracing for eth_call. --- ethcore/src/client/client.rs | 2 +- ethcore/src/client/mod.rs | 2 ++ ethcore/src/miner/miner.rs | 2 +- rpc/src/v1/impls/traces.rs | 29 ++++++++++++++++++++++------- rpc/src/v1/traits/traces.rs | 6 +++++- rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/trace.rs | 36 ++++++++++++++++++++++++++++++------ 7 files changed, 62 insertions(+), 17 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 6f0e5b874..da1672812 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -461,7 +461,7 @@ impl BlockChainClient for Client where V: Verifier { // give the sender a sufficient balance state.add_balance(&sender, &(needed_balance - balance)); } - let options = TransactOptions { tracing: false, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; let mut ret = Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, options); // TODO gav move this into Executive. if analytics.state_diffing { diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 0dffb1a1c..947da4904 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -52,6 +52,8 @@ use error::Error as EthError; /// Options concerning what analytics we run on the call. #[derive(Eq, PartialEq, Default, Clone, Copy, Debug)] pub struct CallAnalytics { + /// Make a transaction trace. + pub transaction_tracing: bool, /// Make a VM trace. pub vm_tracing: bool, /// Make a diff. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 18a253ea9..4abef5554 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -276,7 +276,7 @@ impl MinerService for Miner { // give the sender max balance state.sub_balance(&sender, &balance); state.add_balance(&sender, &U256::max_value()); - let options = TransactOptions { tracing: false, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; let mut ret = Executive::new(&mut state, &env_info, self.engine(), chain.vm_factory()).transact(t, options); // TODO gav move this into Executive. diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index b6015d498..8a1627836 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -28,7 +28,7 @@ use ethcore::state_diff::StateDiff; use ethcore::account_diff::{Diff, Existance}; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::Traces; -use v1::types::{TraceFilter, Trace, BlockNumber, Index, CallRequest}; +use v1::types::{TraceFilter, LocalizedTrace, Trace, BlockNumber, Index, CallRequest}; /// Traces api implementation. pub struct TracesClient where C: BlockChainClient, M: MinerService { @@ -156,7 +156,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: .and_then(|(filter, )| { let client = take_weak!(self.client); let traces = client.filter_traces(filter.into()); - let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(Trace::from).collect()); + let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); to_value(&traces) }) } @@ -166,7 +166,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: .and_then(|(block_number,)| { let client = take_weak!(self.client); let traces = client.block_traces(block_number.into()); - let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(Trace::from).collect()); + let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); to_value(&traces) }) } @@ -176,7 +176,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: .and_then(|(transaction_hash,)| { let client = take_weak!(self.client); let traces = client.transaction_traces(TransactionID::Hash(transaction_hash)); - let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(Trace::from).collect()); + let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); to_value(&traces) }) } @@ -190,17 +190,32 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: address: address.into_iter().map(|i| i.value()).collect() }; let trace = client.trace(id); - let trace = trace.map(Trace::from); + let trace = trace.map(LocalizedTrace::from); to_value(&trace) }) } + fn trace_call(&self, params: Params) -> Result { + trace!(target: "jsonrpc", "trace_call: {:?}", params); + from_params(params) + .and_then(|(request,)| { + let signed = try!(self.sign_call(request)); + let r = take_weak!(self.client).call(&signed, CallAnalytics{ transaction_tracing: true, vm_tracing: false, state_diffing: false }); + if let Ok(executed) = r { + if let Some(trace) = executed.trace { + return to_value(&Trace::from(trace)); + } + } + Ok(Value::Null) + }) + } + fn vm_trace_call(&self, params: Params) -> Result { trace!(target: "jsonrpc", "vm_trace_call: {:?}", params); from_params(params) .and_then(|(request,)| { let signed = try!(self.sign_call(request)); - let r = take_weak!(self.client).call(&signed, CallAnalytics{ vm_tracing: true, state_diffing: false }); + let r = take_weak!(self.client).call(&signed, CallAnalytics{ transaction_tracing: false, vm_tracing: true, state_diffing: false }); if let Ok(executed) = r { if let Some(vm_trace) = executed.vm_trace { return Ok(vm_trace_to_object(&vm_trace)); @@ -215,7 +230,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: from_params(params) .and_then(|(request,)| { let signed = try!(self.sign_call(request)); - let r = take_weak!(self.client).call(&signed, CallAnalytics{ vm_tracing: false, state_diffing: true }); + let r = take_weak!(self.client).call(&signed, CallAnalytics{ transaction_tracing: false, vm_tracing: false, state_diffing: true }); if let Ok(executed) = r { if let Some(state_diff) = executed.state_diff { return Ok(state_diff_to_object(&state_diff)); diff --git a/rpc/src/v1/traits/traces.rs b/rpc/src/v1/traits/traces.rs index 4b58b12ac..c00b52822 100644 --- a/rpc/src/v1/traits/traces.rs +++ b/rpc/src/v1/traits/traces.rs @@ -32,11 +32,14 @@ pub trait Traces: Sized + Send + Sync + 'static { /// Returns all traces produced at given block. fn block_traces(&self, _: Params) -> Result; + /// Executes the given call and returns the transaction trace for it. + fn trace_call(&self, _: Params) -> Result; + /// Executes the given call and returns the VM trace for it. fn vm_trace_call(&self, _: Params) -> Result; /// Executes the given call and returns the diff for it. - fn state_diff_call(&self, params: Params) -> Result; + fn state_diff_call(&self, _: Params) -> Result; /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { @@ -46,6 +49,7 @@ pub trait Traces: Sized + Send + Sync + 'static { delegate.add_method("trace_transaction", Traces::transaction_traces); delegate.add_method("trace_block", Traces::block_traces); + delegate.add_method("trace_traceCall", Traces::trace_call); delegate.add_method("trace_vmTraceCall", Traces::vm_trace_call); delegate.add_method("trace_stateDiffCall", Traces::state_diff_call); diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index b4e82a28b..ba10fbe18 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -41,5 +41,5 @@ pub use self::transaction::Transaction; pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification}; pub use self::call_request::CallRequest; pub use self::receipt::Receipt; -pub use self::trace::Trace; +pub use self::trace::{Trace, LocalizedTrace}; pub use self::trace_filter::TraceFilter; diff --git a/rpc/src/v1/types/trace.rs b/rpc/src/v1/types/trace.rs index 6ea58543a..070b7d7d0 100644 --- a/rpc/src/v1/types/trace.rs +++ b/rpc/src/v1/types/trace.rs @@ -16,7 +16,7 @@ use util::{Address, U256, H256}; use ethcore::trace::trace; -use ethcore::trace::LocalizedTrace; +use ethcore::trace::{Trace as EthTrace, LocalizedTrace as EthLocalizedTrace}; use v1::types::Bytes; /// Create response @@ -161,7 +161,7 @@ impl From for Res { /// Trace #[derive(Debug, Serialize)] -pub struct Trace { +pub struct LocalizedTrace { /// Action action: Action, /// Result @@ -185,9 +185,9 @@ pub struct Trace { block_hash: H256, } -impl From for Trace { - fn from(t: LocalizedTrace) -> Self { - Trace { +impl From for LocalizedTrace { + fn from(t: EthLocalizedTrace) -> Self { + LocalizedTrace { action: From::from(t.action), result: From::from(t.result), trace_address: t.trace_address.into_iter().map(From::from).collect(), @@ -200,6 +200,30 @@ impl From for Trace { } } +/// Trace +#[derive(Debug, Serialize)] +pub struct Trace { + /// Depth within the call trace tree. + depth: usize, + /// Action + action: Action, + /// Result + result: Res, + /// Subtraces + subtraces: Vec, +} + +impl From for Trace { + fn from(t: EthTrace) -> Self { + Trace { + depth: t.depth.into(), + action: t.action.into(), + result: t.result.into(), + subtraces: t.subs.into_iter().map(From::from).collect(), + } + } +} + #[cfg(test)] mod tests { use serde_json; @@ -209,7 +233,7 @@ mod tests { #[test] fn test_trace_serialize() { - let t = Trace { + let t = LocalizedTrace { action: Action::Call(Call { from: Address::from(4), to: Address::from(5), From 7dc05f1bcca045fa1117559e76a64155b26b932e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 2 Jun 2016 16:30:28 +0200 Subject: [PATCH 2/8] Unify tracing interface into a single call. --- rpc/src/v1/impls/traces.rs | 49 +++++++++++++------------------------ rpc/src/v1/traits/traces.rs | 15 +++--------- 2 files changed, 20 insertions(+), 44 deletions(-) diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 8a1627836..745f1c036 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -28,7 +28,7 @@ use ethcore::state_diff::StateDiff; use ethcore::account_diff::{Diff, Existance}; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::Traces; -use v1::types::{TraceFilter, LocalizedTrace, Trace, BlockNumber, Index, CallRequest}; +use v1::types::{TraceFilter, LocalizedTrace, Trace, BlockNumber, Index, CallRequest, Bytes}; /// Traces api implementation. pub struct TracesClient where C: BlockChainClient, M: MinerService { @@ -195,46 +195,31 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: }) } - fn trace_call(&self, params: Params) -> Result { - trace!(target: "jsonrpc", "trace_call: {:?}", params); + fn call(&self, params: Params) -> Result { + trace!(target: "jsonrpc", "call: {:?}", params); from_params(params) - .and_then(|(request,)| { + .and_then(|(request, flags)| { + let flags: Vec = flags; + let analytics = CallAnalytics { + transaction_tracing: flags.contains(&("trace".to_owned())), + vm_tracing: flags.contains(&("vmTrace".to_owned())), + state_diffing: flags.contains(&("stateDiff".to_owned())), + }; let signed = try!(self.sign_call(request)); - let r = take_weak!(self.client).call(&signed, CallAnalytics{ transaction_tracing: true, vm_tracing: false, state_diffing: false }); + let r = take_weak!(self.client).call(&signed, analytics); if let Ok(executed) = r { + // TODO maybe add other stuff to this? + let mut ret = map!["output".to_owned() => to_value(&Bytes(executed.output)).unwrap()]; if let Some(trace) = executed.trace { - return to_value(&Trace::from(trace)); + ret.insert("trace".to_owned(), to_value(&Trace::from(trace)).unwrap()); } - } - Ok(Value::Null) - }) - } - - fn vm_trace_call(&self, params: Params) -> Result { - trace!(target: "jsonrpc", "vm_trace_call: {:?}", params); - from_params(params) - .and_then(|(request,)| { - let signed = try!(self.sign_call(request)); - let r = take_weak!(self.client).call(&signed, CallAnalytics{ transaction_tracing: false, vm_tracing: true, state_diffing: false }); - if let Ok(executed) = r { if let Some(vm_trace) = executed.vm_trace { - return Ok(vm_trace_to_object(&vm_trace)); + ret.insert("vmTrace".to_owned(), vm_trace_to_object(&vm_trace)); } - } - Ok(Value::Null) - }) - } - - fn state_diff_call(&self, params: Params) -> Result { - trace!(target: "jsonrpc", "state_diff_call: {:?}", params); - from_params(params) - .and_then(|(request,)| { - let signed = try!(self.sign_call(request)); - let r = take_weak!(self.client).call(&signed, CallAnalytics{ transaction_tracing: false, vm_tracing: false, state_diffing: true }); - if let Ok(executed) = r { if let Some(state_diff) = executed.state_diff { - return Ok(state_diff_to_object(&state_diff)); + ret.insert("stateDiff".to_owned(), state_diff_to_object(&state_diff)); } + return Ok(Value::Object(ret)) } Ok(Value::Null) }) diff --git a/rpc/src/v1/traits/traces.rs b/rpc/src/v1/traits/traces.rs index c00b52822..45fa916be 100644 --- a/rpc/src/v1/traits/traces.rs +++ b/rpc/src/v1/traits/traces.rs @@ -32,14 +32,8 @@ pub trait Traces: Sized + Send + Sync + 'static { /// Returns all traces produced at given block. fn block_traces(&self, _: Params) -> Result; - /// Executes the given call and returns the transaction trace for it. - fn trace_call(&self, _: Params) -> Result; - - /// Executes the given call and returns the VM trace for it. - fn vm_trace_call(&self, _: Params) -> Result; - - /// Executes the given call and returns the diff for it. - fn state_diff_call(&self, _: Params) -> Result; + /// Executes the given call and returns a number of possible traces for it. + fn call(&self, _: Params) -> Result; /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { @@ -48,10 +42,7 @@ pub trait Traces: Sized + Send + Sync + 'static { delegate.add_method("trace_get", Traces::trace); delegate.add_method("trace_transaction", Traces::transaction_traces); delegate.add_method("trace_block", Traces::block_traces); - - delegate.add_method("trace_traceCall", Traces::trace_call); - delegate.add_method("trace_vmTraceCall", Traces::vm_trace_call); - delegate.add_method("trace_stateDiffCall", Traces::state_diff_call); + delegate.add_method("trace_call", Traces::call); delegate } From 1fa8f108d920f4890f440de7af04650dd26cc782 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 6 Jun 2016 00:24:21 +0200 Subject: [PATCH 3/8] StateDiff uses serde preprocessor. --- rpc/src/v1/impls/traces.rs | 11 +++--- rpc/src/v1/types/bytes.rs | 6 ++++ rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/trace.rs | 71 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 745f1c036..817420f3c 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -20,15 +20,12 @@ use std::sync::{Weak, Arc}; use jsonrpc_core::*; use std::collections::BTreeMap; use util::{H256, U256, FixedHash, Uint}; -use serde; use ethcore::client::{BlockChainClient, CallAnalytics, TransactionID, TraceId}; use ethcore::trace::VMTrace; use ethcore::miner::MinerService; -use ethcore::state_diff::StateDiff; -use ethcore::account_diff::{Diff, Existance}; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::Traces; -use v1::types::{TraceFilter, LocalizedTrace, Trace, BlockNumber, Index, CallRequest, Bytes}; +use v1::types::{TraceFilter, LocalizedTrace, Trace, BlockNumber, Index, CallRequest, Bytes, StateDiff}; /// Traces api implementation. pub struct TracesClient where C: BlockChainClient, M: MinerService { @@ -108,7 +105,7 @@ fn vm_trace_to_object(t: &VMTrace) -> Value { ret.insert("ops".to_owned(), Value::Array(ops)); Value::Object(ret) } - +/* fn diff_to_object(d: &Diff) -> Value where T: serde::Serialize + Eq { let mut ret = BTreeMap::new(); match *d { @@ -149,7 +146,7 @@ fn state_diff_to_object(t: &StateDiff) -> Value { ])) }).collect::>()) } - +*/ impl Traces for TracesClient where C: BlockChainClient + 'static, M: MinerService + 'static { fn filter(&self, params: Params) -> Result { from_params::<(TraceFilter,)>(params) @@ -217,7 +214,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: ret.insert("vmTrace".to_owned(), vm_trace_to_object(&vm_trace)); } if let Some(state_diff) = executed.state_diff { - ret.insert("stateDiff".to_owned(), state_diff_to_object(&state_diff)); + ret.insert("stateDiff".to_owned(), to_value(&StateDiff::from(state_diff)).unwrap()); } return Ok(Value::Object(ret)) } diff --git a/rpc/src/v1/types/bytes.rs b/rpc/src/v1/types/bytes.rs index 76d84d0dd..1bf5deb75 100644 --- a/rpc/src/v1/types/bytes.rs +++ b/rpc/src/v1/types/bytes.rs @@ -36,6 +36,12 @@ impl Bytes { } } +impl From> for Bytes { + fn from(bytes: Vec) -> Bytes { + Bytes(bytes) + } +} + impl Serialize for Bytes { fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index ba10fbe18..fc2a19987 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -41,5 +41,5 @@ pub use self::transaction::Transaction; pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification}; pub use self::call_request::CallRequest; pub use self::receipt::Receipt; -pub use self::trace::{Trace, LocalizedTrace}; +pub use self::trace::{Trace, LocalizedTrace, StateDiff}; pub use self::trace_filter::TraceFilter; diff --git a/rpc/src/v1/types/trace.rs b/rpc/src/v1/types/trace.rs index 070b7d7d0..ede99b448 100644 --- a/rpc/src/v1/types/trace.rs +++ b/rpc/src/v1/types/trace.rs @@ -14,11 +14,82 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::collections::BTreeMap; use util::{Address, U256, H256}; +use serde::{Serialize, Serializer}; use ethcore::trace::trace; use ethcore::trace::{Trace as EthTrace, LocalizedTrace as EthLocalizedTrace}; +use ethcore::state_diff; +use ethcore::account_diff; use v1::types::Bytes; +#[derive(Debug, Serialize)] +/// Aux type for Diff::Changed. +pub struct ChangedType where T: Serialize { + from: T, + to: T, +} + +#[derive(Debug, Serialize)] +/// Serde-friendly `Diff` shadow. +pub enum Diff where T: Serialize { + #[serde(rename="=")] + Same, + #[serde(rename="+")] + Born(T), + #[serde(rename="-")] + Died(T), + #[serde(rename="*")] + Changed(ChangedType), +} + +impl From> for Diff where T: Eq, U: Serialize + From { + fn from(c: account_diff::Diff) -> Self { + match c { + account_diff::Diff::Same => Diff::Same, + account_diff::Diff::Born(t) => Diff::Born(t.into()), + account_diff::Diff::Died(t) => Diff::Died(t.into()), + account_diff::Diff::Changed(t, u) => Diff::Changed(ChangedType{from: t.into(), to: u.into()}), + } + } +} + +#[derive(Debug, Serialize)] +/// Serde-friendly `AccountDiff` shadow. +pub struct AccountDiff { + pub balance: Diff, + pub nonce: Diff, + pub code: Diff, + pub storage: BTreeMap>, +} + +impl From for AccountDiff { + fn from(c: account_diff::AccountDiff) -> Self { + AccountDiff { + balance: c.balance.into(), + nonce: c.nonce.into(), + code: c.code.into(), + storage: c.storage.into_iter().map(|(k, v)| (k, v.into())).collect(), + } + } +} + +/// Serde-friendly `StateDiff` shadow. +pub struct StateDiff(BTreeMap); + +impl Serialize for StateDiff { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer { + Serialize::serialize(&self.0, serializer) + } +} + +impl From for StateDiff { + fn from(c: state_diff::StateDiff) -> Self { + StateDiff(c.0.into_iter().map(|(k, v)| (k, v.into())).collect()) + } +} + /// Create response #[derive(Debug, Serialize)] pub struct Create { From 0cb1affd48ebfe0de3094203f8e084a9ac38123d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 6 Jun 2016 15:18:38 -0700 Subject: [PATCH 4/8] Use serialize framework for VMTrace JSON. --- rpc/src/v1/impls/traces.rs | 96 +---------------------------- rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/trace.rs | 121 ++++++++++++++++++++++++++++++++++++- 3 files changed, 124 insertions(+), 95 deletions(-) diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 817420f3c..452255383 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -19,13 +19,12 @@ use std::sync::{Weak, Arc}; use jsonrpc_core::*; use std::collections::BTreeMap; -use util::{H256, U256, FixedHash, Uint}; +use util::{H256, FixedHash}; use ethcore::client::{BlockChainClient, CallAnalytics, TransactionID, TraceId}; -use ethcore::trace::VMTrace; use ethcore::miner::MinerService; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::Traces; -use v1::types::{TraceFilter, LocalizedTrace, Trace, BlockNumber, Index, CallRequest, Bytes, StateDiff}; +use v1::types::{TraceFilter, LocalizedTrace, Trace, BlockNumber, Index, CallRequest, Bytes, StateDiff, VMTrace}; /// Traces api implementation. pub struct TracesClient where C: BlockChainClient, M: MinerService { @@ -58,95 +57,6 @@ impl TracesClient where C: BlockChainClient, M: MinerService { } } -fn vm_trace_to_object(t: &VMTrace) -> Value { - let mut ret = BTreeMap::new(); - ret.insert("code".to_owned(), to_value(&t.code).unwrap()); - - let mut subs = t.subs.iter(); - let mut next_sub = subs.next(); - - let ops = t.operations - .iter() - .enumerate() - .map(|(i, op)| { - let mut m = map![ - "pc".to_owned() => to_value(&op.pc).unwrap(), - "cost".to_owned() => match op.gas_cost <= U256::from(!0u64) { - true => to_value(&op.gas_cost.low_u64()), - false => to_value(&op.gas_cost), - }.unwrap() - ]; - if let Some(ref ex) = op.executed { - let mut em = map![ - "used".to_owned() => to_value(&ex.gas_used.low_u64()).unwrap(), - "push".to_owned() => to_value(&ex.stack_push).unwrap() - ]; - if let Some(ref md) = ex.mem_diff { - em.insert("mem".to_owned(), Value::Object(map![ - "off".to_owned() => to_value(&md.offset).unwrap(), - "data".to_owned() => to_value(&md.data).unwrap() - ])); - } - if let Some(ref sd) = ex.store_diff { - em.insert("store".to_owned(), Value::Object(map![ - "key".to_owned() => to_value(&sd.location).unwrap(), - "val".to_owned() => to_value(&sd.value).unwrap() - ])); - } - m.insert("ex".to_owned(), Value::Object(em)); - } - if next_sub.is_some() && next_sub.unwrap().parent_step == i { - m.insert("sub".to_owned(), vm_trace_to_object(next_sub.unwrap())); - next_sub = subs.next(); - } - Value::Object(m) - }) - .collect::>(); - ret.insert("ops".to_owned(), Value::Array(ops)); - Value::Object(ret) -} -/* -fn diff_to_object(d: &Diff) -> Value where T: serde::Serialize + Eq { - let mut ret = BTreeMap::new(); - match *d { - Diff::Same => { - ret.insert("diff".to_owned(), Value::String("=".to_owned())); - } - Diff::Born(ref x) => { - ret.insert("diff".to_owned(), Value::String("+".to_owned())); - ret.insert("+".to_owned(), to_value(x).unwrap()); - } - Diff::Died(ref x) => { - ret.insert("diff".to_owned(), Value::String("-".to_owned())); - ret.insert("-".to_owned(), to_value(x).unwrap()); - } - Diff::Changed(ref from, ref to) => { - ret.insert("diff".to_owned(), Value::String("*".to_owned())); - ret.insert("-".to_owned(), to_value(from).unwrap()); - ret.insert("+".to_owned(), to_value(to).unwrap()); - } - }; - Value::Object(ret) -} - -fn state_diff_to_object(t: &StateDiff) -> Value { - Value::Object(t.iter().map(|(address, account)| { - (address.hex(), Value::Object(map![ - "existance".to_owned() => Value::String(match account.existance() { - Existance::Born => "+", - Existance::Alive => ".", - Existance::Died => "-", - }.to_owned()), - "balance".to_owned() => diff_to_object(&account.balance), - "nonce".to_owned() => diff_to_object(&account.nonce), - "code".to_owned() => diff_to_object(&account.code), - "storage".to_owned() => Value::Object(account.storage.iter().map(|(key, val)| { - (key.hex(), diff_to_object(&val)) - }).collect::>()) - ])) - }).collect::>()) -} -*/ impl Traces for TracesClient where C: BlockChainClient + 'static, M: MinerService + 'static { fn filter(&self, params: Params) -> Result { from_params::<(TraceFilter,)>(params) @@ -211,7 +121,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: ret.insert("trace".to_owned(), to_value(&Trace::from(trace)).unwrap()); } if let Some(vm_trace) = executed.vm_trace { - ret.insert("vmTrace".to_owned(), vm_trace_to_object(&vm_trace)); + ret.insert("vmTrace".to_owned(), to_value(&VMTrace::from(vm_trace)).unwrap()); } if let Some(state_diff) = executed.state_diff { ret.insert("stateDiff".to_owned(), to_value(&StateDiff::from(state_diff)).unwrap()); diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index fc2a19987..3f07bfb31 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -41,5 +41,5 @@ pub use self::transaction::Transaction; pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification}; pub use self::call_request::CallRequest; pub use self::receipt::Receipt; -pub use self::trace::{Trace, LocalizedTrace, StateDiff}; +pub use self::trace::{Trace, LocalizedTrace, StateDiff, VMTrace}; pub use self::trace_filter::TraceFilter; diff --git a/rpc/src/v1/types/trace.rs b/rpc/src/v1/types/trace.rs index ede99b448..5b4192886 100644 --- a/rpc/src/v1/types/trace.rs +++ b/rpc/src/v1/types/trace.rs @@ -15,14 +15,133 @@ // along with Parity. If not, see . use std::collections::BTreeMap; -use util::{Address, U256, H256}; +use util::{Address, U256, H256, Uint}; use serde::{Serialize, Serializer}; use ethcore::trace::trace; use ethcore::trace::{Trace as EthTrace, LocalizedTrace as EthLocalizedTrace}; +use ethcore::trace as et; use ethcore::state_diff; use ethcore::account_diff; use v1::types::Bytes; +#[derive(Debug, Serialize)] +/// A diff of some chunk of memory. +pub struct MemoryDiff { + /// Offset into memory the change begins. + pub off: usize, + /// The changed data. + pub data: Vec, +} + +impl From for MemoryDiff { + fn from(c: et::MemoryDiff) -> Self { + MemoryDiff { + off: c.offset, + data: c.data, + } + } +} + +#[derive(Debug, Serialize)] +/// A diff of some storage value. +pub struct StorageDiff { + /// Which key in storage is changed. + pub key: U256, + /// What the value has been changed to. + pub val: U256, +} + +impl From for StorageDiff { + fn from(c: et::StorageDiff) -> Self { + StorageDiff { + key: c.location, + val: c.value, + } + } +} + +#[derive(Debug, Serialize)] +/// A record of an executed VM operation. +pub struct VMExecutedOperation { + /// The total gas used. + #[serde(rename="used")] + pub used: u64, + /// The stack item placed, if any. + pub push: Vec, + /// If altered, the memory delta. + #[serde(rename="mem")] + pub mem: Option, + /// The altered storage value, if any. + #[serde(rename="store")] + pub store: Option, +} + +impl From for VMExecutedOperation { + fn from(c: et::VMExecutedOperation) -> Self { + VMExecutedOperation { + used: c.gas_used.low_u64(), + push: c.stack_push, + mem: c.mem_diff.map(From::from), + store: c.store_diff.map(From::from), + } + } +} + +#[derive(Debug, Serialize)] +/// A record of the execution of a single VM operation. +pub struct VMOperation { + /// The program counter. + pub pc: usize, + /// The gas cost for this instruction. + pub cost: u64, + /// Information concerning the execution of the operation. + pub ex: Option, + /// Subordinate trace of the CALL/CREATE if applicable. + pub sub: Option, +} + +impl From<(et::VMOperation, Option)> for VMOperation { + fn from(c: (et::VMOperation, Option)) -> Self { + VMOperation { + pc: c.0.pc, + cost: c.0.gas_cost.low_u64(), + ex: c.0.executed.map(From::from), + sub: c.1.map(From::from), + } + } +} + +#[derive(Debug, Serialize)] +/// A record of a full VM trace for a CALL/CREATE. +pub struct VMTrace { + /// The code to be executed. + pub code: Vec, + /// The operations executed. + pub ops: Vec, +} + +impl From for VMTrace { + fn from(c: et::VMTrace) -> Self { + let mut subs = c.subs.into_iter(); + let mut next_sub = subs.next(); + VMTrace { + code: c.code, + ops: c.operations + .into_iter() + .enumerate() + .map(|(i, op)| (op, { + let have_sub = next_sub.is_some() && next_sub.as_ref().unwrap().parent_step == i; + if have_sub { + let r = next_sub.clone(); + next_sub = subs.next(); + r + } else { None } + }).into()) + .collect(), + } + } +} + #[derive(Debug, Serialize)] /// Aux type for Diff::Changed. pub struct ChangedType where T: Serialize { From e6d141e14f18b021ef7d8c3b555a91291c47a863 Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Tue, 7 Jun 2016 19:14:03 +0400 Subject: [PATCH 5/8] fixed path for testnet config (#1231) --- parity/configuration.rs | 8 +++++--- util/src/path.rs | 8 ++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/parity/configuration.rs b/parity/configuration.rs index 6185d2cf7..30d77c35d 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -285,8 +285,10 @@ impl Configuration { cors.map_or_else(Vec::new, |c| c.split(',').map(|s| s.to_owned()).collect()) } - fn geth_ipc_path() -> String { - path::ethereum::with_default("geth.ipc").to_str().unwrap().to_owned() + fn geth_ipc_path(&self) -> String { + if self.args.flag_testnet { path::ethereum::with_testnet("geth.ipc") } + else { path::ethereum::with_default("geth.ipc") } + .to_str().unwrap().to_owned() } pub fn keys_iterations(&self) -> u32 { @@ -350,7 +352,7 @@ impl Configuration { } fn ipc_path(&self) -> String { - if self.args.flag_geth { Self::geth_ipc_path() } + if self.args.flag_geth { self.geth_ipc_path() } else { Configuration::replace_home(&self.args.flag_ipcpath.clone().unwrap_or(self.args.flag_ipc_path.clone())) } } } diff --git a/util/src/path.rs b/util/src/path.rs index 3a8dcaaae..899650149 100644 --- a/util/src/path.rs +++ b/util/src/path.rs @@ -53,4 +53,12 @@ pub mod ethereum { pth.push(s); pth } + + /// Get the specific folder inside default ethereum installation configured for testnet + pub fn with_testnet(s: &str) -> PathBuf { + let mut pth = default(); + pth.push("testnet"); + pth.push(s); + pth + } } From f61ee1a5f1c9e2e32f3a7295cae55d35267482be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 7 Jun 2016 17:21:19 +0200 Subject: [PATCH 6/8] SystemUIs authorization (#1233) * Initial implementation of AuthCodeStore for SystemUIs * SystemUIs authorization * Renaming SystemUI -> SignerUI * Fixing clippy warnings * Lowering time threshold * Bumping sysui * Fixing test --- Cargo.lock | 3 +- parity/cli.rs | 8 +- parity/configuration.rs | 5 + parity/main.rs | 19 ++- parity/signer.rs | 37 ++++- rpc/src/v1/types/transaction_request.rs | 2 +- signer/Cargo.toml | 1 + signer/src/authcode_store.rs | 187 ++++++++++++++++++++++++ signer/src/lib.rs | 10 +- signer/src/ws_server/mod.rs | 11 +- signer/src/ws_server/session.rs | 54 +++++-- util/src/keys/directory.rs | 3 +- 12 files changed, 316 insertions(+), 24 deletions(-) create mode 100644 signer/src/authcode_store.rs diff --git a/Cargo.lock b/Cargo.lock index 6c3f03f1c..0f519bc84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,6 +374,7 @@ dependencies = [ "jsonrpc-core 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-minimal-sysui 0.1.0 (git+https://github.com/ethcore/parity-dapps-minimal-sysui-rs.git)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.4.6 (git+https://github.com/ethcore/ws-rs.git)", ] @@ -940,7 +941,7 @@ dependencies = [ [[package]] name = "parity-minimal-sysui" version = "0.1.0" -source = "git+https://github.com/ethcore/parity-dapps-minimal-sysui-rs.git#bc5d76f9666ce19993e6f7b636a3a7af329ea19e" +source = "git+https://github.com/ethcore/parity-dapps-minimal-sysui-rs.git#cb27ae09ee18773ccca6ba2ac74fa3128047a652" [[package]] name = "phf" diff --git a/parity/cli.rs b/parity/cli.rs index bb67ee5c6..fbaa8bd89 100644 --- a/parity/cli.rs +++ b/parity/cli.rs @@ -26,6 +26,7 @@ Usage: parity account (new | list) [options] parity import [ ] [options] parity export [ ] [options] + parity signer new-token [options] parity [options] Protocol Options: @@ -100,9 +101,11 @@ API and Console Options: [default: $HOME/.parity/dapps] --signer Enable Trusted Signer WebSocket endpoint used by - System UIs. + Signer UIs. --signer-port PORT Specify the port of Trusted Signer server [default: 8180]. + --signer-path PATH Specify directory where Signer UIs tokens should + be stored. [default: $HOME/.parity/signer] Sealing/Mining Options: --force-sealing Force the node to author new blocks as if it were @@ -205,6 +208,8 @@ pub struct Args { pub cmd_list: bool, pub cmd_export: bool, pub cmd_import: bool, + pub cmd_signer: bool, + pub cmd_new_token: bool, pub arg_pid_file: String, pub arg_file: Option, pub flag_chain: String, @@ -244,6 +249,7 @@ pub struct Args { pub flag_dapps_path: String, pub flag_signer: bool, pub flag_signer_port: u16, + pub flag_signer_path: String, pub flag_force_sealing: bool, pub flag_author: String, pub flag_usd_per_tx: String, diff --git a/parity/configuration.rs b/parity/configuration.rs index 30d77c35d..66ca93316 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -41,6 +41,7 @@ pub struct Directories { pub keys: String, pub db: String, pub dapps: String, + pub signer: String, } impl Configuration { @@ -331,11 +332,15 @@ impl Configuration { ::std::fs::create_dir_all(&keys_path).unwrap_or_else(|e| die_with_io_error("main", e)); let dapps_path = Configuration::replace_home(&self.args.flag_dapps_path); ::std::fs::create_dir_all(&dapps_path).unwrap_or_else(|e| die_with_io_error("main", e)); + let signer_path = Configuration::replace_home(&self.args.flag_signer_path); + ::std::fs::create_dir_all(&signer_path).unwrap_or_else(|e| die_with_io_error("main", e)); + Directories { keys: keys_path, db: db_path, dapps: dapps_path, + signer: signer_path, } } diff --git a/parity/main.rs b/parity/main.rs index 74d14bfd6..016b20117 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -93,7 +93,7 @@ use informant::Informant; use die::*; use cli::print_version; use rpc::RpcServer; -use signer::SignerServer; +use signer::{SignerServer, new_token}; use dapps::WebappServer; use io_handler::ClientIoHandler; use configuration::Configuration; @@ -137,6 +137,11 @@ fn execute(conf: Configuration) { return; } + if conf.args.cmd_signer { + execute_signer(conf); + return; + } + execute_client(conf, spec, client_config); } @@ -241,6 +246,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig) let signer_server = signer::start(signer::Configuration { enabled: deps_for_rpc_apis.signer_enabled, port: conf.args.flag_signer_port, + signer_path: conf.directories().signer, }, signer::Dependencies { panic_handler: panic_handler.clone(), apis: deps_for_rpc_apis.clone(), @@ -439,6 +445,17 @@ fn execute_import(conf: Configuration) { client.flush_queue(); } +fn execute_signer(conf: Configuration) { + if !conf.args.cmd_new_token { + die!("Unknown command."); + } + + let path = conf.directories().signer; + new_token(path).unwrap_or_else(|e| { + die!("Error generating token: {:?}", e) + }); +} + fn execute_account_cli(conf: Configuration) { use util::keys::store::SecretStore; use rpassword::read_password; diff --git a/parity/signer.rs b/parity/signer.rs index a7de993fb..f8ff699df 100644 --- a/parity/signer.rs +++ b/parity/signer.rs @@ -14,21 +14,28 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::io; +use std::path::PathBuf; use std::sync::Arc; use util::panics::{PanicHandler, ForwardPanic}; +use util::keys::directory::restrict_permissions_owner; use die::*; use rpc_apis; +const CODES_FILENAME: &'static str = "authcodes"; + #[cfg(feature = "ethcore-signer")] use ethcore_signer as signer; #[cfg(feature = "ethcore-signer")] pub use ethcore_signer::Server as SignerServer; + #[cfg(not(feature = "ethcore-signer"))] pub struct SignerServer; pub struct Configuration { pub enabled: bool, pub port: u16, + pub signer_path: String, } pub struct Dependencies { @@ -44,6 +51,25 @@ pub fn start(conf: Configuration, deps: Dependencies) -> Option { } } +fn codes_path(path: String) -> PathBuf { + let mut p = PathBuf::from(path); + p.push(CODES_FILENAME); + let _ = restrict_permissions_owner(&p); + p +} + + +#[cfg(feature = "ethcore-signer")] +pub fn new_token(path: String) -> io::Result<()> { + let path = codes_path(path); + let mut codes = try!(signer::AuthCodes::from_file(&path)); + let code = try!(codes.generate_new()); + try!(codes.to_file(&path)); + println!("New token has been generated. Copy the code below to your Signer UI:"); + println!("{}", code); + Ok(()) +} + #[cfg(feature = "ethcore-signer")] fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer { let addr = format!("127.0.0.1:{}", conf.port).parse().unwrap_or_else(|_| { @@ -51,7 +77,10 @@ fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer { }); let start_result = { - let server = signer::ServerBuilder::new(deps.apis.signer_queue.clone()); + let server = signer::ServerBuilder::new( + deps.apis.signer_queue.clone(), + codes_path(conf.signer_path), + ); let server = rpc_apis::setup_rpc(server, deps.apis, rpc_apis::ApiSet::SafeContext); server.start(addr) }; @@ -67,8 +96,12 @@ fn do_start(conf: Configuration, deps: Dependencies) -> SignerServer { } #[cfg(not(feature = "ethcore-signer"))] -fn do_start(conf: Configuration) -> ! { +fn do_start(_conf: Configuration) -> ! { die!("Your Parity version has been compiled without Trusted Signer support.") } +#[cfg(not(feature = "ethcore-signer"))] +pub fn new_token(_path: String) -> ! { + die!("Your Parity version has been compiled without Trusted Signer support.") +} diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 93d6a479b..e7237b2c6 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -49,7 +49,7 @@ pub struct TransactionConfirmation { pub transaction: TransactionRequest, } -/// Possible modifications to the confirmed transaction sent by SystemUI +/// Possible modifications to the confirmed transaction sent by `SignerUI` #[derive(Debug, PartialEq, Deserialize)] pub struct TransactionModification { /// Modified gas price diff --git a/signer/Cargo.toml b/signer/Cargo.toml index 22bc58e20..ae5f4b42a 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -11,6 +11,7 @@ build = "build.rs" rustc_version = "0.1" [dependencies] +rand = "0.3.14" jsonrpc-core = "2.0" log = "0.3" env_logger = "0.3" diff --git a/signer/src/authcode_store.rs b/signer/src/authcode_store.rs new file mode 100644 index 000000000..92e86a73e --- /dev/null +++ b/signer/src/authcode_store.rs @@ -0,0 +1,187 @@ +// 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 . + +use rand::Rng; +use rand::os::OsRng; +use std::io; +use std::io::{Read, Write}; +use std::fs; +use std::path::Path; +use std::time; +use util::{H256, Hashable}; + +/// Providing current time in seconds +pub trait TimeProvider { + /// Returns timestamp (in seconds since epoch) + fn now(&self) -> u64; +} + +impl u64> TimeProvider for F { + fn now(&self) -> u64 { + self() + } +} + +/// Default implementation of `TimeProvider` using system time. +#[derive(Default)] +pub struct DefaultTimeProvider; + +impl TimeProvider for DefaultTimeProvider { + fn now(&self) -> u64 { + time::UNIX_EPOCH.elapsed().expect("Valid time has to be set in your system.").as_secs() + } +} + +/// No of seconds the hash is valid +const TIME_THRESHOLD: u64 = 2; +const TOKEN_LENGTH: usize = 16; + +/// Manages authorization codes for `SignerUIs` +pub struct AuthCodes { + codes: Vec, + now: T, +} + +impl AuthCodes { + + /// Reads `AuthCodes` from file and creates new instance using `DefaultTimeProvider`. + pub fn from_file(file: &Path) -> io::Result { + let content = { + if let Ok(mut file) = fs::File::open(file) { + let mut s = String::new(); + let _ = try!(file.read_to_string(&mut s)); + s + } else { + "".into() + } + }; + let codes = content.lines() + .filter(|f| f.len() >= TOKEN_LENGTH) + .map(String::from) + .collect(); + Ok(AuthCodes { + codes: codes, + now: DefaultTimeProvider::default(), + }) + } + +} + +impl AuthCodes { + + /// Writes all `AuthCodes` to a disk. + pub fn to_file(&self, file: &Path) -> io::Result<()> { + let mut file = try!(fs::File::create(file)); + let content = self.codes.join("\n"); + file.write_all(content.as_bytes()) + } + + /// Creates a new `AuthCodes` store with given `TimeProvider`. + pub fn new(codes: Vec, now: T) -> Self { + AuthCodes { + codes: codes, + now: now, + } + } + + /// Checks if given hash is correct identifier of `SignerUI` + pub fn is_valid(&self, hash: &H256, time: u64) -> bool { + let now = self.now.now(); + // check time + if time >= now + TIME_THRESHOLD || time <= now - TIME_THRESHOLD { + warn!(target: "signer", "Received old authentication request."); + return false; + } + + // look for code + self.codes.iter() + .any(|code| &format!("{}:{}", code, time).sha3() == hash) + } + + /// Generates and returns a new code that can be used by `SignerUIs` + pub fn generate_new(&mut self) -> io::Result { + let mut rng = try!(OsRng::new()); + let code = rng.gen_ascii_chars().take(TOKEN_LENGTH).collect::(); + let readable_code = code.as_bytes() + .chunks(4) + .filter_map(|f| String::from_utf8(f.to_vec()).ok()) + .collect::>() + .join("-"); + info!(target: "signer", "New authentication token generated."); + self.codes.push(code); + Ok(readable_code) + } +} + + +#[cfg(test)] +mod tests { + + use util::{H256, Hashable}; + use super::*; + + fn generate_hash(val: &str, time: u64) -> H256 { + format!("{}:{}", val, time).sha3() + } + + #[test] + fn should_return_true_if_hash_is_valid() { + // given + let code = "23521352asdfasdfadf"; + let time = 99; + let codes = AuthCodes::new(vec![code.into()], || 100); + + // when + let res = codes.is_valid(&generate_hash(code, time), time); + + // then + assert_eq!(res, true); + } + + #[test] + fn should_return_false_if_code_is_unknown() { + // given + let code = "23521352asdfasdfadf"; + let time = 99; + let codes = AuthCodes::new(vec!["1".into()], || 100); + + // when + let res = codes.is_valid(&generate_hash(code, time), time); + + // then + assert_eq!(res, false); + } + + #[test] + fn should_return_false_if_hash_is_valid_but_time_is_invalid() { + // given + let code = "23521352asdfasdfadf"; + let time = 105; + let time2 = 95; + let codes = AuthCodes::new(vec![code.into()], || 100); + + // when + let res1 = codes.is_valid(&generate_hash(code, time), time); + let res2 = codes.is_valid(&generate_hash(code, time2), time2); + + // then + assert_eq!(res1, false); + assert_eq!(res2, false); + } + +} + + diff --git a/signer/src/lib.rs b/signer/src/lib.rs index fb3e76cca..3aaed8bcf 100644 --- a/signer/src/lib.rs +++ b/signer/src/lib.rs @@ -23,8 +23,8 @@ //! This module manages your private keys and accounts/identities //! that can be used within Dapps. //! -//! It exposes API (over `WebSockets`) accessed by System UIs. -//! Each transaction sent by Dapp is broadcasted to System UIs +//! It exposes API (over `WebSockets`) accessed by Signer UIs. +//! Each transaction sent by Dapp is broadcasted to Signer UIs //! and their responsibility is to confirm (or confirm and sign) //! the transaction for you. //! @@ -38,13 +38,14 @@ //! //! fn main() { //! let queue = Arc::new(ConfirmationsQueue::default()); -//! let _server = ServerBuilder::new(queue).start("127.0.0.1:8084".parse().unwrap()); +//! let _server = ServerBuilder::new(queue, "/tmp/authcodes".into()).start("127.0.0.1:8084".parse().unwrap()); //! } //! ``` #[macro_use] extern crate log; extern crate env_logger; +extern crate rand; extern crate ethcore_util as util; extern crate ethcore_rpc as rpc; @@ -52,7 +53,10 @@ extern crate jsonrpc_core; extern crate ws; extern crate parity_minimal_sysui as sysui; +mod authcode_store; mod ws_server; + +pub use authcode_store::*; pub use ws_server::*; #[cfg(test)] diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs index 3b4924230..76c0fbb16 100644 --- a/signer/src/ws_server/mod.rs +++ b/signer/src/ws_server/mod.rs @@ -19,6 +19,7 @@ use ws; use std; use std::thread; +use std::path::PathBuf; use std::default::Default; use std::ops::Drop; use std::sync::Arc; @@ -51,6 +52,7 @@ impl From for ServerError { pub struct ServerBuilder { queue: Arc, handler: Arc, + authcodes_path: PathBuf, } impl Extendable for ServerBuilder { @@ -61,17 +63,18 @@ impl Extendable for ServerBuilder { impl ServerBuilder { /// Creates new `ServerBuilder` - pub fn new(queue: Arc) -> Self { + pub fn new(queue: Arc, authcodes_path: PathBuf) -> Self { ServerBuilder { queue: queue, handler: Arc::new(IoHandler::new()), + authcodes_path: authcodes_path, } } /// 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::start(addr, self.handler, self.queue) + Server::start(addr, self.handler, self.queue, self.authcodes_path) } } @@ -86,7 +89,7 @@ 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, queue: Arc) -> Result { + fn start(addr: SocketAddr, handler: Arc, queue: Arc, authcodes_path: PathBuf) -> Result { let config = { let mut config = ws::Settings::default(); config.max_connections = 10; @@ -96,7 +99,7 @@ impl Server { // Create WebSocket let origin = format!("{}", addr); - let ws = try!(ws::Builder::new().with_settings(config).build(session::Factory::new(handler, origin))); + let ws = try!(ws::Builder::new().with_settings(config).build(session::Factory::new(handler, origin, authcodes_path))); let panic_handler = PanicHandler::new_in_arc(); let ph = panic_handler.clone(); diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs index 54be63eaf..153bf6622 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -18,8 +18,12 @@ use ws; use sysui; +use authcode_store::AuthCodes; +use std::path::{PathBuf, Path}; use std::sync::Arc; +use std::str::FromStr; use jsonrpc_core::IoHandler; +use util::H256; fn origin_is_allowed(self_origin: &str, header: Option<&Vec>) -> bool { match header { @@ -36,13 +40,32 @@ fn origin_is_allowed(self_origin: &str, header: Option<&Vec>) -> bool { } } -fn auth_is_valid(_header: Option<&Vec>) -> bool { - true +fn auth_is_valid(codes: &Path, protocols: ws::Result>) -> bool { + match protocols { + Ok(ref protocols) if protocols.len() == 1 => { + protocols.iter().any(|protocol| { + let mut split = protocol.split('_'); + let auth = split.next().and_then(|v| H256::from_str(v).ok()); + let time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok()); + + if let (Some(auth), Some(time)) = (auth, time) { + // Check if the code is valid + AuthCodes::from_file(codes) + .map(|codes| codes.is_valid(&auth, time)) + .unwrap_or(false) + } else { + false + } + }) + }, + _ => false + } } pub struct Session { out: ws::Sender, self_origin: String, + authcodes_path: PathBuf, handler: Arc, } @@ -53,17 +76,25 @@ impl ws::Handler for Session { // Check request origin and host header. if !origin_is_allowed(&self.self_origin, origin) && !origin_is_allowed(&self.self_origin, host) { + warn!(target: "signer", "Blocked connection to Signer API from untrusted origin."); return Ok(ws::Response::forbidden("You are not allowed to access system ui.".into())); } - // Check authorization - if !auth_is_valid(req.header("authorization")) { - return Ok(ws::Response::forbidden("You are not authorized.".into())); - } - // Detect if it's a websocket request. if req.header("sec-websocket-key").is_some() { - return ws::Response::from_request(req); + // Check authorization + if !auth_is_valid(&self.authcodes_path, req.protocols()) { + info!(target: "signer", "Unauthorized connection to Signer API blocked."); + return Ok(ws::Response::forbidden("You are not authorized.".into())); + } + + let protocols = req.protocols().expect("Existence checked by authorization."); + let protocol = protocols.get(0).expect("Proved by authorization."); + return ws::Response::from_request(req).map(|mut res| { + // To make WebSockets connection successful we need to send back the protocol header. + res.set_protocol(protocol); + res + }); } // Otherwise try to serve a page. @@ -101,13 +132,15 @@ impl ws::Handler for Session { pub struct Factory { handler: Arc, self_origin: String, + authcodes_path: PathBuf, } impl Factory { - pub fn new(handler: Arc, self_origin: String) -> Self { + pub fn new(handler: Arc, self_origin: String, authcodes_path: PathBuf) -> Self { Factory { handler: handler, self_origin: self_origin, + authcodes_path: authcodes_path, } } } @@ -118,8 +151,9 @@ impl ws::Factory for Factory { fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler { Session { out: sender, - self_origin: self.self_origin.clone(), handler: self.handler.clone(), + self_origin: self.self_origin.clone(), + authcodes_path: self.authcodes_path.clone(), } } } diff --git a/util/src/keys/directory.rs b/util/src/keys/directory.rs index 3f4100163..20be7df7b 100644 --- a/util/src/keys/directory.rs +++ b/util/src/keys/directory.rs @@ -465,7 +465,8 @@ pub struct KeyDirectory { cache_usage: RwLock>, } -fn restrict_permissions_owner(file_path: &Path) -> Result<(), i32> { +/// Restricts the permissions of given path only to the owner. +pub fn restrict_permissions_owner(file_path: &Path) -> Result<(), i32> { let cstr = ::std::ffi::CString::new(file_path.to_str().unwrap()).unwrap(); match unsafe { ::libc::chmod(cstr.as_ptr(), ::libc::S_IWUSR | ::libc::S_IRUSR) } { 0 => Ok(()), From bf9173e673b8c76103f8ae13fe460de9f8ba32c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 7 Jun 2016 17:25:01 +0200 Subject: [PATCH 7/8] Fixing signer behaviour when confirming transaction with wrong password. (#1237) * Avoid removing transactions when trying to confirm and the password is invalid * Fix order --- dapps/src/apps/mod.rs | 2 +- rpc/src/v1/impls/personal_signer.rs | 3 +-- rpc/src/v1/tests/mocked/personal_signer.rs | 24 ++++++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 11c693c37..9f6a5c745 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -47,8 +47,8 @@ pub fn all_endpoints(dapps_path: String) -> Endpoints { PageEndpoint::new_safe_to_embed(parity_dapps_builtins::App::default()) )); pages.insert("proxy".into(), ProxyPac::boxed()); - insert::(&mut pages, "status"); insert::(&mut pages, "parity"); + insert::(&mut pages, "status"); // Optional dapps wallet_page(&mut pages); diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs index 148330ced..88bf9a5d1 100644 --- a/rpc/src/v1/impls/personal_signer.rs +++ b/rpc/src/v1/impls/personal_signer.rs @@ -81,8 +81,7 @@ impl PersonalSigner for SignerClient Date: Tue, 7 Jun 2016 19:33:32 +0200 Subject: [PATCH 8/8] Signer RPC method to check if signer is enabled (#1238) * API to check if signer is enabled * Fixing compilation warnings --- parity/rpc_apis.rs | 2 +- rpc/src/v1/impls/personal.rs | 9 ++++++++- rpc/src/v1/impls/traces.rs | 2 +- rpc/src/v1/tests/mocked/personal.rs | 16 +++++++++++++++- rpc/src/v1/traits/personal.rs | 4 ++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index b54693582..34dd3a387 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -147,7 +147,7 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet } }, Api::Personal => { - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner).to_delegate()); + server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner, deps.signer_enabled).to_delegate()); if deps.signer_enabled { server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_queue).to_delegate()); } diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 93d13aed7..bb570a4e0 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -31,22 +31,29 @@ pub struct PersonalClient accounts: Weak, client: Weak, miner: Weak, + signer_enabled: bool, } impl PersonalClient where A: AccountProvider, C: MiningBlockChainClient, M: MinerService { /// Creates new PersonalClient - pub fn new(store: &Arc, client: &Arc, miner: &Arc) -> Self { + pub fn new(store: &Arc, client: &Arc, miner: &Arc, signer_enabled: bool) -> Self { PersonalClient { accounts: Arc::downgrade(store), client: Arc::downgrade(client), miner: Arc::downgrade(miner), + signer_enabled: signer_enabled, } } } impl Personal for PersonalClient where A: AccountProvider, C: MiningBlockChainClient, M: MinerService { + + fn signer_enabled(&self, _: Params) -> Result { + to_value(&self.signer_enabled) + } + fn accounts(&self, _: Params) -> Result { let store = take_weak!(self.accounts); match store.accounts() { diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 9e5ca0093..caf549c84 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -19,7 +19,7 @@ use std::sync::{Weak, Arc}; use jsonrpc_core::*; use std::collections::BTreeMap; -use util::{H256, U256, Uint}; +use util::H256; use ethcore::client::{BlockChainClient, CallAnalytics, TransactionID, TraceId}; use ethcore::miner::MinerService; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 8bc3ab3c8..16d8e620f 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -53,7 +53,7 @@ fn setup() -> PersonalTester { let accounts = accounts_provider(); let client = blockchain_client(); let miner = miner_service(); - let personal = PersonalClient::new(&accounts, &client, &miner); + let personal = PersonalClient::new(&accounts, &client, &miner, false); let io = IoHandler::new(); io.add_delegate(personal.to_delegate()); @@ -68,6 +68,20 @@ fn setup() -> PersonalTester { tester } +#[test] +fn should_return_false_if_signer_is_disabled() { + // given + let tester = setup(); + + // when + let request = r#"{"jsonrpc": "2.0", "method": "personal_signerEnabled", "params": [], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + + + // then + assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); +} + #[test] fn accounts() { let tester = setup(); diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index a36358766..c2c6a9625 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -33,9 +33,13 @@ pub trait Personal: Sized + Send + Sync + 'static { /// Sends transaction and signs it in single call. The account is not unlocked in such case. fn sign_and_send_transaction(&self, _: Params) -> Result; + /// Returns `true` if Trusted Signer is enabled, `false` otherwise. + fn signer_enabled(&self, _: Params) -> Result; + /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); + delegate.add_method("personal_signerEnabled", Personal::signer_enabled); delegate.add_method("personal_listAccounts", Personal::accounts); delegate.add_method("personal_newAccount", Personal::new_account); delegate.add_method("personal_unlockAccount", Personal::unlock_account);