RPC for importing geth keys (#1916)

* Address book for local addresses.

* Simple disk-backed address book.

* Add test and keep file with keys.

* View and import Geth keys.

* Fix test.

* Fix up author info.

[ci:skip]
This commit is contained in:
Gav Wood
2016-08-11 18:31:28 +02:00
committed by GitHub
parent f114a933a3
commit ca54b8e493
16 changed files with 307 additions and 39 deletions

View File

@@ -16,15 +16,16 @@
//! Account management.
use std::fmt;
use std::{fs, fmt};
use std::collections::HashMap;
use std::time::{Instant, Duration};
use util::{Address as H160, H256, H520, Mutex, RwLock};
use std::path::PathBuf;
use ethjson::misc::AccountMeta;
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
use ethstore::dir::{KeyDirectory};
use ethstore::ethkey::{Address as SSAddress, Message as SSMessage, Secret as SSSecret, Random, Generator};
/// Type of unlock.
#[derive(Clone)]
enum Unlock {
@@ -131,32 +132,74 @@ impl KeyDirectory for NullDir {
}
}
/// Disk-backed map from Address to String. Uses JSON.
struct AddressBook {
path: PathBuf,
cache: HashMap<H160, AccountMeta>,
}
impl AddressBook {
pub fn new(path: String) -> Self {
trace!(target: "addressbook", "new({})", path);
let mut path: PathBuf = path.into();
path.push("address_book.json");
trace!(target: "addressbook", "path={:?}", path);
let mut r = AddressBook {
path: path,
cache: HashMap::new(),
};
r.revert();
r
}
pub fn get(&self) -> HashMap<H160, AccountMeta> {
self.cache.clone()
}
pub fn set_name(&mut self, a: H160, name: String) {
let mut x = self.cache.get(&a)
.map(|a| a.clone())
.unwrap_or(AccountMeta{name: Default::default(), meta: "{}".to_owned(), uuid: None});
x.name = name;
self.cache.insert(a, x);
self.save();
}
pub fn set_meta(&mut self, a: H160, meta: String) {
let mut x = self.cache.get(&a)
.map(|a| a.clone())
.unwrap_or(AccountMeta{name: "Anonymous".to_owned(), meta: Default::default(), uuid: None});
x.meta = meta;
self.cache.insert(a, x);
self.save();
}
fn revert(&mut self) {
trace!(target: "addressbook", "revert");
let _ = fs::File::open(self.path.clone())
.map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e))
.and_then(|f| AccountMeta::read_address_map(&f)
.map_err(|e| warn!(target: "addressbook", "Couldn't read address book: {}", e))
.and_then(|m| { self.cache = m; Ok(()) })
);
}
fn save(&mut self) {
trace!(target: "addressbook", "save");
let _ = fs::File::create(self.path.clone())
.map_err(|e| warn!(target: "addressbook", "Couldn't open address book for writing: {}", e))
.and_then(|mut f| AccountMeta::write_address_map(&self.cache, &mut f)
.map_err(|e| warn!(target: "addressbook", "Couldn't write to address book: {}", e))
);
}
}
/// Account management.
/// Responsible for unlocking accounts.
pub struct AccountProvider {
unlocked: Mutex<HashMap<SSAddress, AccountData>>,
sstore: Box<SecretStore>,
}
/// Collected account metadata
#[derive(Clone, Debug, PartialEq)]
pub struct AccountMeta {
/// The name of the account.
pub name: String,
/// The rest of the metadata of the account.
pub meta: String,
/// The 128-bit UUID of the account, if it has one (brain-wallets don't).
pub uuid: Option<String>,
}
impl Default for AccountMeta {
fn default() -> Self {
AccountMeta {
name: String::new(),
meta: "{}".to_owned(),
uuid: None,
}
}
address_book: Mutex<AddressBook>,
}
impl AccountProvider {
@@ -164,6 +207,7 @@ impl AccountProvider {
pub fn new(sstore: Box<SecretStore>) -> Self {
AccountProvider {
unlocked: Mutex::new(HashMap::new()),
address_book: Mutex::new(AddressBook::new(sstore.local_path().into())),
sstore: sstore,
}
}
@@ -172,6 +216,7 @@ impl AccountProvider {
pub fn transient_provider() -> Self {
AccountProvider {
unlocked: Mutex::new(HashMap::new()),
address_book: Mutex::new(AddressBook::new(Default::default())),
sstore: Box::new(EthStore::open(Box::new(NullDir::default())).unwrap())
}
}
@@ -209,6 +254,23 @@ impl AccountProvider {
Ok(accounts)
}
/// Returns each address along with metadata.
pub fn addresses_info(&self) -> Result<HashMap<H160, AccountMeta>, Error> {
Ok(self.address_book.lock().get())
}
/// Returns each address along with metadata.
pub fn set_address_name<A>(&self, account: A, name: String) -> Result<(), Error> where Address: From<A> {
let account = Address::from(account).into();
Ok(self.address_book.lock().set_name(account, name))
}
/// Returns each address along with metadata.
pub fn set_address_meta<A>(&self, account: A, meta: String) -> Result<(), Error> where Address: From<A> {
let account = Address::from(account).into();
Ok(self.address_book.lock().set_meta(account, meta))
}
/// Returns each account along with name and meta.
pub fn accounts_info(&self) -> Result<HashMap<H160, AccountMeta>, Error> {
let r: HashMap<H160, AccountMeta> = try!(self.sstore.accounts())
@@ -320,13 +382,38 @@ impl AccountProvider {
let signature = try!(self.sstore.sign(&account, &password, &message));
Ok(H520(signature.into()))
}
/// Returns the underlying `SecretStore` reference if one exists.
pub fn list_geth_accounts(&self, testnet: bool) -> Vec<H160> {
self.sstore.list_geth_accounts(testnet).into_iter().map(|a| Address::from(a).into()).collect()
}
/// Returns the underlying `SecretStore` reference if one exists.
pub fn import_geth_accounts(&self, desired: Vec<H160>, testnet: bool) -> Result<Vec<H160>, Error> {
let desired = desired.into_iter().map(|a| Address::from(a).into()).collect();
Ok(try!(self.sstore.import_geth_accounts(desired, testnet)).into_iter().map(|a| Address::from(a).into()).collect())
}
}
#[cfg(test)]
mod tests {
use super::AccountProvider;
use super::{AccountProvider, AddressBook};
use std::collections::HashMap;
use ethjson::misc::AccountMeta;
use ethstore::ethkey::{Generator, Random};
use std::time::Duration;
use devtools::RandomTempPath;
#[test]
fn should_save_and_reload_address_book() {
let temp = RandomTempPath::create_dir();
let path = temp.as_str().to_owned();
let mut b = AddressBook::new(path.clone());
b.set_name(1.into(), "One".to_owned());
b.set_meta(1.into(), "{1:1}".to_owned());
let b = AddressBook::new(path);
assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]);
}
#[test]
fn unlock_account_temp() {