Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

RPC for importing geth keys #1916

Merged
merged 7 commits into from
Aug 11, 2016
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 111 additions & 24 deletions ethcore/src/account_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -131,39 +132,82 @@ 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 {
/// Creates new account provider.
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,
}
}
Expand All @@ -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())
}
}
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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() {
Expand Down
4 changes: 3 additions & 1 deletion ethstore/src/dir/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use ethkey::Address;
use {json, SafeAccount, Error};
use super::KeyDirectory;

const IGNORED_FILES: &'static [&'static str] = &["thumbs.db"];
const IGNORED_FILES: &'static [&'static str] = &["thumbs.db", "address_book.json"];

#[cfg(not(windows))]
fn restrict_permissions_to_owner(file_path: &Path) -> Result<(), i32> {
Expand Down Expand Up @@ -149,6 +149,8 @@ impl KeyDirectory for DiskDirectory {
Some((path, _)) => fs::remove_file(path).map_err(From::from)
}
}

fn path(&self) -> Option<&PathBuf> { Some(&self.path) }
}


Expand Down
2 changes: 2 additions & 0 deletions ethstore/src/dir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

use ethkey::Address;
use std::path::{PathBuf};
use {SafeAccount, Error};

mod disk;
Expand All @@ -30,6 +31,7 @@ pub trait KeyDirectory: Send + Sync {
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
fn remove(&self, address: &Address) -> Result<(), Error>;
fn path(&self) -> Option<&PathBuf> { None }
}

pub use self::disk::DiskDirectory;
Expand Down
13 changes: 13 additions & 0 deletions ethstore/src/ethstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use {Error, SecretStore};
use json;
use json::UUID;
use presale::PresaleWallet;
use import;

pub struct EthStore {
dir: Box<KeyDirectory>,
Expand Down Expand Up @@ -173,4 +174,16 @@ impl SecretStore for EthStore {
// save to file
self.save(account)
}

fn local_path(&self) -> String {
self.dir.path().map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| String::new())
}

fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {
import::read_geth_accounts(testnet)
}

fn import_geth_accounts(&self, desired: Vec<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
import::import_geth_accounts(&*self.dir, desired.into_iter().collect(), testnet)
}
}
38 changes: 37 additions & 1 deletion ethstore/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

use std::collections::HashSet;
use ethkey::Address;
use dir::KeyDirectory;
use dir::{GethDirectory, KeyDirectory, DirectoryType};
use Error;

pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
Expand All @@ -31,3 +31,39 @@ pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Add
Ok(address)
}).collect()
}

/// Provide a `HashSet` of all accounts available for import from the Geth keystore.
pub fn read_geth_accounts(testnet: bool) -> Vec<Address> {
let t = if testnet {
DirectoryType::Testnet
} else {
DirectoryType::Main
};

GethDirectory::open(t)
.load()
.map(|d| d.into_iter().map(|a| a.address).collect())
.unwrap_or_else(|_| Vec::new())
}

/// Import specific `desired` accounts from the Geth keystore into `dst`.
pub fn import_geth_accounts(dst: &KeyDirectory, desired: HashSet<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
let t = if testnet {
DirectoryType::Testnet
} else {
DirectoryType::Main
};

let src = GethDirectory::open(t);
let accounts = try!(src.load());
let existing_accounts = try!(dst.load()).into_iter().map(|a| a.address).collect::<HashSet<_>>();

accounts.into_iter()
.filter(|a| !existing_accounts.contains(&a.address))
.filter(|a| desired.contains(&a.address))
.map(|a| {
let address = a.address.clone();
try!(dst.insert(a));
Ok(address)
}).collect()
}
3 changes: 1 addition & 2 deletions ethstore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ mod secret_store;
pub use self::account::SafeAccount;
pub use self::error::Error;
pub use self::ethstore::EthStore;
pub use self::import::import_accounts;
pub use self::import::{import_accounts, read_geth_accounts};
pub use self::presale::PresaleWallet;
pub use self::secret_store::SecretStore;
pub use self::random::random_phrase;

6 changes: 6 additions & 0 deletions ethstore/src/secret_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,11 @@ pub trait SecretStore: Send + Sync {
fn set_name(&self, address: &Address, name: String) -> Result<(), Error>;

fn set_meta(&self, address: &Address, meta: String) -> Result<(), Error>;

fn local_path(&self) -> String;

fn list_geth_accounts(&self, testnet: bool) -> Vec<Address>;

fn import_geth_accounts(&self, desired: Vec<Address>, testnet: bool) -> Result<Vec<Address>, Error>;
}

17 changes: 16 additions & 1 deletion json/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
//! Lenient hash json deserialization for test json files.

use std::str::FromStr;
use serde::{Deserialize, Deserializer, Error};
use serde::{Deserialize, Deserializer, Serialize, Serializer, Error};
use serde::de::Visitor;
use rustc_serialize::hex::ToHex;
use util::hash::{H64 as Hash64, Address as Hash160, H256 as Hash256, H2048 as Hash2048};


Expand All @@ -34,6 +35,12 @@ macro_rules! impl_hash {
}
}

impl From<$inner> for $name {
fn from(i: $inner) -> Self {
$name(i)
}
}

impl Deserialize for $name {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
Expand Down Expand Up @@ -66,6 +73,14 @@ macro_rules! impl_hash {
deserializer.deserialize(HashVisitor)
}
}

impl Serialize for $name {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
let mut hex = "0x".to_owned();
hex.push_str(&self.0.to_hex());
serializer.serialize_str(&hex)
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions json/src/lib.rs.in
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ pub mod vm;
pub mod maybe;
pub mod state;
pub mod transaction;
pub mod misc;
Loading