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 json::{self, Uuid, OpaqueKeyFile};
use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation, OpaqueSecret}; use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation, OpaqueSecret};
const REFRESH_TIME_SEC: u64 = 5;
/// Accounts store. /// Accounts store.
pub struct EthStore { pub struct EthStore {
store: EthMultiStore, 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> { fn get(&self, account: &StoreAccountRef) -> Result<SafeAccount, Error> {
let mut accounts = self.store.get_accounts(account)?.into_iter(); let mut accounts = self.store.get_accounts(account)?.into_iter();
accounts.next().ok_or(Error::InvalidAccount) accounts.next().ok_or(Error::InvalidAccount)
@ -254,6 +262,7 @@ pub struct EthMultiStore {
struct Timestamp { struct Timestamp {
dir_hash: Option<u64>, dir_hash: Option<u64>,
last_checked: Instant, last_checked: Instant,
refresh_time: Duration,
} }
impl EthMultiStore { impl EthMultiStore {
@ -272,16 +281,28 @@ impl EthMultiStore {
timestamp: Mutex::new(Timestamp { timestamp: Mutex::new(Timestamp {
dir_hash: None, dir_hash: None,
last_checked: Instant::now(), last_checked: Instant::now(),
// by default we never refresh accounts
refresh_time: Duration::from_secs(u64::max_value()),
}), }),
}; };
store.reload_accounts()?; store.reload_accounts()?;
Ok(store) 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> { fn reload_if_changed(&self) -> Result<(), Error> {
let mut last_timestamp = self.timestamp.lock(); let mut last_timestamp = self.timestamp.lock();
let now = Instant::now(); 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()?); let dir_hash = Some(self.dir.unique_repr()?);
last_timestamp.last_checked = now; last_timestamp.last_checked = now;
if last_timestamp.dir_hash == dir_hash { if last_timestamp.dir_hash == dir_hash {
@ -319,22 +340,23 @@ impl EthMultiStore {
} }
fn get_accounts(&self, account: &StoreAccountRef) -> Result<Vec<SafeAccount>, Error> { fn get_accounts(&self, account: &StoreAccountRef) -> Result<Vec<SafeAccount>, Error> {
{ let from_cache = |account| {
let cache = self.cache.read(); let cache = self.cache.read();
if let Some(accounts) = cache.get(account) { if let Some(accounts) = cache.get(account) {
if !accounts.is_empty() { 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()?; self.reload_if_changed()?;
let cache = self.cache.read(); from_cache(account).ok_or(Error::InvalidAccount)
let accounts = cache.get(account).ok_or(Error::InvalidAccount)?; }
if accounts.is_empty() {
Err(Error::InvalidAccount)
} else {
Ok(accounts.clone())
} }
} }
@ -470,11 +492,20 @@ impl SimpleSecretStore for EthMultiStore {
} }
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> { fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
let read_from_cache = |address: &Address| {
use std::collections::Bound; use std::collections::Bound;
self.reload_if_changed()?;
let cache = self.cache.read(); let cache = self.cache.read();
let mut r = cache.range((Bound::Included(*address), Bound::Included(*address))); 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> { fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {

View File

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

View File

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

View File

@ -188,6 +188,7 @@ impl str::FromStr for ResealPolicy {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct AccountsConfig { pub struct AccountsConfig {
pub iterations: u32, pub iterations: u32,
pub refresh_time: u64,
pub testnet: bool, pub testnet: bool,
pub password_files: Vec<String>, pub password_files: Vec<String>,
pub unlocked_accounts: Vec<Address>, pub unlocked_accounts: Vec<Address>,
@ -199,6 +200,7 @@ impl Default for AccountsConfig {
fn default() -> Self { fn default() -> Self {
AccountsConfig { AccountsConfig {
iterations: 10240, iterations: 10240,
refresh_time: 5,
testnet: false, testnet: false,
password_files: Vec::new(), password_files: Vec::new(),
unlocked_accounts: 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( let account_provider = AccountProvider::new(
Box::new(EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))?), Box::new(ethstore),
account_settings); account_settings,
);
for a in cfg.unlocked_accounts { for a in cfg.unlocked_accounts {
// Check if the account exists // Check if the account exists