Make accounts refresh time configurable. (#7345)

* Configurable accounts refresh time.

* Fix tests.
This commit is contained in:
Tomasz Drwięga 2017-12-22 04:33:49 +01:00 committed by Svyatoslav Nikolsky
parent 276496fb4b
commit 83447c201b
5 changed files with 67 additions and 20 deletions

View File

@ -29,8 +29,6 @@ use presale::PresaleWallet;
use json::{self, Uuid, OpaqueKeyFile};
use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation, OpaqueSecret};
const REFRESH_TIME_SEC: u64 = 5;
/// Accounts store.
pub struct EthStore {
store: EthMultiStore,
@ -49,6 +47,16 @@ impl EthStore {
})
}
/// Modify account refresh timeout - how often they are re-read from `KeyDirectory`.
///
/// Setting this to low values (or 0) will cause new accounts to be picked up quickly,
/// although it may induce heavy disk reads and is not recommended if you manage many keys (say over 10k).
///
/// By default refreshing is disabled, so only accounts created using this instance of `EthStore` are taken into account.
pub fn set_refresh_time(&self, time: Duration) {
self.store.set_refresh_time(time)
}
fn get(&self, account: &StoreAccountRef) -> Result<SafeAccount, Error> {
let mut accounts = self.store.get_accounts(account)?.into_iter();
accounts.next().ok_or(Error::InvalidAccount)
@ -254,6 +262,7 @@ pub struct EthMultiStore {
struct Timestamp {
dir_hash: Option<u64>,
last_checked: Instant,
refresh_time: Duration,
}
impl EthMultiStore {
@ -272,16 +281,28 @@ impl EthMultiStore {
timestamp: Mutex::new(Timestamp {
dir_hash: None,
last_checked: Instant::now(),
// by default we never refresh accounts
refresh_time: Duration::from_secs(u64::max_value()),
}),
};
store.reload_accounts()?;
Ok(store)
}
/// Modify account refresh timeout - how often they are re-read from `KeyDirectory`.
///
/// Setting this to low values (or 0) will cause new accounts to be picked up quickly,
/// although it may induce heavy disk reads and is not recommended if you manage many keys (say over 10k).
///
/// By default refreshing is disabled, so only accounts created using this instance of `EthStore` are taken into account.
pub fn set_refresh_time(&self, time: Duration) {
self.timestamp.lock().refresh_time = time;
}
fn reload_if_changed(&self) -> Result<(), Error> {
let mut last_timestamp = self.timestamp.lock();
let now = Instant::now();
if (now - last_timestamp.last_checked) > Duration::from_secs(REFRESH_TIME_SEC) {
if now - last_timestamp.last_checked > last_timestamp.refresh_time {
let dir_hash = Some(self.dir.unique_repr()?);
last_timestamp.last_checked = now;
if last_timestamp.dir_hash == dir_hash {
@ -319,22 +340,23 @@ impl EthMultiStore {
}
fn get_accounts(&self, account: &StoreAccountRef) -> Result<Vec<SafeAccount>, Error> {
{
let from_cache = |account| {
let cache = self.cache.read();
if let Some(accounts) = cache.get(account) {
if !accounts.is_empty() {
return Ok(accounts.clone())
}
return Some(accounts.clone())
}
}
None
};
match from_cache(account) {
Some(accounts) => Ok(accounts),
None => {
self.reload_if_changed()?;
let cache = self.cache.read();
let accounts = cache.get(account).ok_or(Error::InvalidAccount)?;
if accounts.is_empty() {
Err(Error::InvalidAccount)
} else {
Ok(accounts.clone())
from_cache(account).ok_or(Error::InvalidAccount)
}
}
}
@ -470,11 +492,20 @@ impl SimpleSecretStore for EthMultiStore {
}
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
let read_from_cache = |address: &Address| {
use std::collections::Bound;
self.reload_if_changed()?;
let cache = self.cache.read();
let mut r = cache.range((Bound::Included(*address), Bound::Included(*address)));
r.next().ok_or(Error::InvalidAccount).map(|(k, _)| k.clone())
r.next().map(|(k, _)| k.clone())
};
match read_from_cache(address) {
Some(account) => Ok(account),
None => {
self.reload_if_changed()?;
read_from_cache(address).ok_or(Error::InvalidAccount)
}
}
}
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {

View File

@ -335,6 +335,10 @@ usage! {
"--keys-iterations=[NUM]",
"Specify the number of iterations to use when deriving key from the password (bigger is more secure)",
ARG arg_accounts_refresh: (u64) = 5u64, or |c: &Config| otry!(c.account).refresh_time.clone(),
"--accounts-refresh=[TIME]",
"Specify the cache time of accounts read from disk. If you manage thousands of accounts set this to 0 to disable refresh.",
ARG arg_unlock: (Option<String>) = None, or |c: &Config| otry!(c.account).unlock.as_ref().map(|vec| vec.join(",")),
"--unlock=[ACCOUNTS]",
"Unlock ACCOUNTS for the duration of the execution. ACCOUNTS is a comma-delimited list of addresses. Implies --no-ui.",
@ -1009,6 +1013,7 @@ struct Account {
unlock: Option<Vec<String>>,
password: Option<Vec<String>>,
keys_iterations: Option<u32>,
refresh_time: Option<u64>,
disable_hardware: Option<bool>,
fast_unlock: Option<bool>,
}
@ -1428,6 +1433,7 @@ mod tests {
arg_unlock: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
arg_password: vec!["~/.safe/password.file".into()],
arg_keys_iterations: 10240u32,
arg_accounts_refresh: 5u64,
flag_no_hardware_wallets: false,
flag_fast_unlock: false,
@ -1665,6 +1671,7 @@ mod tests {
unlock: Some(vec!["0x1".into(), "0x2".into(), "0x3".into()]),
password: Some(vec!["passwdfile path".into()]),
keys_iterations: None,
refresh_time: None,
disable_hardware: None,
fast_unlock: None,
}),

View File

@ -484,6 +484,7 @@ impl Configuration {
fn accounts_config(&self) -> Result<AccountsConfig, String> {
let cfg = AccountsConfig {
iterations: self.args.arg_keys_iterations,
refresh_time: self.args.arg_accounts_refresh,
testnet: self.args.flag_testnet,
password_files: self.args.arg_password.clone(),
unlocked_accounts: to_addresses(&self.args.arg_unlock)?,

View File

@ -188,6 +188,7 @@ impl str::FromStr for ResealPolicy {
#[derive(Debug, PartialEq)]
pub struct AccountsConfig {
pub iterations: u32,
pub refresh_time: u64,
pub testnet: bool,
pub password_files: Vec<String>,
pub unlocked_accounts: Vec<Address>,
@ -199,6 +200,7 @@ impl Default for AccountsConfig {
fn default() -> Self {
AccountsConfig {
iterations: 10240,
refresh_time: 5,
testnet: false,
password_files: Vec::new(),
unlocked_accounts: Vec::new(),

View File

@ -909,9 +909,15 @@ fn prepare_account_provider(spec: &SpecType, dirs: &Directories, data_dir: &str,
],
},
};
let ethstore = EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))?;
if cfg.refresh_time > 0 {
ethstore.set_refresh_time(::std::time::Duration::from_secs(cfg.refresh_time));
}
let account_provider = AccountProvider::new(
Box::new(EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))?),
account_settings);
Box::new(ethstore),
account_settings,
);
for a in cfg.unlocked_accounts {
// Check if the account exists