mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
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:
+5
-5
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user