diff --git a/Cargo.lock b/Cargo.lock index f01839f0b..2d0803f65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,11 +289,11 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", - "parity-dapps-builtins 0.5.0 (git+https://github.com/ethcore/parity-dapps-builtins-rs.git)", - "parity-dapps-dao 0.3.0 (git+https://github.com/ethcore/parity-dapps-dao-rs.git)", - "parity-dapps-makerotc 0.2.0 (git+https://github.com/ethcore/parity-dapps-makerotc-rs.git)", + "parity-dapps-builtins 0.5.1 (git+https://github.com/ethcore/parity-dapps-builtins-rs.git)", + "parity-dapps-dao 0.4.0 (git+https://github.com/ethcore/parity-dapps-dao-rs.git)", + "parity-dapps-makerotc 0.3.0 (git+https://github.com/ethcore/parity-dapps-makerotc-rs.git)", "parity-dapps-status 0.5.0 (git+https://github.com/ethcore/parity-dapps-status-rs.git)", - "parity-dapps-wallet 0.5.0 (git+https://github.com/ethcore/parity-dapps-wallet-rs.git)", + "parity-dapps-wallet 0.6.0 (git+https://github.com/ethcore/parity-dapps-wallet-rs.git)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -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)", ] @@ -899,24 +900,24 @@ dependencies = [ [[package]] name = "parity-dapps-builtins" -version = "0.5.0" -source = "git+https://github.com/ethcore/parity-dapps-builtins-rs.git#8bbf0421e376f9496d70adc62c1c6d7f492df817" +version = "0.5.1" +source = "git+https://github.com/ethcore/parity-dapps-builtins-rs.git#d3c95d62ffaa57016b162a9a9f0e6dd629dab423" dependencies = [ "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", ] [[package]] name = "parity-dapps-dao" -version = "0.3.0" -source = "git+https://github.com/ethcore/parity-dapps-dao-rs.git#5723ccc5b93010caa8f4aee248d89ae13401389b" +version = "0.4.0" +source = "git+https://github.com/ethcore/parity-dapps-dao-rs.git#18f4b839b20fbdf8e0d163e14d25aafee603ac4b" dependencies = [ "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", ] [[package]] name = "parity-dapps-makerotc" -version = "0.2.0" -source = "git+https://github.com/ethcore/parity-dapps-makerotc-rs.git#39e654469ab659dc5570ba1ec94ca0f943aaff16" +version = "0.3.0" +source = "git+https://github.com/ethcore/parity-dapps-makerotc-rs.git#7b771f217a3eefeb9a976c7ed470ca49fd9a9daa" dependencies = [ "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", ] @@ -924,15 +925,15 @@ dependencies = [ [[package]] name = "parity-dapps-status" version = "0.5.0" -source = "git+https://github.com/ethcore/parity-dapps-status-rs.git#e1caeeacf4b29da586572798881974521e6e8caf" +source = "git+https://github.com/ethcore/parity-dapps-status-rs.git#53e159f52013be5d2e8ba7eca35f605ad6e3bfa9" dependencies = [ "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-rs.git)", ] [[package]] name = "parity-dapps-wallet" -version = "0.5.0" -source = "git+https://github.com/ethcore/parity-dapps-wallet-rs.git#25402ce0a02ae49eb66c9e3852b392267a027ea3" +version = "0.6.0" +source = "git+https://github.com/ethcore/parity-dapps-wallet-rs.git#ad23b093d47527333a262c95e6fb20a97d15d6e6" dependencies = [ "parity-dapps 0.3.0 (git+https://github.com/ethcore/parity-dapps-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" @@ -1453,7 +1454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" version = "0.4.6" -source = "git+https://github.com/ethcore/ws-rs.git#c0c2a3fc30dc77c4e6d4d90756f8bc3b5cfbc311" +source = "git+https://github.com/ethcore/ws-rs.git#5b28de58421b017b01f4565b2c35a46679707789" dependencies = [ "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 7f1968d02..2394df31a 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -24,9 +24,9 @@ parity-dapps = { git = "https://github.com/ethcore/parity-dapps-rs.git", version # List of apps parity-dapps-status = { git = "https://github.com/ethcore/parity-dapps-status-rs.git", version = "0.5.0" } parity-dapps-builtins = { git = "https://github.com/ethcore/parity-dapps-builtins-rs.git", version = "0.5.0" } -parity-dapps-wallet = { git = "https://github.com/ethcore/parity-dapps-wallet-rs.git", version = "0.5.0", optional = true } -parity-dapps-dao = { git = "https://github.com/ethcore/parity-dapps-dao-rs.git", version = "0.3.0", optional = true } -parity-dapps-makerotc = { git = "https://github.com/ethcore/parity-dapps-makerotc-rs.git", version = "0.2.0", optional = true } +parity-dapps-wallet = { git = "https://github.com/ethcore/parity-dapps-wallet-rs.git", version = "0.6.0", optional = true } +parity-dapps-dao = { git = "https://github.com/ethcore/parity-dapps-dao-rs.git", version = "0.4.0", optional = true } +parity-dapps-makerotc = { git = "https://github.com/ethcore/parity-dapps-makerotc-rs.git", version = "0.3.0", optional = true } mime_guess = { version = "1.6.1" } clippy = { version = "0.0.71", optional = true} 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/ethcore/src/account.rs b/ethcore/src/account.rs index 5e0dc335a..29922dda5 100644 --- a/ethcore/src/account.rs +++ b/ethcore/src/account.rs @@ -116,7 +116,12 @@ impl Account { /// Get (and cache) the contents of the trie's storage at `key`. pub fn storage_at(&self, db: &AccountDB, key: &H256) -> H256 { self.storage_overlay.borrow_mut().entry(key.clone()).or_insert_with(||{ - (Filth::Clean, H256::from(SecTrieDB::new(db, &self.storage_root).get(key.bytes()).map_or(U256::zero(), |v| -> U256 {decode(v)}))) + let db = SecTrieDB::new(db, &self.storage_root) + .expect("Account storage_root initially set to zero (valid) and only altered by SecTrieDBMut. \ + SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \ + using it will not fail."); + + (Filth::Clean, H256::from(db.get(key.bytes()).map_or(U256::zero(), |v| -> U256 {decode(v)}))) }).1.clone() } @@ -204,7 +209,10 @@ impl Account { /// Commit the `storage_overlay` to the backing DB and update `storage_root`. pub fn commit_storage(&mut self, db: &mut AccountDBMut) { - let mut t = SecTrieDBMut::from_existing(db, &mut self.storage_root); + let mut t = SecTrieDBMut::from_existing(db, &mut self.storage_root) + .expect("Account storage_root initially set to zero (valid) and only altered by SecTrieDBMut. \ + SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \ + using it will not fail."); for (k, &mut (ref mut f, ref mut v)) in self.storage_overlay.borrow_mut().iter_mut() { if f == &Filth::Dirty { // cast key and value to trait type, diff --git a/ethcore/src/basic_authority.rs b/ethcore/src/basic_authority.rs index b4a938642..c732ce711 100644 --- a/ethcore/src/basic_authority.rs +++ b/ethcore/src/basic_authority.rs @@ -278,7 +278,7 @@ mod tests { spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let vm_factory = Default::default(); - let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, addr.clone(), 3141562.into(), vec![]); + let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, addr.clone(), 3141562.into(), vec![]).unwrap(); let b = b.close_and_lock(); let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 0b75f5a7e..74f54a057 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -38,23 +38,7 @@ pub struct Block { impl Block { /// Returns true if the given bytes form a valid encoding of a block in RLP. - // TODO: implement Decoder for this and have this use that. pub fn is_good(b: &[u8]) -> bool { - /* - let urlp = UntrustedRlp::new(&b); - if !urlp.is_list() || urlp.item_count() != 3 || urlp.size() != b.len() { return false; } - if urlp.val_at::
(0).is_err() { return false; } - - if !urlp.at(1).unwrap().is_list() { return false; } - if urlp.at(1).unwrap().iter().find(|i| i.as_val::().is_err()).is_some() { - return false; - } - - if !urlp.at(2).unwrap().is_list() { return false; } - if urlp.at(2).unwrap().iter().find(|i| i.as_val::
().is_err()).is_some() { - return false; - } - true*/ UntrustedRlp::new(b).as_val::().is_ok() } } @@ -228,9 +212,20 @@ pub struct SealedBlock { impl<'x> OpenBlock<'x> { #[cfg_attr(feature="dev", allow(too_many_arguments))] /// Create a new `OpenBlock` ready for transaction pushing. - pub fn new(engine: &'x Engine, vm_factory: &'x EvmFactory, tracing: bool, db: Box, parent: &Header, last_hashes: LastHashes, author: Address, gas_floor_target: U256, extra_data: Bytes) -> Self { + pub fn new( + engine: &'x Engine, + vm_factory: &'x EvmFactory, + tracing: bool, + db: Box, + parent: &Header, + last_hashes: LastHashes, + author: Address, + gas_floor_target: U256, + extra_data: Bytes + ) -> Result { + let state = try!(State::from_existing(db, parent.state_root().clone(), engine.account_start_nonce())); let mut r = OpenBlock { - block: ExecutedBlock::new(State::from_existing(db, parent.state_root().clone(), engine.account_start_nonce()), tracing), + block: ExecutedBlock::new(state, tracing), engine: engine, vm_factory: vm_factory, last_hashes: last_hashes, @@ -245,7 +240,7 @@ impl<'x> OpenBlock<'x> { engine.populate_from_parent(&mut r.block.base.header, parent, gas_floor_target); engine.on_new_block(&mut r.block); - r + Ok(r) } /// Alter the author for the block. @@ -464,12 +459,12 @@ impl IsBlock for SealedBlock { pub fn enact(header: &Header, transactions: &[SignedTransaction], uncles: &[Header], engine: &Engine, tracing: bool, db: Box, parent: &Header, last_hashes: LastHashes, vm_factory: &EvmFactory) -> Result { { if ::log::max_log_level() >= ::log::LogLevel::Trace { - let s = State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce()); + let s = try!(State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce())); trace!("enact(): root={}, author={}, author_balance={}\n", s.root(), header.author(), s.balance(&header.author())); } } - let mut b = OpenBlock::new(engine, vm_factory, tracing, db, parent, last_hashes, header.author().clone(), 3141562.into(), header.extra_data().clone()); + let mut b = try!(OpenBlock::new(engine, vm_factory, tracing, db, parent, last_hashes, header.author().clone(), 3141562.into(), header.extra_data().clone())); b.set_difficulty(*header.difficulty()); b.set_gas_limit(*header.gas_limit()); b.set_timestamp(header.timestamp()); @@ -514,7 +509,7 @@ mod tests { spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let vm_factory = Default::default(); - let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, Address::zero(), 3141562.into(), vec![]); + let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, Address::zero(), 3141562.into(), vec![]).unwrap(); let b = b.close_and_lock(); let _ = b.seal(engine.deref(), vec![]); } @@ -530,7 +525,8 @@ mod tests { let mut db = db_result.take(); spec.ensure_db_good(db.as_hashdb_mut()); let vm_factory = Default::default(); - let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), 3141562.into(), vec![]).close_and_lock().seal(engine.deref(), vec![]).unwrap(); + let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), 3141562.into(), vec![]).unwrap() + .close_and_lock().seal(engine.deref(), vec![]).unwrap(); let orig_bytes = b.rlp_bytes(); let orig_db = b.drain(); @@ -557,7 +553,7 @@ mod tests { let mut db = db_result.take(); spec.ensure_db_good(db.as_hashdb_mut()); let vm_factory = Default::default(); - let mut open_block = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), 3141562.into(), vec![]); + let mut open_block = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), 3141562.into(), vec![]).unwrap(); let mut uncle1_header = Header::new(); uncle1_header.extra_data = b"uncle1".to_vec(); let mut uncle2_header = Header::new(); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index fcf8cbdd4..213ee9755 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -386,18 +386,14 @@ impl Client where V: Verifier { let root = HeaderView::new(&header).state_root(); - // TODO [rob]: refactor State::from_existing so we avoid doing redundant lookups. - if !db.contains(&root) { - return None; - } - - Some(State::from_existing(db, root, self.engine.account_start_nonce())) + State::from_existing(db, root, self.engine.account_start_nonce()).ok() }) } /// Get a copy of the best block's state. pub fn state(&self) -> State { State::from_existing(self.state_db.lock().unwrap().boxed_clone(), HeaderView::new(&self.best_block_header()).state_root(), self.engine.account_start_nonce()) + .expect("State root of best block header always valid.") } /// Get info on the cache. @@ -479,7 +475,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. @@ -774,7 +770,8 @@ impl MiningBlockChainClient for Client where V: Verifier { author, gas_floor_target, extra_data, - ); + ).expect("OpenBlock::new only fails if parent state root invalid. State root of best block's header is never invalid. \ + Therefore creating an OpenBlock with the best block's header will not fail."); // Add uncles self.chain diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 7579f75f2..eda850d9d 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -51,6 +51,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/error.rs b/ethcore/src/error.rs index 3d7c148ba..98aec917a 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -224,6 +224,8 @@ pub enum Error { PowHashInvalid, /// The value of the nonce or mishash is invalid. PowInvalid, + /// Error concerning TrieDBs + TrieError(TrieError), } impl fmt::Display for Error { @@ -239,6 +241,7 @@ impl fmt::Display for Error { f.write_fmt(format_args!("Unknown engine name ({})", name)), Error::PowHashInvalid => f.write_str("Invalid or out of date PoW hash."), Error::PowInvalid => f.write_str("Invalid nonce or mishash"), + Error::TrieError(ref err) => f.write_fmt(format_args!("{}", err)), } } } @@ -300,6 +303,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: TrieError) -> Error { + Error::TrieError(err) + } +} + // TODO: uncomment below once https://github.com/rust-lang/rust/issues/27336 sorted. /*#![feature(concat_idents)] macro_rules! assimilate { diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 1dadb8a65..746da9069 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -308,7 +308,7 @@ mod tests { spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let vm_factory = Default::default(); - let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, Address::zero(), 3141562.into(), vec![]); + let b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, Address::zero(), 3141562.into(), vec![]).unwrap(); let b = b.close(); assert_eq!(b.state().balance(&Address::zero()), U256::from_str("4563918244f40000").unwrap()); } @@ -323,7 +323,7 @@ mod tests { spec.ensure_db_good(db.as_hashdb_mut()); let last_hashes = vec![genesis_header.hash()]; let vm_factory = Default::default(); - let mut b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, Address::zero(), 3141562.into(), vec![]); + let mut b = OpenBlock::new(engine.deref(), &vm_factory, false, db, &genesis_header, last_hashes, Address::zero(), 3141562.into(), vec![]).unwrap(); let mut uncle = Header::new(); let uncle_author = address_from_hex("ef2d6d194084c2de36e0dabfce45d046b37d1106"); uncle.author = uncle_author.clone(); diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index 1f5712c54..9c9e48342 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -62,7 +62,7 @@ mod tests { let mut db_result = get_temp_journal_db(); let mut db = db_result.take(); spec.ensure_db_good(db.as_hashdb_mut()); - let s = State::from_existing(db, genesis_header.state_root.clone(), engine.account_start_nonce()); + let s = State::from_existing(db, genesis_header.state_root.clone(), engine.account_start_nonce()).unwrap(); assert_eq!(s.balance(&address_from_hex("0000000000000000000000000000000000000001")), U256::from(1u64)); assert_eq!(s.balance(&address_from_hex("0000000000000000000000000000000000000002")), U256::from(1u64)); assert_eq!(s.balance(&address_from_hex("0000000000000000000000000000000000000003")), U256::from(1u64)); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 489173996..ceddb31bc 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -20,7 +20,7 @@ use std::sync::atomic::AtomicBool; use util::*; use util::keys::store::{AccountProvider}; use views::{BlockView, HeaderView}; -use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockChainClient, BlockID, CallAnalytics}; +use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; use block::{ClosedBlock, IsBlock}; use error::*; use transaction::SignedTransaction; @@ -279,7 +279,7 @@ impl MinerService for Miner { // 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(), chain.vm_factory()).transact(t, options); // TODO gav move this into Executive. diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index 06a365bdd..204cea618 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -44,6 +44,9 @@ pub struct State { account_start_nonce: U256, } +const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with valid root. Creating a SecTrieDB with a valid root will not fail. \ + Therefore creating a SecTrieDB with this state's root will not fail."; + impl State { /// Creates new state with empty state root #[cfg(test)] @@ -64,18 +67,17 @@ impl State { } /// Creates new state with existing state root - pub fn from_existing(db: Box, root: H256, account_start_nonce: U256) -> State { - { - // trie should panic! if root does not exist - let _ = SecTrieDB::new(db.as_hashdb(), &root); - } - - State { - db: db, - root: root, - cache: RefCell::new(HashMap::new()), - snapshots: RefCell::new(Vec::new()), - account_start_nonce: account_start_nonce, + pub fn from_existing(db: Box, root: H256, account_start_nonce: U256) -> Result { + if !db.as_hashdb().contains(&root) { + Err(TrieError::InvalidStateRoot) + } else { + Ok(State { + db: db, + root: root, + cache: RefCell::new(HashMap::new()), + snapshots: RefCell::new(Vec::new()), + account_start_nonce: account_start_nonce, + }) } } @@ -154,7 +156,8 @@ impl State { /// Determine whether an account exists. pub fn exists(&self, a: &Address) -> bool { - self.cache.borrow().get(&a).unwrap_or(&None).is_some() || SecTrieDB::new(self.db.as_hashdb(), &self.root).contains(&a) + let db = SecTrieDB::new(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); + self.cache.borrow().get(&a).unwrap_or(&None).is_some() || db.contains(&a) } /// Get the balance of account `a`. @@ -245,7 +248,7 @@ impl State { } { - let mut trie = SecTrieDBMut::from_existing(db, root); + let mut trie = SecTrieDBMut::from_existing(db, root).unwrap(); for (address, ref a) in accounts.iter() { match **a { Some(ref account) => trie.insert(address, &account.rlp()), @@ -287,7 +290,7 @@ impl State { fn query_pod(&mut self, query: &PodState) { for (ref address, ref pod_account) in query.get() { if self.get(address, true).is_some() { - for (ref key, _) in &pod_account.storage { + for key in pod_account.storage.keys() { self.storage_at(address, key); } } @@ -308,7 +311,8 @@ impl State { fn get<'a>(&'a self, a: &Address, require_code: bool) -> &'a Option { let have_key = self.cache.borrow().contains_key(a); if !have_key { - self.insert_cache(a, SecTrieDB::new(self.db.as_hashdb(), &self.root).get(&a).map(Account::from_rlp)) + let db = SecTrieDB::new(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); + self.insert_cache(a, db.get(&a).map(Account::from_rlp)) } if require_code { if let Some(ref mut account) = self.cache.borrow_mut().get_mut(a).unwrap().as_mut() { @@ -328,7 +332,8 @@ impl State { fn require_or_from<'a, F: FnOnce() -> Account, G: FnOnce(&mut Account)>(&self, a: &Address, require_code: bool, default: F, not_default: G) -> &'a mut Account { let have_key = self.cache.borrow().contains_key(a); if !have_key { - self.insert_cache(a, SecTrieDB::new(self.db.as_hashdb(), &self.root).get(&a).map(Account::from_rlp)) + let db = SecTrieDB::new(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); + self.insert_cache(a, db.get(&a).map(Account::from_rlp)) } else { self.note_cache(a); } @@ -1145,7 +1150,7 @@ fn code_from_database() { state.drop() }; - let state = State::from_existing(db, root, U256::from(0u8)); + let state = State::from_existing(db, root, U256::from(0u8)).unwrap(); assert_eq!(state.code(&a), Some([1u8, 2, 3].to_vec())); } @@ -1160,7 +1165,7 @@ fn storage_at_from_database() { state.drop() }; - let s = State::from_existing(db, root, U256::from(0u8)); + let s = State::from_existing(db, root, U256::from(0u8)).unwrap(); assert_eq!(s.storage_at(&a, &H256::from(&U256::from(01u64))), H256::from(&U256::from(69u64))); } @@ -1177,7 +1182,7 @@ fn get_from_database() { state.drop() }; - let state = State::from_existing(db, root, U256::from(0u8)); + let state = State::from_existing(db, root, U256::from(0u8)).unwrap(); assert_eq!(state.balance(&a), U256::from(69u64)); assert_eq!(state.nonce(&a), U256::from(1u64)); } @@ -1210,7 +1215,7 @@ fn remove_from_database() { }; let (root, db) = { - let mut state = State::from_existing(db, root, U256::from(0u8)); + let mut state = State::from_existing(db, root, U256::from(0u8)).unwrap(); assert_eq!(state.exists(&a), true); assert_eq!(state.nonce(&a), U256::from(1u64)); state.kill_account(&a); @@ -1220,7 +1225,7 @@ fn remove_from_database() { state.drop() }; - let state = State::from_existing(db, root, U256::from(0u8)); + let state = State::from_existing(db, root, U256::from(0u8)).unwrap(); assert_eq!(state.exists(&a), false); assert_eq!(state.nonce(&a), U256::from(0u64)); } diff --git a/parity/cli.rs b/parity/cli.rs index bb67ee5c6..b8aa88299 100644 --- a/parity/cli.rs +++ b/parity/cli.rs @@ -23,9 +23,11 @@ Parity. Ethereum Client. Usage: parity daemon [options] - parity account (new | list) [options] + parity account (new | list ) [options] + parity account import ... [options] parity import [ ] [options] parity export [ ] [options] + parity signer new-token [options] parity [options] Protocol Options: @@ -75,14 +77,14 @@ API and Console Options: --jsonrpc-apis APIS Specify the APIs available through the JSONRPC interface. APIS is a comma-delimited list of API name. Possible name are web3, eth, net, personal, - ethcore, traces. - [default: web3,eth,net,personal,traces]. + ethcore, ethcore_set, traces. + [default: web3,eth,net,ethcore,personal,traces]. --ipc-off Disable JSON-RPC over IPC service. --ipc-path PATH Specify custom path for JSON-RPC over IPC service [default: $HOME/.parity/jsonrpc.ipc]. --ipc-apis APIS Specify custom API set available via JSON-RPC over - IPC [default: web3,eth,net,personal,traces]. + IPC [default: web3,eth,net,ethcore,personal,traces]. --dapps-off Disable the Dapps server (e.g. status page). --dapps-port PORT Specify the port portion of the Dapps server @@ -100,9 +102,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,8 +209,11 @@ 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 arg_path: Vec, pub flag_chain: String, pub flag_db_path: String, pub flag_identity: String, @@ -244,6 +251,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 6185d2cf7..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 { @@ -285,8 +286,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 { @@ -329,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, } } @@ -350,7 +357,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/parity/main.rs b/parity/main.rs index 74d14bfd6..58d5fe18e 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; @@ -465,6 +482,11 @@ fn execute_account_cli(conf: Configuration) { for &(addr, _) in &secret_store.accounts().unwrap() { println!("{:?}", addr); } + return; + } + if conf.args.cmd_import { + let imported = util::keys::import_keys_paths(&mut secret_store, &conf.args.arg_path).unwrap(); + println!("Imported {} keys", imported); } } diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index b54693582..fea5d0135 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -40,7 +40,9 @@ pub enum Api { Net, Eth, Personal, + Signer, Ethcore, + EthcoreSet, Traces, Rpc, } @@ -66,7 +68,9 @@ impl FromStr for Api { "net" => Ok(Net), "eth" => Ok(Eth), "personal" => Ok(Personal), + "signer" => Ok(Signer), "ethcore" => Ok(Ethcore), + "ethcore_set" => Ok(EthcoreSet), "traces" => Ok(Traces), "rpc" => Ok(Rpc), e => Err(ApiError::UnknownApi(e.into())), @@ -94,7 +98,9 @@ fn to_modules(apis: &[Api]) -> BTreeMap { Api::Net => ("net", "1.0"), Api::Eth => ("eth", "1.0"), Api::Personal => ("personal", "1.0"), + Api::Signer => ("signer", "1.0"), Api::Ethcore => ("ethcore", "1.0"), + Api::EthcoreSet => ("ethcore_set", "1.0"), Api::Traces => ("traces", "1.0"), Api::Rpc => ("rpc", "1.0"), }; @@ -112,22 +118,22 @@ pub fn from_str(apis: Vec<&str>) -> Vec { }) } -fn list_apis(apis: ApiSet, signer_enabled: bool) -> Vec { +fn list_apis(apis: ApiSet) -> Vec { match apis { ApiSet::List(apis) => apis, - ApiSet::UnsafeContext if signer_enabled => { - vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc] - } + ApiSet::UnsafeContext => { + vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] + }, _ => { - vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Ethcore, Api::Traces, Api::Rpc] - } + vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] + }, } } pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet) -> T { use ethcore_rpc::v1::*; - let apis = list_apis(apis, deps.signer_enabled); + let apis = list_apis(apis); for api in &apis { match *api { Api::Web3 => { @@ -147,14 +153,17 @@ 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()); - if deps.signer_enabled { - server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_queue).to_delegate()); - } + server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner, deps.signer_enabled).to_delegate()); + }, + Api::Signer => { + server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_queue).to_delegate()); }, Api::Ethcore => { server.add_delegate(EthcoreClient::new(&deps.miner, deps.logger.clone(), deps.settings.clone()).to_delegate()) }, + Api::EthcoreSet => { + server.add_delegate(EthcoreSetClient::new(&deps.miner).to_delegate()) + }, Api::Traces => { server.add_delegate(TracesClient::new(&deps.client, &deps.miner).to_delegate()) }, 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/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index d9d5859d2..b3fe894c6 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . //! Ethcore-specific rpc implementation. -use util::{U256, Address, RotatingLogger, FixedHash}; +use util::RotatingLogger; use util::network_settings::NetworkSettings; use util::misc::version_data; use std::sync::{Arc, Weak}; @@ -23,7 +23,6 @@ use std::ops::Deref; use std::collections::BTreeMap; use jsonrpc_core::*; use ethcore::miner::MinerService; -use ethcore::client::{BlockChainClient}; use v1::traits::Ethcore; use v1::types::{Bytes}; @@ -49,41 +48,6 @@ impl EthcoreClient where M: MinerService { impl Ethcore for EthcoreClient where M: MinerService + 'static { - fn set_min_gas_price(&self, params: Params) -> Result { - from_params::<(U256,)>(params).and_then(|(gas_price,)| { - take_weak!(self.miner).set_minimal_gas_price(gas_price); - to_value(&true) - }) - } - - fn set_gas_floor_target(&self, params: Params) -> Result { - from_params::<(U256,)>(params).and_then(|(gas_floor_target,)| { - take_weak!(self.miner).set_gas_floor_target(gas_floor_target); - to_value(&true) - }) - } - - fn set_extra_data(&self, params: Params) -> Result { - from_params::<(Bytes,)>(params).and_then(|(extra_data,)| { - take_weak!(self.miner).set_extra_data(extra_data.to_vec()); - to_value(&true) - }) - } - - fn set_author(&self, params: Params) -> Result { - from_params::<(Address,)>(params).and_then(|(author,)| { - take_weak!(self.miner).set_author(author); - to_value(&true) - }) - } - - fn set_transactions_limit(&self, params: Params) -> Result { - from_params::<(usize,)>(params).and_then(|(limit,)| { - take_weak!(self.miner).set_transactions_limit(limit); - to_value(&true) - }) - } - fn transactions_limit(&self, _: Params) -> Result { to_value(&take_weak!(self.miner).transactions_limit()) } diff --git a/rpc/src/v1/impls/ethcore_set.rs b/rpc/src/v1/impls/ethcore_set.rs new file mode 100644 index 000000000..b07dcbf9e --- /dev/null +++ b/rpc/src/v1/impls/ethcore_set.rs @@ -0,0 +1,78 @@ +// 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 . + +/// Ethcore-specific rpc interface for operations altering the settings. +use util::{U256, Address}; +use std::sync::{Arc, Weak}; +use jsonrpc_core::*; +use ethcore::miner::MinerService; +use v1::traits::EthcoreSet; +use v1::types::{Bytes}; + +/// Ethcore-specific rpc interface for operations altering the settings. +pub struct EthcoreSetClient where + M: MinerService { + + miner: Weak, +} + +impl EthcoreSetClient where M: MinerService { + /// Creates new `EthcoreSetClient`. + pub fn new(miner: &Arc) -> Self { + EthcoreSetClient { + miner: Arc::downgrade(miner), + } + } +} + +impl EthcoreSet for EthcoreSetClient where M: MinerService + 'static { + + fn set_min_gas_price(&self, params: Params) -> Result { + from_params::<(U256,)>(params).and_then(|(gas_price,)| { + take_weak!(self.miner).set_minimal_gas_price(gas_price); + to_value(&true) + }) + } + + fn set_gas_floor_target(&self, params: Params) -> Result { + from_params::<(U256,)>(params).and_then(|(gas_floor_target,)| { + take_weak!(self.miner).set_gas_floor_target(gas_floor_target); + to_value(&true) + }) + } + + fn set_extra_data(&self, params: Params) -> Result { + from_params::<(Bytes,)>(params).and_then(|(extra_data,)| { + take_weak!(self.miner).set_extra_data(extra_data.to_vec()); + to_value(&true) + }) + } + + fn set_author(&self, params: Params) -> Result { + from_params::<(Address,)>(params).and_then(|(author,)| { + take_weak!(self.miner).set_author(author); + to_value(&true) + }) + } + + fn set_transactions_limit(&self, params: Params) -> Result { + from_params::<(usize,)>(params).and_then(|(limit,)| { + take_weak!(self.miner).set_transactions_limit(limit); + to_value(&true) + }) + } + +} diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index 9e154a1c5..7fdf57249 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -37,6 +37,7 @@ mod net; mod personal; mod personal_signer; mod ethcore; +mod ethcore_set; mod traces; mod rpc; @@ -48,6 +49,7 @@ pub use self::net::NetClient; pub use self::personal::PersonalClient; pub use self::personal_signer::SignerClient; pub use self::ethcore::EthcoreClient; +pub use self::ethcore_set::EthcoreSetClient; pub use self::traces::TracesClient; pub use self::rpc::RpcClient; 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/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 where C: BlockChainClient, M: MinerService { @@ -61,102 +57,13 @@ 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) .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 +73,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 +83,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,36 +97,36 @@ 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 vm_trace_call(&self, params: Params) -> Result { - trace!(target: "jsonrpc", "vm_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{ vm_tracing: true, 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 { + ret.insert("trace".to_owned(), to_value(&Trace::from(trace)).unwrap()); + } if let Some(vm_trace) = executed.vm_trace { - return Ok(vm_trace_to_object(&vm_trace)); + ret.insert("vmTrace".to_owned(), to_value(&VMTrace::from(vm_trace)).unwrap()); } - } - 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{ 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(), to_value(&StateDiff::from(state_diff)).unwrap()); } + return Ok(Value::Object(ret)) } Ok(Value::Null) }) diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index 54628d892..b4d3693f3 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -25,6 +25,6 @@ pub mod traits; pub mod tests; pub mod types; -pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalSigner, Net, Ethcore, Traces, Rpc}; +pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalSigner, Net, Ethcore, EthcoreSet, Traces, Rpc}; pub use self::impls::*; pub use self::helpers::{SigningQueue, ConfirmationsQueue}; diff --git a/rpc/src/v1/tests/mocked/ethcore.rs b/rpc/src/v1/tests/mocked/ethcore.rs index 1685a598b..90103adef 100644 --- a/rpc/src/v1/tests/mocked/ethcore.rs +++ b/rpc/src/v1/tests/mocked/ethcore.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use std::str::FromStr; use jsonrpc_core::IoHandler; -use v1::{Ethcore, EthcoreClient}; +use v1::{Ethcore, EthcoreClient, EthcoreSet, EthcoreSetClient}; use ethcore::miner::MinerService; use v1::tests::helpers::TestMinerService; use util::numbers::*; @@ -49,12 +49,16 @@ fn ethcore_client(miner: &Arc) -> EthcoreClient) -> EthcoreSetClient { + EthcoreSetClient::new(&miner) +} + #[test] fn rpc_ethcore_extra_data() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_extraData", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"0x01020304","id":1}"#; @@ -68,9 +72,9 @@ fn rpc_ethcore_default_extra_data() { use util::ToPretty; let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_defaultExtraData", "params": [], "id": 1}"#; let response = format!(r#"{{"jsonrpc":"2.0","result":"0x{}","id":1}}"#, misc::version_data().to_hex()); @@ -81,9 +85,9 @@ fn rpc_ethcore_default_extra_data() { #[test] fn rpc_ethcore_gas_floor_target() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_gasFloorTarget", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"0x3039","id":1}"#; @@ -94,9 +98,9 @@ fn rpc_ethcore_gas_floor_target() { #[test] fn rpc_ethcore_min_gas_price() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_minGasPrice", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"0x01312d00","id":1}"#; @@ -107,9 +111,9 @@ fn rpc_ethcore_min_gas_price() { #[test] fn rpc_ethcore_set_min_gas_price() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setMinGasPrice", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; @@ -121,9 +125,9 @@ fn rpc_ethcore_set_min_gas_price() { #[test] fn rpc_ethcore_set_gas_floor_target() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setGasFloorTarget", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; @@ -135,9 +139,9 @@ fn rpc_ethcore_set_gas_floor_target() { #[test] fn rpc_ethcore_set_extra_data() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setExtraData", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; @@ -149,9 +153,9 @@ fn rpc_ethcore_set_extra_data() { #[test] fn rpc_ethcore_set_author() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setAuthor", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; @@ -169,6 +173,7 @@ fn rpc_ethcore_dev_logs() { let ethcore = EthcoreClient::new(&miner, logger.clone(), settings()).to_delegate(); let io = IoHandler::new(); io.add_delegate(ethcore); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_devLogs", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["b","a"],"id":1}"#; @@ -179,9 +184,9 @@ fn rpc_ethcore_dev_logs() { #[test] fn rpc_ethcore_dev_logs_levels() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_devLogsLevels", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"rpc=trace","id":1}"#; @@ -191,9 +196,9 @@ fn rpc_ethcore_dev_logs_levels() { #[test] fn rpc_ethcore_set_transactions_limit() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setTransactionsLimit", "params":[10240240], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; @@ -205,9 +210,9 @@ fn rpc_ethcore_set_transactions_limit() { #[test] fn rpc_ethcore_transactions_limit() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_transactionsLimit", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":1024,"id":1}"#; @@ -218,9 +223,9 @@ fn rpc_ethcore_transactions_limit() { #[test] fn rpc_ethcore_net_chain() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_netChain", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"testchain","id":1}"#; @@ -231,9 +236,9 @@ fn rpc_ethcore_net_chain() { #[test] fn rpc_ethcore_net_max_peers() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_netMaxPeers", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":25,"id":1}"#; @@ -244,9 +249,9 @@ fn rpc_ethcore_net_max_peers() { #[test] fn rpc_ethcore_net_port() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_netPort", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":30303,"id":1}"#; @@ -257,9 +262,9 @@ fn rpc_ethcore_net_port() { #[test] fn rpc_ethcore_rpc_settings() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_rpcSettings", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":{"enabled":true,"interface":"all","port":8545},"id":1}"#; @@ -270,9 +275,9 @@ fn rpc_ethcore_rpc_settings() { #[test] fn rpc_ethcore_node_name() { let miner = miner_service(); - let ethcore = ethcore_client(&miner).to_delegate(); let io = IoHandler::new(); - io.add_delegate(ethcore); + io.add_delegate(ethcore_client(&miner).to_delegate()); + io.add_delegate(ethcore_set_client(&miner).to_delegate()); let request = r#"{"jsonrpc": "2.0", "method": "ethcore_nodeName", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"mynode","id":1}"#; 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/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/personal_signer.rs index cd1f81d9a..cf7661331 100644 --- a/rpc/src/v1/tests/mocked/personal_signer.rs +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -120,6 +120,30 @@ fn should_reject_transaction_from_queue_without_dispatching() { assert_eq!(tester.miner.imported_transactions.lock().unwrap().len(), 0); } +#[test] +fn should_not_remove_transaction_if_password_is_invalid() { + // given + let tester = signer_tester(); + tester.queue.add_request(TransactionRequest { + 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, + 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 response = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + + // then + assert_eq!(tester.io.handle_request(&request), Some(response.to_owned())); + assert_eq!(tester.queue.requests().len(), 1); +} + #[test] fn should_confirm_transaction_and_dispatch() { // given diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/ethcore.rs index a0e12222a..4ce3eca59 100644 --- a/rpc/src/v1/traits/ethcore.rs +++ b/rpc/src/v1/traits/ethcore.rs @@ -21,21 +21,6 @@ use jsonrpc_core::*; /// Ethcore-specific rpc interface. pub trait Ethcore: Sized + Send + Sync + 'static { - /// Sets new minimal gas price for mined blocks. - fn set_min_gas_price(&self, _: Params) -> Result; - - /// Sets new gas floor target for mined blocks. - fn set_gas_floor_target(&self, _: Params) -> Result; - - /// Sets new extra data for mined blocks. - fn set_extra_data(&self, _: Params) -> Result; - - /// Sets new author for mined block. - fn set_author(&self, _: Params) -> Result; - - /// Sets the limits for transaction queue. - fn set_transactions_limit(&self, _: Params) -> Result; - /// Returns current transactions limit. fn transactions_limit(&self, _: Params) -> Result; @@ -75,11 +60,6 @@ pub trait Ethcore: Sized + Send + Sync + 'static { /// 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("ethcore_setMinGasPrice", Ethcore::set_min_gas_price); - delegate.add_method("ethcore_setGasFloorTarget", Ethcore::set_gas_floor_target); - delegate.add_method("ethcore_setExtraData", Ethcore::set_extra_data); - delegate.add_method("ethcore_setAuthor", Ethcore::set_author); - delegate.add_method("ethcore_setTransactionsLimit", Ethcore::set_transactions_limit); delegate.add_method("ethcore_extraData", Ethcore::extra_data); delegate.add_method("ethcore_gasFloorTarget", Ethcore::gas_floor_target); diff --git a/rpc/src/v1/traits/ethcore_set.rs b/rpc/src/v1/traits/ethcore_set.rs new file mode 100644 index 000000000..332c505b6 --- /dev/null +++ b/rpc/src/v1/traits/ethcore_set.rs @@ -0,0 +1,51 @@ +// 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 . + +//! Ethcore-specific rpc interface for operations altering the settings. + +use std::sync::Arc; +use jsonrpc_core::*; + +/// Ethcore-specific rpc interface for operations altering the settings. +pub trait EthcoreSet: Sized + Send + Sync + 'static { + + /// Sets new minimal gas price for mined blocks. + fn set_min_gas_price(&self, _: Params) -> Result; + + /// Sets new gas floor target for mined blocks. + fn set_gas_floor_target(&self, _: Params) -> Result; + + /// Sets new extra data for mined blocks. + fn set_extra_data(&self, _: Params) -> Result; + + /// Sets new author for mined block. + fn set_author(&self, _: Params) -> Result; + + /// Sets the limits for transaction queue. + fn set_transactions_limit(&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("ethcore_setMinGasPrice", EthcoreSet::set_min_gas_price); + delegate.add_method("ethcore_setGasFloorTarget", EthcoreSet::set_gas_floor_target); + delegate.add_method("ethcore_setExtraData", EthcoreSet::set_extra_data); + delegate.add_method("ethcore_setAuthor", EthcoreSet::set_author); + delegate.add_method("ethcore_setTransactionsLimit", EthcoreSet::set_transactions_limit); + + delegate + } +} diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index d994ffc24..3ca11b654 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -21,6 +21,7 @@ pub mod eth; pub mod net; pub mod personal; pub mod ethcore; +pub mod ethcore_set; pub mod traces; pub mod rpc; @@ -29,6 +30,7 @@ pub use self::eth::{Eth, EthFilter, EthSigning}; pub use self::net::Net; pub use self::personal::{Personal, PersonalSigner}; pub use self::ethcore::Ethcore; +pub use self::ethcore_set::EthcoreSet; pub use self::traces::Traces; pub use self::rpc::Rpc; 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); diff --git a/rpc/src/v1/traits/traces.rs b/rpc/src/v1/traits/traces.rs index 4b58b12ac..45fa916be 100644 --- a/rpc/src/v1/traits/traces.rs +++ b/rpc/src/v1/traits/traces.rs @@ -32,11 +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 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; + /// 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 { @@ -45,9 +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_vmTraceCall", Traces::vm_trace_call); - delegate.add_method("trace_stateDiffCall", Traces::state_diff_call); + delegate.add_method("trace_call", Traces::call); delegate } 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 b4e82a28b..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; +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 6ea58543a..d6226aea0 100644 --- a/rpc/src/v1/types/trace.rs +++ b/rpc/src/v1/types/trace.rs @@ -14,11 +14,201 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use util::{Address, U256, H256}; +use std::collections::BTreeMap; +use util::{Address, U256, H256, Uint}; +use serde::{Serialize, Serializer}; use ethcore::trace::trace; -use ethcore::trace::LocalizedTrace; +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: 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 { @@ -161,7 +351,7 @@ impl From for Res { /// Trace #[derive(Debug, Serialize)] -pub struct Trace { +pub struct LocalizedTrace { /// Action action: Action, /// Result @@ -185,9 +375,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,16 +390,41 @@ 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; + use std::collections::BTreeMap; use util::{U256, H256, Address}; use v1::types::Bytes; use super::*; #[test] fn test_trace_serialize() { - let t = Trace { + let t = LocalizedTrace { action: Action::Call(Call { from: Address::from(4), to: Address::from(5), @@ -232,6 +447,71 @@ mod tests { assert_eq!(serialized, r#"{"action":{"call":{"from":"0x0000000000000000000000000000000000000004","to":"0x0000000000000000000000000000000000000005","value":"0x06","gas":"0x07","input":"0x1234"}},"result":{"call":{"gasUsed":"0x08","output":"0x5678"}},"traceAddress":["0x0a"],"subtraces":"0x01","transactionPosition":"0x0b","transactionHash":"0x000000000000000000000000000000000000000000000000000000000000000c","blockNumber":"0x0d","blockHash":"0x000000000000000000000000000000000000000000000000000000000000000e"}"#); } + #[test] + fn test_vmtrace_serialize() { + let t = VMTrace { + code: vec![0, 1, 2, 3], + ops: vec![ + VMOperation { + pc: 0, + cost: 10, + ex: None, + sub: None, + }, + VMOperation { + pc: 1, + cost: 11, + ex: Some(VMExecutedOperation { + used: 10, + push: vec![69.into()], + mem: None, + store: None, + }), + sub: Some(VMTrace { + code: vec![0], + ops: vec![ + VMOperation { + pc: 0, + cost: 0, + ex: Some(VMExecutedOperation { + used: 10, + push: vec![42.into()], + mem: Some(MemoryDiff {off: 42, data: vec![1, 2, 3]}), + store: Some(StorageDiff {key: 69.into(), val: 42.into()}), + }), + sub: None, + } + ] + }), + } + ] + }; + let serialized = serde_json::to_string(&t).unwrap(); + assert_eq!(serialized, r#"{"code":[0,1,2,3],"ops":[{"pc":0,"cost":10,"ex":null,"sub":null},{"pc":1,"cost":11,"ex":{"used":10,"push":["0x45"],"mem":null,"store":null},"sub":{"code":[0],"ops":[{"pc":0,"cost":0,"ex":{"used":10,"push":["0x2a"],"mem":{"off":42,"data":[1,2,3]},"store":{"key":"0x45","val":"0x2a"}},"sub":null}]}}]}"#); + } + + #[test] + fn test_statediff_serialize() { + let t = StateDiff(map![ + 42.into() => AccountDiff { + balance: Diff::Same, + nonce: Diff::Born(1.into()), + code: Diff::Same, + storage: map![ + 42.into() => Diff::Same + ] + }, + 69.into() => AccountDiff { + balance: Diff::Same, + nonce: Diff::Changed(ChangedType { from: 1.into(), to: 0.into() }), + code: Diff::Died(vec![96].into()), + storage: map![], + } + ]); + let serialized = serde_json::to_string(&t).unwrap(); + assert_eq!(serialized, r#"{"0x000000000000000000000000000000000000002a":{"balance":{"=":[]},"nonce":{"+":"0x01"},"code":{"=":[]},"storage":{"0x000000000000000000000000000000000000000000000000000000000000002a":{"=":[]}}},"0x0000000000000000000000000000000000000045":{"balance":{"=":[]},"nonce":{"*":{"from":"0x01","to":"0x00"}},"code":{"-":"0x60"},"storage":{}}}"#); + } + #[test] fn test_action_serialize() { let actions = vec![Action::Call(Call { 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 0d55fd906..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; @@ -95,7 +98,8 @@ impl Server { }; // Create WebSocket - let ws = try!(ws::Builder::new().with_settings(config).build(session::Factory::new(handler))); + let origin = format!("{}", addr); + 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 979914f06..8cc3f5d07 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -18,33 +18,97 @@ 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 { + None => false, + Some(h) => { + let v = String::from_utf8(h.clone()).ok(); + match v { + Some(ref origin) if origin.starts_with("chrome-extension://") => true, + Some(ref origin) if origin.starts_with(self_origin) => true, + Some(ref origin) if origin.starts_with(&format!("http://{}", self_origin)) => true, + _ => false + } + } + } +} + +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, } impl ws::Handler for Session { fn on_request(&mut self, req: &ws::Request) -> ws::Result<(ws::Response)> { + let origin = req.header("origin").or_else(|| req.header("Origin")); + let host = req.header("host").or_else(|| req.header("Host")); + + // 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(format!("You are not allowed to access system ui. Use: http://{}", self.self_origin))); + } + // 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. sysui::handle(req.resource()) .map_or_else( // return error - || ws::Response::from_request(req), + || Ok(ws::Response::not_found("Page not found".into())), // or serve the file |f| { let content_len = format!("{}", f.content.as_bytes().len()); let mut res = ws::Response::ok(f.content.into()); { let mut headers = res.headers_mut(); - headers.push(("Server".into(), b"Parity/SystemUI".to_vec())); + headers.push(("Server".into(), b"Parity/SignerUI".to_vec())); headers.push(("Connection".into(), b"Closed".to_vec())); headers.push(("Content-Length".into(), content_len.as_bytes().to_vec())); headers.push(("Content-Type".into(), f.mime.as_bytes().to_vec())); @@ -67,12 +131,16 @@ impl ws::Handler for Session { pub struct Factory { handler: Arc, + self_origin: String, + authcodes_path: PathBuf, } impl Factory { - pub fn new(handler: Arc) -> Self { + pub fn new(handler: Arc, self_origin: String, authcodes_path: PathBuf) -> Self { Factory { handler: handler, + self_origin: self_origin, + authcodes_path: authcodes_path, } } } @@ -84,6 +152,8 @@ impl ws::Factory for Factory { Session { out: sender, handler: self.handler.clone(), + self_origin: self.self_origin.clone(), + authcodes_path: self.authcodes_path.clone(), } } } diff --git a/util/res/pat/p1.json b/util/res/pat/p1.json new file mode 100644 index 000000000..afc376774 --- /dev/null +++ b/util/res/pat/p1.json @@ -0,0 +1,21 @@ +{ + "address": "3f49624084b67849c7b4e805c5988c21a430f9d9", + "Crypto": { + "cipher": "aes-128-ctr", + "ciphertext": "9f27e3dd4fc73e7103ed61e5493662189a3eb52223ae49e3d1deacc04c889eae", + "cipherparams": { + "iv": "457494bf05f2618c397dc74dbb5181c0" + }, + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "db14edb18c41ee7f5ec4397df89c3a2ae4d0af60884c52bb54ce490574f8df33" + }, + "mac": "572d24532438d31fdf513c744a3ff26c933ffda5744ee42bc71661cbe3f2112e" + }, + "id": "62a0ad73-556d-496a-8e1c-0783d30d3ace", + "version": 3 +} diff --git a/util/res/pat/p2.json b/util/res/pat/p2.json new file mode 100644 index 000000000..b14922037 --- /dev/null +++ b/util/res/pat/p2.json @@ -0,0 +1,21 @@ +{ + "address": "5ba4dcf897e97c2bdf8315b9ef26c13c085988cf", + "Crypto": { + "cipher": "aes-128-ctr", + "ciphertext": "d4a08ec930163778273920f6ad1d49b71836337be6fd9863993ac700a612fddd", + "cipherparams": { + "iv": "89ce5ec129fc27cd5bcbeb8c92bdad50" + }, + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "612ab108dc37e69ee8af37a7b24bf7f2234086d7bbf945bacdeccce331f7f84a" + }, + "mac": "4152caa7444e06784223d735cea80cd2690b4c587ad8db3d5529442227b25695" + }, + "id": "35086353-fb12-4029-b56b-033cd61ce35b", + "version": 3 +} 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(()), diff --git a/util/src/keys/geth_import.rs b/util/src/keys/geth_import.rs index a72c570fa..509ebc89f 100644 --- a/util/src/keys/geth_import.rs +++ b/util/src/keys/geth_import.rs @@ -80,9 +80,10 @@ pub fn import_geth_key(secret_store: &mut SecretStore, geth_keyfile_path: &Path) } /// Imports all geth keys in the directory -pub fn import_geth_keys(secret_store: &mut SecretStore, geth_keyfiles_directory: &Path) -> Result<(), ImportError> { +pub fn import_geth_keys(secret_store: &mut SecretStore, geth_keyfiles_directory: &Path) -> Result { use std::path::PathBuf; let geth_files = try!(enumerate_geth_keys(geth_keyfiles_directory)); + let mut total = 0; for &(ref address, ref file_path) in &geth_files { let mut path = PathBuf::new(); path.push(geth_keyfiles_directory); @@ -90,18 +91,45 @@ pub fn import_geth_keys(secret_store: &mut SecretStore, geth_keyfiles_directory: if let Err(e) = import_geth_key(secret_store, Path::new(&path)) { warn!("Skipped geth address {}, error importing: {:?}", address, e) } + else { total = total + 1} } - Ok(()) + Ok(total) } /// Gets the default geth keystore directory. -/// -/// Based on https://github.com/ethereum/go-ethereum/blob/e553215/common/path.go#L75 pub fn keystore_dir(is_testnet: bool) -> PathBuf { path::ethereum::with_default(if is_testnet {"testnet/keystore"} else {"keystore"}) } +/// Imports key(s) from provided file/directory +pub fn import_keys_path(secret_store: &mut SecretStore, path: &str) -> Result { + // check if it is just one file or directory + if let Ok(meta) = fs::metadata(path) { + if meta.is_file() { + try!(import_geth_key(secret_store, Path::new(path))); + return Ok(1); + } + else if meta.is_dir() { + return Ok(try!(fs::read_dir(path)).fold( + 0, + |total, p| + total + + match p { + Ok(dir_entry) => import_keys_path(secret_store, dir_entry.path().to_str().unwrap()).unwrap_or_else(|_| 0), + Err(e) => { warn!("Error importing dir entry: {:?}", e); 0 }, + } + )) + } + } + Ok(0) +} + +/// Imports all keys from list of provided files/directories +pub fn import_keys_paths(secret_store: &mut SecretStore, path: &[String]) -> Result { + Ok(path.iter().fold(0, |total, ref p| total + import_keys_path(secret_store, &p).unwrap_or_else(|_| 0))) +} + #[cfg(test)] mod tests { use super::*; @@ -115,10 +143,21 @@ mod tests { } } + fn pat_path() -> &'static str { + match ::std::fs::metadata("res") { + Ok(_) => "res/pat", + Err(_) => "util/res/pat" + } + } + fn test_path_param(param_val: &'static str) -> String { test_path().to_owned() + param_val } + fn pat_path_param(param_val: &'static str) -> String { + pat_path().to_owned() + param_val + } + #[test] fn can_enumerate() { let keys = enumerate_geth_keys(Path::new(test_path())).unwrap(); @@ -191,4 +230,32 @@ mod tests { assert!(val.is_ok()); assert_eq!(32, val.unwrap().len()); } + + #[test] + fn can_import_by_filename() { + let temp = ::devtools::RandomTempPath::create_dir(); + let mut secret_store = SecretStore::new_in(temp.as_path()); + + let amount = import_keys_path(&mut secret_store, &pat_path_param("/p1.json")).unwrap(); + assert_eq!(1, amount); + } + + #[test] + fn can_import_by_dir() { + let temp = ::devtools::RandomTempPath::create_dir(); + let mut secret_store = SecretStore::new_in(temp.as_path()); + + let amount = import_keys_path(&mut secret_store, pat_path()).unwrap(); + assert_eq!(2, amount); + } + + #[test] + fn can_import_mulitple() { + let temp = ::devtools::RandomTempPath::create_dir(); + let mut secret_store = SecretStore::new_in(temp.as_path()); + + let amount = import_keys_paths(&mut secret_store, &[pat_path_param("/p1.json"), pat_path_param("/p2.json")]).unwrap(); + assert_eq!(2, amount); + } + } diff --git a/util/src/keys/mod.rs b/util/src/keys/mod.rs index 38fa51eca..39c39aeef 100644 --- a/util/src/keys/mod.rs +++ b/util/src/keys/mod.rs @@ -23,3 +23,4 @@ mod test_account_provider; pub use self::store::AccountProvider; pub use self::test_account_provider::{TestAccount, TestAccountProvider}; +pub use self::geth_import::import_keys_paths; 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 + } } diff --git a/util/src/trie/mod.rs b/util/src/trie/mod.rs index b8b98880d..79135162c 100644 --- a/util/src/trie/mod.rs +++ b/util/src/trie/mod.rs @@ -16,6 +16,8 @@ //! Trie interface and implementation. +use std::fmt; + /// Export the trietraits module. pub mod trietraits; /// Export the standardmap module. @@ -33,9 +35,22 @@ pub mod sectriedb; /// Export the sectriedbmut module. pub mod sectriedbmut; -pub use self::trietraits::*; -pub use self::standardmap::*; -pub use self::triedbmut::*; -pub use self::triedb::*; -pub use self::sectriedbmut::*; -pub use self::sectriedb::*; +pub use self::trietraits::{Trie, TrieMut}; +pub use self::standardmap::{Alphabet, StandardMap, ValueMode}; +pub use self::triedbmut::TrieDBMut; +pub use self::triedb::TrieDB; +pub use self::sectriedbmut::SecTrieDBMut; +pub use self::sectriedb::SecTrieDB; + +/// Trie Errors +#[derive(Debug)] +pub enum TrieError { + /// Attempted to create a trie with a state root not in the DB. + InvalidStateRoot, +} + +impl fmt::Display for TrieError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Trie Error: Invalid state root.") + } +} \ No newline at end of file diff --git a/util/src/trie/sectriedb.rs b/util/src/trie/sectriedb.rs index 5558344fa..eda4ac58b 100644 --- a/util/src/trie/sectriedb.rs +++ b/util/src/trie/sectriedb.rs @@ -16,9 +16,10 @@ use hash::*; use sha3::*; -use hashdb::*; -use super::triedb::*; -use super::trietraits::*; +use hashdb::HashDB; +use super::triedb::TrieDB; +use super::trietraits::Trie; +use super::TrieError; /// A `Trie` implementation which hashes keys and uses a generic `HashDB` backing database. /// @@ -29,10 +30,12 @@ pub struct SecTrieDB<'db> { impl<'db> SecTrieDB<'db> { /// Create a new trie with the backing database `db` and empty `root` + /// /// Initialise to the state entailed by the genesis block. /// This guarantees the trie is built correctly. - pub fn new(db: &'db HashDB, root: &'db H256) -> Self { - SecTrieDB { raw: TrieDB::new(db, root) } + /// Returns an error if root does not exist. + pub fn new(db: &'db HashDB, root: &'db H256) -> Result { + Ok(SecTrieDB { raw: try!(TrieDB::new(db, root)) }) } /// Get a reference to the underlying raw `TrieDB` struct. @@ -60,8 +63,9 @@ impl<'db> Trie for SecTrieDB<'db> { #[test] fn trie_to_sectrie() { - use memorydb::*; - use super::triedbmut::*; + use memorydb::MemoryDB; + use super::triedbmut::TrieDBMut; + use super::trietraits::TrieMut; let mut memdb = MemoryDB::new(); let mut root = H256::new(); @@ -69,6 +73,6 @@ fn trie_to_sectrie() { let mut t = TrieDBMut::new(&mut memdb, &mut root); t.insert(&(&[0x01u8, 0x23]).sha3(), &[0x01u8, 0x23]); } - let t = SecTrieDB::new(&memdb, &root); + let t = SecTrieDB::new(&memdb, &root).unwrap(); assert_eq!(t.get(&[0x01u8, 0x23]).unwrap(), &[0x01u8, 0x23]); } diff --git a/util/src/trie/sectriedbmut.rs b/util/src/trie/sectriedbmut.rs index 895a821c9..9c8231af4 100644 --- a/util/src/trie/sectriedbmut.rs +++ b/util/src/trie/sectriedbmut.rs @@ -16,9 +16,10 @@ use hash::*; use sha3::*; -use hashdb::*; -use super::triedbmut::*; -use super::trietraits::*; +use hashdb::HashDB; +use super::triedbmut::TrieDBMut; +use super::trietraits::{Trie, TrieMut}; +use super::TrieError; /// A mutable `Trie` implementation which hashes keys and uses a generic `HashDB` backing database. /// @@ -35,10 +36,11 @@ impl<'db> SecTrieDBMut<'db> { SecTrieDBMut { raw: TrieDBMut::new(db, root) } } - /// Create a new trie with the backing database `db` and `root` - /// Panics, if `root` does not exist - pub fn from_existing(db: &'db mut HashDB, root: &'db mut H256) -> Self { - SecTrieDBMut { raw: TrieDBMut::from_existing(db, root) } + /// Create a new trie with the backing database `db` and `root`. + /// + /// Returns an error if root does not exist. + pub fn from_existing(db: &'db mut HashDB, root: &'db mut H256) -> Result { + Ok(SecTrieDBMut { raw: try!(TrieDBMut::from_existing(db, root)) }) } /// Get the backing database. @@ -81,6 +83,6 @@ fn sectrie_to_trie() { let mut t = SecTrieDBMut::new(&mut memdb, &mut root); t.insert(&[0x01u8, 0x23], &[0x01u8, 0x23]); } - let t = TrieDB::new(&memdb, &root); + let t = TrieDB::new(&memdb, &root).unwrap(); assert_eq!(t.get(&(&[0x01u8, 0x23]).sha3()).unwrap(), &[0x01u8, 0x23]); } diff --git a/util/src/trie/triedb.rs b/util/src/trie/triedb.rs index 1c6d2236b..fccd5da90 100644 --- a/util/src/trie/triedb.rs +++ b/util/src/trie/triedb.rs @@ -18,8 +18,9 @@ use common::*; use hashdb::*; use nibbleslice::*; use rlp::*; -use super::trietraits::*; -use super::node::*; +use super::trietraits::Trie; +use super::node::Node; +use super::TrieError; /// A `Trie` implementation using a generic `HashDB` backing database. /// @@ -41,7 +42,7 @@ use super::node::*; /// let mut memdb = MemoryDB::new(); /// let mut root = H256::new(); /// TrieDBMut::new(&mut memdb, &mut root).insert(b"foo", b"bar"); -/// let t = TrieDB::new(&memdb, &root); +/// let t = TrieDB::new(&memdb, &root).unwrap(); /// assert!(t.contains(b"foo")); /// assert_eq!(t.get(b"foo").unwrap(), b"bar"); /// assert!(t.db_items_remaining().is_empty()); @@ -57,16 +58,16 @@ pub struct TrieDB<'db> { #[cfg_attr(feature="dev", allow(wrong_self_convention))] impl<'db> TrieDB<'db> { /// Create a new trie with the backing database `db` and `root` - /// Panics, if `root` does not exist - pub fn new(db: &'db HashDB, root: &'db H256) -> Self { + /// Returns an error if `root` does not exist + pub fn new(db: &'db HashDB, root: &'db H256) -> Result { if !db.contains(root) { - flushln!("TrieDB::new({}): Trie root not found!", root); - panic!("Trie root not found!"); - } - TrieDB { - db: db, - root: root, - hash_count: 0 + Err(TrieError::InvalidStateRoot) + } else { + Ok(TrieDB { + db: db, + root: root, + hash_count: 0 + }) } } @@ -356,6 +357,7 @@ impl<'db> fmt::Debug for TrieDB<'db> { #[test] fn iterator() { + use super::trietraits::TrieMut; use memorydb::*; use super::triedbmut::*; @@ -369,6 +371,6 @@ fn iterator() { t.insert(&x, &x); } } - assert_eq!(d.iter().map(|i|i.to_vec()).collect::>(), TrieDB::new(&memdb, &root).iter().map(|x|x.0).collect::>()); - assert_eq!(d, TrieDB::new(&memdb, &root).iter().map(|x|x.1).collect::>()); + assert_eq!(d.iter().map(|i|i.to_vec()).collect::>(), TrieDB::new(&memdb, &root).unwrap().iter().map(|x|x.0).collect::>()); + assert_eq!(d, TrieDB::new(&memdb, &root).unwrap().iter().map(|x|x.1).collect::>()); } diff --git a/util/src/trie/triedbmut.rs b/util/src/trie/triedbmut.rs index 3d75fa3e1..afabd6437 100644 --- a/util/src/trie/triedbmut.rs +++ b/util/src/trie/triedbmut.rs @@ -18,9 +18,10 @@ use common::*; use hashdb::*; use nibbleslice::*; use rlp::*; -use super::node::*; -use super::journal::*; -use super::trietraits::*; +use super::node::Node; +use super::journal::Journal; +use super::trietraits::{Trie, TrieMut}; +use super::TrieError; /// A `Trie` implementation using a generic `HashDB` backing database. /// @@ -84,17 +85,16 @@ impl<'db> TrieDBMut<'db> { } /// Create a new trie with the backing database `db` and `root`. - /// Panics, if `root` does not exist. - // TODO: return Result - pub fn from_existing(db: &'db mut HashDB, root: &'db mut H256) -> Self { + /// Returns an error if `root` does not exist. + pub fn from_existing(db: &'db mut HashDB, root: &'db mut H256) -> Result { if !db.exists(root) { - flushln!("Trie root not found {}", root); - panic!("Trie root not found!"); - } - TrieDBMut { - db: db, - root: root, - hash_count: 0 + Err(TrieError::InvalidStateRoot) + } else { + Ok(TrieDBMut { + db: db, + root: root, + hash_count: 0 + }) } }