Merge milestone/2.1.0 into master (#199)

* version bump for next potential release

* Merge master into milestone/2.1.0 (#182)

* Derive --version output dynamically from cargo package version (#174)

* add --txid to the `wallet txs` command (#176)

* add --txid to the `wallet txs` command

* add test for `wallet txs` command with `--txid` parameter

* Refactor - Split WalletCommAdapter into multiple traits (#180)

* Derive --version output dynamically from cargo package version (#174)

* add server auth argument to http client

* Revert "add server auth argument to http client"

This reverts commit f52a8d2c7cdfb8583af5716ad621eb560811d6ee.

* modify WalletCommAdapter, moving dest argument into fields on implementors,
visiting havok on automated tests, at least one of which is now out of date and failing

* Split WalletCommAdapter into four traits, one for each of its intended behaviors.

* Remove two vestigals
1. args, a stringly typed argument to put_tx
2. NullAdapter, which is no longer used

* Remove unused "params" argument from listen method.

* Re-add previously existing TODO comment

* Fix non-test build

* completely Fix non-test build

* Full Lifecycle API Support (#184)

* refactoring wallet lib traits

* rustfmt

* rustfmt

* add new files

* explicit lifetime specifiers on all wallet traits

* rustfmt

* modify apis to use new walletinst

* rustfmt

* converting controller crate

* rustfmt

* controller crate compiling

* rustfmt

* compilation

* rustfmt

* Remove config from wallet, implement open_wallet, close_wallet in lifecycle provider, remove password + open_with_credentials from WalletBackend + impl

* rustfmt

* full compilation, changing recovery + init to new model

* rustfmt

* wallet initialisation working, init command output and flow identical to v2.0.0 wallet

* rustfmt

* fix listener and owner api startup

* rustfmt

* rustfmt

* move encryption test

* rustfmt

* fix api doctests

* rustfmt

* fix for most tests in controller crate

* rustfmt

* fix for check tests in controller crate

* fix main wallet tests

* rustfmt

* add explicit functions to handle mnemonic recovery, fix CLI recovery workflow

* rustfmt

* update keybase adapter to use new wallet format

* rustfmt

* test fix

* remove debug output
This commit is contained in:
Yeastplume
2019-07-29 13:25:03 +01:00
committed by GitHub
parent 7b6d597d39
commit c40e8a915b
54 changed files with 2405 additions and 1942 deletions
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "grin_wallet_impls"
version = "2.0.1-beta.1"
version = "2.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Concrete types derived from libwallet traits"
license = "Apache-2.0"
@@ -25,8 +25,8 @@ tokio-core = "0.1"
tokio-retry = "0.1"
uuid = { version = "0.7", features = ["serde", "v4"] }
chrono = { version = "0.4.4", features = ["serde"] }
url = "1.7.2"
grin_wallet_util = { path = "../util", version = "2.0.1-beta.1" }
grin_wallet_libwallet = { path = "../libwallet", version = "2.0.1-beta.1" }
grin_wallet_config = { path = "../config", version = "2.0.1-beta.1" }
grin_wallet_util = { path = "../util", version = "2.1.0-beta.1" }
grin_wallet_config = { path = "../config", version = "2.1.0-beta.1" }
grin_wallet_libwallet = { path = "../libwallet", version = "2.1.0-beta.1" }
+10 -35
View File
@@ -16,32 +16,16 @@
use std::fs::File;
use std::io::{Read, Write};
use crate::config::WalletConfig;
use crate::libwallet::{Error, ErrorKind, Slate};
use crate::WalletCommAdapter;
use std::collections::HashMap;
use crate::{SlateGetter, SlatePutter};
use std::path::PathBuf;
#[derive(Clone)]
pub struct FileWalletCommAdapter {}
pub struct PathToSlate(pub PathBuf);
impl FileWalletCommAdapter {
/// Create
pub fn new() -> Box<dyn WalletCommAdapter> {
Box::new(FileWalletCommAdapter {})
}
}
impl WalletCommAdapter for FileWalletCommAdapter {
fn supports_sync(&self) -> bool {
false
}
fn send_tx_sync(&self, _dest: &str, _slate: &Slate) -> Result<Slate, Error> {
unimplemented!();
}
fn send_tx_async(&self, dest: &str, slate: &Slate) -> Result<(), Error> {
let mut pub_tx = File::create(dest)?;
impl SlatePutter for PathToSlate {
fn put_tx(&self, slate: &Slate) -> Result<(), Error> {
let mut pub_tx = File::create(&self.0)?;
pub_tx.write_all(
serde_json::to_string(slate)
.map_err(|_| ErrorKind::SlateSer)?
@@ -50,22 +34,13 @@ impl WalletCommAdapter for FileWalletCommAdapter {
pub_tx.sync_all()?;
Ok(())
}
}
fn receive_tx_async(&self, params: &str) -> Result<Slate, Error> {
let mut pub_tx_f = File::open(params)?;
impl SlateGetter for PathToSlate {
fn get_tx(&self) -> Result<Slate, Error> {
let mut pub_tx_f = File::open(&self.0)?;
let mut content = String::new();
pub_tx_f.read_to_string(&mut content)?;
Ok(Slate::deserialize_upgrade(&content)?)
}
fn listen(
&self,
_params: HashMap<String, String>,
_config: WalletConfig,
_passphrase: &str,
_account: &str,
_node_api_secret: Option<String>,
) -> Result<(), Error> {
unimplemented!();
}
}
+34 -46
View File
@@ -15,23 +15,28 @@
/// HTTP Wallet 'plugin' implementation
use crate::api;
use crate::libwallet::{Error, ErrorKind, Slate};
use crate::WalletCommAdapter;
use config::WalletConfig;
use crate::SlateSender;
use serde::Serialize;
use serde_json::{json, Value};
use std::collections::HashMap;
use url::Url;
#[derive(Clone)]
pub struct HTTPWalletCommAdapter {}
pub struct HttpSlateSender {
base_url: Url,
}
impl HTTPWalletCommAdapter {
/// Create
pub fn new() -> Box<dyn WalletCommAdapter> {
Box::new(HTTPWalletCommAdapter {})
impl HttpSlateSender {
/// Create, return Err if scheme is not "http"
pub fn new(base_url: Url) -> Result<HttpSlateSender, SchemeNotHttp> {
if base_url.scheme() != "http" {
Err(SchemeNotHttp)
} else {
Ok(HttpSlateSender { base_url })
}
}
/// Check version of the other wallet
fn check_other_version(&self, url: &str) -> Result<(), Error> {
/// Check version of the listening wallet
fn check_other_version(&self) -> Result<(), Error> {
let req = json!({
"jsonrpc": "2.0",
"method": "check_version",
@@ -39,7 +44,7 @@ impl HTTPWalletCommAdapter {
"params": []
});
let res: String = post(url, None, &req).map_err(|e| {
let res: String = post(&self.base_url, None, &req).map_err(|e| {
let mut report = format!("Performing version check (is recipient listening?): {}", e);
let err_string = format!("{}", e);
if err_string.contains("404") {
@@ -88,24 +93,15 @@ impl HTTPWalletCommAdapter {
}
}
impl WalletCommAdapter for HTTPWalletCommAdapter {
fn supports_sync(&self) -> bool {
true
}
fn send_tx_sync(&self, dest: &str, slate: &Slate) -> Result<Slate, Error> {
if &dest[..4] != "http" {
let err_str = format!(
"dest formatted as {} but send -d expected stdout or http://IP:port",
dest
);
error!("{}", err_str,);
Err(ErrorKind::Uri)?
}
let url = format!("{}/v2/foreign", dest);
impl SlateSender for HttpSlateSender {
fn send_tx(&self, slate: &Slate) -> Result<Slate, Error> {
let url: Url = self
.base_url
.join("/v2/foreign")
.expect("/v2/foreign is an invalid url path");
debug!("Posting transaction slate to {}", url);
self.check_other_version(&url)?;
self.check_other_version()?;
// Note: not using easy-jsonrpc as don't want the dependencies in this crate
let req = json!({
@@ -120,7 +116,7 @@ impl WalletCommAdapter for HTTPWalletCommAdapter {
});
trace!("Sending receive_tx request: {}", req);
let res: String = post(url.as_str(), None, &req).map_err(|e| {
let res: String = post(&url, None, &req).map_err(|e| {
let report = format!("Posting transaction slate (is recipient listening?): {}", e);
error!("{}", report);
ErrorKind::ClientCallback(report)
@@ -144,32 +140,24 @@ impl WalletCommAdapter for HTTPWalletCommAdapter {
Ok(slate)
}
}
fn send_tx_async(&self, _dest: &str, _slate: &Slate) -> Result<(), Error> {
unimplemented!();
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct SchemeNotHttp;
fn receive_tx_async(&self, _params: &str) -> Result<Slate, Error> {
unimplemented!();
}
fn listen(
&self,
_params: HashMap<String, String>,
_config: WalletConfig,
_passphrase: &str,
_account: &str,
_node_api_secret: Option<String>,
) -> Result<(), Error> {
unimplemented!();
impl Into<Error> for SchemeNotHttp {
fn into(self) -> Error {
let err_str = format!("url scheme must be http",);
ErrorKind::GenericError(err_str).into()
}
}
pub fn post<IN>(url: &str, api_secret: Option<String>, input: &IN) -> Result<String, api::Error>
pub fn post<IN>(url: &Url, api_secret: Option<String>, input: &IN) -> Result<String, api::Error>
where
IN: Serialize,
{
let req = api::client::create_post_request(url, api_secret, input)?;
// TODO: change create_post_request to accept a url instead of a &str
let req = api::client::create_post_request(url.as_str(), api_secret, input)?;
let res = api::client::send_request(req)?;
Ok(res)
}
+78 -48
View File
@@ -14,11 +14,13 @@
// Keybase Wallet Plugin
use crate::adapters::{SlateReceiver, SlateSender};
use crate::config::WalletConfig;
use crate::keychain::ExtKeychain;
use crate::libwallet::api_impl::foreign;
use crate::libwallet::{Error, ErrorKind, Slate};
use crate::{instantiate_wallet, HTTPNodeClient, WalletCommAdapter};
use failure::ResultExt;
use crate::libwallet::{Error, ErrorKind, Slate, WalletInst};
use crate::util::ZeroingString;
use crate::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
use serde::Serialize;
use serde_json::{from_str, json, to_string, Value};
use std::collections::{HashMap, HashSet};
@@ -36,25 +38,40 @@ const SLATE_NEW: &str = "grin_slate_new";
const SLATE_SIGNED: &str = "grin_slate_signed";
#[derive(Clone)]
pub struct KeybaseWalletCommAdapter {}
pub struct KeybaseChannel(String);
impl KeybaseWalletCommAdapter {
impl KeybaseChannel {
/// Check if keybase is installed and return an adapter object.
pub fn new() -> Box<WalletCommAdapter> {
let mut proc = if cfg!(target_os = "windows") {
Command::new("where")
} else {
Command::new("which")
};
proc.arg("keybase")
.stdout(Stdio::null())
.status()
.expect("Keybase executable not found, make sure it is installed and in your PATH");
pub fn new(channel: String) -> Result<KeybaseChannel, Error> {
// Limit only one recipient
if channel.matches(",").count() > 0 {
return Err(
ErrorKind::GenericError("Only one recipient is supported!".to_owned()).into(),
);
}
Box::new(KeybaseWalletCommAdapter {})
if !keybase_installed() {
return Err(ErrorKind::GenericError(
"Keybase executable not found, make sure it is installed and in your PATH"
.to_owned(),
)
.into());
}
Ok(KeybaseChannel(channel))
}
}
/// Check if keybase executable exists in path
fn keybase_installed() -> bool {
let mut proc = if cfg!(target_os = "windows") {
Command::new("where")
} else {
Command::new("which")
};
proc.arg("keybase").stdout(Stdio::null()).status().is_ok()
}
/// Send a json object to the keybase process. Type `keybase chat api --help` for a list of available methods.
fn api_send(payload: &str) -> Result<Value, Error> {
let mut proc = Command::new("keybase");
@@ -280,23 +297,13 @@ fn poll(nseconds: u64, channel: &str) -> Option<Slate> {
None
}
impl WalletCommAdapter for KeybaseWalletCommAdapter {
fn supports_sync(&self) -> bool {
true
}
// Send a slate to a keybase username then wait for a response for TTL seconds.
fn send_tx_sync(&self, addr: &str, slate: &Slate) -> Result<Slate, Error> {
// Limit only one recipient
if addr.matches(",").count() > 0 {
error!("Only one recipient is supported!");
return Err(ErrorKind::GenericError("Tx rejected".to_owned()))?;
}
impl SlateSender for KeybaseChannel {
/// Send a slate to a keybase username then wait for a response for TTL seconds.
fn send_tx(&self, slate: &Slate) -> Result<Slate, Error> {
let id = slate.id;
// Send original slate to recipient with the SLATE_NEW topic
match send(&slate, addr, SLATE_NEW, TTL) {
match send(&slate, &self.0, SLATE_NEW, TTL) {
true => (),
false => {
return Err(ErrorKind::ClientCallback(
@@ -304,9 +311,9 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
))?;
}
}
info!("tx request has been sent to @{}, tx uuid: {}", addr, id);
info!("tx request has been sent to @{}, tx uuid: {}", &self.0, id);
// Wait for response from recipient with SLATE_SIGNED topic
match poll(TTL as u64, addr) {
match poll(TTL as u64, &self.0) {
Some(slate) => return Ok(slate),
None => {
return Err(ErrorKind::ClientCallback(
@@ -315,30 +322,55 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
}
}
}
}
/// Send a transaction asynchronously (result will be returned via the listener)
fn send_tx_async(&self, _addr: &str, _slate: &Slate) -> Result<(), Error> {
unimplemented!();
}
/// Receive a transaction async. (Actually just read it from wherever and return the slate)
fn receive_tx_async(&self, _params: &str) -> Result<Slate, Error> {
unimplemented!();
/// Receives slates on all channels with topic SLATE_NEW
pub struct KeybaseAllChannels {
_priv: (), // makes KeybaseAllChannels unconstructable without checking for existence of keybase executable
}
impl KeybaseAllChannels {
/// Create a KeybaseAllChannels, return error if keybase executable is not present
pub fn new() -> Result<KeybaseAllChannels, Error> {
if !keybase_installed() {
Err(ErrorKind::GenericError(
"Keybase executable not found, make sure it is installed and in your PATH"
.to_owned(),
)
.into())
} else {
Ok(KeybaseAllChannels { _priv: () })
}
}
}
impl SlateReceiver for KeybaseAllChannels {
/// Start a listener, passing received messages to the wallet api directly
#[allow(unreachable_code)]
fn listen(
&self,
_params: HashMap<String, String>,
config: WalletConfig,
passphrase: &str,
passphrase: ZeroingString,
account: &str,
node_api_secret: Option<String>,
) -> Result<(), Error> {
let node_client = HTTPNodeClient::new(&config.check_node_api_http_addr, node_api_secret);
let wallet = instantiate_wallet(config.clone(), node_client, passphrase, account)
.context(ErrorKind::WalletSeedDecryption)?;
let mut wallet = Box::new(
DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(),
)
as Box<
WalletInst<
'static,
DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
HTTPNodeClient,
ExtKeychain,
>,
>;
let lc = wallet.lc_provider().unwrap();
lc.set_wallet_directory(&config.data_file_dir);
lc.open_wallet(None, passphrase)?;
let wallet_inst = lc.wallet_inst()?;
wallet_inst.set_parent_key_id_by_name(account)?;
info!("Listening for transactions on keybase ...");
loop {
@@ -379,10 +411,8 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
return Err(e);
}
let res = {
let mut w = wallet.lock();
w.open_with_credentials()?;
let r = foreign::receive_tx(&mut *w, &slate, None, None, false);
w.close()?;
let r =
foreign::receive_tx(&mut **wallet_inst, &slate, None, None, false);
r
};
match res {
+61 -21
View File
@@ -15,41 +15,81 @@
mod file;
mod http;
mod keybase;
mod null;
pub use self::file::FileWalletCommAdapter;
pub use self::http::HTTPWalletCommAdapter;
pub use self::keybase::KeybaseWalletCommAdapter;
pub use self::null::NullWalletCommAdapter;
pub use self::file::PathToSlate;
pub use self::http::HttpSlateSender;
pub use self::keybase::{KeybaseAllChannels, KeybaseChannel};
use crate::config::WalletConfig;
use crate::libwallet::{Error, Slate};
use std::collections::HashMap;
/// Encapsulate wallet to wallet communication functions
pub trait WalletCommAdapter {
/// Whether this adapter supports sync mode
fn supports_sync(&self) -> bool;
use crate::libwallet::{Error, ErrorKind, Slate};
use crate::util::ZeroingString;
/// Sends transactions to a corresponding SlateReceiver
pub trait SlateSender {
/// Send a transaction slate to another listening wallet and return result
/// TODO: Probably need a slate wrapper type
fn send_tx_sync(&self, addr: &str, slate: &Slate) -> Result<Slate, Error>;
/// Send a transaction asynchronously (result will be returned via the listener)
fn send_tx_async(&self, addr: &str, slate: &Slate) -> Result<(), Error>;
/// Receive a transaction async. (Actually just read it from wherever and return the slate)
fn receive_tx_async(&self, params: &str) -> Result<Slate, Error>;
fn send_tx(&self, slate: &Slate) -> Result<Slate, Error>;
}
pub trait SlateReceiver {
/// Start a listener, passing received messages to the wallet api directly
/// Takes a wallet config for now to avoid needing all sorts of awkward
/// type parameters on this trait
fn listen(
&self,
params: HashMap<String, String>,
config: WalletConfig,
passphrase: &str,
passphrase: ZeroingString,
account: &str,
node_api_secret: Option<String>,
) -> Result<(), Error>;
}
/// Posts slates to be read later by a corresponding getter
pub trait SlatePutter {
/// Send a transaction asynchronously
fn put_tx(&self, slate: &Slate) -> Result<(), Error>;
}
/// Checks for a transaction from a corresponding SlatePutter, returns the transaction if it exists
pub trait SlateGetter {
/// Receive a transaction async. (Actually just read it from wherever and return the slate)
fn get_tx(&self) -> Result<Slate, Error>;
}
/// select a SlateSender based on method and dest fields from, e.g., SendArgs
pub fn create_sender(method: &str, dest: &str) -> Result<Box<dyn SlateSender>, Error> {
use url::Url;
let invalid = || {
ErrorKind::WalletComms(format!(
"Invalid wallet comm type and destination. method: {}, dest: {}",
method, dest
))
};
Ok(match method {
"http" => {
let url: Url = dest.parse().map_err(|_| invalid())?;
Box::new(HttpSlateSender::new(url).map_err(|_| invalid())?)
}
"keybase" => Box::new(KeybaseChannel::new(dest.to_owned())?),
"self" => {
return Err(ErrorKind::WalletComms(
"No sender implementation for \"self\".".to_string(),
)
.into());
}
"file" => {
return Err(ErrorKind::WalletComms(
"File based transactions must be performed asynchronously.".to_string(),
)
.into());
}
_ => {
return Err(ErrorKind::WalletComms(format!(
"Wallet comm method \"{}\" does not exist.",
method
))
.into());
}
})
}
-59
View File
@@ -1,59 +0,0 @@
// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::config::WalletConfig;
/// Null Output 'plugin' implementation
use crate::libwallet::{Error, Slate};
use crate::WalletCommAdapter;
use std::collections::HashMap;
#[derive(Clone)]
pub struct NullWalletCommAdapter {}
impl NullWalletCommAdapter {
/// Create
pub fn new() -> Box<NullWalletCommAdapter> {
Box::new(NullWalletCommAdapter {})
}
}
impl WalletCommAdapter for NullWalletCommAdapter {
fn supports_sync(&self) -> bool {
true
}
fn send_tx_sync(&self, _dest: &str, slate: &Slate) -> Result<Slate, Error> {
Ok(slate.clone())
}
fn send_tx_async(&self, _dest: &str, _slate: &Slate) -> Result<(), Error> {
Ok(())
}
fn receive_tx_async(&self, _params: &str) -> Result<Slate, Error> {
unimplemented!();
}
fn listen(
&self,
_params: HashMap<String, String>,
_config: WalletConfig,
_passphrase: &str,
_account: &str,
_node_api_secret: Option<String>,
) -> Result<(), Error> {
unimplemented!();
}
}
+54 -48
View File
@@ -15,9 +15,10 @@
use std::cell::RefCell;
use std::{fs, path};
// for writing storedtransaction files
// for writing stored transaction files
use std::fs::File;
use std::io::{Read, Write};
use std::marker::PhantomData;
use std::path::Path;
use failure::ResultExt;
@@ -29,7 +30,7 @@ use crate::keychain::{ChildNumber, ExtKeychain, Identifier, Keychain, SwitchComm
use crate::store::{self, option_to_not_found, to_key, to_key_u64};
use crate::core::core::Transaction;
use crate::core::{global, ser};
use crate::core::ser;
use crate::libwallet::{check_repair, restore};
use crate::libwallet::{
AcctPathMapping, Context, Error, ErrorKind, NodeClient, OutputData, TxLogEntry, WalletBackend,
@@ -37,9 +38,6 @@ use crate::libwallet::{
};
use crate::util;
use crate::util::secp::constants::SECRET_KEY_SIZE;
use crate::util::ZeroingString;
use crate::WalletSeed;
use config::WalletConfig;
pub const DB_DIR: &'static str = "db";
pub const TX_SAVE_DIR: &'static str = "saved_txs";
@@ -54,8 +52,8 @@ const ACCOUNT_PATH_MAPPING_PREFIX: u8 = 'a' as u8;
/// test to see if database files exist in the current directory. If so,
/// use a DB backend for all operations
pub fn wallet_db_exists(config: WalletConfig) -> bool {
let db_path = path::Path::new(&config.data_file_dir).join(DB_DIR);
pub fn wallet_db_exists(data_file_dir: &str) -> bool {
let db_path = path::Path::new(data_file_dir).join(DB_DIR);
db_path.exists()
}
@@ -92,25 +90,33 @@ where
Ok((ret_blind, ret_nonce))
}
pub struct LMDBBackend<C, K> {
pub struct LMDBBackend<'ck, C, K>
where
C: NodeClient + 'ck,
K: Keychain + 'ck,
{
db: store::Store,
config: WalletConfig,
/// passphrase: TODO better ways of dealing with this other than storing
passphrase: ZeroingString,
data_file_dir: String,
/// Keychain
pub keychain: Option<K>,
/// Parent path to use by default for output operations
parent_key_id: Identifier,
/// wallet to node client
w2n_client: C,
///phantom
_phantom: &'ck PhantomData<C>,
}
impl<C, K> LMDBBackend<C, K> {
pub fn new(config: WalletConfig, passphrase: &str, n_client: C) -> Result<Self, Error> {
let db_path = path::Path::new(&config.data_file_dir).join(DB_DIR);
impl<'ck, C, K> LMDBBackend<'ck, C, K>
where
C: NodeClient + 'ck,
K: Keychain + 'ck,
{
pub fn new(data_file_dir: &str, n_client: C) -> Result<Self, Error> {
let db_path = path::Path::new(data_file_dir).join(DB_DIR);
fs::create_dir_all(&db_path).expect("Couldn't create wallet backend directory!");
let stored_tx_path = path::Path::new(&config.data_file_dir).join(TX_SAVE_DIR);
let stored_tx_path = path::Path::new(data_file_dir).join(TX_SAVE_DIR);
fs::create_dir_all(&stored_tx_path)
.expect("Couldn't create wallet backend tx storage directory!");
@@ -136,11 +142,11 @@ impl<C, K> LMDBBackend<C, K> {
let res = LMDBBackend {
db: store,
config: config.clone(),
passphrase: ZeroingString::from(passphrase),
data_file_dir: data_file_dir.to_owned(),
keychain: None,
parent_key_id: LMDBBackend::<C, K>::default_path(),
w2n_client: n_client,
_phantom: &PhantomData,
};
Ok(res)
}
@@ -154,38 +160,36 @@ impl<C, K> LMDBBackend<C, K> {
/// Just test to see if database files exist in the current directory. If
/// so, use a DB backend for all operations
pub fn exists(config: WalletConfig) -> bool {
let db_path = path::Path::new(&config.data_file_dir).join(DB_DIR);
pub fn exists(data_file_dir: &str) -> bool {
let db_path = path::Path::new(data_file_dir).join(DB_DIR);
db_path.exists()
}
}
impl<C, K> WalletBackend<C, K> for LMDBBackend<C, K>
impl<'ck, C, K> WalletBackend<'ck, C, K> for LMDBBackend<'ck, C, K>
where
C: NodeClient,
K: Keychain,
C: NodeClient + 'ck,
K: Keychain + 'ck,
{
/// Initialise with whatever stored credentials we have
fn open_with_credentials(&mut self) -> Result<(), Error> {
let wallet_seed = WalletSeed::from_file(&self.config, &self.passphrase)
.context(ErrorKind::CallbackImpl("Error opening wallet"))?;
self.keychain = Some(
wallet_seed
.derive_keychain(global::is_floonet())
.context(ErrorKind::CallbackImpl("Error deriving keychain"))?,
);
Ok(())
/// Set the keychain, which should already have been opened
fn set_keychain(&mut self, k: Box<K>) {
self.keychain = Some(*k);
}
/// Close wallet and remove any stored credentials (TBD)
/// Close wallet
fn close(&mut self) -> Result<(), Error> {
//TODO: Ensure this is zeroed?
self.keychain = None;
Ok(())
}
/// Return the keychain being used
fn keychain(&mut self) -> &mut K {
self.keychain.as_mut().unwrap()
fn keychain(&mut self) -> Result<&mut K, Error> {
if self.keychain.is_some() {
Ok(self.keychain.as_mut().unwrap())
} else {
Err(ErrorKind::KeychainDoesntExist.into())
}
}
/// Return the node client being used
@@ -199,16 +203,18 @@ where
amount: u64,
id: &Identifier,
) -> Result<Option<String>, Error> {
if self.config.no_commit_cache == Some(true) {
//TODO: Check if this is really necessary, it's the only thing
//preventing removing the need for config in the wallet backend
/*if self.config.no_commit_cache == Some(true) {
Ok(None)
} else {
Ok(Some(util::to_hex(
self.keychain()
.commit(amount, &id, &SwitchCommitmentType::Regular)?
.0
.to_vec(), // TODO: proper support for different switch commitment schemes
)))
}
} else {*/
Ok(Some(util::to_hex(
self.keychain()?
.commit(amount, &id, &SwitchCommitmentType::Regular)?
.0
.to_vec(), // TODO: proper support for different switch commitment schemes
)))
/*}*/
}
/// Set parent path by account name
@@ -263,7 +269,7 @@ where
&mut slate_id.to_vec(),
participant_id as u64,
);
let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(self.keychain(), slate_id)?;
let (blind_xor_key, nonce_xor_key) = private_ctx_xor_keys(self.keychain()?, slate_id)?;
let mut ctx: Context = option_to_not_found(
self.db.get_ser(&ctx_key),
@@ -294,7 +300,7 @@ where
fn store_tx(&self, uuid: &str, tx: &Transaction) -> Result<(), Error> {
let filename = format!("{}.grintx", uuid);
let path = path::Path::new(&self.config.data_file_dir)
let path = path::Path::new(&self.data_file_dir)
.join(TX_SAVE_DIR)
.join(filename);
let path_buf = Path::new(&path).to_path_buf();
@@ -310,7 +316,7 @@ where
Some(f) => f,
None => return Ok(None),
};
let path = path::Path::new(&self.config.data_file_dir)
let path = path::Path::new(&self.data_file_dir)
.join(TX_SAVE_DIR)
.join(filename);
let tx_file = Path::new(&path).to_path_buf();
@@ -382,7 +388,7 @@ where
C: NodeClient,
K: Keychain,
{
_store: &'a LMDBBackend<C, K>,
_store: &'a LMDBBackend<'a, C, K>,
db: RefCell<Option<store::Batch<'a>>>,
/// Keychain
keychain: Option<K>,
+41 -22
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Grin Developers
// Copyright 2019 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -29,40 +29,59 @@ use grin_wallet_util::grin_core as core;
use grin_wallet_util::grin_keychain as keychain;
use grin_wallet_util::grin_store as store;
use grin_wallet_util::grin_util as util;
extern crate grin_wallet_config as config;
use grin_wallet_config as config;
mod adapters;
mod backends;
mod error;
mod lifecycle;
mod node_clients;
mod seed;
pub mod test_framework;
pub use crate::adapters::{
FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter, NullWalletCommAdapter,
WalletCommAdapter,
create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, SlateGetter,
SlatePutter, SlateReceiver, SlateSender,
};
pub use crate::backends::{wallet_db_exists, LMDBBackend};
pub use crate::error::{Error, ErrorKind};
pub use crate::lifecycle::DefaultLCProvider;
pub use crate::node_clients::HTTPNodeClient;
pub use crate::seed::{EncryptedWalletSeed, WalletSeed, SEED_FILE};
use crate::util::Mutex;
use std::sync::Arc;
use crate::keychain::{ExtKeychain, Keychain};
use libwallet::{NodeClient, WalletBackend, WalletInst};
use libwallet::{NodeClient, WalletInst, WalletLCProvider};
/// Helper to create an instance of the LMDB wallet
pub fn instantiate_wallet(
wallet_config: config::WalletConfig,
node_client: impl NodeClient + 'static,
passphrase: &str,
account: &str,
) -> Result<Arc<Mutex<WalletInst<impl NodeClient, keychain::ExtKeychain>>>, Error> {
// First test decryption, so we can abort early if we have the wrong password
let _ = WalletSeed::from_file(&wallet_config, passphrase)?;
let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, node_client)?;
db_wallet.set_parent_key_id_by_name(account)?;
info!("Using LMDB Backend for wallet");
Ok(Arc::new(Mutex::new(db_wallet)))
/// Main wallet instance
pub struct DefaultWalletImpl<'a, C>
where
C: NodeClient + 'a,
{
lc_provider: DefaultLCProvider<'a, C, ExtKeychain>,
}
impl<'a, C> DefaultWalletImpl<'a, C>
where
C: NodeClient + 'a,
{
pub fn new(node_client: C) -> Result<Self, Error> {
let lc_provider = DefaultLCProvider::new(node_client);
Ok(DefaultWalletImpl {
lc_provider: lc_provider,
})
}
}
impl<'a, L, C, K> WalletInst<'a, L, C, K> for DefaultWalletImpl<'a, C>
where
DefaultLCProvider<'a, C, ExtKeychain>: WalletLCProvider<'a, C, K>,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
fn lc_provider(
&mut self,
) -> Result<&mut (dyn WalletLCProvider<'a, C, K> + 'a), libwallet::Error> {
Ok(&mut self.lc_provider)
}
}
+230
View File
@@ -0,0 +1,230 @@
// Copyright 2019 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Default wallet lifecycle provider
use crate::config::{config, GlobalWalletConfig, GRIN_WALLET_DIR};
use crate::core::global;
use crate::keychain::Keychain;
use crate::libwallet::{Error, ErrorKind, NodeClient, WalletBackend, WalletLCProvider};
use crate::lifecycle::seed::WalletSeed;
use crate::util::ZeroingString;
use crate::LMDBBackend;
use failure::ResultExt;
use std::path::PathBuf;
pub struct DefaultLCProvider<'a, C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
{
data_dir: String,
node_client: C,
backend: Option<Box<dyn WalletBackend<'a, C, K> + 'a>>,
}
impl<'a, C, K> DefaultLCProvider<'a, C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
{
/// Create new provider
pub fn new(node_client: C) -> Self {
DefaultLCProvider {
node_client,
data_dir: "default".to_owned(),
backend: None,
}
}
}
impl<'a, C, K> WalletLCProvider<'a, C, K> for DefaultLCProvider<'a, C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
{
fn set_wallet_directory(&mut self, dir: &str) {
self.data_dir = dir.to_owned();
}
fn create_config(&self, chain_type: &global::ChainTypes, file_name: &str) -> Result<(), Error> {
let mut default_config = GlobalWalletConfig::for_chain(chain_type);
let mut config_file_name = PathBuf::from(self.data_dir.clone());
config_file_name.push(file_name);
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
if config_file_name.exists() && data_dir_name.exists() {
let msg = format!(
"{} already exists in the target directory ({}). Please remove it first",
file_name,
config_file_name.to_str().unwrap()
);
return Err(ErrorKind::Lifecycle(msg).into());
}
// just leave as is if file exists but there's no data dir
if config_file_name.exists() {
return Ok(());
}
default_config.update_paths(&PathBuf::from(self.data_dir.clone()));
let res = default_config.write_to_file(config_file_name.to_str().unwrap());
if let Err(e) = res {
let msg = format!(
"Error creating config file as ({}): {}",
config_file_name.to_str().unwrap(),
e
);
return Err(ErrorKind::Lifecycle(msg).into());
}
info!(
"File {} configured and created",
config_file_name.to_str().unwrap(),
);
let mut api_secret_path = PathBuf::from(self.data_dir.clone());
api_secret_path.push(PathBuf::from(config::API_SECRET_FILE_NAME));
if !api_secret_path.exists() {
config::init_api_secret(&api_secret_path).unwrap();
} else {
config::check_api_secret(&api_secret_path).unwrap();
}
Ok(())
}
fn create_wallet(
&mut self,
_name: Option<&str>,
mnemonic: Option<ZeroingString>,
mnemonic_length: usize,
password: ZeroingString,
) -> Result<(), Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
let _ = WalletSeed::init_file(&data_dir_name, mnemonic_length, mnemonic, password);
info!("Wallet seed file created");
let _wallet: LMDBBackend<'a, C, K> =
match LMDBBackend::new(&data_dir_name, self.node_client.clone()) {
Err(e) => {
let msg = format!("Error creating wallet: {}, Data Dir: {}", e, &data_dir_name);
return Err(ErrorKind::Lifecycle(msg).into());
}
Ok(d) => d,
};
info!("Wallet database backend created at {}", data_dir_name);
Ok(())
}
fn open_wallet(&mut self, _name: Option<&str>, password: ZeroingString) -> Result<(), Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
let mut wallet: LMDBBackend<'a, C, K> =
match LMDBBackend::new(&data_dir_name, self.node_client.clone()) {
Err(e) => {
let msg = format!("Error opening wallet: {}, Data Dir: {}", e, &data_dir_name);
return Err(ErrorKind::Lifecycle(msg).into());
}
Ok(d) => d,
};
let wallet_seed = WalletSeed::from_file(&data_dir_name, password)
.context(ErrorKind::Lifecycle("Error opening wallet".into()))?;
let keychain = wallet_seed
.derive_keychain(global::is_floonet())
.context(ErrorKind::Lifecycle("Error deriving keychain".into()))?;
wallet.set_keychain(Box::new(keychain));
self.backend = Some(Box::new(wallet));
Ok(())
}
fn close_wallet(&mut self, _name: Option<&str>) -> Result<(), Error> {
match self.backend.as_mut() {
Some(b) => b.close()?,
None => {}
};
self.backend = None;
Ok(())
}
fn wallet_exists(&self, _name: Option<&str>) -> Result<bool, Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
let res = WalletSeed::seed_file_exists(&data_dir_name).context(ErrorKind::CallbackImpl(
"Error checking for wallet existence",
))?;
Ok(res)
}
fn get_mnemonic(
&self,
_name: Option<&str>,
password: ZeroingString,
) -> Result<ZeroingString, Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
let wallet_seed = WalletSeed::from_file(&data_dir_name, password).context(
ErrorKind::Lifecycle("Error opening wallet seed file".into()),
)?;
let res = wallet_seed
.to_mnemonic()
.context(ErrorKind::Lifecycle("Error recovering wallet seed".into()))?;
Ok(ZeroingString::from(res))
}
fn validate_mnemonic(&self, mnemonic: ZeroingString) -> Result<(), Error> {
match WalletSeed::from_mnemonic(mnemonic) {
Ok(_) => Ok(()),
Err(_) => Err(ErrorKind::GenericError("Validating mnemonic".into()))?,
}
}
fn recover_from_mnemonic(
&self,
mnemonic: ZeroingString,
password: ZeroingString,
) -> Result<(), Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
WalletSeed::recover_from_phrase(data_dir_name, mnemonic, password).context(
ErrorKind::Lifecycle("Error recovering from mnemonic".into()),
)?;
Ok(())
}
fn change_password(&self, _old: String, _new: String) -> Result<(), Error> {
unimplemented!()
}
fn delete_wallet(&self, _name: Option<String>, _password: String) -> Result<(), Error> {
unimplemented!()
}
fn wallet_inst(&mut self) -> Result<&mut Box<dyn WalletBackend<'a, C, K> + 'a>, Error> {
match self.backend.as_mut() {
None => {
let msg = "Wallet has not been opened".into();
Err(ErrorKind::Lifecycle(msg).into())
}
Some(_) => Ok(&mut *self.backend.as_mut().unwrap()),
}
}
}
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2019 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod default;
mod seed;
pub use self::default::DefaultLCProvider;
@@ -27,7 +27,6 @@ use ring::{digest, pbkdf2};
use crate::keychain::{mnemonic, Keychain};
use crate::util;
use crate::{Error, ErrorKind};
use config::WalletConfig;
use failure::ResultExt;
pub const SEED_FILE: &'static str = "wallet.seed";
@@ -40,21 +39,21 @@ impl WalletSeed {
WalletSeed(bytes.to_vec())
}
pub fn from_mnemonic(word_list: &str) -> Result<WalletSeed, Error> {
let res = mnemonic::to_entropy(word_list);
pub fn from_mnemonic(word_list: util::ZeroingString) -> Result<WalletSeed, Error> {
let res = mnemonic::to_entropy(&word_list);
match res {
Ok(s) => Ok(WalletSeed::from_bytes(&s)),
Err(_) => Err(ErrorKind::Mnemonic.into()),
}
}
pub fn from_hex(hex: &str) -> Result<WalletSeed, Error> {
pub fn _from_hex(hex: &str) -> Result<WalletSeed, Error> {
let bytes = util::from_hex(hex.to_string())
.context(ErrorKind::GenericError("Invalid hex".to_owned()))?;
Ok(WalletSeed::from_bytes(&bytes))
}
pub fn to_hex(&self) -> String {
pub fn _to_hex(&self) -> String {
util::to_hex(self.0.to_vec())
}
@@ -66,7 +65,7 @@ impl WalletSeed {
}
}
pub fn derive_keychain_old(old_wallet_seed: [u8; 32], password: &str) -> Vec<u8> {
pub fn _derive_keychain_old(old_wallet_seed: [u8; 32], password: &str) -> Vec<u8> {
let seed = blake2::blake2b::blake2b(64, password.as_bytes(), &old_wallet_seed);
seed.as_bytes().to_vec()
}
@@ -85,35 +84,27 @@ impl WalletSeed {
WalletSeed(seed)
}
pub fn seed_file_exists(wallet_config: &WalletConfig) -> Result<(), Error> {
let seed_file_path = &format!(
"{}{}{}",
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE,
);
pub fn seed_file_exists(data_file_dir: &str) -> Result<bool, Error> {
let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
println!("Seed file path: {}", seed_file_path);
if Path::new(seed_file_path).exists() {
return Err(ErrorKind::WalletSeedExists(seed_file_path.to_owned()))?;
Ok(true)
} else {
Ok(false)
}
Ok(())
}
pub fn backup_seed(wallet_config: &WalletConfig) -> Result<(), Error> {
let seed_file_name = &format!(
"{}{}{}",
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE,
);
pub fn backup_seed(data_file_dir: &str) -> Result<(), Error> {
let seed_file_name = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
let mut path = Path::new(seed_file_name).to_path_buf();
path.pop();
let mut backup_seed_file_name = format!(
"{}{}{}.bak",
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE
);
let mut backup_seed_file_name =
format!("{}{}{}.bak", data_file_dir, MAIN_SEPARATOR, SEED_FILE);
let mut i = 1;
while Path::new(&backup_seed_file_name).exists() {
backup_seed_file_name = format!(
"{}{}{}.bak.{}",
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE, i
);
backup_seed_file_name =
format!("{}{}{}.bak.{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE, i);
i += 1;
}
path.push(backup_seed_file_name.clone());
@@ -127,20 +118,19 @@ impl WalletSeed {
}
pub fn recover_from_phrase(
wallet_config: &WalletConfig,
word_list: &str,
password: &str,
data_file_dir: &str,
word_list: util::ZeroingString,
password: util::ZeroingString,
) -> Result<(), Error> {
let seed_file_path = &format!(
"{}{}{}",
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE,
);
if WalletSeed::seed_file_exists(wallet_config).is_err() {
WalletSeed::backup_seed(wallet_config)?;
let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
println!("data file dir: {}", data_file_dir);
if let Ok(true) = WalletSeed::seed_file_exists(data_file_dir) {
println!("seed file exists");
WalletSeed::backup_seed(data_file_dir)?;
}
if !Path::new(&wallet_config.data_file_dir).exists() {
if !Path::new(&data_file_dir).exists() {
return Err(ErrorKind::WalletDoesntExist(
wallet_config.data_file_dir.clone(),
data_file_dir.to_owned(),
"To create a new wallet from a recovery phrase, use 'grin-wallet init -r'"
.to_owned(),
))?;
@@ -155,34 +145,22 @@ impl WalletSeed {
Ok(())
}
pub fn show_recovery_phrase(&self) -> Result<(), Error> {
println!("Your recovery phrase is:");
println!();
println!("{}", self.to_mnemonic()?);
println!();
println!("Please back-up these words in a non-digital format.");
Ok(())
}
pub fn init_file(
wallet_config: &WalletConfig,
data_file_dir: &str,
seed_length: usize,
recovery_phrase: Option<util::ZeroingString>,
password: &str,
password: util::ZeroingString,
) -> Result<WalletSeed, Error> {
// create directory if it doesn't exist
fs::create_dir_all(&wallet_config.data_file_dir).context(ErrorKind::IO)?;
fs::create_dir_all(data_file_dir).context(ErrorKind::IO)?;
let seed_file_path = &format!(
"{}{}{}",
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE,
);
let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
warn!("Generating wallet seed file at: {}", seed_file_path);
let _ = WalletSeed::seed_file_exists(wallet_config)?;
let _ = WalletSeed::seed_file_exists(data_file_dir)?;
let seed = match recovery_phrase {
Some(p) => WalletSeed::from_mnemonic(&p)?,
Some(p) => WalletSeed::from_mnemonic(p)?,
None => WalletSeed::init_new(seed_length),
};
@@ -191,18 +169,18 @@ impl WalletSeed {
let mut file = File::create(seed_file_path).context(ErrorKind::IO)?;
file.write_all(&enc_seed_json.as_bytes())
.context(ErrorKind::IO)?;
seed.show_recovery_phrase()?;
Ok(seed)
}
pub fn from_file(wallet_config: &WalletConfig, password: &str) -> Result<WalletSeed, Error> {
pub fn from_file(
data_file_dir: &str,
password: util::ZeroingString,
) -> Result<WalletSeed, Error> {
// TODO: Is this desirable any more?
// create directory if it doesn't exist
fs::create_dir_all(&wallet_config.data_file_dir).context(ErrorKind::IO)?;
fs::create_dir_all(data_file_dir).context(ErrorKind::IO)?;
let seed_file_path = &format!(
"{}{}{}",
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE,
);
let seed_file_path = &format!("{}{}{}", data_file_dir, MAIN_SEPARATOR, SEED_FILE,);
debug!("Using wallet seed file at: {}", seed_file_path);
@@ -212,7 +190,7 @@ impl WalletSeed {
file.read_to_string(&mut buffer).context(ErrorKind::IO)?;
let enc_seed: EncryptedWalletSeed =
serde_json::from_str(&buffer).context(ErrorKind::Format)?;
let wallet_seed = enc_seed.decrypt(password)?;
let wallet_seed = enc_seed.decrypt(&password)?;
Ok(wallet_seed)
} else {
error!(
@@ -240,7 +218,10 @@ pub struct EncryptedWalletSeed {
impl EncryptedWalletSeed {
/// Create a new encrypted seed from the given seed + password
pub fn from_seed(seed: &WalletSeed, password: &str) -> Result<EncryptedWalletSeed, Error> {
pub fn from_seed(
seed: &WalletSeed,
password: util::ZeroingString,
) -> Result<EncryptedWalletSeed, Error> {
let salt: [u8; 8] = thread_rng().gen();
let nonce: [u8; 12] = thread_rng().gen();
let password = password.as_bytes();
@@ -289,3 +270,28 @@ impl EncryptedWalletSeed {
Ok(WalletSeed::from_bytes(&decrypted_data))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::ZeroingString;
#[test]
fn wallet_seed_encrypt() {
let password = ZeroingString::from("passwoid");
let wallet_seed = WalletSeed::init_new(32);
let mut enc_wallet_seed =
EncryptedWalletSeed::from_seed(&wallet_seed, password.clone()).unwrap();
println!("EWS: {:?}", enc_wallet_seed);
let decrypted_wallet_seed = enc_wallet_seed.decrypt(&password).unwrap();
assert_eq!(wallet_seed, decrypted_wallet_seed);
// Wrong password
let decrypted_wallet_seed = enc_wallet_seed.decrypt("");
assert!(decrypted_wallet_seed.is_err());
// Wrong nonce
enc_wallet_seed.nonce = "wrongnonce".to_owned();
let decrypted_wallet_seed = enc_wallet_seed.decrypt(&password);
assert!(decrypted_wallet_seed.is_err());
}
}
+34 -66
View File
@@ -15,7 +15,6 @@
use crate::api;
use crate::chain;
use crate::chain::Chain;
use crate::config::WalletConfig;
use crate::core;
use crate::core::core::{OutputFeatures, OutputIdentifier, Transaction};
use crate::core::{consensus, global, pow};
@@ -23,13 +22,10 @@ use crate::keychain;
use crate::libwallet;
use crate::libwallet::api_impl::{foreign, owner};
use crate::libwallet::{
BlockFees, CbData, InitTxArgs, NodeClient, WalletBackend, WalletInfo, WalletInst,
BlockFees, CbData, InitTxArgs, NodeClient, WalletInfo, WalletInst, WalletLCProvider,
};
use crate::util;
use crate::util::secp::pedersen;
use crate::util::Mutex;
use crate::LMDBBackend;
use crate::WalletSeed;
use chrono::Duration;
use std::sync::Arc;
use std::thread;
@@ -105,14 +101,15 @@ pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: CbDa
/// adds a reward output to a wallet, includes that reward in a block, mines
/// the block and adds it to the chain, with option transactions included.
/// Helpful for building up precise wallet balances for testing.
pub fn award_block_to_wallet<C, K>(
pub fn award_block_to_wallet<'a, L, C, K>(
chain: &Chain,
txs: Vec<&Transaction>,
wallet: Arc<Mutex<dyn WalletInst<C, K>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
) -> Result<(), libwallet::Error>
where
C: NodeClient,
K: keychain::Keychain,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
{
// build block fees
let prev = chain.head_header().unwrap();
@@ -124,10 +121,9 @@ where
};
// build coinbase (via api) and add block
let coinbase_tx = {
let mut w = wallet.lock();
w.open_with_credentials()?;
let res = foreign::build_coinbase(&mut *w, &block_fees, false)?;
w.close()?;
let mut w_lock = wallet.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
let res = foreign::build_coinbase(&mut **w, &block_fees, false)?;
res
};
add_block_with_reward(chain, txs, coinbase_tx.clone());
@@ -135,15 +131,16 @@ where
}
/// Award a blocks to a wallet directly
pub fn award_blocks_to_wallet<C, K>(
pub fn award_blocks_to_wallet<'a, L, C, K>(
chain: &Chain,
wallet: Arc<Mutex<dyn WalletInst<C, K>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
number: usize,
pause_between: bool,
) -> Result<(), libwallet::Error>
where
C: NodeClient,
K: keychain::Keychain,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
{
for _ in 0..number {
award_block_to_wallet(chain, vec![], wallet.clone())?;
@@ -154,50 +151,22 @@ where
Ok(())
}
/// dispatch a db wallet
pub fn create_wallet<C, K>(
dir: &str,
n_client: C,
rec_phrase: Option<&str>,
) -> Arc<Mutex<dyn WalletInst<C, K>>>
where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let z_string = match rec_phrase {
Some(s) => Some(util::ZeroingString::from(s)),
None => None,
};
let mut wallet_config = WalletConfig::default();
wallet_config.data_file_dir = String::from(dir);
let _ = WalletSeed::init_file(&wallet_config, 32, z_string, "");
let mut wallet = LMDBBackend::new(wallet_config.clone(), "", n_client)
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config));
wallet.open_with_credentials().unwrap_or_else(|e| {
panic!(
"Error initializing wallet: {:?} Config: {:?}",
e, wallet_config
)
});
Arc::new(Mutex::new(wallet))
}
/// send an amount to a destination
pub fn send_to_dest<T: ?Sized, C, K>(
wallet: Arc<Mutex<dyn WalletInst<C, K>>>,
pub fn send_to_dest<'a, L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
client: LocalWalletClient,
dest: &str,
amount: u64,
test_mode: bool,
) -> Result<(), libwallet::Error>
where
T: WalletBackend<C, K>,
C: NodeClient,
K: keychain::Keychain,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
{
let slate = {
let mut w = wallet.lock();
w.open_with_credentials()?;
let mut w_lock = wallet.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
let args = InitTxArgs {
src_acct_name: None,
amount,
@@ -207,15 +176,15 @@ where
selection_strategy_is_use_all: true,
..Default::default()
};
let slate_i = owner::init_send_tx(&mut *w, args, test_mode)?;
let slate_i = owner::init_send_tx(&mut **w, args, test_mode)?;
let slate = client.send_tx_slate_direct(dest, &slate_i)?;
owner::tx_lock_outputs(&mut *w, &slate, 0)?;
let slate = owner::finalize_tx(&mut *w, &slate)?;
w.close()?;
owner::tx_lock_outputs(&mut **w, &slate, 0)?;
let slate = owner::finalize_tx(&mut **w, &slate)?;
slate
};
let client = {
let mut w = wallet.lock();
let mut w_lock = wallet.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
w.w2n_client().clone()
};
owner::post_tx(&client, &slate.tx, false)?; // mines a block
@@ -223,18 +192,17 @@ where
}
/// get wallet info totals
pub fn wallet_info<T: ?Sized, C, K>(
wallet: Arc<Mutex<dyn WalletInst<C, K>>>,
pub fn wallet_info<'a, L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
) -> Result<WalletInfo, libwallet::Error>
where
T: WalletBackend<C, K>,
C: NodeClient,
K: keychain::Keychain,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: keychain::Keychain + 'a,
{
let mut w = wallet.lock();
w.open_with_credentials()?;
let (wallet_refreshed, wallet_info) = owner::retrieve_summary_info(&mut *w, true, 1)?;
let mut w_lock = wallet.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
let (wallet_refreshed, wallet_info) = owner::retrieve_summary_info(&mut **w, true, 1)?;
assert!(wallet_refreshed);
w.close()?;
Ok(wallet_info)
}
+24 -71
View File
@@ -19,23 +19,23 @@
use crate::api;
use crate::chain::types::NoopAdapter;
use crate::chain::Chain;
use crate::config::WalletConfig;
use crate::core::core::verifier_cache::LruVerifierCache;
use crate::core::core::Transaction;
use crate::core::global::{set_mining_mode, ChainTypes};
use crate::core::{pow, ser};
use crate::keychain::Keychain;
use crate::libwallet;
use crate::libwallet::api_impl::foreign;
use crate::libwallet::{NodeClient, NodeVersionInfo, Slate, TxWrapper, WalletInst};
use crate::libwallet::{
NodeClient, NodeVersionInfo, Slate, TxWrapper, WalletInst, WalletLCProvider,
};
use crate::util;
use crate::util::secp::pedersen;
use crate::util::secp::pedersen::Commitment;
use crate::util::{Mutex, RwLock};
use crate::{libwallet, WalletCommAdapter};
use failure::ResultExt;
use serde_json;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
@@ -57,10 +57,11 @@ pub struct WalletProxyMessage {
/// communicates with a chain instance or other wallet
/// listener APIs via message queues
pub struct WalletProxy<C, K>
pub struct WalletProxy<'a, L, C, K>
where
C: NodeClient,
K: Keychain,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
/// directory to create the chain in
pub chain_dir: String,
@@ -71,7 +72,7 @@ where
String,
(
Sender<WalletProxyMessage>,
Arc<Mutex<dyn WalletInst<LocalWalletClient, K>>>,
Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
),
>,
/// simulate json send to another client
@@ -81,16 +82,13 @@ where
pub rx: Receiver<WalletProxyMessage>,
/// queue control
pub running: Arc<AtomicBool>,
/// Phantom
phantom_c: PhantomData<C>,
/// Phantom
phantom_k: PhantomData<K>,
}
impl<C, K> WalletProxy<C, K>
impl<'a, L, C, K> WalletProxy<'a, L, C, K>
where
C: NodeClient,
K: Keychain,
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
/// Create a new client that will communicate with the given grin node
pub fn new(chain_dir: &str) -> Self {
@@ -115,8 +113,6 @@ where
rx: rx,
wallets: HashMap::new(),
running: Arc::new(AtomicBool::new(false)),
phantom_c: PhantomData,
phantom_k: PhantomData,
};
retval
}
@@ -126,7 +122,7 @@ where
&mut self,
addr: &str,
tx: Sender<WalletProxyMessage>,
wallet: Arc<Mutex<dyn WalletInst<LocalWalletClient, K>>>,
wallet: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K> + 'a>>>,
) {
self.wallets.insert(addr.to_owned(), (tx, wallet));
}
@@ -212,11 +208,10 @@ where
)?;
;
{
let mut w = wallet.1.lock();
w.open_with_credentials()?;
let mut w_lock = wallet.1.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
// receive tx
slate = foreign::receive_tx(&mut *w, &slate, None, None, false)?;
w.close()?;
slate = foreign::receive_tx(&mut **w, &slate, None, None, false)?;
}
Ok(WalletProxyMessage {
@@ -344,55 +339,6 @@ impl LocalWalletClient {
}
}
impl WalletCommAdapter for LocalWalletClient {
fn supports_sync(&self) -> bool {
true
}
/// Send the slate to a listening wallet instance
fn send_tx_sync(&self, dest: &str, slate: &Slate) -> Result<Slate, libwallet::Error> {
let m = WalletProxyMessage {
sender_id: self.id.clone(),
dest: dest.to_owned(),
method: "send_tx_slate".to_owned(),
body: serde_json::to_string(slate).unwrap(),
};
{
let p = self.proxy_tx.lock();
p.send(m).context(libwallet::ErrorKind::ClientCallback(
"Send TX Slate".to_owned(),
))?;
}
let r = self.rx.lock();
let m = r.recv().unwrap();
trace!("Received send_tx_slate response: {:?}", m.clone());
Ok(
serde_json::from_str(&m.body).context(libwallet::ErrorKind::ClientCallback(
"Parsing send_tx_slate response".to_owned(),
))?,
)
}
fn send_tx_async(&self, _dest: &str, _slate: &Slate) -> Result<(), libwallet::Error> {
unimplemented!();
}
fn receive_tx_async(&self, _params: &str) -> Result<Slate, libwallet::Error> {
unimplemented!();
}
fn listen(
&self,
_params: HashMap<String, String>,
_config: WalletConfig,
_passphrase: &str,
_account: &str,
_node_api_secret: Option<String>,
) -> Result<(), libwallet::Error> {
unimplemented!();
}
}
impl NodeClient for LocalWalletClient {
fn node_url(&self) -> &str {
"node"
@@ -535,3 +481,10 @@ impl NodeClient for LocalWalletClient {
Ok((o.highest_index, o.last_retrieved_index, api_outputs))
}
}
unsafe impl<'a, L, C, K> Send for WalletProxy<'a, L, C, K>
where
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
}
-36
View File
@@ -1,36 +0,0 @@
// Copyright 2018 The Grin Developers
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Wallet seed encryption tests
extern crate grin_wallet_impls as impls;
use impls::{EncryptedWalletSeed, WalletSeed};
#[test]
fn wallet_seed_encrypt() {
let password = "passwoid";
let wallet_seed = WalletSeed::init_new(32);
let mut enc_wallet_seed = EncryptedWalletSeed::from_seed(&wallet_seed, password).unwrap();
println!("EWS: {:?}", enc_wallet_seed);
let decrypted_wallet_seed = enc_wallet_seed.decrypt(password).unwrap();
assert_eq!(wallet_seed, decrypted_wallet_seed);
// Wrong password
let decrypted_wallet_seed = enc_wallet_seed.decrypt("");
assert!(decrypted_wallet_seed.is_err());
// Wrong nonce
enc_wallet_seed.nonce = "wrongnonce".to_owned();
let decrypted_wallet_seed = enc_wallet_seed.decrypt(password);
assert!(decrypted_wallet_seed.is_err());
}