diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index bc7ffdfed..81e246e8c 100644 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -25,7 +25,7 @@ use std::collections::HashMap; use std::time::{Instant, Duration}; use util::{RwLock, Itertools}; use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, SafeAccount, EthStore, EthMultiStore, random_string}; -use ethstore::dir::{KeyDirectory}; +use ethstore::dir::{KeyDirectory, MemoryDirectory}; use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethjson::misc::AccountMeta; pub use ethstore::ethkey::Signature; @@ -73,54 +73,11 @@ impl From for Error { } } -#[derive(Default)] -struct NullDir { - accounts: RwLock>>, -} - -impl KeyDirectory for NullDir { - fn load(&self) -> Result, SSError> { - Ok(self.accounts.read().values().cloned().flatten().collect()) - } - - fn update(&self, account: SafeAccount) -> Result { - let mut lock = self.accounts.write(); - let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); - // If the filename is the same we just need to replace the entry - accounts.retain(|acc| acc.filename != account.filename); - accounts.push(account.clone()); - Ok(account) - } - - fn insert(&self, account: SafeAccount) -> Result { - let mut lock = self.accounts.write(); - let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); - accounts.push(account.clone()); - Ok(account) - } - - fn remove(&self, account: &SafeAccount) -> Result<(), SSError> { - let mut accounts = self.accounts.write(); - let is_empty = if let Some(mut accounts) = accounts.get_mut(&account.address) { - if let Some(position) = accounts.iter().position(|acc| acc == account) { - accounts.remove(position); - } - accounts.is_empty() - } else { - false - }; - if is_empty { - accounts.remove(&account.address); - } - Ok(()) - } -} - /// Dapp identifier pub type DappId = String; fn transient_sstore() -> EthMultiStore { - EthMultiStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed") + EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed") } type AccountToken = String; @@ -155,7 +112,7 @@ impl AccountProvider { unlocked: RwLock::new(HashMap::new()), address_book: RwLock::new(AddressBook::transient()), dapps_settings: RwLock::new(DappsSettingsStore::transient()), - sstore: Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed")), + sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")), transient_sstore: transient_sstore(), } } diff --git a/ethstore/src/dir/memory.rs b/ethstore/src/dir/memory.rs new file mode 100644 index 000000000..c4f20f0e9 --- /dev/null +++ b/ethstore/src/dir/memory.rs @@ -0,0 +1,67 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::collections::HashMap; +use parking_lot::RwLock; +use itertools::Itertools; +use ethkey::Address; + +use {SafeAccount, Error}; +use super::KeyDirectory; + +#[derive(Default)] +pub struct MemoryDirectory { + accounts: RwLock>>, +} + +impl KeyDirectory for MemoryDirectory { + fn load(&self) -> Result, Error> { + Ok(self.accounts.read().values().cloned().flatten().collect()) + } + + fn update(&self, account: SafeAccount) -> Result { + let mut lock = self.accounts.write(); + let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); + // If the filename is the same we just need to replace the entry + accounts.retain(|acc| acc.filename != account.filename); + accounts.push(account.clone()); + Ok(account) + } + + fn insert(&self, account: SafeAccount) -> Result { + let mut lock = self.accounts.write(); + let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); + accounts.push(account.clone()); + Ok(account) + } + + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + let mut accounts = self.accounts.write(); + let is_empty = if let Some(mut accounts) = accounts.get_mut(&account.address) { + if let Some(position) = accounts.iter().position(|acc| acc == account) { + accounts.remove(position); + } + accounts.is_empty() + } else { + false + }; + if is_empty { + accounts.remove(&account.address); + } + Ok(()) + } +} + diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index e2c6c1117..2d7b98d58 100644 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -19,6 +19,7 @@ use {SafeAccount, Error}; mod disk; mod geth; +mod memory; mod parity; pub enum DirectoryType { @@ -36,4 +37,5 @@ pub trait KeyDirectory: Send + Sync { pub use self::disk::DiskDirectory; pub use self::geth::GethDirectory; +pub use self::memory::MemoryDirectory; pub use self::parity::ParityDirectory; diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 158402a60..7540edb69 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -16,18 +16,16 @@ use std::collections::BTreeMap; use std::mem; -use ethkey::KeyPair; +use parking_lot::RwLock; + use crypto::KEY_ITERATIONS; use random::Random; -use ethkey::{Signature, Address, Message, Secret, Public}; +use ethkey::{Signature, Address, Message, Secret, Public, KeyPair}; use dir::KeyDirectory; use account::SafeAccount; -use {Error, SimpleSecretStore, SecretStore}; -use json; -use json::UUID; -use parking_lot::RwLock; +use json::{self, UUID}; use presale::PresaleWallet; -use import; +use {import, Error, SimpleSecretStore, SecretStore}; pub struct EthStore { store: EthMultiStore, @@ -323,8 +321,120 @@ impl SimpleSecretStore for EthMultiStore { #[cfg(test)] mod tests { - #[test] - fn should_have_some_tests() { - assert_eq!(true, false) + + use dir::MemoryDirectory; + use ethkey::{Random, Generator, KeyPair}; + use secret_store::{SimpleSecretStore, SecretStore}; + use super::{EthStore, EthMultiStore}; + + fn keypair() -> KeyPair { + Random.generate().unwrap() } + + fn store() -> EthStore { + EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory always load successfuly; qed") + } + + fn multi_store() -> EthMultiStore { + EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory always load successfuly; qed") + } + + #[test] + fn should_insert_account_successfully() { + // given + let store = store(); + let keypair = keypair(); + + // when + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + + // then + assert_eq!(address, keypair.address()); + assert!(store.get(&address).is_ok(), "Should contain account."); + assert_eq!(store.accounts().unwrap().len(), 1, "Should have one account."); + } + + #[test] + fn should_update_meta_and_name() { + // given + let store = store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + assert_eq!(&store.meta(&address).unwrap(), "{}"); + assert_eq!(&store.name(&address).unwrap(), ""); + + // when + store.set_meta(&address, "meta".into()).unwrap(); + store.set_name(&address, "name".into()).unwrap(); + + // then + assert_eq!(&store.meta(&address).unwrap(), "meta"); + assert_eq!(&store.name(&address).unwrap(), "name"); + assert_eq!(store.accounts().unwrap().len(), 1); + } + + #[test] + fn should_remove_account() { + // given + let store = store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + + // when + store.remove_account(&address, "test").unwrap(); + + // then + assert_eq!(store.accounts().unwrap().len(), 0, "Should remove account."); + } + + #[test] + fn should_return_true_if_password_is_correct() { + // given + let store = store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + + // when + let res1 = store.test_password(&address, "x").unwrap(); + let res2 = store.test_password(&address, "test").unwrap(); + + assert!(!res1, "First password should be invalid."); + assert!(res2, "Second password should be correct."); + } + + #[test] + fn multistore_should_be_able_to_have_the_same_account_twice() { + // given + let store = multi_store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + let address2 = store.insert_account(keypair.secret().clone(), "xyz").unwrap(); + assert_eq!(address, address2); + + // when + assert!(store.remove_account(&address, "test").is_ok(), "First password should work."); + assert_eq!(store.accounts().unwrap().len(), 1); + + assert!(store.remove_account(&address, "xyz").is_ok(), "Second password should work too."); + assert_eq!(store.accounts().unwrap().len(), 0); + } + + #[test] + fn should_copy_account() { + // given + let store = store(); + let multi_store = multi_store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + assert_eq!(multi_store.accounts().unwrap().len(), 0); + + // when + store.copy_account(&multi_store, &address, "test", "xyz").unwrap(); + + // then + assert!(store.test_password(&address, "test").unwrap(), "First password should work for store."); + assert!(multi_store.sign(&address, "xyz", &Default::default()).is_ok(), "Second password should work for second store."); + assert_eq!(multi_store.accounts().unwrap().len(), 1); + } + }