Persistent tracking of dapps (#4302)

* Tests for RPC

* Extracting dapp_id from Origin and x-parity-origin

* Separate type for DappId

* Persistent tracking of recent dapps

* Fixing tests

* Exposing dapp timestamps

* Fixing import to work on stable

* Fixing test again
This commit is contained in:
Tomasz Drwięga
2017-01-30 10:59:46 +01:00
committed by Gav Wood
parent 47e1c5e2f1
commit cf348dae60
15 changed files with 336 additions and 127 deletions

View File

@@ -74,7 +74,18 @@ impl From<SSError> for Error {
}
/// Dapp identifier
pub type DappId = String;
#[derive(Default, Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct DappId(String);
impl From<DappId> for String {
fn from(id: DappId) -> String { id.0 }
}
impl From<String> for DappId {
fn from(id: String) -> DappId { DappId(id) }
}
impl<'a> From<&'a str> for DappId {
fn from(id: &'a str) -> DappId { DappId(id.to_owned()) }
}
fn transient_sstore() -> EthMultiStore {
EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")
@@ -181,7 +192,7 @@ impl AccountProvider {
}
/// Gets a list of dapps recently requesting accounts.
pub fn recent_dapps(&self) -> Result<Vec<DappId>, Error> {
pub fn recent_dapps(&self) -> Result<HashMap<DappId, u64>, Error> {
Ok(self.dapps_settings.read().recent_dapps())
}
@@ -405,7 +416,7 @@ impl AccountProvider {
#[cfg(test)]
mod tests {
use super::{AccountProvider, Unlock};
use super::{AccountProvider, Unlock, DappId};
use std::time::Instant;
use ethstore::ethkey::{Generator, Random};
@@ -466,7 +477,7 @@ mod tests {
fn should_set_dapps_addresses() {
// given
let ap = AccountProvider::transient_provider();
let app = "app1".to_owned();
let app = DappId("app1".into());
// set `AllAccounts` policy
ap.set_new_dapps_whitelist(None).unwrap();

View File

@@ -17,11 +17,17 @@
//! Address Book and Dapps Settings Store
use std::{fs, fmt, hash, ops};
use std::collections::{HashMap, VecDeque};
use std::sync::atomic::{self, AtomicUsize};
use std::collections::HashMap;
use std::path::PathBuf;
use ethstore::ethkey::Address;
use ethjson::misc::{AccountMeta, DappsSettings as JsonSettings, NewDappsPolicy as JsonNewDappsPolicy};
use ethjson::misc::{
AccountMeta,
DappsSettings as JsonSettings,
DappsHistory as JsonDappsHistory,
NewDappsPolicy as JsonNewDappsPolicy,
};
use account_provider::DappId;
/// Disk-backed map from Address to String. Uses JSON.
@@ -35,7 +41,7 @@ impl AddressBook {
let mut r = AddressBook {
cache: DiskMap::new(path, "address_book.json".into())
};
r.cache.revert(AccountMeta::read_address_map);
r.cache.revert(AccountMeta::read);
r
}
@@ -52,7 +58,7 @@ impl AddressBook {
}
fn save(&self) {
self.cache.save(AccountMeta::write_address_map)
self.cache.save(AccountMeta::write)
}
/// Sets new name for given address.
@@ -134,7 +140,51 @@ impl From<NewDappsPolicy> for JsonNewDappsPolicy {
}
}
const MAX_RECENT_DAPPS: usize = 10;
/// Transient dapps data
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct TransientDappsData {
/// Timestamp of last access
pub last_accessed: u64,
}
impl From<JsonDappsHistory> for TransientDappsData {
fn from(s: JsonDappsHistory) -> Self {
TransientDappsData {
last_accessed: s.last_accessed,
}
}
}
impl From<TransientDappsData> for JsonDappsHistory {
fn from(s: TransientDappsData) -> Self {
JsonDappsHistory {
last_accessed: s.last_accessed,
}
}
}
enum TimeProvider {
Clock,
Incremenetal(AtomicUsize)
}
impl TimeProvider {
fn get(&self) -> u64 {
match *self {
TimeProvider::Clock => {
::std::time::UNIX_EPOCH.elapsed()
.expect("Correct time is required to be set")
.as_secs()
},
TimeProvider::Incremenetal(ref time) => {
time.fetch_add(1, atomic::Ordering::SeqCst) as u64
},
}
}
}
const MAX_RECENT_DAPPS: usize = 50;
/// Disk-backed map from DappId to Settings. Uses JSON.
pub struct DappsSettingsStore {
@@ -142,8 +192,10 @@ pub struct DappsSettingsStore {
settings: DiskMap<DappId, DappsSettings>,
/// New Dapps Policy
policy: DiskMap<String, NewDappsPolicy>,
/// Recently Accessed Dapps (transient)
recent: VecDeque<DappId>,
/// Transient Data of recently Accessed Dapps
history: DiskMap<DappId, TransientDappsData>,
/// Time
time: TimeProvider,
}
impl DappsSettingsStore {
@@ -152,10 +204,12 @@ impl DappsSettingsStore {
let mut r = DappsSettingsStore {
settings: DiskMap::new(path.clone(), "dapps_accounts.json".into()),
policy: DiskMap::new(path.clone(), "dapps_policy.json".into()),
recent: VecDeque::with_capacity(MAX_RECENT_DAPPS),
history: DiskMap::new(path.clone(), "dapps_history.json".into()),
time: TimeProvider::Clock,
};
r.settings.revert(JsonSettings::read_dapps_settings);
r.policy.revert(JsonNewDappsPolicy::read_new_dapps_policy);
r.settings.revert(JsonSettings::read);
r.policy.revert(JsonNewDappsPolicy::read);
r.history.revert(JsonDappsHistory::read);
r
}
@@ -164,7 +218,8 @@ impl DappsSettingsStore {
DappsSettingsStore {
settings: DiskMap::transient(),
policy: DiskMap::transient(),
recent: VecDeque::with_capacity(MAX_RECENT_DAPPS),
history: DiskMap::transient(),
time: TimeProvider::Incremenetal(AtomicUsize::new(1)),
}
}
@@ -178,24 +233,36 @@ impl DappsSettingsStore {
self.policy.get("default").cloned().unwrap_or(NewDappsPolicy::AllAccounts)
}
/// Returns recent dapps (in order of last request)
pub fn recent_dapps(&self) -> Vec<DappId> {
self.recent.iter().cloned().collect()
/// Returns recent dapps with last accessed timestamp
pub fn recent_dapps(&self) -> HashMap<DappId, u64> {
self.history.iter().map(|(k, v)| (k.clone(), v.last_accessed)).collect()
}
/// Marks recent dapp as used
pub fn mark_dapp_used(&mut self, dapp: DappId) {
self.recent.retain(|id| id != &dapp);
self.recent.push_front(dapp);
while self.recent.len() > MAX_RECENT_DAPPS {
self.recent.pop_back();
{
let mut entry = self.history.entry(dapp).or_insert_with(|| Default::default());
entry.last_accessed = self.time.get();
}
// Clear extraneous entries
while self.history.len() > MAX_RECENT_DAPPS {
let min = self.history.iter()
.min_by_key(|&(_, ref v)| v.last_accessed)
.map(|(ref k, _)| k.clone())
.cloned();
match min {
Some(k) => self.history.remove(&k),
None => break,
};
}
self.history.save(JsonDappsHistory::write);
}
/// Sets current new dapps policy
pub fn set_policy(&mut self, policy: NewDappsPolicy) {
self.policy.insert("default".into(), policy);
self.policy.save(JsonNewDappsPolicy::write_new_dapps_policy);
self.policy.save(JsonNewDappsPolicy::write);
}
/// Sets accounts for specific dapp.
@@ -204,7 +271,7 @@ impl DappsSettingsStore {
let mut settings = self.settings.entry(id).or_insert_with(DappsSettings::default);
settings.accounts = accounts;
}
self.settings.save(JsonSettings::write_dapps_settings);
self.settings.save(JsonSettings::write);
}
}
@@ -280,6 +347,7 @@ impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
#[cfg(test)]
mod tests {
use super::{AddressBook, DappsSettingsStore, DappsSettings, NewDappsPolicy};
use account_provider::DappId;
use std::collections::HashMap;
use ethjson::misc::AccountMeta;
use devtools::RandomTempPath;
@@ -333,18 +401,22 @@ mod tests {
}
#[test]
fn should_maintain_a_list_of_recent_dapps() {
fn should_maintain_a_map_of_recent_dapps() {
let mut store = DappsSettingsStore::transient();
assert!(store.recent_dapps().is_empty(), "Initially recent dapps should be empty.");
store.mark_dapp_used("dapp1".into());
assert_eq!(store.recent_dapps(), vec!["dapp1".to_owned()]);
let dapp1: DappId = "dapp1".into();
let dapp2: DappId = "dapp2".into();
store.mark_dapp_used(dapp1.clone());
let recent = store.recent_dapps();
assert_eq!(recent.len(), 1);
assert_eq!(recent.get(&dapp1), Some(&1));
store.mark_dapp_used("dapp2".into());
assert_eq!(store.recent_dapps(), vec!["dapp2".to_owned(), "dapp1".to_owned()]);
store.mark_dapp_used("dapp1".into());
assert_eq!(store.recent_dapps(), vec!["dapp1".to_owned(), "dapp2".to_owned()]);
store.mark_dapp_used(dapp2.clone());
let recent = store.recent_dapps();
assert_eq!(recent.len(), 2);
assert_eq!(recent.get(&dapp1), Some(&1));
assert_eq!(recent.get(&dapp2), Some(&2));
}
#[test]
@@ -353,7 +425,7 @@ mod tests {
let temp = RandomTempPath::create_dir();
let path = temp.as_str().to_owned();
let mut store = DappsSettingsStore::new(path.clone());
// Test default policy
assert_eq!(store.policy(), NewDappsPolicy::AllAccounts);