Provide wallet 'plugin' architecture (#1983)

* remove receive_coinbase fn from wallet to wallet client

* start moving clients into separate mod

* rustfmt

* move wallet client + start listener into wallet 'clients'

* rustfmt

* refactor API to make it more modular and completely decouple sending

* rustfmt

* further decouple API from sending methods

* rustfmt

* remove wallet to wallet client trait

* rustfmt

* rename / refactor client + adapters

* rustfmt

* add adapter concept

* add midding node_clients dir

* add file and null adapters

* rustfmt

* remove receive from owner api

* factor out receiving slates into trait

* rustfmt

* adding listen trait

* rustfmt

* change listener to use trait

* rustfmt

* add test for file-based exchange

* replace http api send command

* rustfmt

* move controller out of libwallet and into top-level wallet dir

* rustfmt

* add moved controller
This commit is contained in:
Yeastplume
2018-11-19 19:47:40 +00:00
committed by GitHub
parent 66acee8f71
commit 5ba163fa66
31 changed files with 1145 additions and 976 deletions
+124 -178
View File
@@ -13,15 +13,11 @@
// limitations under the License.
use clap::ArgMatches;
use serde_json as json;
use std::fs::File;
use std::io::Read;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// Wallet commands processing
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use util::Mutex;
use api::TLSConfig;
use config::GlobalWalletConfig;
@@ -29,8 +25,8 @@ use core::{core, global};
use grin_wallet::libwallet::ErrorKind;
use grin_wallet::{self, controller, display, libwallet};
use grin_wallet::{
HTTPWalletToNodeClient, HTTPWalletToWalletClient, LMDBBackend, WalletBackend, WalletConfig,
WalletInst, WalletSeed,
instantiate_wallet, FileWalletCommAdapter, HTTPNodeClient, HTTPWalletCommAdapter, LMDBBackend,
NullWalletCommAdapter, WalletConfig, WalletSeed,
};
use keychain;
use servers::start_webwallet_server;
@@ -52,31 +48,6 @@ pub fn seed_exists(wallet_config: WalletConfig) -> bool {
false
}
}
pub fn instantiate_wallet(
wallet_config: WalletConfig,
passphrase: &str,
account: &str,
node_api_secret: Option<String>,
) -> Arc<Mutex<WalletInst<HTTPWalletToNodeClient, HTTPWalletToWalletClient, keychain::ExtKeychain>>>
{
let client_n =
HTTPWalletToNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret);
let client_w = HTTPWalletToWalletClient::new();
let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, client_n, client_w)
.unwrap_or_else(|e| {
panic!(
"Error creating DB wallet: {} Config: {:?}",
e, wallet_config
);
});
db_wallet
.set_parent_key_id_by_name(account)
.unwrap_or_else(|e| {
panic!("Error starting wallet: {}", e);
});
info!("Using LMDB Backend for wallet");
Arc::new(Mutex::new(db_wallet))
}
pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i32 {
// just get defaults from the global config
@@ -110,18 +81,14 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
WalletSeed::init_file(&wallet_config).expect("Failed to init wallet seed file.");
info!("Wallet seed file created");
let client_n =
HTTPWalletToNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret);
let client_w = HTTPWalletToWalletClient::new();
let _: LMDBBackend<
HTTPWalletToNodeClient,
HTTPWalletToWalletClient,
keychain::ExtKeychain,
> = LMDBBackend::new(wallet_config.clone(), "", client_n, client_w).unwrap_or_else(|e| {
panic!(
"Error creating DB for wallet: {} Config: {:?}",
e, wallet_config
);
});
HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret);
let _: LMDBBackend<HTTPNodeClient, keychain::ExtKeychain> =
LMDBBackend::new(wallet_config.clone(), "", client_n).unwrap_or_else(|e| {
panic!(
"Error creating DB for wallet: {} Config: {:?}",
e, wallet_config
);
});
info!("Wallet database backend created");
// give logging thread a moment to catch up
thread::sleep(Duration::from_millis(200));
@@ -145,13 +112,6 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
Some(p) => p,
};
let wallet = instantiate_wallet(
wallet_config.clone(),
passphrase,
account,
node_api_secret.clone(),
);
// Handle listener startup commands
{
let api_secret = get_first_line(wallet_config.api_secret_path.clone());
@@ -173,18 +133,37 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
if let Some(port) = listen_args.value_of("port") {
wallet_config.api_listen_port = port.parse().unwrap();
}
controller::foreign_listener(
wallet.clone(),
&wallet_config.api_listen_addr(),
tls_conf,
).unwrap_or_else(|e| {
panic!(
"Error creating wallet listener: {:?} Config: {:?}",
e, wallet_config
);
});
let mut params = HashMap::new();
params.insert(
"api_listen_addr".to_owned(),
wallet_config.api_listen_addr(),
);
if let Some(t) = tls_conf {
params.insert("certificate".to_owned(), t.certificate);
params.insert("private_key".to_owned(), t.private_key);
}
let adapter = HTTPWalletCommAdapter::new();
adapter
.listen(
params,
wallet_config.clone(),
passphrase,
account,
node_api_secret.clone(),
).unwrap_or_else(|e| {
panic!(
"Error creating wallet listener: {:?} Config: {:?}",
e, wallet_config
);
});
}
("owner_api", Some(_api_args)) => {
let wallet = instantiate_wallet(
wallet_config.clone(),
passphrase,
account,
node_api_secret.clone(),
);
// TLS is disabled because we bind to localhost
controller::owner_listener(wallet.clone(), "127.0.0.1:13420", api_secret, None)
.unwrap_or_else(|e| {
@@ -195,6 +174,12 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
});
}
("web", Some(_api_args)) => {
let wallet = instantiate_wallet(
wallet_config.clone(),
passphrase,
account,
node_api_secret.clone(),
);
// start owner listener and run static file server
start_webwallet_server();
controller::owner_listener(wallet.clone(), "127.0.0.1:13420", api_secret, tls_conf)
@@ -209,6 +194,13 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
};
}
let wallet = instantiate_wallet(
wallet_config.clone(),
passphrase,
account,
node_api_secret.clone(),
);
let res = controller::owner_single_use(wallet.clone(), |api| {
match wallet_args.subcommand() {
("account", Some(acct_args)) => {
@@ -299,122 +291,79 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
})?;
let fluff = send_args.is_present("fluff");
let max_outputs = 500;
if method == "http" {
if dest.starts_with("http://") || dest.starts_with("https://") {
let result = api.issue_send_tx(
amount,
minimum_confirmations,
if method == "http" && !dest.starts_with("http://") && !dest.starts_with("https://")
{
return Err(ErrorKind::GenericError(format!(
"HTTP Destination should start with http://: or https://: {}",
dest
)).into());
}
let result = api.initiate_tx(
None,
amount,
minimum_confirmations,
max_outputs,
change_outputs,
selection_strategy == "all",
);
let (mut slate, lock_fn) = match result {
Ok(s) => {
info!(
"Tx created: {} grin to {} (strategy '{}')",
core::amount_to_hr_string(amount, false),
dest,
max_outputs,
change_outputs,
selection_strategy == "all",
selection_strategy,
);
let slate = match result {
Ok(s) => {
info!(
"Tx created: {} grin to {} (strategy '{}')",
core::amount_to_hr_string(amount, false),
dest,
selection_strategy,
);
s
}
Err(e) => {
error!("Tx not created: {}", e);
match e.kind() {
// user errors, don't backtrace
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
libwallet::ErrorKind::FeeDispute { .. } => {}
libwallet::ErrorKind::FeeExceedsAmount { .. } => {}
_ => {
// otherwise give full dump
error!("Backtrace: {}", e.backtrace().unwrap());
}
};
return Err(e);
s
}
Err(e) => {
error!("Tx not created: {}", e);
match e.kind() {
// user errors, don't backtrace
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
libwallet::ErrorKind::FeeDispute { .. } => {}
libwallet::ErrorKind::FeeExceedsAmount { .. } => {}
_ => {
// otherwise give full dump
error!("Backtrace: {}", e.backtrace().unwrap());
}
};
let result = api.post_tx(&slate, fluff);
match result {
Ok(_) => {
info!("Tx sent",);
Ok(())
}
Err(e) => {
error!("Tx not sent: {}", e);
Err(e)
}
}
} else {
return Err(ErrorKind::GenericError(format!(
"HTTP Destination should start with http://: or https://: {}",
dest
)).into());
return Err(e);
}
} else if method == "self" {
let result = api.issue_self_tx(
amount,
minimum_confirmations,
max_outputs,
change_outputs,
selection_strategy == "all",
account,
dest,
);
let slate = match result {
Ok(s) => {
info!(
"Tx created: {} grin to self, source acct: {} dest_acct: {} (strategy '{}')",
core::amount_to_hr_string(amount, false),
account,
dest,
selection_strategy,
);
s
}
Err(e) => {
error!("Tx not created: {}", e);
match e.kind() {
// user errors, don't backtrace
libwallet::ErrorKind::NotEnoughFunds { .. } => {}
libwallet::ErrorKind::FeeDispute { .. } => {}
libwallet::ErrorKind::FeeExceedsAmount { .. } => {}
_ => {
// otherwise give full dump
error!("Backtrace: {}", e.backtrace().unwrap());
}
};
return Err(e);
}
};
};
let adapter = match method {
"http" => HTTPWalletCommAdapter::new(),
"file" => FileWalletCommAdapter::new(),
"self" => NullWalletCommAdapter::new(),
_ => NullWalletCommAdapter::new(),
};
if adapter.supports_sync() {
slate = adapter.send_tx_sync(dest, &slate)?;
if method == "self" {
controller::foreign_single_use(wallet, |api| {
api.receive_tx(&mut slate, Some(dest))?;
Ok(())
})?;
}
api.finalize_tx(&mut slate)?;
} else {
adapter.send_tx_async(dest, &slate)?;
}
api.tx_lock_outputs(&slate, lock_fn)?;
if adapter.supports_sync() {
let result = api.post_tx(&slate, fluff);
match result {
Ok(_) => {
info!("Tx sent",);
Ok(())
return Ok(());
}
Err(e) => {
error!("Tx not sent: {}", e);
Err(e)
return Err(e);
}
}
} else if method == "file" {
api.send_tx(
true,
amount,
minimum_confirmations,
dest,
max_outputs,
change_outputs,
selection_strategy == "all",
).map_err(|e| ErrorKind::GenericError(format!("Send failed. e={:?}", e)))?;
Ok(())
} else {
return Err(ErrorKind::GenericError(format!(
"unsupported payment method: {}",
method
)).into());
}
Ok(())
}
("receive", Some(send_args)) => {
let mut receive_result: Result<(), grin_wallet::libwallet::Error> = Ok(());
@@ -426,18 +375,18 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
ErrorKind::GenericError(format!("File {} not found.", tx_file)).into(),
);
}
let res = controller::foreign_single_use(wallet, |api| {
receive_result = api.file_receive_tx(tx_file);
let adapter = FileWalletCommAdapter::new();
let mut slate = adapter.receive_tx_async(tx_file)?;
controller::foreign_single_use(wallet, |api| {
api.receive_tx(&mut slate, Some(account))?;
Ok(())
});
if res.is_err() {
return res;
} else {
info!(
"Response file {}.response generated, sending it back to the transaction originator.",
tx_file,
);
}
})?;
let send_tx = format!("{}.response", tx_file);
adapter.send_tx_async(&send_tx, &slate)?;
info!(
"Response file {}.response generated, sending it back to the transaction originator.",
tx_file,
);
receive_result
}
("finalize", Some(send_args)) => {
@@ -450,11 +399,8 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
ErrorKind::GenericError(format!("File {} not found.", tx_file)).into(),
);
}
let mut pub_tx_f = File::open(tx_file)?;
let mut content = String::new();
pub_tx_f.read_to_string(&mut content)?;
let mut slate: grin_wallet::libtx::slate::Slate = json::from_str(&content)
.map_err(|_| grin_wallet::libwallet::ErrorKind::Format)?;
let adapter = FileWalletCommAdapter::new();
let mut slate = adapter.receive_tx_async(tx_file)?;
let _ = api.finalize_tx(&mut slate).expect("Finalize failed");
let result = api.post_tx(&slate, fluff);