Make accounts refresh time configurable. (#7345)
* Configurable accounts refresh time. * Fix tests.
This commit is contained in:
parent
276496fb4b
commit
83447c201b
@ -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> {
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
|
@ -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)?,
|
||||||
|
@ -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(),
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user