Unlock account with timeout for geth compatibility (#1854) (#1858)

* Unlock account with timeout for geth compatibility

* Fixed test
This commit is contained in:
Arkadiy Paronyan 2016-08-06 09:26:06 +02:00 committed by GitHub
parent aa9b152f76
commit 801cf6fcba
4 changed files with 57 additions and 26 deletions

View File

@ -17,9 +17,9 @@
//! Account management. //! Account management.
use std::fmt; use std::fmt;
use std::sync::RwLock;
use std::collections::HashMap; use std::collections::HashMap;
use util::{Address as H160, H256, H520}; use std::time::{Instant, Duration};
use util::{Address as H160, H256, H520, Mutex};
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore}; use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
use ethstore::dir::{KeyDirectory}; use ethstore::dir::{KeyDirectory};
use ethstore::ethkey::{Address as SSAddress, Message as SSMessage, Secret as SSSecret, Random, Generator}; use ethstore::ethkey::{Address as SSAddress, Message as SSMessage, Secret as SSSecret, Random, Generator};
@ -32,6 +32,8 @@ enum Unlock {
/// Account unlocked permantently can always sign message. /// Account unlocked permantently can always sign message.
/// Use with caution. /// Use with caution.
Perm, Perm,
/// Account unlocked with a timeout
Timed((Instant, u32)),
} }
/// Data associated with account. /// Data associated with account.
@ -126,7 +128,7 @@ impl KeyDirectory for NullDir {
/// Account management. /// Account management.
/// Responsible for unlocking accounts. /// Responsible for unlocking accounts.
pub struct AccountProvider { pub struct AccountProvider {
unlocked: RwLock<HashMap<SSAddress, AccountData>>, unlocked: Mutex<HashMap<SSAddress, AccountData>>,
sstore: Box<SecretStore>, sstore: Box<SecretStore>,
} }
@ -134,7 +136,7 @@ impl AccountProvider {
/// Creates new account provider. /// Creates new account provider.
pub fn new(sstore: Box<SecretStore>) -> Self { pub fn new(sstore: Box<SecretStore>) -> Self {
AccountProvider { AccountProvider {
unlocked: RwLock::new(HashMap::new()), unlocked: Mutex::new(HashMap::new()),
sstore: sstore, sstore: sstore,
} }
} }
@ -142,7 +144,7 @@ impl AccountProvider {
/// Creates not disk backed provider. /// Creates not disk backed provider.
pub fn transient_provider() -> Self { pub fn transient_provider() -> Self {
AccountProvider { AccountProvider {
unlocked: RwLock::new(HashMap::new()), unlocked: Mutex::new(HashMap::new()),
sstore: Box::new(EthStore::open(Box::new(NullDir)).unwrap()) sstore: Box::new(EthStore::open(Box::new(NullDir)).unwrap())
} }
} }
@ -176,21 +178,18 @@ impl AccountProvider {
let _ = try!(self.sstore.sign(&account, &password, &Default::default())); let _ = try!(self.sstore.sign(&account, &password, &Default::default()));
// check if account is already unlocked pernamently, if it is, do nothing // check if account is already unlocked pernamently, if it is, do nothing
{ let mut unlocked = self.unlocked.lock().unwrap();
let unlocked = self.unlocked.read().unwrap();
if let Some(data) = unlocked.get(&account) { if let Some(data) = unlocked.get(&account) {
if let Unlock::Perm = data.unlock { if let Unlock::Perm = data.unlock {
return Ok(()) return Ok(())
} }
} }
}
let data = AccountData { let data = AccountData {
unlock: unlock, unlock: unlock,
password: password, password: password,
}; };
let mut unlocked = self.unlocked.write().unwrap();
unlocked.insert(account, data); unlocked.insert(account, data);
Ok(()) Ok(())
} }
@ -205,10 +204,15 @@ impl AccountProvider {
self.unlock_account(account, password, Unlock::Temp) self.unlock_account(account, password, Unlock::Temp)
} }
/// Unlocks account temporarily with a timeout.
pub fn unlock_account_timed<A>(&self, account: A, password: String, duration_ms: u32) -> Result<(), Error> where Address: From<A> {
self.unlock_account(account, password, Unlock::Timed((Instant::now(), duration_ms)))
}
/// Checks if given account is unlocked /// Checks if given account is unlocked
pub fn is_unlocked<A>(&self, account: A) -> bool where Address: From<A> { pub fn is_unlocked<A>(&self, account: A) -> bool where Address: From<A> {
let account = Address::from(account).into(); let account = Address::from(account).into();
let unlocked = self.unlocked.read().unwrap(); let unlocked = self.unlocked.lock().unwrap();
unlocked.get(&account).is_some() unlocked.get(&account).is_some()
} }
@ -218,14 +222,19 @@ impl AccountProvider {
let message = Message::from(message).into(); let message = Message::from(message).into();
let data = { let data = {
let unlocked = self.unlocked.read().unwrap(); let mut unlocked = self.unlocked.lock().unwrap();
try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone() let data = try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone();
};
if let Unlock::Temp = data.unlock { if let Unlock::Temp = data.unlock {
let mut unlocked = self.unlocked.write().unwrap();
unlocked.remove(&account).expect("data exists: so key must exist: qed"); 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
};
let signature = try!(self.sstore.sign(&account, &data.password, &message)); let signature = try!(self.sstore.sign(&account, &data.password, &message));
Ok(H520(signature.into())) Ok(H520(signature.into()))
@ -244,6 +253,7 @@ impl AccountProvider {
mod tests { mod tests {
use super::AccountProvider; use super::AccountProvider;
use ethstore::ethkey::{Generator, Random}; use ethstore::ethkey::{Generator, Random};
use std::time::Duration;
#[test] #[test]
fn unlock_account_temp() { fn unlock_account_temp() {
@ -269,4 +279,16 @@ mod tests {
assert!(ap.sign(kp.address(), [0u8; 32]).is_ok()); assert!(ap.sign(kp.address(), [0u8; 32]).is_ok());
assert!(ap.sign(kp.address(), [0u8; 32]).is_ok()); assert!(ap.sign(kp.address(), [0u8; 32]).is_ok());
} }
#[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());
assert!(ap.sign(kp.address(), [0u8; 32]).is_ok());
::std::thread::sleep(Duration::from_millis(2000));
assert!(ap.sign(kp.address(), [0u8; 32]).is_err());
}
} }

View File

@ -156,7 +156,7 @@ pub fn setup_rpc<T: Extendable>(server: T, deps: Arc<Dependencies>, apis: ApiSet
} }
}, },
Api::Personal => { Api::Personal => {
server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner, deps.signer_port).to_delegate()); server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner, deps.signer_port, !deps.allow_pending_receipt_query).to_delegate());
}, },
Api::Signer => { Api::Signer => {
server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_queue).to_delegate()); server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_queue).to_delegate());

