2016-06-20 00:10:34 +02:00
|
|
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
|
|
|
// This file is part of Parity.
|
|
|
|
|
|
|
|
// Parity is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
|
|
|
// Parity is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
//! Account management.
|
|
|
|
|
2016-08-11 18:31:28 +02:00
|
|
|
use std::{fs, fmt};
|
2016-06-20 00:10:34 +02:00
|
|
|
use std::collections::HashMap;
|
2016-08-11 18:31:28 +02:00
|
|
|
use std::path::PathBuf;
|
2016-08-15 15:09:00 +02:00
|
|
|
use std::time::{Instant, Duration};
|
|
|
|
use util::{Mutex, RwLock};
|
2016-06-20 00:10:34 +02:00
|
|
|
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
|
|
|
|
use ethstore::dir::{KeyDirectory};
|
2016-08-15 15:09:00 +02:00
|
|
|
use ethstore::ethkey::{Address, Message, Secret, Random, Generator};
|
|
|
|
use ethjson::misc::AccountMeta;
|
|
|
|
pub use ethstore::ethkey::Signature;
|
2016-06-20 00:10:34 +02:00
|
|
|
|
|
|
|
/// Type of unlock.
|
|
|
|
#[derive(Clone)]
|
|
|
|
enum Unlock {
|
|
|
|
/// If account is unlocked temporarily, it should be locked after first usage.
|
|
|
|
Temp,
|
|
|
|
/// Account unlocked permantently can always sign message.
|
|
|
|
/// Use with caution.
|
|
|
|
Perm,
|
2016-08-05 23:33:14 +02:00
|
|
|
/// Account unlocked with a timeout
|
|
|
|
Timed((Instant, u32)),
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Data associated with account.
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct AccountData {
|
|
|
|
unlock: Unlock,
|
|
|
|
password: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `AccountProvider` errors.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Error {
|
|
|
|
/// Returned when account is not unlocked.
|
|
|
|
NotUnlocked,
|
|
|
|
/// Returned when signing fails.
|
|
|
|
SStore(SSError),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for Error {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
|
|
match *self {
|
|
|
|
Error::NotUnlocked => write!(f, "Account is locked"),
|
|
|
|
Error::SStore(ref e) => write!(f, "{}", e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<SSError> for Error {
|
|
|
|
fn from(e: SSError) -> Self {
|
|
|
|
Error::SStore(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-31 10:44:17 +02:00
|
|
|
#[derive(Default)]
|
|
|
|
struct NullDir {
|
2016-08-15 15:09:00 +02:00
|
|
|
accounts: RwLock<HashMap<Address, SafeAccount>>,
|
2016-07-31 10:44:17 +02:00
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
|
|
|
|
impl KeyDirectory for NullDir {
|
|
|
|
fn load(&self) -> Result<Vec<SafeAccount>, SSError> {
|
2016-07-31 10:44:17 +02:00
|
|
|
Ok(self.accounts.read().values().cloned().collect())
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2016-07-25 10:45:45 +02:00
|
|
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, SSError> {
|
2016-07-31 10:44:17 +02:00
|
|
|
self.accounts.write().insert(account.address.clone(), account.clone());
|
2016-07-25 10:45:45 +02:00
|
|
|
Ok(account)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2016-08-15 15:09:00 +02:00
|
|
|
fn remove(&self, address: &Address) -> Result<(), SSError> {
|
2016-07-31 10:44:17 +02:00
|
|
|
self.accounts.write().remove(address);
|
2016-06-20 00:10:34 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-11 18:31:28 +02:00
|
|
|
/// Disk-backed map from Address to String. Uses JSON.
|
|
|
|
struct AddressBook {
|
|
|
|
path: PathBuf,
|
2016-08-15 15:09:00 +02:00
|
|
|
cache: HashMap<Address, AccountMeta>,
|
2016-08-11 18:31:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl AddressBook {
|
|
|
|
pub fn new(path: String) -> Self {
|
|
|
|
trace!(target: "addressbook", "new({})", path);
|
|
|
|
let mut path: PathBuf = path.into();
|
|
|
|
path.push("address_book.json");
|
|
|
|
trace!(target: "addressbook", "path={:?}", path);
|
|
|
|
let mut r = AddressBook {
|
|
|
|
path: path,
|
|
|
|
cache: HashMap::new(),
|
|
|
|
};
|
|
|
|
r.revert();
|
|
|
|
r
|
|
|
|
}
|
|
|
|
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn get(&self) -> HashMap<Address, AccountMeta> {
|
2016-08-11 18:31:28 +02:00
|
|
|
self.cache.clone()
|
|
|
|
}
|
|
|
|
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn set_name(&mut self, a: Address, name: String) {
|
2016-08-11 18:31:28 +02:00
|
|
|
let mut x = self.cache.get(&a)
|
2016-08-17 16:06:41 +02:00
|
|
|
.cloned()
|
|
|
|
.unwrap_or_else(|| AccountMeta {name: Default::default(), meta: "{}".to_owned(), uuid: None});
|
2016-08-11 18:31:28 +02:00
|
|
|
x.name = name;
|
|
|
|
self.cache.insert(a, x);
|
|
|
|
self.save();
|
|
|
|
}
|
|
|
|
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn set_meta(&mut self, a: Address, meta: String) {
|
2016-08-11 18:31:28 +02:00
|
|
|
let mut x = self.cache.get(&a)
|
2016-08-17 16:06:41 +02:00
|
|
|
.cloned()
|
|
|
|
.unwrap_or_else(|| AccountMeta {name: "Anonymous".to_owned(), meta: Default::default(), uuid: None});
|
2016-08-11 18:31:28 +02:00
|
|
|
x.meta = meta;
|
|
|
|
self.cache.insert(a, x);
|
|
|
|
self.save();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn revert(&mut self) {
|
|
|
|
trace!(target: "addressbook", "revert");
|
|
|
|
let _ = fs::File::open(self.path.clone())
|
|
|
|
.map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e))
|
|
|
|
.and_then(|f| AccountMeta::read_address_map(&f)
|
|
|
|
.map_err(|e| warn!(target: "addressbook", "Couldn't read address book: {}", e))
|
|
|
|
.and_then(|m| { self.cache = m; Ok(()) })
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn save(&mut self) {
|
|
|
|
trace!(target: "addressbook", "save");
|
|
|
|
let _ = fs::File::create(self.path.clone())
|
|
|
|
.map_err(|e| warn!(target: "addressbook", "Couldn't open address book for writing: {}", e))
|
|
|
|
.and_then(|mut f| AccountMeta::write_address_map(&self.cache, &mut f)
|
|
|
|
.map_err(|e| warn!(target: "addressbook", "Couldn't write to address book: {}", e))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
/// Account management.
|
|
|
|
/// Responsible for unlocking accounts.
|
|
|
|
pub struct AccountProvider {
|
2016-08-15 15:09:00 +02:00
|
|
|
unlocked: Mutex<HashMap<Address, AccountData>>,
|
2016-06-20 00:10:34 +02:00
|
|
|
sstore: Box<SecretStore>,
|
2016-08-11 18:31:28 +02:00
|
|
|
address_book: Mutex<AddressBook>,
|
2016-07-24 17:38:21 +02:00
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
impl AccountProvider {
|
|
|
|
/// Creates new account provider.
|
|
|
|
pub fn new(sstore: Box<SecretStore>) -> Self {
|
|
|
|
AccountProvider {
|
2016-08-05 23:33:14 +02:00
|
|
|
unlocked: Mutex::new(HashMap::new()),
|
2016-08-11 18:31:28 +02:00
|
|
|
address_book: Mutex::new(AddressBook::new(sstore.local_path().into())),
|
2016-06-20 00:10:34 +02:00
|
|
|
sstore: sstore,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates not disk backed provider.
|
|
|
|
pub fn transient_provider() -> Self {
|
|
|
|
AccountProvider {
|
2016-08-05 23:33:14 +02:00
|
|
|
unlocked: Mutex::new(HashMap::new()),
|
2016-08-11 18:31:28 +02:00
|
|
|
address_book: Mutex::new(AddressBook::new(Default::default())),
|
2016-07-31 10:44:17 +02:00
|
|
|
sstore: Box::new(EthStore::open(Box::new(NullDir::default())).unwrap())
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates new random account.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn new_account(&self, password: &str) -> Result<Address, Error> {
|
2016-06-20 00:10:34 +02:00
|
|
|
let secret = Random.generate().unwrap().secret().clone();
|
|
|
|
let address = try!(self.sstore.insert_account(secret, password));
|
2016-08-15 15:09:00 +02:00
|
|
|
Ok(address)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Inserts new account into underlying store.
|
|
|
|
/// Does not unlock account!
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error> {
|
|
|
|
let address = try!(self.sstore.insert_account(secret, password));
|
|
|
|
Ok(address)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2016-08-10 17:57:40 +02:00
|
|
|
/// Import a new presale wallet.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn import_presale(&self, presale_json: &[u8], password: &str) -> Result<Address, Error> {
|
2016-08-10 17:57:40 +02:00
|
|
|
let address = try!(self.sstore.import_presale(presale_json, password));
|
|
|
|
Ok(Address::from(address).into())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Import a new presale wallet.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn import_wallet(&self, json: &[u8], password: &str) -> Result<Address, Error> {
|
2016-08-10 17:57:40 +02:00
|
|
|
let address = try!(self.sstore.import_wallet(json, password));
|
|
|
|
Ok(Address::from(address).into())
|
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
/// Returns addresses of all accounts.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn accounts(&self) -> Result<Vec<Address>, Error> {
|
|
|
|
let accounts = try!(self.sstore.accounts());
|
2016-07-31 10:44:17 +02:00
|
|
|
Ok(accounts)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2016-08-11 18:31:28 +02:00
|
|
|
/// Returns each address along with metadata.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn addresses_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
2016-08-11 18:31:28 +02:00
|
|
|
Ok(self.address_book.lock().get())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns each address along with metadata.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn set_address_name(&self, account: Address, name: String) -> Result<(), Error> {
|
2016-08-11 18:31:28 +02:00
|
|
|
Ok(self.address_book.lock().set_name(account, name))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns each address along with metadata.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn set_address_meta(&self, account: Address, meta: String) -> Result<(), Error> {
|
2016-08-11 18:31:28 +02:00
|
|
|
Ok(self.address_book.lock().set_meta(account, meta))
|
|
|
|
}
|
|
|
|
|
2016-07-24 17:38:21 +02:00
|
|
|
/// Returns each account along with name and meta.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
|
|
|
let r: HashMap<Address, AccountMeta> = try!(self.sstore.accounts())
|
2016-07-24 17:38:21 +02:00
|
|
|
.into_iter()
|
2016-08-17 16:06:41 +02:00
|
|
|
.map(|a| (a.clone(), self.account_meta(a).ok().unwrap_or_default()))
|
2016-07-24 17:38:21 +02:00
|
|
|
.collect();
|
|
|
|
Ok(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns each account along with name and meta.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn account_meta(&self, account: Address) -> Result<AccountMeta, Error> {
|
2016-07-24 17:38:21 +02:00
|
|
|
Ok(AccountMeta {
|
|
|
|
name: try!(self.sstore.name(&account)),
|
|
|
|
meta: try!(self.sstore.meta(&account)),
|
|
|
|
uuid: self.sstore.uuid(&account).ok().map(Into::into), // allowed to not have a UUID
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns each account along with name and meta.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn set_account_name(&self, account: Address, name: String) -> Result<(), Error> {
|
2016-07-24 17:38:21 +02:00
|
|
|
try!(self.sstore.set_name(&account, name));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns each account along with name and meta.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn set_account_meta(&self, account: Address, meta: String) -> Result<(), Error> {
|
2016-07-24 17:38:21 +02:00
|
|
|
try!(self.sstore.set_meta(&account, meta));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
/// Helper method used for unlocking accounts.
|
2016-08-15 15:09:00 +02:00
|
|
|
fn unlock_account(&self, account: Address, password: String, unlock: Unlock) -> Result<(), Error> {
|
2016-06-20 00:10:34 +02:00
|
|
|
// verify password by signing dump message
|
|
|
|
// result may be discarded
|
|
|
|
let _ = try!(self.sstore.sign(&account, &password, &Default::default()));
|
|
|
|
|
|
|
|
// check if account is already unlocked pernamently, if it is, do nothing
|
2016-08-05 23:33:14 +02:00
|
|
|
let mut unlocked = self.unlocked.lock();
|
|
|
|
if let Some(data) = unlocked.get(&account) {
|
|
|
|
if let Unlock::Perm = data.unlock {
|
|
|
|
return Ok(())
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let data = AccountData {
|
|
|
|
unlock: unlock,
|
|
|
|
password: password,
|
|
|
|
};
|
|
|
|
|
|
|
|
unlocked.insert(account, data);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Unlocks account permanently.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn unlock_account_permanently(&self, account: Address, password: String) -> Result<(), Error> {
|
2016-06-20 00:10:34 +02:00
|
|
|
self.unlock_account(account, password, Unlock::Perm)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Unlocks account temporarily (for one signing).
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn unlock_account_temporarily(&self, account: Address, password: String) -> Result<(), Error> {
|
2016-06-20 00:10:34 +02:00
|
|
|
self.unlock_account(account, password, Unlock::Temp)
|
|
|
|
}
|
|
|
|
|
2016-08-05 23:33:14 +02:00
|
|
|
/// Unlocks account temporarily with a timeout.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn unlock_account_timed(&self, account: Address, password: String, duration_ms: u32) -> Result<(), Error> {
|
2016-08-05 23:33:14 +02:00
|
|
|
self.unlock_account(account, password, Unlock::Timed((Instant::now(), duration_ms)))
|
|
|
|
}
|
|
|
|
|
2016-06-22 21:32:26 +02:00
|
|
|
/// Checks if given account is unlocked
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn is_unlocked(&self, account: Address) -> bool {
|
2016-08-05 23:33:14 +02:00
|
|
|
let unlocked = self.unlocked.lock();
|
2016-06-22 21:32:26 +02:00
|
|
|
unlocked.get(&account).is_some()
|
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
/// Signs the message. Account must be unlocked.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn sign(&self, account: Address, message: Message) -> Result<Signature, Error> {
|
2016-06-20 00:10:34 +02:00
|
|
|
let data = {
|
2016-08-05 23:33:14 +02:00
|
|
|
let mut unlocked = self.unlocked.lock();
|
|
|
|
let data = try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone();
|
|
|
|
if let Unlock::Temp = data.unlock {
|
|
|
|
unlocked.remove(&account).expect("data exists: so key must exist: qed");
|
|
|
|
}
|
|
|
|
if let Unlock::Timed((ref start, ref duration)) = data.unlock {
|
|
|
|
if start.elapsed() > Duration::from_millis(*duration as u64) {
|
|
|
|
unlocked.remove(&account).expect("data exists: so key must exist: qed");
|
|
|
|
return Err(Error::NotUnlocked);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data
|
2016-06-20 00:10:34 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
let signature = try!(self.sstore.sign(&account, &data.password, &message));
|
2016-08-15 15:09:00 +02:00
|
|
|
Ok(signature)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Unlocks an account, signs the message, and locks it again.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn sign_with_password(&self, account: Address, password: String, message: Message) -> Result<Signature, Error> {
|
2016-06-20 00:10:34 +02:00
|
|
|
let signature = try!(self.sstore.sign(&account, &password, &message));
|
2016-08-15 15:09:00 +02:00
|
|
|
Ok(signature)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
2016-08-11 18:31:28 +02:00
|
|
|
|
|
|
|
/// Returns the underlying `SecretStore` reference if one exists.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {
|
2016-08-11 18:31:28 +02:00
|
|
|
self.sstore.list_geth_accounts(testnet).into_iter().map(|a| Address::from(a).into()).collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the underlying `SecretStore` reference if one exists.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn import_geth_accounts(&self, desired: Vec<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
|
|
|
|
self.sstore.import_geth_accounts(desired, testnet).map_err(Into::into)
|
2016-08-11 18:31:28 +02:00
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2016-08-11 18:31:28 +02:00
|
|
|
use super::{AccountProvider, AddressBook};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use ethjson::misc::AccountMeta;
|
2016-06-20 00:10:34 +02:00
|
|
|
use ethstore::ethkey::{Generator, Random};
|
2016-08-05 23:33:14 +02:00
|
|
|
use std::time::Duration;
|
2016-08-11 18:31:28 +02:00
|
|
|
use devtools::RandomTempPath;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_save_and_reload_address_book() {
|
|
|
|
let temp = RandomTempPath::create_dir();
|
|
|
|
let path = temp.as_str().to_owned();
|
|
|
|
let mut b = AddressBook::new(path.clone());
|
|
|
|
b.set_name(1.into(), "One".to_owned());
|
|
|
|
b.set_meta(1.into(), "{1:1}".to_owned());
|
|
|
|
let b = AddressBook::new(path);
|
|
|
|
assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]);
|
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlock_account_temp() {
|
|
|
|
let kp = Random.generate().unwrap();
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
|
|
|
assert!(ap.unlock_account_temporarily(kp.address(), "test1".into()).is_err());
|
|
|
|
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
2016-08-15 15:09:00 +02:00
|
|
|
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
|
|
|
assert!(ap.sign(kp.address(), Default::default()).is_err());
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlock_account_perm() {
|
|
|
|
let kp = Random.generate().unwrap();
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
|
|
|
assert!(ap.unlock_account_permanently(kp.address(), "test1".into()).is_err());
|
|
|
|
assert!(ap.unlock_account_permanently(kp.address(), "test".into()).is_ok());
|
2016-08-15 15:09:00 +02:00
|
|
|
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
|
|
|
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
2016-06-20 00:10:34 +02:00
|
|
|
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
2016-08-15 15:09:00 +02:00
|
|
|
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
|
|
|
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
2016-08-05 23:33:14 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlock_account_timer() {
|
|
|
|
let kp = Random.generate().unwrap();
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
|
|
|
assert!(ap.unlock_account_timed(kp.address(), "test1".into(), 2000).is_err());
|
|
|
|
assert!(ap.unlock_account_timed(kp.address(), "test".into(), 2000).is_ok());
|
2016-08-15 15:09:00 +02:00
|
|
|
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
2016-08-05 23:33:14 +02:00
|
|
|
::std::thread::sleep(Duration::from_millis(2000));
|
2016-08-15 15:09:00 +02:00
|
|
|
assert!(ap.sign(kp.address(), Default::default()).is_err());
|
2016-08-05 23:33:14 +02:00
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|