Merge branch 'master' into ui-2

This commit is contained in:
Jaco Greeff 2017-05-22 11:50:15 +02:00
commit 368e3d1f51
90 changed files with 3425 additions and 1084 deletions

2
Cargo.lock generated
View File

@ -1784,7 +1784,7 @@ dependencies = [
[[package]]
name = "parity-ui-precompiled"
version = "1.4.0"
source = "git+https://github.com/paritytech/js-precompiled.git#69ff5bc9fa4c64a0657308a64a45a95b3d984950"
source = "git+https://github.com/paritytech/js-precompiled.git#6597fc70499226546fdcb35e7c09f9347f4f3c07"
dependencies = [
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@ -1564,7 +1564,7 @@ mod tests {
}
fn secret() -> Secret {
Secret::from_slice(&"".sha3()).unwrap()
"".sha3().into()
}
#[test]

View File

@ -39,7 +39,6 @@ use super::signer::EngineSigner;
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
/// `AuthorityRound` params.
#[derive(Debug, PartialEq)]
pub struct AuthorityRoundParams {
/// Gas limit divisor.
pub gas_limit_bound_divisor: U256,
@ -52,7 +51,7 @@ pub struct AuthorityRoundParams {
/// Starting step,
pub start_step: Option<u64>,
/// Valid validators.
pub validators: ethjson::spec::ValidatorSet,
pub validators: Box<ValidatorSet>,
/// Chain score validation transition block.
pub validate_score_transition: u64,
/// Number of first block where EIP-155 rules are validated.
@ -66,7 +65,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
AuthorityRoundParams {
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
step_duration: Duration::from_secs(p.step_duration.into()),
validators: p.validators,
validators: new_validator_set(p.validators),
block_reward: p.block_reward.map_or_else(U256::zero, Into::into),
registrar: p.registrar.map_or_else(Address::new, Into::into),
start_step: p.start_step.map(Into::into),
@ -209,7 +208,7 @@ impl AuthorityRound {
proposed: AtomicBool::new(false),
client: RwLock::new(None),
signer: Default::default(),
validators: new_validator_set(our_params.validators),
validators: our_params.validators,
validate_score_transition: our_params.validate_score_transition,
eip155_transition: our_params.eip155_transition,
validate_step_transition: our_params.validate_step_transition,
@ -382,14 +381,22 @@ impl Engine for AuthorityRound {
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
}
// Ensure header is from the step after parent.
let parent_step = header_step(parent)?;
// Ensure header is from the step after parent.
if step == parent_step
|| (header.number() >= self.validate_step_transition && step <= parent_step) {
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
self.validators.report_malicious(header.author(), header.number(), Default::default());
Err(EngineError::DoubleVote(header.author().clone()))?;
}
// Report skipped primaries.
if step > parent_step + 1 {
for s in parent_step + 1..step {
let skipped_primary = self.step_proposer(&parent.hash(), s);
trace!(target: "engine", "Author {} did not build his block on top of the intermediate designated primary {}.", header.author(), skipped_primary);
self.validators.report_benign(&skipped_primary, header.number());
}
}
let gas_limit_divisor = self.gas_limit_bound_divisor;
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
@ -460,16 +467,18 @@ impl Engine for AuthorityRound {
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use util::*;
use header::Header;
use error::{Error, BlockError};
use ethkey::Secret;
use rlp::encode;
use block::*;
use tests::helpers::*;
use account_provider::AccountProvider;
use spec::Spec;
use engines::Seal;
use engines::{Seal, Engine};
use engines::validator_set::TestSet;
use super::{AuthorityRoundParams, AuthorityRound};
#[test]
fn has_valid_metadata() {
@ -513,8 +522,8 @@ mod tests {
#[test]
fn generates_seal_and_does_not_double_propose() {
let tap = Arc::new(AccountProvider::transient_provider());
let addr1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "1").unwrap();
let addr2 = tap.insert_account(Secret::from_slice(&"2".sha3()).unwrap(), "2").unwrap();
let addr1 = tap.insert_account("1".sha3().into(), "1").unwrap();
let addr2 = tap.insert_account("2".sha3().into(), "2").unwrap();
let spec = Spec::new_test_round();
let engine = &*spec.engine;
@ -545,7 +554,7 @@ mod tests {
#[test]
fn proposer_switching() {
let tap = AccountProvider::transient_provider();
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
let addr = tap.insert_account("0".sha3().into(), "0").unwrap();
let mut parent_header: Header = Header::default();
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
@ -570,7 +579,7 @@ mod tests {
#[test]
fn rejects_future_block() {
let tap = AccountProvider::transient_provider();
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
let addr = tap.insert_account("0".sha3().into(), "0").unwrap();
let mut parent_header: Header = Header::default();
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
@ -596,7 +605,7 @@ mod tests {
#[test]
fn rejects_step_backwards() {
let tap = AccountProvider::transient_provider();
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
let addr = tap.insert_account("0".sha3().into(), "0").unwrap();
let mut parent_header: Header = Header::default();
parent_header.set_seal(vec![encode(&4usize).to_vec()]);
@ -616,4 +625,32 @@ mod tests {
header.set_seal(vec![encode(&3usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
}
#[test]
fn reports_skipped() {
let last_benign = Arc::new(AtomicUsize::new(0));
let params = AuthorityRoundParams {
gas_limit_bound_divisor: U256::from_str("400").unwrap(),
step_duration: Default::default(),
block_reward: Default::default(),
registrar: Default::default(),
start_step: Some(1),
validators: Box::new(TestSet::new(Default::default(), last_benign.clone())),
validate_score_transition: 0,
validate_step_transition: 0,
eip155_transition: 0,
};
let aura = AuthorityRound::new(Default::default(), params, Default::default()).unwrap();
let mut parent_header: Header = Header::default();
parent_header.set_seal(vec![encode(&1usize).to_vec()]);
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
let mut header: Header = Header::default();
header.set_number(1);
header.set_gas_limit(U256::from_str("222222").unwrap());
header.set_seal(vec![encode(&3usize).to_vec()]);
assert!(aura.verify_block_family(&header, &parent_header, None).is_ok());
assert_eq!(last_benign.load(AtomicOrdering::SeqCst), 1);
}
}

View File

@ -229,7 +229,6 @@ mod tests {
use error::{BlockError, Error};
use tests::helpers::*;
use account_provider::AccountProvider;
use ethkey::Secret;
use header::Header;
use spec::Spec;
use engines::Seal;
@ -281,7 +280,7 @@ mod tests {
#[test]
fn can_generate_seal() {
let tap = AccountProvider::transient_provider();
let addr = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
let addr = tap.insert_account("".sha3().into(), "").unwrap();
let spec = new_test_authority();
let engine = &*spec.engine;
@ -299,7 +298,7 @@ mod tests {
#[test]
fn seals_internally() {
let tap = AccountProvider::transient_provider();
let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
let authority = tap.insert_account("".sha3().into(), "").unwrap();
let engine = new_test_authority().engine;
assert!(!engine.seals_internally().unwrap());

View File

@ -200,7 +200,6 @@ pub fn message_hash(vote_step: VoteStep, block_hash: H256) -> H256 {
mod tests {
use util::*;
use rlp::*;
use ethkey::Secret;
use account_provider::AccountProvider;
use header::Header;
use super::super::Step;
@ -250,7 +249,7 @@ mod tests {
#[test]
fn generate_and_verify() {
let tap = Arc::new(AccountProvider::transient_provider());
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
let addr = tap.insert_account("0".sha3().into(), "0").unwrap();
tap.unlock_account_permanently(addr, "0".into()).unwrap();
let mi = message_info_rlp(&VoteStep::new(123, 2, Step::Precommit), Some(H256::default()));

View File

@ -42,7 +42,7 @@ use evm::Schedule;
use state::CleanupMode;
use io::IoService;
use super::signer::EngineSigner;
use super::validator_set::{ValidatorSet, new_validator_set};
use super::validator_set::ValidatorSet;
use super::transition::TransitionHandler;
use super::vote_collector::VoteCollector;
use self::message::*;
@ -124,7 +124,7 @@ impl Tendermint {
proposal: RwLock::new(None),
proposal_parent: Default::default(),
last_proposed: Default::default(),
validators: new_validator_set(our_params.validators),
validators: our_params.validators,
});
let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak<Engine>, Box::new(our_params.timeouts));
engine.step_service.register_handler(Arc::new(handler))?;
@ -659,7 +659,6 @@ mod tests {
use block::*;
use error::{Error, BlockError};
use header::Header;
use ethkey::Secret;
use client::chain_notify::ChainNotify;
use miner::MinerService;
use tests::helpers::*;
@ -708,7 +707,7 @@ mod tests {
}
fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address {
let addr = tap.insert_account(Secret::from_slice(&acc.sha3()).unwrap(), acc).unwrap();
let addr = tap.insert_account(acc.sha3().into(), acc).unwrap();
tap.unlock_account_permanently(addr, acc.into()).unwrap();
addr
}

View File

@ -19,16 +19,16 @@
use ethjson;
use util::{U256, Uint, Address};
use time::Duration;
use super::super::validator_set::{ValidatorSet, new_validator_set};
use super::super::transition::Timeouts;
use super::Step;
/// `Tendermint` params.
#[derive(Debug)]
pub struct TendermintParams {
/// Gas limit divisor.
pub gas_limit_bound_divisor: U256,
/// List of validators.
pub validators: ethjson::spec::ValidatorSet,
pub validators: Box<ValidatorSet>,
/// Timeout durations for different steps.
pub timeouts: TendermintTimeouts,
/// Block reward.
@ -82,7 +82,7 @@ impl From<ethjson::spec::TendermintParams> for TendermintParams {
let dt = TendermintTimeouts::default();
TendermintParams {
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
validators: p.validators,
validators: new_validator_set(p.validators),
timeouts: TendermintTimeouts {
propose: p.timeout_propose.map_or(dt.propose, to_duration),
prevote: p.timeout_prevote.map_or(dt.prevote, to_duration),

View File

@ -116,7 +116,6 @@ impl ValidatorSet for ValidatorContract {
mod tests {
use util::*;
use rlp::encode;
use ethkey::Secret;
use spec::Spec;
use header::Header;
use account_provider::AccountProvider;
@ -140,7 +139,7 @@ mod tests {
#[test]
fn reports_validators() {
let tap = Arc::new(AccountProvider::transient_provider());
let v1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "").unwrap();
let v1 = tap.insert_account("1".sha3().into(), "").unwrap();
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, Some(tap.clone()));
client.engine().register_client(Arc::downgrade(&client));
let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap();

View File

@ -16,6 +16,8 @@
/// Validator lists.
#[cfg(test)]
mod test;
mod simple_list;
mod safe_contract;
mod contract;
@ -28,6 +30,8 @@ use ethjson::spec::ValidatorSet as ValidatorSpec;
use client::Client;
use header::{Header, BlockNumber};
#[cfg(test)]
pub use self::test::TestSet;
pub use self::simple_list::SimpleList;
use self::contract::ValidatorContract;
use self::safe_contract::ValidatorSafeContract;

View File

@ -166,9 +166,9 @@ mod tests {
fn uses_current_set() {
::env_logger::init().unwrap();
let tap = Arc::new(AccountProvider::transient_provider());
let s0 = Secret::from_slice(&"0".sha3()).unwrap();
let s0: Secret = "0".sha3().into();
let v0 = tap.insert_account(s0.clone(), "").unwrap();
let v1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "").unwrap();
let v1 = tap.insert_account("1".sha3().into(), "").unwrap();
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_multi, Some(tap));
client.engine().register_client(Arc::downgrade(&client));

View File

@ -294,9 +294,9 @@ mod tests {
#[test]
fn knows_validators() {
let tap = Arc::new(AccountProvider::transient_provider());
let s0 = Secret::from_slice(&"1".sha3()).unwrap();
let s0: Secret = "1".sha3().into();
let v0 = tap.insert_account(s0.clone(), "").unwrap();
let v1 = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "").unwrap();
let v1 = tap.insert_account("0".sha3().into(), "").unwrap();
let network_id = Spec::new_validator_safe_contract().network_id();
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, Some(tap));
client.engine().register_client(Arc::downgrade(&client));

View File

@ -0,0 +1,88 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/// Used for Engine testing.
use std::str::FromStr;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use util::{Arc, Bytes, H256, Address, HeapSizeOf};
use engines::Call;
use header::{Header, BlockNumber};
use super::{ValidatorSet, SimpleList};
/// Set used for testing with a single validator.
pub struct TestSet {
validator: SimpleList,
last_malicious: Arc<AtomicUsize>,
last_benign: Arc<AtomicUsize>,
}
impl TestSet {
pub fn new(last_malicious: Arc<AtomicUsize>, last_benign: Arc<AtomicUsize>) -> Self {
TestSet {
validator: SimpleList::new(vec![Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()]),
last_malicious: last_malicious,
last_benign: last_benign,
}
}
}
impl HeapSizeOf for TestSet {
fn heap_size_of_children(&self) -> usize {
self.validator.heap_size_of_children()
}
}
impl ValidatorSet for TestSet {
fn default_caller(&self, _block_id: ::ids::BlockId) -> Box<Call> {
Box::new(|_, _| Err("Test set doesn't require calls.".into()))
}
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[::receipt::Receipt]>)
-> ::engines::EpochChange
{
::engines::EpochChange::No
}
fn epoch_proof(&self, _header: &Header, _caller: &Call) -> Result<Vec<u8>, String> {
Ok(Vec::new())
}
fn epoch_set(&self, _header: &Header, _: &[u8]) -> Result<(u64, SimpleList), ::error::Error> {
Ok((0, self.validator.clone()))
}
fn contains_with_caller(&self, bh: &H256, address: &Address, _: &Call) -> bool {
self.validator.contains(bh, address)
}
fn get_with_caller(&self, bh: &H256, nonce: usize, _: &Call) -> Address {
self.validator.get(bh, nonce)
}
fn count_with_caller(&self, _bh: &H256, _: &Call) -> usize {
1
}
fn report_malicious(&self, _validator: &Address, block: BlockNumber, _proof: Bytes) {
self.last_malicious.store(block as usize, AtomicOrdering::SeqCst)
}
fn report_benign(&self, _validator: &Address, block: BlockNumber) {
self.last_benign.store(block as usize, AtomicOrdering::SeqCst)
}
}

View File

@ -962,7 +962,7 @@ mod tests {
use types::executed::CallType;
fn secret() -> Secret {
Secret::from_slice(&"".sha3()).unwrap()
"".sha3().into()
}
#[test]

View File

@ -27,7 +27,7 @@ use devtools::*;
use miner::Miner;
use spec::Spec;
use views::BlockView;
use ethkey::{KeyPair, Secret};
use ethkey::KeyPair;
use transaction::{PendingTransaction, Transaction, Action, Condition};
use miner::MinerService;
@ -296,7 +296,7 @@ fn change_history_size() {
#[test]
fn does_not_propagate_delayed_transactions() {
let key = KeyPair::from_secret(Secret::from_slice(&"test".sha3()).unwrap()).unwrap();
let key = KeyPair::from_secret("test".sha3().into()).unwrap();
let secret = key.secret();
let tx0 = PendingTransaction::new(Transaction {
nonce: 0.into(),

View File

@ -113,7 +113,7 @@ impl HeapSizeOf for Transaction {
impl From<ethjson::state::Transaction> for SignedTransaction {
fn from(t: ethjson::state::Transaction) -> Self {
let to: Option<ethjson::hash::Address> = t.to.into();
let secret = t.secret.map(|s| Secret::from_slice(&s.0).expect("Valid secret expected."));
let secret = t.secret.map(|s| Secret::from_slice(&s.0));
let tx = Transaction {
nonce: t.nonce.into(),
gas_price: t.gas_price.into(),

View File

@ -192,7 +192,7 @@ pub mod ecdh {
let sec = key::SecretKey::from_slice(context, &secret)?;
let shared = ecdh::SharedSecret::new_raw(context, &publ, &sec);
Secret::from_slice(&shared[0..32])
Secret::from_unsafe_slice(&shared[0..32])
.map_err(|_| Error::Secp(SecpError::InvalidSecretKey))
}
}

View File

@ -38,7 +38,7 @@ impl Generator for Brain {
match i > 16384 {
false => i += 1,
true => {
if let Ok(secret) = Secret::from_slice(&secret) {
if let Ok(secret) = Secret::from_unsafe_slice(&secret) {
let result = KeyPair::from_secret(secret);
if result.as_ref().ok().map_or(false, |r| r.address()[0] == 0) {
return result;

View File

@ -99,8 +99,7 @@ impl ExtendedSecret {
pub fn derive<T>(&self, index: Derivation<T>) -> ExtendedSecret where T: Label {
let (derived_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index);
let derived_secret = Secret::from_slice(&*derived_key)
.expect("Derivation always produced a valid private key; qed");
let derived_secret = Secret::from_slice(&*derived_key);
ExtendedSecret::with_code(derived_secret, next_chain_code)
}
@ -181,7 +180,7 @@ impl ExtendedKeyPair {
pub fn with_seed(seed: &[u8]) -> Result<ExtendedKeyPair, DerivationError> {
let (master_key, chain_code) = derivation::seed_pair(seed);
Ok(ExtendedKeyPair::with_secret(
Secret::from_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?,
Secret::from_unsafe_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?,
chain_code,
))
}
@ -402,7 +401,7 @@ mod tests {
fn test_extended<F>(f: F, test_private: H256) where F: Fn(ExtendedSecret) -> ExtendedSecret {
let (private_seed, chain_code) = master_chain_basic();
let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed).unwrap(), chain_code);
let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed), chain_code);
let derived = f(extended_secret);
assert_eq!(**derived.as_raw(), test_private);
}

View File

@ -62,7 +62,7 @@ impl KeyPair {
}
pub fn from_secret_slice(slice: &[u8]) -> Result<KeyPair, Error> {
Self::from_secret(Secret::from_slice(slice)?)
Self::from_secret(Secret::from_unsafe_slice(slice)?)
}
pub fn from_keypair(sec: key::SecretKey, publ: key::PublicKey) -> Self {

View File

@ -33,7 +33,7 @@ impl fmt::Debug for Secret {
}
impl Secret {
fn from_slice_unchecked(key: &[u8]) -> Self {
pub fn from_slice(key: &[u8]) -> Self {
assert_eq!(32, key.len(), "Caller should provide 32-byte length slice");
let mut h = H256::default();
@ -41,11 +41,17 @@ impl Secret {
Secret { inner: h }
}
pub fn from_slice(key: &[u8]) -> Result<Self, Error> {
/// Imports and validates the key.
pub fn from_unsafe_slice(key: &[u8]) -> Result<Self, Error> {
let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?;
Ok(secret.into())
}
/// Checks validity of this key.
pub fn check_validity(&self) -> Result<(), Error> {
self.to_secp256k1_secret().map(|_| ())
}
/// Inplace add one secret key to another (scalar + scalar)
pub fn add(&mut self, other: &Secret) -> Result<(), Error> {
let mut key_secret = self.to_secp256k1_secret()?;
@ -121,14 +127,25 @@ impl Secret {
impl FromStr for Secret {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let hash = H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?;
Self::from_slice(&hash)
Ok(H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?.into())
}
}
impl From<H256> for Secret {
fn from(s: H256) -> Self {
Secret::from_slice(&s)
}
}
impl From<&'static str> for Secret {
fn from(s: &'static str) -> Self {
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s))
}
}
impl From<key::SecretKey> for Secret {
fn from(key: key::SecretKey) -> Self {
Self::from_slice_unchecked(&key[0..32])
Self::from_slice(&key[0..32])
}
}

View File

@ -122,7 +122,7 @@ impl Crypto {
}
let secret = self.do_decrypt(password, 32)?;
Ok(Secret::from_slice(&secret)?)
Ok(Secret::from_unsafe_slice(&secret)?)
}
/// Try to decrypt and return result as is

View File

@ -90,7 +90,7 @@ pub trait VaultKeyDirectory: KeyDirectory {
fn set_meta(&self, meta: &str) -> Result<(), Error>;
}
pub use self::disk::RootDiskDirectory;
pub use self::disk::{RootDiskDirectory, DiskKeyFileManager, KeyFileManager};
pub use self::memory::MemoryDirectory;
pub use self::vault::VaultDiskDirectory;

View File

@ -15,10 +15,29 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::HashSet;
use std::path::Path;
use std::fs;
use ethkey::Address;
use dir::{paths, KeyDirectory, RootDiskDirectory};
use dir::{paths, KeyDirectory, RootDiskDirectory, DiskKeyFileManager, KeyFileManager};
use Error;
/// Import an account from a file.
pub fn import_account(path: &Path, dst: &KeyDirectory) -> Result<Address, Error> {
let key_manager = DiskKeyFileManager;
let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>();
let filename = path.file_name().and_then(|n| n.to_str()).map(|f| f.to_owned());
let account = fs::File::open(&path)
.map_err(Into::into)
.and_then(|file| key_manager.read(filename, file))?;
let address = account.address.clone();
if !existing_accounts.contains(&address) {
dst.insert(account)?;
}
Ok(address)
}
/// Import all accounts from one directory to the other.
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
let accounts = src.load()?;

View File

@ -57,7 +57,7 @@ mod secret_store;
pub use self::account::{SafeAccount, Crypto};
pub use self::error::Error;
pub use self::ethstore::{EthStore, EthMultiStore};
pub use self::import::{import_accounts, read_geth_accounts};
pub use self::import::{import_account, import_accounts, read_geth_accounts};
pub use self::json::OpaqueKeyFile as KeyFile;
pub use self::presale::PresaleWallet;
pub use self::secret_store::{

View File

@ -50,7 +50,7 @@ impl PresaleWallet {
let len = crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword)?;
let unpadded = &key[..len];
let secret = Secret::from_slice(&unpadded.keccak256())?;
let secret = Secret::from_unsafe_slice(&unpadded.keccak256())?;
if let Ok(kp) = KeyPair::from_secret(secret) {
if kp.address() == self.address {
return Ok(kp)

View File

@ -7,6 +7,8 @@
"transform-decorators-legacy",
"transform-class-properties",
"transform-object-rest-spread",
"transform-es2015-modules-commonjs",
"transform-runtime",
"lodash",
"recharts"
],
@ -25,7 +27,6 @@
},
"test": {
"plugins": [
"transform-runtime",
[ "babel-plugin-webpack-alias", { "config": "webpack/test.js" } ]
]
}

View File

@ -1,6 +1,6 @@
{
"name": "parity.js",
"version": "1.7.80",
"version": "1.7.82",
"main": "release/index.js",
"jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>",
@ -77,11 +77,11 @@
"babel-plugin-recharts": "1.1.0",
"babel-plugin-transform-class-properties": "6.23.0",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
"babel-plugin-transform-object-rest-spread": "6.23.0",
"babel-plugin-transform-react-remove-prop-types": "0.3.2",
"babel-plugin-transform-runtime": "6.23.0",
"babel-plugin-webpack-alias": "2.1.2",
"babel-polyfill": "6.23.0",
"babel-preset-env": "1.1.9",
"babel-preset-es2015": "6.22.0",
"babel-preset-es2016": "6.22.0",
@ -183,6 +183,7 @@
"promise-worker": "1.1.1",
"react": "15.4.2",
"react-dom": "15.4.2",
"react-inspector": "paritytech/react-inspector",
"react-intl": "2.1.5",
"react-router": "3.0.0",
"react-router-redux": "4.0.7",

View File

@ -23,7 +23,7 @@ import { Db, Eth, Parity, Net, Personal, Shh, Signer, Trace, Web3 } from './rpc'
import Subscriptions from './subscriptions';
import util from './util';
import { isFunction } from './util/types';
import { LocalAccountsMiddleware } from './local';
// import { LocalAccountsMiddleware } from './local';
export default class Api extends EventEmitter {
constructor (transport, allowSubscriptions = true) {
@ -54,9 +54,9 @@ export default class Api extends EventEmitter {
const middleware = this.parity
.nodeKind()
.then((nodeKind) => {
if (nodeKind.availability === 'public') {
return LocalAccountsMiddleware;
}
// if (nodeKind.availability === 'public') {
// return LocalAccountsMiddleware;
// }
return null;
})

59
js/src/dapps/console.js Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import 'codemirror/addon/dialog/dialog';
import 'codemirror/addon/dialog/dialog.css';
import 'codemirror/addon/hint/javascript-hint';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/search/match-highlighter';
import 'codemirror/addon/search/search';
import 'codemirror/addon/search/searchcursor';
import 'codemirror/keymap/sublime';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/javascript/javascript';
// Custom codemirror style
import './console/codemirror.css';
import Application from './console/Application';
import '../../assets/fonts/Roboto/font.css';
import '../../assets/fonts/RobotoMono/font.css';
import './style.css';
ReactDOM.render(
<AppContainer>
<Application />
</AppContainer>,
document.querySelector('#container')
);
if (module.hot) {
module.hot.accept('./console/Application/index.js', () => {
require('./console/Application/index.js');
ReactDOM.render(
<AppContainer>
<Application />
</AppContainer>,
document.querySelector('#container')
);
});
}

View File

@ -0,0 +1,65 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.app {
display: flex;
flex-direction: column;
font-family: Arial, sans-serif;
font-size: 11px;
height: 100vh;
overflow: hidden;
}
textarea,
input {
font-family: dejavu sans mono, monospace;
outline: none;
}
code,
pre {
font-family: dejavu sans mono, monospace;
font-size: 11px;
}
.header {
flex: 0 0 auto;
}
.view {
display: flex;
flex: 1;
flex-direction: column;
}
.eval {
flex: 0 1 auto;
font-family: dejavu sans mono, monospace;
overflow: auto;
}
.input {
border-top: 1px solid #eee;
display: flex;
flex: 1 1 auto;
min-height: 50px;
}
.status {
flex: 0 0 auto;
font-family: dejavu sans mono, monospace;
}

View File

@ -0,0 +1,94 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import { api } from '../parity';
import Console from '../Console';
import Header from '../Header';
import Input from '../Input';
import Settings from '../Settings';
import Snippets from '../Snippets';
import Watches from '../Watches';
import ApplicationStore from './application.store';
import WatchesStore from '../Watches/watches.store';
import styles from './application.css';
@observer
export default class Application extends Component {
application = ApplicationStore.get();
watches = WatchesStore.get();
componentWillMount () {
this.watches.add('time', () => new Date());
this.watches.add('blockNumber', api.eth.blockNumber, api);
}
render () {
return (
<div className={ styles.app }>
<div className={ styles.header }>
<Header />
</div>
{ this.renderView() }
<div className={ styles.status }>
<Watches />
</div>
</div>
);
}
renderView () {
const { view } = this.application;
if (view === 'console') {
return (
<div className={ styles.view }>
<div className={ styles.eval }>
<Console />
</div>
<div className={ styles.input }>
<Input />
</div>
</div>
);
}
if (view === 'settings') {
return (
<div className={ styles.view }>
<Settings />
</div>
);
}
if (view === 'snippets') {
return (
<div className={ styles.view }>
<Snippets />
</div>
);
}
return null;
}
}

View File

@ -0,0 +1,42 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
let instance;
export default class ApplicationStore {
@observable view = this.views[0].id;
views = [
{ label: 'Console', id: 'console' },
{ label: 'Snippets', id: 'snippets' },
{ label: 'Settings', id: 'settings' }
];
static get () {
if (!instance) {
instance = new ApplicationStore();
}
return instance;
}
@action
setView (view) {
this.view = view;
}
}

View File

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

View File

@ -0,0 +1,55 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
background: #f8f8f8;
box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.5);
font-family: dejavu sans mono, monospace;
left: 20px;
position: absolute;
max-height: 300px;
overflow: auto;
}
.item {
background-color: white;
padding: 0.25em 0.25em 0.25em 0.35em;
display: flex;
justify-content: space-between;
&.selected {
background-color: rgb(64, 115, 244);
&,
.proto {
color: white;
}
}
&:hover {
cursor: default;
}
&:hover:not(.selected) {
background-color: rgb(230, 236, 255);
}
.proto {
color: gray;
margin-left: 1em;
}
}

View File

@ -0,0 +1,96 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import AutocompleteStore from './autocomplete.store';
import styles from './autocomplete.css';
@observer
export default class Autocomplete extends Component {
autocompleteStore = AutocompleteStore.get();
render () {
if (!this.autocompleteStore.show) {
return null;
}
return (
<div
className={ styles.container }
style={ this.autocompleteStore.position }
>
{ this.renderAutocompletes() }
</div>
);
}
renderAutocompletes () {
const { selected, values } = this.autocompleteStore;
const displayedProto = {};
return values.map((autocomplete, index) => {
const { name, prototypeName } = autocomplete;
const onClick = () => this.handleClick(index);
const setRef = (node) => this.setRef(index, node);
const proto = !displayedProto[prototypeName]
? (
<span className={ styles.proto }>
{ prototypeName }
</span>
)
: null;
if (!displayedProto[prototypeName]) {
displayedProto[prototypeName] = true;
}
const classes = [ styles.item ];
if (index === selected) {
classes.push(styles.selected);
}
return (
<div
className={ classes.join(' ') }
key={ index }
onClick={ onClick }
ref={ setRef }
>
<span>
{ name }
</span>
{ proto }
</div>
);
});
}
handleClick = (index) => {
this.autocompleteStore.select(index);
};
setRef = (index, node) => {
const element = ReactDOM.findDOMNode(node);
this.autocompleteStore.setElement(index, element);
};
}

View File

@ -0,0 +1,234 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import { evaluate } from '../utils';
let instance;
export default class AutocompleteStore {
@observable values = [];
@observable position = {};
@observable show = false;
@observable selected = null;
elements = {};
inputNode = null;
lastObject = null;
lastObjectPropertyNames = [];
static get () {
if (!instance) {
instance = new AutocompleteStore();
}
return instance;
}
get hasSelected () {
return this.selected !== null;
}
clearCache () {
this.lastObject = null;
this.lastObjectPropertyNames = null;
}
@action
focus (offset = 1) {
if (this.values.length === 0) {
this.selected = null;
return;
}
this.selected = this.selected === null
? (
offset === 1
? 0
: this.values.length - 1
)
: (this.values.length + this.selected + offset) % (this.values.length);
if (this.isVisible(this.selected)) {
return;
}
const element = this.elements[this.selected];
if (!element) {
return;
}
element.scrollIntoView(offset === -1);
}
focusOnInput () {
if (!this.inputNode) {
return;
}
this.inputNode.focus();
}
@action
hide () {
this.show = false;
this.selected = null;
}
isVisible (index) {
const element = this.elements[index];
if (!element) {
return false;
}
const eBoundings = element.getBoundingClientRect();
const pBoundings = element.parentElement.getBoundingClientRect();
if (eBoundings.top < pBoundings.top || eBoundings.bottom > pBoundings.bottom) {
return false;
}
return true;
}
select (inputStore, _index = this.selected) {
const index = _index === null
? 0
: _index;
if (!this.values[index]) {
console.warn(`autocomplete::select has been called on AutocompleteStore with wrong value ${index}`);
return;
}
const { name } = this.values[index];
const { input } = inputStore;
const objects = input.split('.');
objects[objects.length - 1] = name;
const nextInput = objects.join('.');
this.hide();
this.focusOnInput();
return inputStore.updateInput(nextInput, false);
}
setElement (index, element) {
this.elements[index] = element;
}
setInputNode (node) {
this.inputNode = node;
}
@action
setPosition () {
if (!this.inputNode) {
return;
}
const inputBoundings = this.inputNode.getBoundingClientRect();
const bodyBoundings = document.body.getBoundingClientRect();
// display on bottom of input
if (inputBoundings.top < bodyBoundings.height / 2) {
const nextPosition = {
top: 20
};
this.position = nextPosition;
return;
}
// display on top of input
const nextPosition = {
bottom: inputBoundings.height
};
this.position = nextPosition;
return;
}
@action
setValues (values) {
this.values = values;
this.selected = null;
const show = values.length > 0;
// Reveal autocomplete
if (!this.show && show) {
this.setPosition();
}
this.show = show;
}
update (input) {
if (input.length === 0) {
return this.setValues([]);
}
const objects = input.split('.');
const suffix = objects.pop().toLowerCase();
const prefix = objects.join('.');
const object = prefix.length > 0
? prefix
: 'window';
if (object !== this.lastObject) {
const evalResult = evaluate(object);
if (evalResult.error) {
this.lastObjectProperties = [];
} else {
this.lastObjectProperties = getAllProperties(evalResult.result);
}
this.lastObject = object;
}
const autocompletes = this.lastObjectProperties.filter((property) => {
return property.name.toLowerCase().includes(suffix);
});
return this.setValues(autocompletes);
}
}
function getAllProperties (object) {
const propertyNames = {};
while (object) {
const prototypeName = object && object.constructor && object.constructor.name || '';
Object.getOwnPropertyNames(object)
.sort()
.forEach((name) => {
if (Object.prototype.hasOwnProperty.call(propertyNames, name)) {
return;
}
propertyNames[name] = { name, prototypeName };
});
object = Object.getPrototypeOf(object);
}
return Object.values(propertyNames);
}

View File

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

View File

@ -0,0 +1,58 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.result {
border-top: 1px solid #eee;
display: flex;
font-family: dejavu sans mono, monospace;
padding: 0.35em 0.25em;
&.error {
background-color: hsl(0, 100%, 97%);
.text {
color: red;
}
}
&.warn {
background-color: hsl(50, 100%, 95%);
}
}
.type {
font-weight: bold !important;
font-size: 8pt;
padding: 0 0.5em 0 0.25em;
}
.time {
color: gray;
padding: 0 1em 0 0.5em;
}
.token {
white-space: pre-wrap;
}
.text {
display: flex;
}
.text .token:not(:first-child) {
margin-left: 0.5em;
}

View File

@ -0,0 +1,118 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { ObjectInspector } from 'react-inspector';
import ConsoleStore from './console.store';
import SettingsStore from '../Settings/settings.store';
import styles from './console.css';
const ICONS = {
debug: '&nbsp;',
error: '✖',
info: '',
input: '&gt;',
log: '&nbsp;',
result: '&lt;',
warn: '⚠'
};
@observer
export default class Console extends Component {
consoleStore = ConsoleStore.get();
settingsStore = SettingsStore.get();
render () {
return (
<div ref={ this.setRef }>
{ this.renderResults() }
</div>
);
}
renderResults () {
const { logs } = this.consoleStore;
return logs.map((data, index) => {
const { type, timestamp } = data;
const values = this.consoleStore.logValues[index];
const classes = [ styles.result, styles[type] ];
return (
<div
className={ classes.join(' ') }
key={ index }
>
<span
className={ styles.type }
dangerouslySetInnerHTML={ { __html: ICONS[type] || '' } }
/>
{ this.renderTimestamp(timestamp) }
<span className={ styles.text }>
{
values.map((value, valueIndex) => (
<span
className={ styles.token }
key={ valueIndex }
>
{ this.toString(value) }
</span>
))
}
</span>
</div>
);
});
}
renderTimestamp (timestamp) {
const { displayTimestamps } = this.settingsStore;
if (!displayTimestamps) {
return null;
}
return (
<span className={ styles.time }>
{ new Date(timestamp).toISOString().slice(11, 23) }
</span>
);
}
setRef = (node) => {
const element = ReactDOM.findDOMNode(node);
this.consoleStore.setNode(element);
};
toString (value) {
if (typeof value === 'string') {
return value;
}
if (value instanceof Error) {
return value.toString();
}
return (
<ObjectInspector data={ value } />
);
}
}

View File

@ -0,0 +1,126 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import AutocompleteStore from '../Autocomplete/autocomplete.store';
import { evaluate } from '../utils';
let instance;
export default class ConsoleStore {
@observable logs = [];
autocompleteStore = AutocompleteStore.get();
logValues = [];
node = null;
constructor () {
this.attachConsole();
}
static get () {
if (!instance) {
instance = new ConsoleStore();
}
return instance;
}
attachConsole () {
['debug', 'error', 'info', 'log', 'warn'].forEach((level) => {
const old = window.console[level].bind(window.console);
window.console[level] = (...args) => {
old(...args);
this.log({ type: level, values: args });
};
});
}
@action
clear () {
this.logs = [];
this.logValues = [];
}
evaluate (input) {
this.log({ type: 'input', value: input });
setTimeout(() => {
const { result, error } = evaluate(input);
let value = error || result;
const type = error
? 'error'
: 'result';
if (typeof value === 'string') {
value = `"${value}"`;
}
if (value && typeof value === 'object' && typeof value.then === 'function') {
return value
.then((result) => {
this.log({ type: 'result', value: result });
})
.catch((error) => {
this.log({ type: 'error', value: error });
});
}
this.log({ type, value });
});
}
@action
log ({ type, value, values }) {
this.logs.push({
type,
timestamp: Date.now()
});
if (values) {
this.logValues.push(values);
} else {
this.logValues.push([ value ]);
}
this.autocompleteStore.setPosition();
this.scroll();
}
setNode (node) {
this.node = node;
this.scroll();
}
scroll () {
if (!this.node) {
return;
}
setTimeout(() => {
if (this.node.children.length === 0) {
return;
}
// Scroll to the last child
this.node
.children[this.node.children.length - 1]
.scrollIntoView(false);
}, 50);
}
}

View File

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

View File

@ -0,0 +1,51 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
background-color: #f3f3f3;
border-bottom: 1px solid #ccc;
font-size: 12px;
padding: 0 0.5em;
}
.tabs {
display: flex;
}
.tab {
align-items: center;
box-sizing: border-box;
border: 1px solid transparent;
color: #333;
cursor: default;
display: flex;
height: 24px;
line-height: 15px;
margin-top: 2px;
padding: 2px 6px 2px 4px;
&:hover,
&.active:hover {
background-color: #e5e5e5;
}
&.active {
background-color: white;
border: 1px solid #ccc;
border-bottom: none;
}
}

View File

@ -0,0 +1,65 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import ApplicationStore from '../Application/application.store';
import styles from './header.css';
@observer
export default class Header extends Component {
application = ApplicationStore.get();
render () {
return (
<div className={ styles.container }>
<div className={ styles.tabs }>
{ this.renderTabs() }
</div>
</div>
);
}
renderTabs () {
const { view } = this.application;
return this.application.views.map((tab) => {
const { label, id } = tab;
const classes = [ styles.tab ];
const onClick = () => this.handleClickTab(id);
if (id === view) {
classes.push(styles.active);
}
return (
<div
className={ classes.join(' ') }
key={ id }
onClick={ onClick }
>
{ label }
</div>
);
});
}
handleClickTab = (id) => {
this.application.setView(id);
};
}

View File

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

View File

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

View File

@ -0,0 +1,46 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.type {
color: #59f;
font-weight: bold !important;
font-size: 11px;
padding: 0 0.5em 0 0.25em;
}
.inputContainer {
flex: 1;
}
.input {
border: 0;
margin: 0;
padding: 0;
color: black;
height: 100%;
font-size: 11px;
resize: none;
width: 100%;
}
.container {
border-top: 1px solid lightgray;
display: flex;
flex: 1;
padding: 0.25em;
position: relative;
}

View File

@ -0,0 +1,145 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import keycode from 'keycode';
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Autocomplete from '../Autocomplete';
import AutocompleteStore from '../Autocomplete/autocomplete.store';
import ConsoleStore from '../Console/console.store';
import InputStore from './input.store';
import SettingsStore from '../Settings/settings.store';
import styles from './input.css';
@observer
export default class Input extends Component {
autocompleteStore = AutocompleteStore.get();
consoleStore = ConsoleStore.get();
inputStore = InputStore.get();
settingsStore = SettingsStore.get();
render () {
const { input } = this.inputStore;
return (
<div className={ styles.container }>
<Autocomplete />
<span className={ styles.type }>&gt;</span>
<div className={ styles.inputContainer }>
<textarea
autoFocus
className={ styles.input }
onChange={ this.handleChange }
onKeyDown={ this.handleKeyDown }
ref={ this.setRef }
rows={ input.split('\n').length }
type='text'
value={ input }
/>
</div>
</div>
);
}
handleChange = (event) => {
const { value } = event.target;
this.inputStore.updateInput(value);
};
handleKeyDown = (event) => {
const { executeOnEnter } = this.settingsStore;
const { input } = this.inputStore;
const codeName = keycode(event);
const multilines = input.split('\n').length > 1;
// Clear console with CTRL+L
if (codeName === 'l' && event.ctrlKey) {
event.preventDefault();
event.stopPropagation();
return this.consoleStore.clear();
}
if (codeName === 'esc') {
event.preventDefault();
event.stopPropagation();
return this.autocompleteStore.hide();
}
if (codeName === 'enter') {
if (event.shiftKey) {
return;
}
// If not execute on enter: execute on
// enter + CTRL
if (!executeOnEnter && !event.ctrlKey) {
return;
}
event.preventDefault();
event.stopPropagation();
if (this.autocompleteStore.hasSelected) {
return this.autocompleteStore.select(this.inputStore);
}
if (input.length > 0) {
return this.inputStore.execute();
}
}
if (codeName === 'up' && !multilines) {
event.preventDefault();
event.stopPropagation();
if (this.autocompleteStore.show) {
return this.autocompleteStore.focus(-1);
}
return this.inputStore.selectHistory(-1);
}
if (codeName === 'down' && !multilines) {
event.preventDefault();
event.stopPropagation();
if (this.autocompleteStore.show) {
return this.autocompleteStore.focus(1);
}
return this.inputStore.selectHistory(1);
}
if (codeName === 'left' && this.autocompleteStore.show) {
return this.autocompleteStore.hide();
}
if (codeName === 'right' && this.autocompleteStore.show) {
event.preventDefault();
event.stopPropagation();
return this.autocompleteStore.select(this.inputStore);
}
};
setRef = (node) => {
this.inputStore.setInputNode(ReactDOM.findDOMNode(node));
};
}

View File

@ -0,0 +1,124 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import store from 'store';
import AutocompleteStore from '../Autocomplete/autocomplete.store';
import ConsoleStore from '../Console/console.store';
const LS_HISTORY_KEY = '_console::history';
const MAX_HISTORY_LINES = 5;
let instance;
export default class InputStore {
@observable input = '';
autocompleteStore = AutocompleteStore.get();
consoleStore = ConsoleStore.get();
history = [];
historyOffset = null;
inputNode = null;
lastInput = '';
constructor () {
this.loadHistory();
}
static get () {
if (!instance) {
instance = new InputStore();
}
return instance;
}
setInputNode (node) {
this.inputNode = node;
this.autocompleteStore.setInputNode(node);
}
@action
updateInput (nextValue = '', updateAutocomplete = true) {
this.input = nextValue;
const multilines = nextValue.split('\n').length > 1;
if (updateAutocomplete && !multilines) {
this.autocompleteStore.update(nextValue);
}
}
selectHistory (_offset) {
// No history
if (this.history.length === 0) {
return;
}
if (this.historyOffset === null) {
// Can't go down if no history selected
if (_offset === 1) {
return;
}
this.historyOffset = this.history.length - 1;
this.lastInput = this.input;
return this.updateInput(this.history[this.historyOffset], false);
}
if (_offset === 1 && this.historyOffset === this.history.length - 1) {
this.historyOffset = null;
return this.updateInput(this.lastInput);
}
this.historyOffset = Math.max(0, this.historyOffset + _offset);
const nextInput = this.history[this.historyOffset];
this.updateInput(nextInput, false);
}
execute () {
const { input } = this;
this.pushToHistory(input);
this.consoleStore.evaluate(input);
this.updateInput('');
this.historyOffset = null;
this.autocompleteStore.clearCache();
}
pushToHistory (input) {
// Don't stack twice the same input in
// history
if (this.history[this.history.length - 1] !== input) {
this.history.push(input);
}
this.saveHistory();
}
loadHistory () {
this.history = store.get(LS_HISTORY_KEY) || [];
}
saveHistory () {
if (this.history.length > MAX_HISTORY_LINES) {
this.history = this.history.slice(-1 * MAX_HISTORY_LINES);
}
store.set(LS_HISTORY_KEY, this.history.slice());
}
}

View File

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

View File

@ -0,0 +1,32 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
display: flex;
flex-direction: row;
font-family: Arial, sans-serif;
font-size: 12px;
padding: 0.5em 1em;
}
.option {
align-items: center;
display: flex;
flex: 0 0 50%;
flex-direction: row;
margin: 0.5em 0;
}

View File

@ -0,0 +1,70 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import SettingsStore from './settings.store';
import styles from './settings.css';
@observer
export default class Settings extends Component {
settingsStore = SettingsStore.get();
render () {
const { displayTimestamps, executeOnEnter } = this.settingsStore;
return (
<div className={ styles.container }>
<div className={ styles.option }>
<input
checked={ executeOnEnter }
id='executeOnEnter'
onChange={ this.handleExecuteOnEnterChange }
type='checkbox'
/>
<label htmlFor='executeOnEnter'>
Execute on <code>Enter</code>
</label>
</div>
<div className={ styles.option }>
<input
checked={ displayTimestamps }
id='displayTimestamps'
onChange={ this.handleDisplayTimestampsChange }
type='checkbox'
/>
<label htmlFor='displayTimestamps'>
Show timestamps
</label>
</div>
</div>
);
}
handleDisplayTimestampsChange = (event) => {
const { checked } = event.target;
this.settingsStore.setDisplayTimestamps(checked);
};
handleExecuteOnEnterChange = (event) => {
const { checked } = event.target;
this.settingsStore.setExecuteOnEnter(checked);
};
}

View File

@ -0,0 +1,71 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import store from 'store';
const LS_SETTINGS_KEY = '_console::settings';
let instance;
export default class SettingsStore {
@observable displayTimestamps = true;
@observable executeOnEnter = true;
constructor () {
this.load();
}
static get () {
if (!instance) {
instance = new SettingsStore();
}
return instance;
}
load () {
const settings = store.get(LS_SETTINGS_KEY) || {};
const { executeOnEnter, displayTimestamps } = settings;
if (executeOnEnter !== undefined) {
this.setExecuteOnEnter(executeOnEnter);
}
if (displayTimestamps !== undefined) {
this.setDisplayTimestamps(displayTimestamps);
}
}
save () {
const { executeOnEnter, displayTimestamps } = this;
const settings = { executeOnEnter, displayTimestamps };
store.set(LS_SETTINGS_KEY, settings);
}
@action
setDisplayTimestamps (value) {
this.displayTimestamps = value;
this.save();
}
@action
setExecuteOnEnter (value) {
this.executeOnEnter = value;
this.save();
}
}

View File

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

View File

@ -0,0 +1,122 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
display: flex;
flex: 1;
flex-direction: row;
}
.panel {
border-right: 1px solid lightgray;
display: flex;
flex-direction: column;
min-width: 200px;
}
.add {
align-items: center;
background-color: #fcfcfc;
border-bottom: 1px solid lightgray;
cursor: default;
display: flex;
padding: 0.5em 1em;
.plus {
font-size: 15px;
font-weight: bold !important;
margin-right: 5px;
}
&:hover {
background-color: #f0f0f0;
}
}
.list {
display: flex;
flex-direction: column;
margin-top: 3px;
}
.code {
display: flex;
flex: 1;
flex-direction: column;
.console {
border-top: 1px solid lightgray;
max-height: 200px;
flex: 0 0 0;
> * {
overflow: auto;
height: 100%;
}
}
> * {
flex: 1;
}
:global(.CodeMirror) {
height: 100%;
}
}
.file {
align-items: center;
cursor: default;
display: flex;
padding: 0.5em 0.5em 0.5em 1em;
&.selected {
background-color: #f0f0f0;
}
&:hover {
background-color: rgb(230, 236, 255);
}
.pristine {
font-size: 20px;
margin-right: 3px;
height: 13px;
}
.remove {
cursor: default;
display: inline-flex;
font-size: 14px;
margin-left: -0.25em;
margin-right: 0.25em;
}
}
.inputContainer {
background-color: white;
border: solid 1px #d8d8d8;
margin-right: 0.5em;
padding: 3px;
width: 100%;
}
.input {
border: none;
font: 11px Arial;
width: 100%;
}

View File

@ -0,0 +1,221 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import keycode from 'keycode';
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import CodeMirror from 'react-codemirror';
import EventListener from 'react-event-listener';
import Console from '../Console';
import SnippetsStore from './snippets.store';
import styles from './snippets.css';
@observer
export default class Snippets extends Component {
snippetsStore = SnippetsStore.get();
render () {
const { code } = this.snippetsStore;
return (
<div className={ styles.container }>
<EventListener
onKeyDown={ this.handleKeyDown }
target='window'
/>
<div className={ styles.panel }>
<div
className={ styles.add }
onClick={ this.handleAddFile }
>
<span className={ styles.plus }>+</span>
<span>New Snippet</span>
</div>
<div className={ styles.list }>
{ this.renderFiles() }
</div>
</div>
<div className={ styles.code }>
<CodeMirror
ref={ this.setRef }
onChange={ this.handleChange }
options={ {
autofocus: true,
extraKeys: {
'Ctrl-Space': 'autocomplete'
},
keyMap: 'sublime',
highlightSelectionMatches: {
delay: 0,
showToken: false
},
lineNumbers: true,
mode: 'javascript'
} }
value={ code }
/>
<div className={ styles.console }>
<Console />
</div>
</div>
</div>
);
}
renderFiles () {
const { files } = this.snippetsStore;
return files
.values()
.sort((fa, fb) => fa.name.localeCompare(fb.name))
.map((file) => this.renderFile(file));
}
renderFile (file) {
const { nextName, renaming, selected } = this.snippetsStore;
const { id, name } = file;
const classes = [ styles.file ];
if (renaming === id) {
return (
<div
className={ classes.join(' ') }
key={ id }
>
<EventListener
onClick={ this.handleSaveName }
target='window'
/>
<div className={ styles.inputContainer }>
<input
autoFocus
className={ styles.input }
onClick={ this.stopPropagation }
onChange={ this.handleNameChange }
onKeyDown={ this.handleRenameKeyDown }
type='text'
value={ nextName }
/>
</div>
</div>
);
}
const onClick = () => this.handleSelectFile(id);
const onDoubleClick = () => this.handleRenameFile(id);
const onRemove = (event) => this.handleRemove(id, event);
if (selected === id) {
classes.push(styles.selected);
}
return (
<div
className={ classes.join(' ') }
key={ id }
onClick={ onClick }
onDoubleClick={ onDoubleClick }
>
<span
className={ styles.remove }
onClick={ onRemove }
title={ `Remove ${name}` }
>
</span>
<span className={ styles.pristine }>
{
file.isPristine
? null
: '*'
}
</span>
<span>
{ name }
</span>
</div>
);
}
handleAddFile = () => {
this.snippetsStore.create();
};
handleSaveName = (event) => {
this.snippetsStore.saveName();
return event;
};
handleChange = (value) => {
this.snippetsStore.edit(value);
};
handleKeyDown = (event) => {
const codeName = keycode(event);
if (codeName === 's' && event.ctrlKey) {
event.preventDefault();
event.stopPropagation();
return this.snippetsStore.save();
}
};
handleNameChange = (event) => {
const { value } = event.target;
this.snippetsStore.updateName(value);
};
handleRemove = (id, event) => {
this.snippetsStore.remove(id);
event.stopPropagation();
};
handleRenameFile = (id) => {
this.snippetsStore.startRename(id);
};
handleRenameKeyDown = (event) => {
const codeName = keycode(event);
if (codeName === 'enter') {
return this.snippetsStore.saveName();
}
if (codeName === 'esc') {
return this.snippetsStore.cancelRename();
}
};
handleSelectFile = (id) => {
this.snippetsStore.select(id);
};
setRef = (node) => {
const codeMirror = node
? node.getCodeMirror()
: null;
this.snippetsStore.setCodeMirror(codeMirror);
};
stopPropagation = (event) => {
event.stopPropagation();
};
}

View File

@ -0,0 +1,249 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import keycode from 'keycode';
import { action, computed, map, observable, transaction } from 'mobx';
import store from 'store';
import { evaluate } from '../utils';
const LS_SNIPPETS_KEY = '_console::snippets';
let instance;
export default class SnippetsStore {
@observable files = map();
@observable nextName = null;
@observable renaming = null;
@observable selected = null;
codeMirror = null;
constructor () {
this.load();
}
static get () {
if (!instance) {
instance = new SnippetsStore();
}
return instance;
}
@computed
get code () {
if (!this.selected) {
return '';
}
return this.files.get(this.selected).content;
}
@action
cancelRename () {
if (!this.renaming || !this.nextName) {
return;
}
this.renaming = null;
this.nextName = null;
}
clearCodeHistory () {
if (this.codeMirror) {
this.codeMirror.doc.clearHistory();
}
}
@action
create () {
const id = this.getNewId();
const file = {
content: '',
isPristine: false,
name: `Snippet #${id}`,
id
};
transaction(() => {
this.files.set(id, file);
this.select(id);
});
}
edit (value) {
if (!this.selected) {
this.create();
}
const file = this.files.get(this.selected);
file.content = value;
this.updateFile(file);
}
evaluate () {
const code = this.code;
if (!code) {
return;
}
const { result, error } = evaluate(code);
if (error) {
console.error(error);
} else {
console.log(result);
}
}
getFromStorage () {
return store.get(LS_SNIPPETS_KEY) || [];
}
getNewId () {
if (this.files.size === 0) {
return 1;
}
const ids = this.files.values().map((file) => file.id);
return Math.max(...ids) + 1;
}
load () {
const files = this.getFromStorage();
transaction(() => {
files.forEach((file) => {
this.files.set(file.id, file);
});
});
}
@action
remove (id) {
transaction(() => {
if (id === this.selected) {
this.selected = null;
}
this.files.delete(id);
const files = this.getFromStorage()
.filter((f) => f.id !== id);
return store.set(LS_SNIPPETS_KEY, files);
});
}
save (_file) {
let file;
if (!_file) {
if (!this.selected) {
return false;
}
file = this.files.get(this.selected);
} else {
file = _file;
}
file.savedContent = file.content;
this.updateFile(file);
this.saveToStorage(file);
}
saveName () {
if (!this.renaming || !this.nextName) {
return;
}
const file = this.files.get(this.renaming);
file.name = this.nextName;
this.save(file);
this.cancelRename();
}
saveToStorage (file) {
const files = this.getFromStorage();
const index = files.findIndex((f) => file.id === f.id);
if (index === -1) {
files.push(file);
} else {
files[index] = file;
}
return store.set(LS_SNIPPETS_KEY, files);
}
@action
select (id) {
this.selected = id;
// Wait for the file content to be loaded
setTimeout(() => {
this.clearCodeHistory();
}, 50);
}
setCodeMirror (codeMirror) {
this.codeMirror = codeMirror;
if (!codeMirror) {
return;
}
this.codeMirror
.on('keydown', (_, event) => {
const codeName = keycode(event);
if (codeName === 'enter' && event.ctrlKey) {
event.preventDefault();
event.stopPropagation();
return this.evaluate();
}
});
}
@action
startRename (id) {
const file = this.files.get(id);
transaction(() => {
this.renaming = id;
this.nextName = file.name;
});
}
@action
updateFile (file) {
file.isPristine = (file.content === file.savedContent);
this.files.set(file.id, file);
}
@action
updateName (value) {
this.nextName = value;
}
}

View File

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

View File

@ -0,0 +1,107 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
background: #eee;
border-top: 1px solid lightgray;
display: flex;
flex-direction: row;
padding: 0.25em;
font-size: 8pt;
color: #888;
}
.watch {
align-items: flex-end;
border-left: 1px solid #ccc;
display: flex;
padding: 0 1em;
&.error .result {
color: red;
}
.result {
color: #00f;
font-weight: bold;
margin-left: 0.5em;
}
}
.add {
color: green;
cursor: default;
font-size: 14px;
font-weight: bold !important;
padding: 0 1em;
&:hover {
background-color: #fcfcfc;
}
&.selected {
background-color: rgb(230, 236, 255);
}
}
.addContainer {
position: relative;
}
.addForm {
bottom: 100%;
display: flex;
padding: 0.5em 0;
position: absolute;
width: 100vw;
.inputContainer {
border: solid 1px #d8d8d8;
margin-right: 0.5em;
padding: 3px;
}
.input {
border: none;
font: 12px Arial;
&.big {
width: 300px;
}
}
.button {
color: black;
font: 12px Arial;
padding: 0 1em;
}
}
.remove {
cursor: default;
display: inline-flex;
font-size: 14px;
margin-right: 0.75em;
* {
line-height: 13px;
}
&:hover {
background-color: #fcfcfc;
}
}

View File

@ -0,0 +1,186 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import WatchesStore from './watches.store';
import styles from './watches.css';
@observer
export default class Watches extends Component {
watchesStore = WatchesStore.get();
render () {
return (
<div className={ styles.container }>
{ this.renderAddWatch() }
{ this.renderWatches() }
</div>
);
}
renderAddForm () {
const { showAdd } = this.watchesStore;
if (!showAdd) {
return null;
}
return (
<div className={ styles.addForm }>
<div className={ styles.inputContainer }>
<input
className={ styles.input }
onChange={ this.handleAddNameChange }
placeholder='Name'
type='text'
/>
</div>
<div className={ styles.inputContainer }>
<input
className={ [ styles.input, styles.big ].join(' ') }
onChange={ this.handleAddFunctionChange }
placeholder='Function'
type='text'
/>
</div>
<div className={ styles.inputContainer }>
<input
className={ styles.input }
onChange={ this.handleAddContextChange }
placeholder='Context'
type='text'
/>
</div>
<button
className={ styles.button }
onClick={ this.handleAddWatch }
>
Add
</button>
</div>
);
}
renderAddWatch () {
const { showAdd } = this.watchesStore;
const classes = [ styles.add ];
if (showAdd) {
classes.push(styles.selected);
}
return (
<div className={ styles.addContainer }>
{ this.renderAddForm() }
<span
className={ classes.join(' ') }
onClick={ this.handleToggleAdd }
>
+
</span>
</div>
);
}
renderWatches () {
const { names } = this.watchesStore;
return names.map((name) => {
const { result, error } = this.watchesStore.get(name);
const classes = [ styles.watch ];
const resultStr = error
? error.toString()
: this.toString(result);
const onClick = () => this.handleRemoveWatch(name);
if (error) {
classes.push(styles.error);
}
return (
<div
className={ classes.join(' ') }
key={ name }
>
<span
className={ styles.remove }
onClick={ onClick }
title={ `Remove "${name}" watch` }
>
<span></span>
</span>
<span>{ name }</span>
<span className={ styles.result }>{ resultStr.toString() }</span>
</div>
);
});
}
handleAddFunctionChange = (event) => {
const { value } = event.target;
this.watchesStore.updateAddFunction(value);
};
handleAddContextChange = (event) => {
const { value } = event.target;
this.watchesStore.updateAddContext(value);
};
handleAddNameChange = (event) => {
const { value } = event.target;
this.watchesStore.updateAddName(value);
};
handleAddWatch = () => {
this.watchesStore.addWatch();
};
handleRemoveWatch = (name) => {
this.watchesStore.remove(name);
};
handleToggleAdd = () => {
this.watchesStore.toggleAdd();
};
toString (result) {
if (result === undefined) {
return 'undefined';
}
if (result === null) {
return 'null';
}
if (typeof result.toFormat === 'function') {
return result.toFormat();
}
if (typeof result.toString === 'function') {
return result.toString();
}
return result;
}
}

View File

@ -0,0 +1,148 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, map, observable } from 'mobx';
import { api } from '../parity';
import { evaluate } from '../utils';
let instance;
export default class WatchesStore {
@observable showAdd = false;
@observable watches = map();
watchesFunctions = {};
constructor () {
api.subscribe('eth_blockNumber', () => {
this.refreshWatches();
});
}
static get () {
if (!instance) {
instance = new WatchesStore();
window.watch = instance.add.bind(instance);
window.unwatch = instance.remove.bind(instance);
}
return instance;
}
@action
add (name, func, context) {
if (!func || (typeof func !== 'function' && typeof func.then !== 'function')) {
return console.error(Error(`cannot watch ${name} ; not a Function/Promise given`));
}
this.watchesFunctions[name] = { func, context };
this.watches.set(name, {});
this.refreshWatches();
}
addWatch () {
this.toggleAdd();
const { addContext, addFunction, addName } = this;
const evaluatedFunction = evaluate(addFunction);
const evaluatedContext = addContext
? evaluate(addContext)
: {};
this.add(addName, evaluatedFunction.result, evaluatedContext.result);
}
get (name) {
return this.watches.get(name);
}
get names () {
return this.watches.keys();
}
refreshWatches () {
const names = this.watches.keys();
const promises = names
.map((name) => {
const { context, func } = this.watchesFunctions[name];
let result;
try {
if (typeof func === 'function') {
result = func.apply(context || this);
} else {
result = func;
}
return Promise.resolve(result);
} catch (error) {
return Promise.reject(error);
}
})
.map((promise, index) => {
const name = names[index];
return promise
.then((result) => {
this.updateWatch(name, result);
})
.catch((error) => {
this.updateWatch(name, error, true);
});
});
return Promise.all(promises);
}
@action
remove (name) {
this.watches.delete(name);
delete this.watchesFunctions[name];
}
@action
toggleAdd () {
this.showAdd = !this.showAdd;
}
updateAddContext (value) {
this.addContext = value;
}
updateAddFunction (value) {
this.addFunction = value;
}
updateAddName (value) {
this.addName = value;
}
@action
updateWatch (name, result, isError = false) {
const next = {};
if (isError) {
next.error = result;
} else {
next.result = result;
}
this.watches.set(name, { ...next });
}
}

View File

@ -0,0 +1,36 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
:global {
.cm-matchhighlight {
background-color: rgba(255, 255, 0, 0.4);
}
.CodeMirror-hints {
font-family: dejavu sans mono, monospace;
font-size: 11px;
border-radius: 0;
border: none;
padding: 0;
}
.CodeMirror-hint {
border-radius: 0;
cursor: default;
padding: 0.25em 0.25em 0.25em 0.35em;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,36 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-eval */
export function evaluate (input) {
try {
const result = eval.apply(window, [ `${input}` ]);
return { result };
} catch (err) {
try {
const result = eval.apply(window, [ `(function () {
var x = ${input};
return x;
})()` ]);
return { result };
} catch (error) {
return { error };
}
}
}

View File

View File

@ -1,221 +0,0 @@
#full-screen {
}
textarea:focus, input:focus{
outline: none;
}
* { padding: 0; margin: 0; }
html, body, #full-screen {
height: 100%;
}
body {
margin: 0;
}
#full-screen {
display: flex;
flex-direction: column;
justify-content: flex-end;
overflow: hidden;
}
#history-wrap {
overflow: auto;
}
#history {
flex: 1 1 auto;
display: flex;
min-height: min-content;
flex-direction: column;
justify-content: flex-end;
}
.entry, #input {
margin: 0;
padding: 0.25em;
display: flex;
flex-direction: row;
align-items: flex-start;
width: 100%;
}
.type {
color: #ccc;
font-weight: bold;
font-family: "Lucida Console", Monaco, monospace;
font-size: 11pt;
padding: 0 0.5em 0 0.25em;
}
.command .type {
color: #aaa;
}
.result .type {
color: #ccc;
}
#input .type {
color: #59f;
}
.addwatch {
background-color: #cfc;
border-top: 1px solid #6e6;
}
.addwatch .type {
color: #040;
}
.addwatch .text {
color: #080;
}
.log.logLevel .type {
color: blue;
}
.log.debugLevel .text {
color: blue;
}
.log.infoLevel .type {
color: #00c;
}
.log.warnLevel {
background-color: #fff8dd;
border-top: 1px solid #fe8;
border-bottom: 1px solid #fe8;
}
.log.warnLevel .text {
color: #440;
}
.log.warnLevel .type {
color: #880;
}
.log.errorLevel, .error {
background-color: #fee;
border-top: 1px solid #fbb;
border-bottom: 1px solid #fbb;
}
.log.errorLevel .text, .error .text {
color: #c00;
}
.log.errorLevel .type, .error .type {
color: #800;
}
span.text {
color: black;
}
.text {
border: 0;
margin: 0;
padding: 0;
color: black;
font-weight: 1000;
font-family: "Lucida Console", Monaco, monospace;
font-size: 11pt;
flex-grow: 1;
}
div.error {
background-color: #fee;
border-top: 1px solid #fbb;
color: red;
}
div.command {
border-top: 1px solid #eee;
}
div#input {
border-top: 1px solid #eee;
}
#status {
background: #eee;
padding: 0.25em;
font-family: "Lucida Console", Monaco, monospace;
font-size: 8pt;
color: #888;
flex: 0 0 auto;
}
#input {
flex: 0 0 auto;
}
#status {
display: flex;
}
.watch {
padding-left: 1em;
padding-right: 1em;
}
.watch:not(:first-child) {
border-left: 1px solid #ccc;
}
.expr {
color: #888;
margin-right: 0.5em;
}
.res {
font-weight: bold;
color: black;
}
.stringType {
color: #c00;
}
.numberType {
color: #00f;
}
.eObject {
color: #00f;
}
.objectType {
font-style: italic;
}
.fieldType {
color: #808;
}
.undefinedType {
color: #888;
}
.functionType {
color: #666;
font-style: italic;
}
#autocomplete-anchor {
position: relative;
}
#autocomplete {
position: absolute;
left: 1.5em;
bottom: 0;
background: #f8f8f8;
box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.5);
max-height: 20em;
overflow: scroll;
}
#autocomplete div {
font-size: small;
font-family: "Lucida Console", Monaco, monospace;
padding: 0.1em 1em 0.1em 0;
border-top: 1px solid #eee;
}
.ac-already {
font-weight: bold;
}
.ac-new {
color: #666;
}
.ac-selected {
background-color: #ccddff;
}

View File

@ -1,19 +0,0 @@
<!doctype html>
<html lang="us">
<head>
<meta charset="utf-8">
<title>JS Console dapp</title>
<link href="console.css" rel='stylesheet' type='text/css'>
<script src="/web3.js"></script>
</head>
<body>
<div id="full-screen">
<div id="eval"></div>
<div id="history-wrap"><div id="history"></div></div>
<div id="autocomplete-anchor"><div id="autocomplete"></div></div>
<div id="input"><span class="type">&gt;</span><input type="text" class="text" id="command" list="input-datalist"></div>
<div id="status"></div>
</div>
<script src="console.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -22,7 +22,7 @@
color: #333;
font-size: 16px;
font-family: 'Roboto', sans-serif;
font-weight: 300 !important;
font-weight: 300;
margin: 0;
padding: 0;
vertical-align: top;
@ -39,7 +39,7 @@
}
:root * {
font-weight: 300 !important;
font-weight: 300;
}
:root :global(#container) > div {

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import 'babel-polyfill/dist/polyfill.js';
import es6Promise from 'es6-promise';
es6Promise.polyfill();

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import 'babel-polyfill/dist/polyfill.js';
import es6Promise from 'es6-promise';
es6Promise.polyfill();

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import 'babel-polyfill/dist/polyfill.js';
import es6Promise from 'es6-promise';
es6Promise.polyfill();

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import 'babel-polyfill';
import 'whatwg-fetch';
import es6Promise from 'es6-promise';

View File

@ -8,6 +8,17 @@
"visible": false,
"noselect": false
},
{
"id": "0xa635a9326814bded464190eddf0bdb90ce92d40ea2359cf553ea80e3c5a4076c",
"url": "console",
"name": "Parity/Web3 console",
"description": "A Javascript development console complete with web3 and parity objects",
"version": "0.3",
"author": "Gav Wood <gavin@parity.io>",
"position": "top-right",
"visible": true,
"secure": true
},
{
"id": "0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f",
"url": "tokendeploy",
@ -85,17 +96,5 @@
"visible": true,
"skipBuild": true,
"skipHistory": true
},
{
"id": "0xa635a9326814bded464190eddf0bdb90ce92d40ea2359cf553ea80e3c5a4076c",
"url": "console",
"name": "Parity/Web3 console",
"description": "A Javascript development console complete with web3 and parity objects",
"version": "0.3",
"author": "Gav Wood <gavin@parity.io>",
"position": "top-right",
"visible": true,
"secure": true,
"skipBuild": true
}
]

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import 'babel-polyfill';
import 'whatwg-fetch';
import es6Promise from 'es6-promise';

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import 'babel-polyfill';
import 'whatwg-fetch';
import es6Promise from 'es6-promise';

View File

@ -56,9 +56,6 @@ module.exports = {
'node-fetch': 'node-fetch'
},
module: {
noParse: [
/babel-polyfill/
],
rules: [
rulesParity,
rulesEs6,

View File

@ -23,7 +23,6 @@ const ENV = process.env.NODE_ENV || 'development';
const DEST = process.env.BUILD_DEST || '.build';
let modules = [
'babel-polyfill',
'bignumber.js',
'blockies',
'brace',

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::path::PathBuf;
use ethcore::ethstore::{EthStore, SecretStore, import_accounts, read_geth_accounts};
use ethcore::ethstore::{EthStore, SecretStore, import_account, import_accounts, read_geth_accounts};
use ethcore::ethstore::dir::RootDiskDirectory;
use ethcore::ethstore::SecretVaultRef;
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
@ -51,10 +51,10 @@ pub struct ImportAccounts {
pub spec: SpecType,
}
/// Parameters for geth accounts' import
/// Parameters for geth accounts' import
#[derive(Debug, PartialEq)]
pub struct ImportFromGethAccounts {
/// import mainnet (false) or testnet (true) accounts
/// import mainnet (false) or testnet (true) accounts
pub testnet: bool,
/// directory to import accounts to
pub to: String,
@ -80,7 +80,7 @@ fn keys_dir(path: String, spec: SpecType) -> Result<RootDiskDirectory, String> {
fn secret_store(dir: Box<RootDiskDirectory>, iterations: Option<u32>) -> Result<EthStore, String> {
match iterations {
Some(i) => EthStore::open_with_iterations(dir, i),
_ => EthStore::open(dir)
_ => EthStore::open(dir)
}.map_err(|e| format!("Could not open keys store: {}", e))
}
@ -94,16 +94,16 @@ fn new(n: NewAccount) -> Result<String, String> {
let secret_store = Box::new(secret_store(dir, Some(n.iterations))?);
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
let new_account = acc_provider.new_account(&password).map_err(|e| format!("Could not create new account: {}", e))?;
Ok(format!("{:?}", new_account))
Ok(format!("0x{:?}", new_account))
}
fn list(list_cmd: ListAccounts) -> Result<String, String> {
let dir = Box::new(keys_dir(list_cmd.path, list_cmd.spec)?);
let secret_store = Box::new(secret_store(dir, None)?);
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
let accounts = acc_provider.accounts();
let accounts = acc_provider.accounts().map_err(|e| format!("{}", e))?;
let result = accounts.into_iter()
.map(|a| format!("{:?}", a))
.map(|a| format!("0x{:?}", a))
.collect::<Vec<String>>()
.join("\n");
@ -113,10 +113,18 @@ fn list(list_cmd: ListAccounts) -> Result<String, String> {
fn import(i: ImportAccounts) -> Result<String, String> {
let to = keys_dir(i.to, i.spec)?;
let mut imported = 0;
for path in &i.from {
let from = RootDiskDirectory::at(path);
imported += import_accounts(&from, &to).map_err(|_| "Importing accounts failed.")?.len();
let path = PathBuf::from(path);
if path.is_dir() {
let from = RootDiskDirectory::at(&path);
imported += import_accounts(&from, &to).map_err(|e| format!("Importing accounts from {:?} failed: {}", path, e))?.len();
} else if path.is_file() {
import_account(&path, &to).map_err(|e| format!("Importing account from {:?} failed: {}", path, e))?;
imported += 1;
}
}
Ok(format!("{} account(s) imported", imported))
}

View File

@ -695,7 +695,7 @@ impl Configuration {
ret.listen_address = listen.map(|l| format!("{}", l));
ret.public_address = public.map(|p| format!("{}", p));
ret.use_secret = match self.args.flag_node_key.as_ref()
.map(|s| s.parse::<Secret>().or_else(|_| Secret::from_slice(&s.sha3())).map_err(|e| format!("Invalid key: {:?}", e))
.map(|s| s.parse::<Secret>().or_else(|_| Secret::from_unsafe_slice(&s.sha3())).map_err(|e| format!("Invalid key: {:?}", e))
) {
None => None,
Some(Ok(key)) => Some(key),

View File

@ -30,6 +30,7 @@ use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions};
use ethcore::snapshot;
use ethcore::verification::queue::VerifierSettings;
use ethcore::ethstore::ethkey;
use light::Cache as LightDataCache;
use ethsync::SyncConfig;
use informant::Informant;
@ -820,9 +821,31 @@ fn prepare_account_provider(spec: &SpecType, dirs: &Directories, data_dir: &str,
}
}
// Add development account if running dev chain:
if let SpecType::Dev = *spec {
insert_dev_account(&account_provider);
}
Ok(account_provider)
}
fn insert_dev_account(account_provider: &AccountProvider) {
let secret: ethkey::Secret = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".into();
let dev_account = ethkey::KeyPair::from_secret(secret.clone()).expect("Valid secret produces valid key;qed");
if let Ok(false) = account_provider.has_account(dev_account.address()) {
match account_provider.insert_account(secret, "") {
Err(e) => warn!("Unable to add development account: {}", e),
Ok(address) => {
let _ = account_provider.set_account_name(address.clone(), "Development Account".into());
let _ = account_provider.set_account_meta(address, ::serde_json::to_string(&(vec![
("description", "Never use this account outside of develoopment chain!"),
("passwordHint","Password is empty string"),
].into_iter().collect::<::std::collections::HashMap<_,_>>())).expect("Serialization of hashmap does not fail."));
},
}
}
}
// Construct an error `String` with an adaptive hint on how to create an account.
fn build_create_account_hint(spec: &SpecType, keys: &str) -> String {
format!("You can create an account via RPC, UI or `parity account new --chain {} --keys-path {}`.", spec, keys)

View File

@ -65,10 +65,10 @@ pub fn main() {
StratumServer::start(
&SocketAddr::new(
IpAddr::from_str(&service_config.listen_addr)
.unwrap_or_else(|e|
.unwrap_or_else(|e| {
println!("Fatal: invalid listen address: '{}' ({:?})", &service_config.listen_addr, e);
std::process::exit(1)
),
}),
service_config.port,
),
job_dispatcher.service().clone(),

View File

@ -93,7 +93,7 @@ impl ParityAccounts for ParityAccountsClient {
fn new_account_from_secret(&self, secret: RpcH256, pass: String) -> Result<RpcH160, Error> {
let store = self.account_provider()?;
let secret = Secret::from_slice(&secret.0)
let secret = Secret::from_unsafe_slice(&secret.0)
.map_err(|e| errors::account("Could not create account.", e))?;
store.insert_account(secret, &pass)
.map(Into::into)

View File

@ -58,7 +58,7 @@ impl SecretStoreClient {
/// Decrypt secret key using account' private key
fn decrypt_secret(&self, address: H160, password: String, key: Bytes) -> Result<Secret, Error> {
self.decrypt_key(address, password, key)
.and_then(|s| Secret::from_slice(&s).map_err(|e| errors::account("invalid secret", e)))
.and_then(|s| Secret::from_unsafe_slice(&s).map_err(|e| errors::account("invalid secret", e)))
}
}

View File

@ -302,7 +302,7 @@ fn rpc_eth_submit_hashrate() {
fn rpc_eth_sign() {
let tester = EthTester::default();
let account = tester.accounts_provider.insert_account(Secret::from_slice(&[69u8; 32]).unwrap(), "abcd").unwrap();
let account = tester.accounts_provider.insert_account(Secret::from_slice(&[69u8; 32]), "abcd").unwrap();
tester.accounts_provider.unlock_account_permanently(account, "abcd".into()).unwrap();
let _message = "0cc175b9c0f1b6a831c399e26977266192eb5ffee6ae2fec3ad71c777531578f".from_hex().unwrap();

View File

@ -200,7 +200,7 @@ fn should_sign_if_account_is_unlocked() {
// given
let tester = eth_signing();
let data = vec![5u8];
let acc = tester.accounts.insert_account(Secret::from_slice(&[69u8; 32]).unwrap(), "test").unwrap();
let acc = tester.accounts.insert_account(Secret::from_slice(&[69u8; 32]), "test").unwrap();
tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap();
// when

View File

@ -1,17 +1,15 @@
verbose=false
max_width=1000
ideal_width=1000
tabs_spaces=4
comment_width=1000
tab_spaces=4
fn_call_width=1000
struct_lit_width=32
fn_arg_indent="Tabbed"
single_line_if_else=true
where_indent="Visual"
where_trailing_comma=true
chain_base_indent="Inherit"
chain_indent="Inherit"
fn_call_style="Visual"
single_line_if_else_max_width=100
trailing_comma="Vertical"
chain_indent="Visual"
chain_one_line_max=100
reorder_imports=true
format_strings=false
chain_overflow_last=false
hard_tabs=true
wrap_match_arms=false

View File

@ -1089,7 +1089,7 @@ mod tests {
use ethcrypto::DEFAULT_MAC;
use ethcrypto::ecies::decrypt;
let decrypt_shadows: Vec<_> = decrypted_secret.decrypt_shadows.unwrap().into_iter()
.map(|c| Secret::from_slice(&decrypt(key_pair.secret(), &DEFAULT_MAC, &c).unwrap()).unwrap())
.map(|c| Secret::from_slice(&decrypt(key_pair.secret(), &DEFAULT_MAC, &c).unwrap()))
.collect();
let decrypted_secret = math::decrypt_with_shadow_coefficients(decrypted_secret.decrypted_secret, decrypted_secret.common_point.unwrap(), decrypt_shadows).unwrap();
assert_eq!(decrypted_secret, SECRET_PLAIN.into());

View File

@ -13,6 +13,10 @@ case $1 in
OPTIONS=""
shift
;;
--no-run)
OPTIONS="--no-run"
shift
;;
*)
# unknown option
;;

View File

@ -1242,7 +1242,7 @@ fn load_key(path: &Path) -> Option<Secret> {
fn key_save_load() {
use ::devtools::RandomTempPath;
let temp_path = RandomTempPath::create_dir();
let key = Secret::from_slice(&H256::random()).unwrap();
let key = H256::random().into();
save_key(temp_path.as_path(), &key);
let r = load_key(temp_path.as_path());
assert_eq!(key, r.unwrap());

View File

@ -77,7 +77,7 @@ impl DBTransaction {
}
}
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten upon write.
/// Insert a key-value pair in the transaction. Any existing value will be overwritten upon write.
pub fn put(&mut self, col: Option<u32>, key: &[u8], value: &[u8]) {
let mut ekey = ElasticArray32::new();
ekey.append_slice(key);
@ -88,7 +88,7 @@ impl DBTransaction {
});
}
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten upon write.
/// Insert a key-value pair in the transaction. Any existing value will be overwritten upon write.
pub fn put_vec(&mut self, col: Option<u32>, key: &[u8], value: Bytes) {
let mut ekey = ElasticArray32::new();
ekey.append_slice(key);
@ -99,7 +99,7 @@ impl DBTransaction {
});
}
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten upon write.
/// Insert a key-value pair in the transaction. Any existing value will be overwritten upon write.
/// Value will be RLP-compressed on flush
pub fn put_compressed(&mut self, col: Option<u32>, key: &[u8], value: Bytes) {
let mut ekey = ElasticArray32::new();