View File

@ -31,16 +31,18 @@ pub struct PersonalClient<C, M> where C: MiningBlockChainClient, M: MinerService
client: Weak<C>, client: Weak<C>,
miner: Weak<M>, miner: Weak<M>,
signer_port: Option<u16>, signer_port: Option<u16>,
allow_perm_unlock: bool,
} }
impl<C, M> PersonalClient<C, M> where C: MiningBlockChainClient, M: MinerService { impl<C, M> PersonalClient<C, M> where C: MiningBlockChainClient, M: MinerService {
/// Creates new PersonalClient /// Creates new PersonalClient
pub fn new(store: &Arc<AccountProvider>, client: &Arc<C>, miner: &Arc<M>, signer_port: Option<u16>) -> Self { pub fn new(store: &Arc<AccountProvider>, client: &Arc<C>, miner: &Arc<M>, signer_port: Option<u16>, allow_perm_unlock: bool) -> Self {
PersonalClient { PersonalClient {
accounts: Arc::downgrade(store), accounts: Arc::downgrade(store),
client: Arc::downgrade(client), client: Arc::downgrade(client),
miner: Arc::downgrade(miner), miner: Arc::downgrade(miner),
signer_port: signer_port, signer_port: signer_port,
allow_perm_unlock: allow_perm_unlock,
} }
} }
} }
@ -71,10 +73,17 @@ impl<C: 'static, M: 'static> Personal for PersonalClient<C, M> where C: MiningBl
} }
fn unlock_account(&self, params: Params) -> Result<Value, Error> { fn unlock_account(&self, params: Params) -> Result<Value, Error> {
from_params::<(Address, String, u64)>(params).and_then( from_params::<(Address, String, Option<u64>)>(params).and_then(
|(account, account_pass, _)|{ |(account, account_pass, duration)|{
let account: Address = account.into();
let store = take_weak!(self.accounts); let store = take_weak!(self.accounts);
match store.unlock_account_temporarily(account, account_pass) { let r = match (self.allow_perm_unlock, duration) {
(false, _) => store.unlock_account_temporarily(account, account_pass),
(true, Some(0)) => store.unlock_account_permanently(account, account_pass),
(true, Some(d)) => store.unlock_account_timed(account, account_pass, d as u32 * 1000),
(true, None) => store.unlock_account_timed(account, account_pass, 300_000),
};
match r {
Ok(_) => Ok(Value::Bool(true)), Ok(_) => Ok(Value::Bool(true)),
Err(_) => Ok(Value::Bool(false)), Err(_) => Ok(Value::Bool(false)),
} }

View File

@ -50,7 +50,7 @@ fn setup(signer: Option<u16>) -> PersonalTester {
let accounts = accounts_provider(); let accounts = accounts_provider();
let client = blockchain_client(); let client = blockchain_client();
let miner = miner_service(); let miner = miner_service();
let personal = PersonalClient::new(&accounts, &client, &miner, signer); let personal = PersonalClient::new(&accounts, &client, &miner, signer, false);
let io = IoHandler::new(); let io = IoHandler::new();
io.add_delegate(personal.to_delegate()); io.add_delegate(personal.to_delegate());