diff --git a/servers/src/mining/mine_block.rs b/servers/src/mining/mine_block.rs index 34e76ed1b..bf56cfdbe 100644 --- a/servers/src/mining/mine_block.rs +++ b/servers/src/mining/mine_block.rs @@ -240,9 +240,12 @@ fn get_coinbase( } Some(wallet_listener_url) => { // Get the wallet coinbase - let url = format!("{}/v1/receive/coinbase", wallet_listener_url.as_str()); + let url = format!( + "{}/v1/wallet/foreign/build_coinbase", + wallet_listener_url.as_str() + ); - let res = wallet::client::create_coinbase(&url, &block_fees)?; + let res = wallet::libwallet::client::create_coinbase(&url, &block_fees)?; let out_bin = util::from_hex(res.output).unwrap(); let kern_bin = util::from_hex(res.kernel).unwrap(); let key_id_bin = util::from_hex(res.key_id).unwrap(); diff --git a/servers/tests/framework/mod.rs b/servers/tests/framework/mod.rs index 806c26bcc..616eb0415 100644 --- a/servers/tests/framework/mod.rs +++ b/servers/tests/framework/mod.rs @@ -268,21 +268,21 @@ impl LocalServerContainer { //panic!("Error initting wallet seed: {}", e); } - let wallet_seed = wallet::WalletSeed::from_file(&self.wallet_config) - .expect("Failed to read wallet seed file."); - - let keychain = wallet_seed - .derive_keychain("grin_test") - .expect("Failed to derive keychain from seed file and passphrase."); - - let wallet = FileWallet::new(self.wallet_config.clone(), keychain).unwrap_or_else(|e| { + let wallet = FileWallet::new(self.wallet_config.clone(), "").unwrap_or_else(|e| { panic!( "Error creating wallet: {:?} Config: {:?}", e, self.wallet_config ) }); - wallet::server::start_rest_apis(wallet, &self.wallet_config.api_listen_addr()); + wallet::controller::foreign_listener(wallet, &self.wallet_config.api_listen_addr()) + .unwrap_or_else(|e| { + panic!( + "Error creating wallet listener: {:?} Config: {:?}", + e, self.wallet_config + ) + }); + self.wallet_is_running = true; } @@ -299,11 +299,12 @@ impl LocalServerContainer { wallet_seed: &wallet::WalletSeed, ) -> wallet::WalletInfo { let keychain = wallet_seed - .derive_keychain("grin_test") + .derive_keychain("") .expect("Failed to derive keychain from seed file and passphrase."); - let mut wallet = FileWallet::new(config.clone(), keychain) + let mut wallet = FileWallet::new(config.clone(), "") .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); - wallet::libwallet::updater::retrieve_info(&mut wallet).unwrap() + wallet.keychain = Some(keychain); + wallet::libwallet::internal::updater::retrieve_info(&mut wallet).unwrap() } pub fn send_amount_to( @@ -321,16 +322,18 @@ impl LocalServerContainer { wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file."); let keychain = wallet_seed - .derive_keychain("grin_test") + .derive_keychain("") .expect("Failed to derive keychain from seed file and passphrase."); let max_outputs = 500; - let mut wallet = FileWallet::new(config.clone(), keychain) + + let mut wallet = FileWallet::new(config.clone(), "grin_test") .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); - let result = wallet::issue_send_tx( + wallet.keychain = Some(keychain); + let result = wallet::libwallet::internal::tx::issue_send_tx( &mut wallet, amount, minimum_confirmations, - dest.to_string(), + dest, max_outputs, selection_strategy == "all", fluff, diff --git a/src/bin/grin.rs b/src/bin/grin.rs index 91ee54b7d..50d484506 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -216,6 +216,9 @@ fn main() { .help("Port on which to run the wallet listener") .takes_value(true))) + .subcommand(SubCommand::with_name("owner_api") + .about("Runs the wallet's local web API.")) + .subcommand(SubCommand::with_name("receive") .about("Processes a JSON transaction file.") .arg(Arg::with_name("input") @@ -411,22 +414,24 @@ fn server_command(server_args: Option<&ArgMatches>, mut global_config: GlobalCon if let Some(true) = server_config.run_wallet_listener { let mut wallet_config = global_config.members.unwrap().wallet; - let wallet_seed = match wallet::WalletSeed::from_file(&wallet_config) { - Ok(ws) => ws, - Err(_) => wallet::WalletSeed::init_file(&wallet_config) - .expect("Failed to create wallet seed file."), + if let Err(_) = wallet::WalletSeed::from_file(&wallet_config) { + wallet::WalletSeed::init_file(&wallet_config) + .expect("Failed to create wallet seed file."); }; - let mut keychain = wallet_seed - .derive_keychain("") - .expect("Failed to derive keychain from seed file and passphrase."); let _ = thread::Builder::new() .name("wallet_listener".to_string()) .spawn(move || { - let wallet = FileWallet::new(wallet_config.clone(), keychain).unwrap_or_else(|e| { + let wallet = FileWallet::new(wallet_config.clone(), "").unwrap_or_else(|e| { panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config) }); - wallet::server::start_rest_apis(wallet, &wallet_config.api_listen_addr()); + wallet::controller::foreign_listener(wallet, &wallet_config.api_listen_addr()) + .unwrap_or_else(|e| { + panic!( + "Error creating wallet listener: {:?} Config: {:?}", + e, wallet_config + ) + }); }); } @@ -524,117 +529,159 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { // Generate the initial wallet seed if we are running "wallet init". if let ("init", Some(_)) = wallet_args.subcommand() { wallet::WalletSeed::init_file(&wallet_config).expect("Failed to init wallet seed file."); - // we are done here with creating the wallet, so just return return; } - let wallet_seed = - wallet::WalletSeed::from_file(&wallet_config).expect("Failed to read wallet seed file."); let passphrase = wallet_args .value_of("pass") .expect("Failed to read passphrase."); - let keychain = wallet_seed - .derive_keychain(&passphrase) - .expect("Failed to derive keychain from seed file and passphrase."); - let mut wallet = FileWallet::new(wallet_config.clone(), keychain) - .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)); - match wallet_args.subcommand() { - ("listen", Some(listen_args)) => { - if let Some(port) = listen_args.value_of("port") { - wallet_config.api_listen_port = port.parse().unwrap(); + // Handle listener startup commands + { + let wallet = FileWallet::new(wallet_config.clone(), passphrase).unwrap_or_else(|e| { + panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config) + }); + match wallet_args.subcommand() { + ("listen", Some(listen_args)) => { + if let Some(port) = listen_args.value_of("port") { + wallet_config.api_listen_port = port.parse().unwrap(); + } + wallet::controller::foreign_listener(wallet, &wallet_config.api_listen_addr()) + .unwrap_or_else(|e| { + panic!( + "Error creating wallet listener: {:?} Config: {:?}", + e, wallet_config + ) + }); } - wallet::server::start_rest_apis(wallet, &wallet_config.api_listen_addr()); - } - ("send", Some(send_args)) => { - let amount = send_args - .value_of("amount") - .expect("Amount to send required"); - let amount = core::core::amount_from_hr_string(amount) - .expect("Could not parse amount as a number with optional decimal point."); - let minimum_confirmations: u64 = send_args - .value_of("minimum_confirmations") - .unwrap() - .parse() - .expect("Could not parse minimum_confirmations as a whole number."); - let selection_strategy = send_args - .value_of("selection_strategy") - .expect("Selection strategy required"); - let dest = send_args - .value_of("dest") - .expect("Destination wallet address required"); - let mut fluff = false; - if send_args.is_present("fluff") { - fluff = true; + ("owner_api", Some(_api_args)) => { + wallet::controller::owner_listener(wallet, "127.0.0.1:13420").unwrap_or_else(|e| { + panic!( + "Error creating wallet api listener: {:?} Config: {:?}", + e, wallet_config + ) + }); } - let max_outputs = 500; - let result = wallet::issue_send_tx( - &mut wallet, - amount, - minimum_confirmations, - dest.to_string(), - max_outputs, - selection_strategy == "all", - fluff, - ); - match result { - Ok(_) => info!( - LOGGER, - "Tx sent: {} grin to {} (strategy '{}')", - amount_to_hr_string(amount), - dest, - selection_strategy, - ), - Err(e) => { - error!(LOGGER, "Tx not sent: {}", e.cause()); - match e.downcast::() { - Ok(le) => { - match le.kind() { + _ => {} + }; + } + + // Handle single-use (command line) owner commands + { + let mut wallet = FileWallet::new(wallet_config.clone(), passphrase).unwrap_or_else(|e| { + panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config) + }); + let _res = wallet::controller::owner_single_use(&mut wallet, |api| { + match wallet_args.subcommand() { + ("send", Some(send_args)) => { + let amount = send_args + .value_of("amount") + .expect("Amount to send required"); + let amount = core::core::amount_from_hr_string(amount) + .expect("Could not parse amount as a number with optional decimal point."); + let minimum_confirmations: u64 = send_args + .value_of("minimum_confirmations") + .unwrap() + .parse() + .expect("Could not parse minimum_confirmations as a whole number."); + let selection_strategy = send_args + .value_of("selection_strategy") + .expect("Selection strategy required"); + let dest = send_args + .value_of("dest") + .expect("Destination wallet address required"); + let mut fluff = false; + if send_args.is_present("fluff") { + fluff = true; + } + let max_outputs = 500; + let result = api.issue_send_tx( + amount, + minimum_confirmations, + dest, + max_outputs, + selection_strategy == "all", + fluff, + ); + match result { + Ok(_) => { + info!( + LOGGER, + "Tx sent: {} grin to {} (strategy '{}')", + amount_to_hr_string(amount), + dest, + selection_strategy, + ); + Ok(()) + } + Err(e) => { + error!(LOGGER, "Tx not sent: {:?}", e); + match e.kind() { // user errors, don't backtrace libwallet::ErrorKind::NotEnoughFunds { .. } => {} libwallet::ErrorKind::FeeDispute { .. } => {} libwallet::ErrorKind::FeeExceedsAmount { .. } => {} _ => { // otherwise give full dump - error!(LOGGER, "Backtrace: {}", le.backtrace().unwrap()); + error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap()); } }; + Err(e) } - _ => {} - }; + } } + ("burn", Some(send_args)) => { + let amount = send_args + .value_of("amount") + .expect("Amount to burn required"); + let amount = core::core::amount_from_hr_string(amount) + .expect("Could not parse amount as number with optional decimal point."); + let minimum_confirmations: u64 = send_args + .value_of("minimum_confirmations") + .unwrap() + .parse() + .expect("Could not parse minimum_confirmations as a whole number."); + let max_outputs = 500; + api.issue_burn_tx(amount, minimum_confirmations, max_outputs) + .unwrap_or_else(|e| { + panic!("Error burning tx: {:?} Config: {:?}", e, wallet_config) + }); + Ok(()) + } + ("info", Some(_)) => { + let _res = wallet::display::info(&api.retrieve_summary_info()?.1) + .unwrap_or_else(|e| { + panic!( + "Error getting wallet info: {:?} Config: {:?}", + e, wallet_config + ) + }); + Ok(()) + } + ("outputs", Some(_)) => { + let (height, validated) = api.node_height()?; + let (_, outputs) = api.retrieve_outputs(show_spent)?; + let _res = + wallet::display::outputs(height, validated, outputs).unwrap_or_else(|e| { + panic!( + "Error getting wallet outputs: {:?} Config: {:?}", + e, wallet_config + ) + }); + Ok(()) + } + ("restore", Some(_)) => { + let _res = api.restore().unwrap_or_else(|e| { + panic!( + "Error getting restoring wallet: {:?} Config: {:?}", + e, wallet_config + ) + }); + Ok(()) + } + _ => panic!("Unknown wallet command, use 'grin help wallet' for details"), } - } - ("burn", Some(send_args)) => { - let amount = send_args - .value_of("amount") - .expect("Amount to burn required"); - let amount = core::core::amount_from_hr_string(amount) - .expect("Could not parse amount as number with optional decimal point."); - let minimum_confirmations: u64 = send_args - .value_of("minimum_confirmations") - .unwrap() - .parse() - .expect("Could not parse minimum_confirmations as a whole number."); - let max_outputs = 500; - wallet::issue_burn_tx(&mut wallet, amount, minimum_confirmations, max_outputs).unwrap(); - } - ("info", Some(_)) => { - let res = wallet::show_info(&mut wallet); - if let Err(e) = res { - println!("Could not get wallet info: {}", e); - } - } - ("outputs", Some(_)) => { - wallet::show_outputs(&mut wallet, show_spent); - } - ("restore", Some(_)) => { - let res = wallet.restore(); - if let Err(e) = res { - println!("Could not restore wallet: {}", e); - } - } - _ => panic!("Unknown wallet command, use 'grin help wallet' for details"), + }); } } diff --git a/wallet/src/info.rs b/wallet/src/display.rs similarity index 51% rename from wallet/src/info.rs rename to wallet/src/display.rs index 74e7e01f4..d07ce4718 100644 --- a/wallet/src/info.rs +++ b/wallet/src/display.rs @@ -12,17 +12,70 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::core; use core::core::amount_to_hr_string; use libwallet::Error; -use libwallet::types::WalletBackend; -use libwallet::updater; +use libwallet::types::{OutputData, WalletInfo}; use prettytable; +use std::io::prelude::*; +use term; -pub fn show_info(wallet: &mut T) -> Result<(), Error> -where - T: WalletBackend, -{ - let wallet_info = updater::retrieve_info(wallet)?; +/// Display outputs in a pretty way +pub fn outputs(cur_height: u64, validated: bool, outputs: Vec) -> Result<(), Error> { + let title = format!("Wallet Outputs - Block Height: {}", cur_height); + println!(); + let mut t = term::stdout().unwrap(); + t.fg(term::color::MAGENTA).unwrap(); + writeln!(t, "{}", title).unwrap(); + t.reset().unwrap(); + + let mut table = table!(); + + table.set_titles(row![ + bMG->"Key Id", + bMG->"Block Height", + bMG->"Locked Until", + bMG->"Status", + bMG->"Is Coinbase?", + bMG->"Num. of Confirmations", + bMG->"Value" + ]); + + for out in outputs { + let key_id = format!("{}", out.key_id); + let height = format!("{}", out.height); + let lock_height = format!("{}", out.lock_height); + let status = format!("{:?}", out.status); + let is_coinbase = format!("{}", out.is_coinbase); + let num_confirmations = format!("{}", out.num_confirmations(cur_height)); + let value = format!("{}", core::amount_to_hr_string(out.value)); + table.add_row(row![ + bFC->key_id, + bFB->height, + bFB->lock_height, + bFR->status, + bFY->is_coinbase, + bFB->num_confirmations, + bFG->value + ]); + } + + table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP); + table.printstd(); + println!(); + + if !validated { + println!( + "\nWARNING: Wallet failed to verify data. \ + The above is from local cache and possibly invalid! \ + (is your `grin server` offline or broken?)" + ); + } + Ok(()) +} + +/// Display summary info in a pretty way +pub fn info(wallet_info: &WalletInfo) -> Result<(), Error> { println!( "\n____ Wallet Summary Info at {} ({}) ____\n", wallet_info.current_height, wallet_info.data_confirmed_from diff --git a/wallet/src/file_wallet.rs b/wallet/src/file_wallet.rs index 183c00ba8..52300149b 100644 --- a/wallet/src/file_wallet.rs +++ b/wallet/src/file_wallet.rs @@ -26,7 +26,7 @@ use tokio_core::reactor; use tokio_retry::Retry; use tokio_retry::strategy::FibonacciBackoff; -use failure::{self, ResultExt}; +use failure::ResultExt; use keychain::{self, Keychain}; use util; @@ -166,9 +166,11 @@ impl WalletSeed { #[derive(Debug, Clone)] pub struct FileWallet { /// Keychain - pub keychain: Keychain, + pub keychain: Option, /// Configuration pub config: WalletConfig, + /// passphrase: TODO better ways of dealing with this other than storing + passphrase: String, /// List of outputs pub outputs: HashMap, /// Data file path @@ -180,9 +182,27 @@ pub struct FileWallet { } impl WalletBackend for FileWallet { + /// Initialise with whatever stored credentials we have + fn open_with_credentials(&mut self) -> Result<(), libwallet::Error> { + let wallet_seed = WalletSeed::from_file(&self.config) + .context(libwallet::ErrorKind::CallbackImpl("Error opening wallet"))?; + self.keychain = Some(wallet_seed.derive_keychain(&self.passphrase).context( + libwallet::ErrorKind::CallbackImpl("Error deriving keychain"), + )?); + // Just blow up password for now after it's been used + self.passphrase = String::from(""); + Ok(()) + } + + /// Close wallet and remove any stored credentials (TBD) + fn close(&mut self) -> Result<(), libwallet::Error> { + self.keychain = None; + Ok(()) + } + /// Return the keychain being used fn keychain(&mut self) -> &mut Keychain { - &mut self.keychain + self.keychain.as_mut().unwrap() } /// Return URL for check node @@ -371,14 +391,21 @@ impl WalletBackend for FileWallet { eligible.reverse(); eligible.iter().take(max_outputs).cloned().collect() } + + /// Restore wallet contents + fn restore(&mut self) -> Result<(), libwallet::Error> { + libwallet::internal::restore::restore(self).context(libwallet::ErrorKind::Restore)?; + Ok(()) + } } impl FileWallet { /// Create a new FileWallet instance - pub fn new(config: WalletConfig, keychain: Keychain) -> Result { + pub fn new(config: WalletConfig, passphrase: &str) -> Result { let mut retval = FileWallet { - keychain: keychain, + keychain: None, config: config.clone(), + passphrase: String::from(passphrase), outputs: HashMap::new(), data_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, DAT_FILE), backup_file_path: format!("{}{}{}", config.data_file_dir, MAIN_SEPARATOR, BCK_FILE), @@ -390,12 +417,6 @@ impl FileWallet { } } - /// Restore wallet contents - pub fn restore(&mut self) -> Result<(), failure::Error> { - libwallet::restore::restore(self).context(libwallet::ErrorKind::Restore)?; - Ok(()) - } - /// Read the wallet data or create brand files if the data /// files don't yet exist fn read_or_create_paths(&mut self) -> Result<(), Error> { diff --git a/wallet/src/handlers.rs b/wallet/src/handlers.rs deleted file mode 100644 index 082148be8..000000000 --- a/wallet/src/handlers.rs +++ /dev/null @@ -1,82 +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 std::sync::{Arc, RwLock}; - -use bodyparser; -use iron::Handler; -use iron::prelude::*; -use iron::status; -use serde_json; - -use core::ser; -use error::{Error, ErrorKind}; -use failure::{Fail, ResultExt}; -use libwallet::types::*; -use receiver::receive_coinbase; -use util; - -pub struct CoinbaseHandler -where - T: WalletBackend, -{ - pub wallet: Arc>, -} - -impl CoinbaseHandler -where - T: WalletBackend, -{ - fn build_coinbase(&self, wallet: &mut T, block_fees: &BlockFees) -> Result { - let (out, kern, block_fees) = - receive_coinbase(wallet, block_fees).context(ErrorKind::Node)?; - - let out_bin = ser::ser_vec(&out).context(ErrorKind::Node)?; - - let kern_bin = ser::ser_vec(&kern).context(ErrorKind::Node)?; - - let key_id_bin = match block_fees.key_id { - Some(key_id) => ser::ser_vec(&key_id).context(ErrorKind::Node)?, - None => vec![], - }; - - Ok(CbData { - output: util::to_hex(out_bin), - kernel: util::to_hex(kern_bin), - key_id: util::to_hex(key_id_bin), - }) - } -} - -// TODO - error handling - what to return if we fail to get the wallet lock for -// some reason... -impl Handler for CoinbaseHandler -where - T: WalletBackend + Send + Sync + 'static, -{ - fn handle(&self, req: &mut Request) -> IronResult { - let struct_body = req.get::>(); - let mut wallet = self.wallet.write().unwrap(); - if let Ok(Some(block_fees)) = struct_body { - let coinbase = self.build_coinbase(&mut wallet, &block_fees) - .map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?; - if let Ok(json) = serde_json::to_string(&coinbase) { - Ok(Response::with((status::Ok, json))) - } else { - Ok(Response::with((status::BadRequest, ""))) - } - } else { - Ok(Response::with((status::BadRequest, ""))) - } - } -} diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 6fb02e7a0..8df123479 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -46,22 +46,13 @@ extern crate grin_core as core; extern crate grin_keychain as keychain; extern crate grin_util as util; -pub mod client; +pub mod display; mod error; pub mod file_wallet; -mod handlers; -mod info; pub mod libtx; pub mod libwallet; -mod outputs; -pub mod receiver; -mod sender; -pub mod server; pub use error::{Error, ErrorKind}; pub use file_wallet::{FileWallet, WalletConfig, WalletSeed}; -pub use info::show_info; +pub use libwallet::controller; pub use libwallet::types::{BlockFees, CbData, WalletInfo}; -pub use outputs::show_outputs; -pub use receiver::WalletReceiver; -pub use sender::{issue_burn_tx, issue_send_tx}; diff --git a/wallet/src/libtx/slate.rs b/wallet/src/libtx/slate.rs index 69f6e3fe7..752e7b482 100644 --- a/wallet/src/libtx/slate.rs +++ b/wallet/src/libtx/slate.rs @@ -24,8 +24,8 @@ use keychain::{BlindSum, BlindingFactor, Keychain}; use libtx::error::{Error, ErrorKind}; use libtx::{aggsig, build, tx_fee}; -use util::secp::key::{PublicKey, SecretKey}; use util::secp::Signature; +use util::secp::key::{PublicKey, SecretKey}; use util::{secp, LOGGER}; /// Public data for each participant in the slate diff --git a/wallet/src/libwallet/api.rs b/wallet/src/libwallet/api.rs new file mode 100644 index 000000000..9b1961a66 --- /dev/null +++ b/wallet/src/libwallet/api.rs @@ -0,0 +1,154 @@ +// 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. + +//! Wrappers around library functions, intended to split functions +//! into external and internal APIs (i.e. functions for the local wallet +//! vs. functions to interact with someone else) +//! Still experimental, not sure this is the best way to do this + +use libtx::slate::Slate; +use libwallet::Error; +use libwallet::internal::{tx, updater}; +use libwallet::types::{BlockFees, CbData, OutputData, WalletBackend, WalletInfo}; + +/// Wrapper around internal API functions, containing a reference to +/// the wallet/keychain that they're acting upon +pub struct APIOwner<'a, W> +where + W: 'a + WalletBackend, +{ + /// Wallet, contains its keychain (TODO: Split these up into 2 traits + /// perhaps) + pub wallet: &'a mut W, +} + +impl<'a, W> APIOwner<'a, W> +where + W: 'a + WalletBackend, +{ + /// Create new API instance + pub fn new(wallet_in: &'a mut W) -> APIOwner<'a, W> { + APIOwner { wallet: wallet_in } + } + + /// Attempt to update and retrieve outputs + /// Return (whether the outputs were validated against a node, OutputData) + pub fn retrieve_outputs( + &mut self, + include_spent: bool, + ) -> Result<(bool, Vec), Error> { + let validated = self.update_outputs(); + Ok(( + validated, + updater::retrieve_outputs(self.wallet, include_spent)?, + )) + } + + /// Retrieve summary info for wallet + pub fn retrieve_summary_info(&mut self) -> Result<(bool, WalletInfo), Error> { + let validated = self.update_outputs(); + Ok((validated, updater::retrieve_info(self.wallet)?)) + } + + /// Issues a send transaction and sends to recipient + /// (TODO: Split into separate functions, create tx, send, complete tx) + pub fn issue_send_tx( + &mut self, + amount: u64, + minimum_confirmations: u64, + dest: &str, + max_outputs: usize, + selection_strategy_is_use_all: bool, + fluff: bool, + ) -> Result<(), Error> { + tx::issue_send_tx( + self.wallet, + amount, + minimum_confirmations, + dest, + max_outputs, + selection_strategy_is_use_all, + fluff, + ) + } + + /// Issue a burn TX + pub fn issue_burn_tx( + &mut self, + amount: u64, + minimum_confirmations: u64, + max_outputs: usize, + ) -> Result<(), Error> { + tx::issue_burn_tx(self.wallet, amount, minimum_confirmations, max_outputs) + } + + /// Attempt to restore contents of wallet + pub fn restore(&mut self) -> Result<(), Error> { + self.wallet.restore() + } + + /// Retrieve current height from node + pub fn node_height(&mut self) -> Result<(u64, bool), Error> { + match updater::get_tip_from_node(self.wallet.node_url()) { + Ok(tip) => Ok((tip.height, true)), + Err(_) => { + let outputs = self.retrieve_outputs(true)?; + let height = match outputs.1.iter().map(|out| out.height).max() { + Some(height) => height, + None => 0, + }; + Ok((height, false)) + } + } + } + + /// Attempt to update outputs in wallet, return whether it was successful + fn update_outputs(&mut self) -> bool { + match updater::refresh_outputs(self.wallet) { + Ok(_) => true, + Err(_) => false, + } + } +} + +/// Wrapper around external API functions, intended to communicate +/// with other parties +pub struct APIForeign<'a, W> +where + W: 'a + WalletBackend, +{ + /// Wallet, contains its keychain (TODO: Split these up into 2 traits + /// perhaps) + pub wallet: &'a mut W, +} + +impl<'a, W> APIForeign<'a, W> +where + W: 'a + WalletBackend, +{ + /// Create new API instance + pub fn new(wallet_in: &'a mut W) -> APIForeign<'a, W> { + APIForeign { wallet: wallet_in } + } + + /// Build a new (potential) coinbase transaction in the wallet + pub fn build_coinbase(&mut self, block_fees: &BlockFees) -> Result { + updater::build_coinbase(self.wallet, block_fees) + } + + /// Receive a transaction from a sender + pub fn receive_tx(&mut self, slate: &mut Slate) -> Result<(), Error> { + tx::receive_tx(self.wallet, slate) + } +} diff --git a/wallet/src/client.rs b/wallet/src/libwallet/client.rs similarity index 87% rename from wallet/src/client.rs rename to wallet/src/libwallet/client.rs index b46be74c5..fc93e4e0e 100644 --- a/wallet/src/client.rs +++ b/wallet/src/libwallet/client.rs @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Client functions: TODO: doesn't really belong here or needs to be +//! traited out + use failure::ResultExt; use futures::{Future, Stream}; use hyper; @@ -33,14 +36,17 @@ pub fn create_coinbase(url: &str, block_fees: &BlockFees) -> Result { error!( LOGGER, - "Failed to get coinbase from {}. Run grin wallet listen", url + "Failed to get coinbase from {}. Run grin wallet listen?", url ); + error!(LOGGER, "Underlying Error: {}", e.cause().unwrap()); + error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap()); Err(e) } Ok(res) => Ok(res), } } +/// Send the slate to a listening wallet instance pub fn send_slate(url: &str, slate: &Slate, fluff: bool) -> Result { let mut core = reactor::Core::new().context(ErrorKind::Hyper)?; let client = hyper::Client::new(&core.handle()); @@ -82,10 +88,12 @@ fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result(wallet: &mut T, f: F) -> Result<(), Error> +where + T: WalletBackend, + F: FnOnce(&mut APIOwner) -> Result<(), Error>, +{ + wallet.open_with_credentials()?; + f(&mut APIOwner::new(wallet))?; + wallet.close()?; + Ok(()) +} + +/// Instantiate wallet Foreign API for a single-use (command line) call +/// Return a function containing a loaded API context to call +pub fn foreign_single_use(wallet: &mut T, f: F) -> Result<(), Error> +where + T: WalletBackend, + F: FnOnce(&mut APIForeign) -> Result<(), Error>, +{ + wallet.open_with_credentials()?; + f(&mut APIForeign::new(wallet))?; + wallet.close()?; + Ok(()) +} + +/// Listener version, providing same API but listening for requests on a +/// port and wrapping the calls +pub fn owner_listener(wallet: T, addr: &str) -> Result<(), Error> +where + T: WalletBackend, + OwnerAPIHandler: Handler, +{ + let api_handler = OwnerAPIHandler { + wallet: Arc::new(Mutex::new(wallet)), + }; + + let router = router!( + receive_tx: get "/wallet/owner/*" => api_handler, + ); + + let mut apis = ApiServer::new("/v1".to_string()); + apis.register_handler(router); + match apis.start(addr) { + Err(e) => error!( + LOGGER, + "Failed to start Grin wallet owner API listener: {}.", e + ), + Ok(_) => info!(LOGGER, "Grin wallet owner API listener started at {}", addr), + }; + Ok(()) +} + +/// Listener version, providing same API but listening for requests on a +/// port and wrapping the calls +pub fn foreign_listener(wallet: T, addr: &str) -> Result<(), Error> +where + T: WalletBackend, + ForeignAPIHandler: Handler, +{ + let api_handler = ForeignAPIHandler { + wallet: Arc::new(Mutex::new(wallet)), + }; + + let router = router!( + receive_tx: post "/wallet/foreign/*" => api_handler, + ); + + let mut apis = ApiServer::new("/v1".to_string()); + apis.register_handler(router); + match apis.start(addr) { + Err(e) => error!( + LOGGER, + "Failed to start Grin wallet foreign listener: {}.", e + ), + Ok(_) => info!(LOGGER, "Grin wallet foreign listener started at {}", addr), + }; + Ok(()) +} +/// API Handler/Wrapper for owner functions + +pub struct OwnerAPIHandler +where + T: WalletBackend, +{ + /// Wallet instance + pub wallet: Arc>, +} + +impl OwnerAPIHandler +where + T: WalletBackend, +{ + fn retrieve_outputs( + &self, + req: &mut Request, + api: &mut APIOwner, + ) -> Result, Error> { + let res = api.retrieve_outputs(false)?; + Ok(res.1) + } + + fn retrieve_summary_info( + &self, + req: &mut Request, + api: &mut APIOwner, + ) -> Result { + let res = api.retrieve_summary_info()?; + Ok(res.1) + } + + fn issue_send_tx(&self, req: &mut Request, api: &mut APIOwner) -> Result<(), Error> { + // TODO: Args + api.issue_send_tx(60, 10, "", 1000, true, true) + } + + fn issue_burn_tx(&self, req: &mut Request, api: &mut APIOwner) -> Result<(), Error> { + // TODO: Args + api.issue_burn_tx(60, 10, 1000) + } + + fn handle_request(&self, req: &mut Request, api: &mut APIOwner) -> IronResult { + let url = req.url.clone(); + let path_elems = url.path(); + match *path_elems.last().unwrap() { + "retrieve_outputs" => json_response_pretty(&self.retrieve_outputs(req, api) + .map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?), + "retrieve_summary_info" => json_response_pretty(&self.retrieve_summary_info(req, api) + .map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?), + "issue_send_tx" => json_response_pretty(&self.issue_send_tx(req, api) + .map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?), + "issue_burn_tx" => json_response_pretty(&self.issue_burn_tx(req, api) + .map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?), + _ => Err(IronError::new( + Fail::compat(ErrorKind::Hyper), + status::BadRequest, + )), + } + } +} + +impl Handler for OwnerAPIHandler +where + T: WalletBackend + Send + Sync + 'static, +{ + fn handle(&self, req: &mut Request) -> IronResult { + // every request should open with stored credentials, + // do its thing and then de-init whatever secrets have been + // stored + let mut wallet = self.wallet.lock().unwrap(); + wallet.open_with_credentials().map_err(|e| { + error!(LOGGER, "Error opening wallet: {:?}", e); + IronError::new(Fail::compat(e), status::BadRequest) + })?; + let mut api = APIOwner::new(&mut *wallet); + let resp_json = self.handle_request(req, &mut api); + api.wallet + .close() + .map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?; + resp_json + } +} + +/// API Handler/Wrapper for foreign functions + +pub struct ForeignAPIHandler +where + T: WalletBackend, +{ + /// Wallet instance + pub wallet: Arc>, +} + +impl ForeignAPIHandler +where + T: WalletBackend, +{ + fn build_coinbase(&self, req: &mut Request, api: &mut APIForeign) -> Result { + let struct_body = req.get::>(); + match struct_body { + Ok(Some(block_fees)) => api.build_coinbase(&block_fees), + Ok(None) => { + error!(LOGGER, "Missing request body: build_coinbase"); + Err(ErrorKind::GenericError( + "Invalid request body: build_coinbase", + ))? + } + Err(e) => { + error!(LOGGER, "Invalid request body: build_coinbase: {:?}", e); + Err(ErrorKind::GenericError( + "Invalid request body: build_coinbase", + ))? + } + } + } + + fn receive_tx(&self, req: &mut Request, api: &mut APIForeign) -> Result { + let struct_body = req.get::>(); + if let Ok(Some(mut slate)) = struct_body { + api.receive_tx(&mut slate)?; + Ok(slate.clone()) + } else { + Err(ErrorKind::GenericError("Invalid request body: receive_tx"))? + } + } + + fn handle_request(&self, req: &mut Request, api: &mut APIForeign) -> IronResult { + let url = req.url.clone(); + let path_elems = url.path(); + match *path_elems.last().unwrap() { + "build_coinbase" => json_response(&self.build_coinbase(req, api) + .map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?), + "receive_tx" => json_response(&self.receive_tx(req, api) + .map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?), + _ => Err(IronError::new( + Fail::compat(ErrorKind::Hyper), + status::BadRequest, + )), + } + } +} + +impl Handler for ForeignAPIHandler +where + T: WalletBackend + Send + Sync + 'static, +{ + fn handle(&self, req: &mut Request) -> IronResult { + // every request should open with stored credentials, + // do its thing and then de-init whatever secrets have been + // stored + let mut wallet = self.wallet.lock().unwrap(); + wallet.open_with_credentials().map_err(|e| { + error!(LOGGER, "Error opening wallet: {:?}", e); + IronError::new(Fail::compat(e), status::BadRequest) + })?; + let mut api = APIForeign::new(&mut *wallet); + let resp_json = self.handle_request(req, &mut api); + api.wallet + .close() + .map_err(|e| IronError::new(Fail::compat(e), status::BadRequest))?; + resp_json + } +} + +// Utility to serialize a struct into JSON and produce a sensible IronResult +// out of it. +fn json_response(s: &T) -> IronResult +where + T: Serialize, +{ + match serde_json::to_string(s) { + Ok(json) => Ok(Response::with((status::Ok, json))), + Err(_) => Ok(Response::with((status::InternalServerError, ""))), + } +} + +// pretty-printed version of above +fn json_response_pretty(s: &T) -> IronResult +where + T: Serialize, +{ + match serde_json::to_string_pretty(s) { + Ok(json) => Ok(Response::with((status::Ok, json))), + Err(_) => Ok(Response::with((status::InternalServerError, ""))), + } +} diff --git a/wallet/src/libwallet/error.rs b/wallet/src/libwallet/error.rs index 2e0ec4ba5..fcb86e837 100644 --- a/wallet/src/libwallet/error.rs +++ b/wallet/src/libwallet/error.rs @@ -74,7 +74,7 @@ pub enum ErrorKind { Secp, /// Callback implementation error conversion - #[fail(display = "Callback Implementation error")] + #[fail(display = "Trait Implementation error")] CallbackImpl(&'static str), /// Callback implementation error conversion diff --git a/wallet/src/libwallet/keys.rs b/wallet/src/libwallet/internal/keys.rs similarity index 100% rename from wallet/src/libwallet/keys.rs rename to wallet/src/libwallet/internal/keys.rs diff --git a/wallet/src/libwallet/internal/mod.rs b/wallet/src/libwallet/internal/mod.rs new file mode 100644 index 000000000..46c20f834 --- /dev/null +++ b/wallet/src/libwallet/internal/mod.rs @@ -0,0 +1,29 @@ +// 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. + +//! lower-level wallet functions which build upon libtx to perform wallet +//! operations + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +pub mod keys; +pub mod restore; +pub mod selection; +pub mod sigcontext; +pub mod tx; +pub mod updater; diff --git a/wallet/src/libwallet/restore.rs b/wallet/src/libwallet/internal/restore.rs similarity index 100% rename from wallet/src/libwallet/restore.rs rename to wallet/src/libwallet/internal/restore.rs diff --git a/wallet/src/libwallet/selection.rs b/wallet/src/libwallet/internal/selection.rs similarity index 99% rename from wallet/src/libwallet/selection.rs rename to wallet/src/libwallet/internal/selection.rs index 37a5bb84a..fc7413885 100644 --- a/wallet/src/libwallet/selection.rs +++ b/wallet/src/libwallet/internal/selection.rs @@ -17,8 +17,8 @@ use keychain::Identifier; use libtx::{build, tx_fee, slate::Slate}; use libwallet::error::{Error, ErrorKind}; +use libwallet::internal::{keys, sigcontext}; use libwallet::types::*; -use libwallet::{keys, sigcontext}; /// Initialise a transaction on the sender side, returns a corresponding /// libwallet transaction slate with the appropriate inputs selected, diff --git a/wallet/src/libwallet/sigcontext.rs b/wallet/src/libwallet/internal/sigcontext.rs similarity index 100% rename from wallet/src/libwallet/sigcontext.rs rename to wallet/src/libwallet/internal/sigcontext.rs diff --git a/wallet/src/sender.rs b/wallet/src/libwallet/internal/tx.rs similarity index 81% rename from wallet/src/sender.rs rename to wallet/src/libwallet/internal/tx.rs index 004efc5bc..5243715a4 100644 --- a/wallet/src/sender.rs +++ b/wallet/src/libwallet/internal/tx.rs @@ -12,32 +12,60 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Transaction buinding functions + use api; -use client; use core::ser; -use error::{Error, ErrorKind}; -use failure::{self, ResultExt}; +use failure::ResultExt; use keychain::{Identifier, Keychain}; +use libtx::slate::Slate; use libtx::{build, tx_fee}; -use libwallet::types::WalletBackend; -use libwallet::{selection, updater}; -use receiver::TxWrapper; +use libwallet::client; +use libwallet::internal::{selection, updater}; +use libwallet::types::{TxWrapper, WalletBackend}; +use libwallet::{Error, ErrorKind}; use util; use util::LOGGER; +/// Receive a tranaction, modifying the slate accordingly (which can then be +/// sent back to sender for posting) +pub fn receive_tx(wallet: &mut T, slate: &mut Slate) -> Result<(), Error> { + // create an output using the amount in the slate + let (_, mut context, receiver_create_fn) = + selection::build_recipient_output_with_slate(wallet, slate).unwrap(); + + // fill public keys + let _ = slate.fill_round_1( + wallet.keychain(), + &mut context.sec_key, + &context.sec_nonce, + 1, + )?; + + // perform partial sig + let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?; + + // Save output in wallet + let _ = receiver_create_fn(wallet); + + Ok(()) +} + /// Issue a new transaction to the provided sender by spending some of our /// wallet /// Outputs. The destination can be "stdout" (for command line) (currently /// disabled) or a URL to the recipients wallet receiver (to be implemented). +/// TBD: this just does a straight http request to recipient.. split this out +/// somehow pub fn issue_send_tx( wallet: &mut T, amount: u64, minimum_confirmations: u64, - dest: String, + dest: &str, max_outputs: usize, selection_strategy_is_use_all: bool, fluff: bool, -) -> Result<(), failure::Error> { +) -> Result<(), Error> { // TODO: Stdout option, probably in a separate implementation if &dest[..4] != "http" { panic!( @@ -84,9 +112,9 @@ pub fn issue_send_tx( 0, )?; - let url = format!("{}/v1/receive/transaction", &dest); + let url = format!("{}/v1/wallet/foreign/receive_tx", dest); debug!(LOGGER, "Posting partial transaction to {}", url); - let mut slate = match client::send_slate(&url, &slate, fluff) { + let mut slate = match client::send_slate(&url, &slate, fluff).context(ErrorKind::Node) { Ok(s) => s, Err(e) => { error!( @@ -117,6 +145,7 @@ pub fn issue_send_tx( Ok(()) } +/// Issue a burn tx pub fn issue_burn_tx( wallet: &mut T, amount: u64, diff --git a/wallet/src/libwallet/updater.rs b/wallet/src/libwallet/internal/updater.rs similarity index 76% rename from wallet/src/libwallet/updater.rs rename to wallet/src/libwallet/internal/updater.rs index f857c000e..a7387614a 100644 --- a/wallet/src/libwallet/updater.rs +++ b/wallet/src/libwallet/internal/updater.rs @@ -20,13 +20,51 @@ use std::collections::HashMap; use std::collections::hash_map::Entry; use api; +use core::consensus::reward; +use core::core::{Output, TxKernel}; +use core::global; +use core::ser; use keychain::Identifier; +use libtx::reward; use libwallet::error::{Error, ErrorKind}; +use libwallet::internal::keys; use libwallet::types::*; use util; use util::LOGGER; use util::secp::pedersen; +/// Retrieve all of the outputs (doesn't attempt to update from node) +pub fn retrieve_outputs( + wallet: &mut T, + show_spent: bool, +) -> Result, Error> { + let root_key_id = wallet.keychain().clone().root_key_id(); + + let mut outputs = vec![]; + + // just read the wallet here, no need for a write lock + let _ = wallet.read_wallet(|wallet_data| { + outputs = wallet_data + .outputs() + .values() + .filter(|out| out.root_key_id == root_key_id) + .filter(|out| { + if show_spent { + true + } else { + out.status != OutputStatus::Spent + } + }) + .collect::>() + .iter() + .map(|&o| o.clone()) + .collect(); + outputs.sort_by_key(|out| out.n_child); + Ok(()) + }); + Ok(outputs) +} + /// Refreshes the outputs in a wallet with the latest information /// from a node pub fn refresh_outputs(wallet: &mut T) -> Result<(), Error> @@ -314,3 +352,87 @@ where }); ret_val } + +/// Build a coinbase output and insert into wallet +pub fn build_coinbase(wallet: &mut T, block_fees: &BlockFees) -> Result +where + T: WalletBackend, +{ + let (out, kern, block_fees) = receive_coinbase(wallet, block_fees).context(ErrorKind::Node)?; + + let out_bin = ser::ser_vec(&out).context(ErrorKind::Node)?; + + let kern_bin = ser::ser_vec(&kern).context(ErrorKind::Node)?; + + let key_id_bin = match block_fees.key_id { + Some(key_id) => ser::ser_vec(&key_id).context(ErrorKind::Node)?, + None => vec![], + }; + + Ok(CbData { + output: util::to_hex(out_bin), + kernel: util::to_hex(kern_bin), + key_id: util::to_hex(key_id_bin), + }) +} + +//TODO: Split up the output creation and the wallet insertion +/// Build a coinbase output and the corresponding kernel +pub fn receive_coinbase( + wallet: &mut T, + block_fees: &BlockFees, +) -> Result<(Output, TxKernel, BlockFees), Error> +where + T: WalletBackend, +{ + let root_key_id = wallet.keychain().root_key_id(); + + let height = block_fees.height; + let lock_height = height + global::coinbase_maturity(); + + // Now acquire the wallet lock and write the new output. + let (key_id, derivation) = wallet.with_wallet(|wallet_data| { + let key_id = block_fees.key_id(); + let (key_id, derivation) = match key_id { + Some(key_id) => keys::retrieve_existing_key(wallet_data, key_id), + None => keys::next_available_key(wallet_data), + }; + + // track the new output and return the stuff needed for reward + wallet_data.add_output(OutputData { + root_key_id: root_key_id.clone(), + key_id: key_id.clone(), + n_child: derivation, + value: reward(block_fees.fees), + status: OutputStatus::Unconfirmed, + height: height, + lock_height: lock_height, + is_coinbase: true, + block: None, + merkle_proof: None, + }); + + (key_id, derivation) + })?; + + debug!( + LOGGER, + "receive_coinbase: built candidate output - {:?}, {}", + key_id.clone(), + derivation, + ); + + let mut block_fees = block_fees.clone(); + block_fees.key_id = Some(key_id.clone()); + + debug!(LOGGER, "receive_coinbase: {:?}", block_fees); + + let (out, kern) = reward::output( + &wallet.keychain(), + &key_id, + block_fees.fees, + block_fees.height, + ).unwrap(); + /* .context(ErrorKind::Keychain)?; */ + Ok((out, kern, block_fees)) +} diff --git a/wallet/src/libwallet/mod.rs b/wallet/src/libwallet/mod.rs index 7a69390ff..b9ca215ca 100644 --- a/wallet/src/libwallet/mod.rs +++ b/wallet/src/libwallet/mod.rs @@ -12,12 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Library specific to the Grin wallet implementation, as distinct from -//! libwallet, which should build transactions without any knowledge of the -//! wallet implementation. - -// TODO: Once this is working, extract a set of traits that wallet -// implementations would need to provide +//! Higher level wallet functions which can be used by callers to operate +//! on the wallet, as well as helpers to invoke and instantiate wallets +//! and listeners #![deny(non_upper_case_globals)] #![deny(non_camel_case_types)] @@ -25,12 +22,11 @@ #![deny(unused_mut)] #![warn(missing_docs)] +pub mod api; +pub mod client; +pub mod controller; mod error; -pub mod keys; -pub mod restore; -pub mod selection; -pub mod sigcontext; +pub mod internal; pub mod types; -pub mod updater; pub use libwallet::error::{Error, ErrorKind}; diff --git a/wallet/src/libwallet/reward.rs b/wallet/src/libwallet/reward.rs deleted file mode 100644 index 7c9629699..000000000 --- a/wallet/src/libwallet/reward.rs +++ /dev/null @@ -1,79 +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. - -//! Builds the blinded output and related signature proof for the block -//! reward. -use keychain; - -use core::consensus::reward; -use core::core::KernelFeatures; -use core::core::{Output, OutputFeatures, ProofMessageElements, TxKernel}; -use libtx::error::Error; -use libtx::{aggsig, proof}; -use util::{kernel_sig_msg, secp, static_secp_instance, LOGGER}; - -/// output a reward output -pub fn output( - keychain: &keychain::Keychain, - key_id: &keychain::Identifier, - fees: u64, - height: u64, -) -> Result<(Output, TxKernel), Error> { - let value = reward(fees); - let commit = keychain.commit(value, key_id)?; - let msg = ProofMessageElements::new(value, key_id); - - trace!(LOGGER, "Block reward - Pedersen Commit is: {:?}", commit,); - - let rproof = proof::create( - keychain, - value, - key_id, - commit, - None, - msg.to_proof_message(), - )?; - - let output = Output { - features: OutputFeatures::COINBASE_OUTPUT, - commit: commit, - proof: rproof, - }; - - let secp = static_secp_instance(); - let secp = secp.lock().unwrap(); - let over_commit = secp.commit_value(reward(fees))?; - let out_commit = output.commitment(); - let excess = secp.commit_sum(vec![out_commit], vec![over_commit])?; - - // NOTE: Remember we sign the fee *and* the lock_height. - // For a coinbase output the fee is 0 and the lock_height is - // the lock_height of the coinbase output itself, - // not the lock_height of the tx (there is no tx for a coinbase output). - // This output will not be spendable earlier than lock_height (and we sign this - // here). - let msg = secp::Message::from_slice(&kernel_sig_msg(0, height))?; - let sig = aggsig::sign_from_key_id(&secp, keychain, &msg, &key_id)?; - - let proof = TxKernel { - features: KernelFeatures::COINBASE_KERNEL, - excess: excess, - excess_sig: sig, - fee: 0, - // lock_height here is the height of the block (tx should be valid immediately) - // *not* the lock_height of the coinbase output (only spendable 1,000 blocks later) - lock_height: height, - }; - Ok((output, proof)) -} diff --git a/wallet/src/libwallet/types.rs b/wallet/src/libwallet/types.rs index 12d6238b6..65840e0b9 100644 --- a/wallet/src/libwallet/types.rs +++ b/wallet/src/libwallet/types.rs @@ -33,6 +33,12 @@ use libwallet::error::{Error, ErrorKind}; /// here expect that the wallet instance has instantiated itself or stored /// whatever credentials it needs pub trait WalletBackend { + /// Initialise with whatever stored credentials we have + fn open_with_credentials(&mut self) -> Result<(), Error>; + + /// Close wallet and remove any stored credentials (TBD) + fn close(&mut self) -> Result<(), Error>; + /// Return the keychain being used fn keychain(&mut self) -> &mut Keychain; @@ -79,6 +85,9 @@ pub trait WalletBackend { max_outputs: usize, select_all: bool, ) -> Vec; + + /// Attempt to restore the contents of a wallet from seed + fn restore(&mut self) -> Result<(), Error>; } /// Information about an output that's being tracked by the wallet. Must be @@ -354,3 +363,10 @@ pub struct WalletInfo { /// node confirming the data pub data_confirmed_from: String, } + +/// Dummy wrapper for the hex-encoded serialized transaction. +#[derive(Serialize, Deserialize)] +pub struct TxWrapper { + /// hex representation of transaction + pub tx_hex: String, +} diff --git a/wallet/src/outputs.rs b/wallet/src/outputs.rs deleted file mode 100644 index 95c857cb4..000000000 --- a/wallet/src/outputs.rs +++ /dev/null @@ -1,103 +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 core::core; -use libwallet::types::{OutputStatus, WalletBackend}; -use libwallet::updater; -use prettytable; -use std::io::prelude::*; -use term; - -pub fn show_outputs(wallet: &mut T, show_spent: bool) { - let root_key_id = wallet.keychain().clone().root_key_id(); - let result = updater::refresh_outputs(wallet); - - // just read the wallet here, no need for a write lock - let _ = wallet.read_wallet(|wallet_data| { - // get the current height via the api - // if we cannot get the current height use the max height known to the wallet - let current_height = match updater::get_tip_from_node(wallet_data.node_url()) { - Ok(tip) => tip.height, - Err(_) => match wallet_data.outputs().values().map(|out| out.height).max() { - Some(height) => height, - None => 0, - }, - }; - - let mut outputs = wallet_data - .outputs() - .values() - .filter(|out| out.root_key_id == root_key_id) - .filter(|out| { - if show_spent { - true - } else { - out.status != OutputStatus::Spent - } - }) - .collect::>(); - outputs.sort_by_key(|out| out.n_child); - - let title = format!("Wallet Outputs - Block Height: {}", current_height); - println!(); - let mut t = term::stdout().unwrap(); - t.fg(term::color::MAGENTA).unwrap(); - writeln!(t, "{}", title).unwrap(); - t.reset().unwrap(); - - let mut table = table!(); - - table.set_titles(row![ - bMG->"Key Id", - bMG->"Block Height", - bMG->"Locked Until", - bMG->"Status", - bMG->"Is Coinbase?", - bMG->"Num. of Confirmations", - bMG->"Value" - ]); - - for out in outputs { - let key_id = format!("{}", out.key_id); - let height = format!("{}", out.height); - let lock_height = format!("{}", out.lock_height); - let status = format!("{:?}", out.status); - let is_coinbase = format!("{}", out.is_coinbase); - let num_confirmations = format!("{}", out.num_confirmations(current_height)); - let value = format!("{}", core::amount_to_hr_string(out.value)); - table.add_row(row![ - bFC->key_id, - bFB->height, - bFB->lock_height, - bFR->status, - bFY->is_coinbase, - bFB->num_confirmations, - bFG->value - ]); - } - - table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP); - table.printstd(); - println!(); - Ok(()) - }); - - if let Err(_) = result { - println!( - "\nWARNING: Wallet failed to verify data. \ - The above is from local cache and possibly invalid! \ - (is your `grin server` offline or broken?)" - ); - } -} diff --git a/wallet/src/receiver.rs b/wallet/src/receiver.rs deleted file mode 100644 index 6ac391b88..000000000 --- a/wallet/src/receiver.rs +++ /dev/null @@ -1,168 +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. - -//! Provides the JSON/HTTP API for wallets to receive payments. Because -//! receiving money in MimbleWimble requires an interactive exchange, a -//! wallet server that's running at all time is required in many cases. - -use std::sync::{Arc, RwLock}; - -use bodyparser; -use iron::Handler; -use iron::prelude::*; -use iron::status; -use serde_json; - -use api; -use core::consensus::reward; -use core::core::{Output, TxKernel}; -use core::global; -use error::Error; -use failure::Fail; -use libtx::{reward, slate::Slate}; -use libwallet::types::*; -use libwallet::{keys, selection}; -use util::LOGGER; - -/// Dummy wrapper for the hex-encoded serialized transaction. -#[derive(Serialize, Deserialize)] -pub struct TxWrapper { - pub tx_hex: String, -} - -/// Component used to receive coins, implements all the receiving end of the -/// wallet REST API as well as some of the command-line operations. -#[derive(Clone)] -pub struct WalletReceiver -where - T: WalletBackend, -{ - pub wallet: Arc>, -} - -impl WalletReceiver -where - T: WalletBackend, -{ - fn handle_send(&self, wallet: &mut T, slate: &mut Slate) -> Result<(), Error> { - // create an output using the amount in the slate - let (_, mut context, receiver_create_fn) = - selection::build_recipient_output_with_slate(wallet, slate).unwrap(); - - // fill public keys - let _ = slate.fill_round_1( - wallet.keychain(), - &mut context.sec_key, - &context.sec_nonce, - 1, - )?; - - // perform partial sig - let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?; - - // Save output in wallet - let _ = receiver_create_fn(wallet); - - Ok(()) - } -} - -impl Handler for WalletReceiver -where - T: WalletBackend + Send + Sync + 'static, -{ - fn handle(&self, req: &mut Request) -> IronResult { - let struct_body = req.get::>(); - let mut wallet = self.wallet.write().unwrap(); - - if let Ok(Some(mut slate)) = struct_body { - let _ = self.handle_send(&mut wallet, &mut slate) - .map_err(|e| { - error!( - LOGGER, - "Handling send -> Problematic slate, looks like this: {:?}", slate - ); - e.context(api::ErrorKind::Internal( - "Error processing partial transaction".to_owned(), - )) - }) - .unwrap(); - let json = serde_json::to_string(&slate).unwrap(); - Ok(Response::with((status::Ok, json))) - } else { - Ok(Response::with((status::BadRequest, ""))) - } - } -} - -//TODO: Split up the output creation and the wallet insertion -/// Build a coinbase output and the corresponding kernel -pub fn receive_coinbase( - wallet: &mut T, - block_fees: &BlockFees, -) -> Result<(Output, TxKernel, BlockFees), Error> -where - T: WalletBackend, -{ - let root_key_id = wallet.keychain().root_key_id(); - - let height = block_fees.height; - let lock_height = height + global::coinbase_maturity(); - - // Now acquire the wallet lock and write the new output. - let (key_id, derivation) = wallet.with_wallet(|wallet_data| { - let key_id = block_fees.key_id(); - let (key_id, derivation) = match key_id { - Some(key_id) => keys::retrieve_existing_key(wallet_data, key_id), - None => keys::next_available_key(wallet_data), - }; - - // track the new output and return the stuff needed for reward - wallet_data.add_output(OutputData { - root_key_id: root_key_id.clone(), - key_id: key_id.clone(), - n_child: derivation, - value: reward(block_fees.fees), - status: OutputStatus::Unconfirmed, - height: height, - lock_height: lock_height, - is_coinbase: true, - block: None, - merkle_proof: None, - }); - - (key_id, derivation) - })?; - - debug!( - LOGGER, - "receive_coinbase: built candidate output - {:?}, {}", - key_id.clone(), - derivation, - ); - - let mut block_fees = block_fees.clone(); - block_fees.key_id = Some(key_id.clone()); - - debug!(LOGGER, "receive_coinbase: {:?}", block_fees); - - let (out, kern) = reward::output( - &wallet.keychain(), - &key_id, - block_fees.fees, - block_fees.height, - ).unwrap(); - /* .context(ErrorKind::Keychain)?; */ - Ok((out, kern, block_fees)) -} diff --git a/wallet/src/server.rs b/wallet/src/server.rs deleted file mode 100644 index 251c95bb8..000000000 --- a/wallet/src/server.rs +++ /dev/null @@ -1,54 +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 api::ApiServer; -use handlers::CoinbaseHandler; -use iron::Handler; -use libwallet::types::WalletBackend; -use receiver::WalletReceiver; -use std::sync::{Arc, RwLock}; -use util::LOGGER; - -pub fn start_rest_apis(in_wallet: T, api_listen_addr: &str) -where - T: WalletBackend, - CoinbaseHandler: Handler, - WalletReceiver: Handler, -{ - info!( - LOGGER, - "Starting the Grin wallet receiving daemon at {}...", api_listen_addr - ); - - let wallet = Arc::new(RwLock::new(in_wallet)); - - let receive_tx_handler = WalletReceiver { - wallet: wallet.clone(), - }; - let coinbase_handler = CoinbaseHandler { - wallet: wallet.clone(), - }; - - let router = router!( - receive_tx: post "/receive/transaction" => receive_tx_handler, - receive_coinbase: post "/receive/coinbase" => coinbase_handler, - ); - - let mut apis = ApiServer::new("/v1".to_string()); - apis.register_handler(router); - match apis.start(api_listen_addr) { - Err(e) => error!(LOGGER, "Failed to start Grin wallet listener: {}.", e), - Ok(_) => info!(LOGGER, "Wallet listener started"), - }; -} diff --git a/wallet/tests/common/mod.rs b/wallet/tests/common/mod.rs index e3db28988..13cfcdcaa 100644 --- a/wallet/tests/common/mod.rs +++ b/wallet/tests/common/mod.rs @@ -28,8 +28,8 @@ use core::core::hash::Hashed; use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel}; use core::{consensus, global, pow}; use wallet::file_wallet::*; +use wallet::libwallet::internal::updater; use wallet::libwallet::types::*; -use wallet::libwallet::updater; use wallet::libwallet::{Error, ErrorKind}; use util::secp::pedersen; @@ -162,7 +162,7 @@ pub fn award_block_to_wallet( key_id: None, height: prev.height + 1, }; - let coinbase_tx = wallet::receiver::receive_coinbase(wallet, &fees); + let coinbase_tx = wallet::libwallet::internal::updater::receive_coinbase(wallet, &fees); let (coinbase_tx, fees) = match coinbase_tx { Ok(t) => ((t.0, t.1), t.2), Err(e) => { @@ -197,10 +197,14 @@ pub fn award_blocks_to_wallet(chain: &Chain, wallet: &mut T, n pub fn create_wallet(dir: &str) -> FileWallet { let mut wallet_config = WalletConfig::default(); wallet_config.data_file_dir = String::from(dir); - let wallet_seed = wallet::WalletSeed::init_file(&wallet_config).unwrap(); - let keychain = wallet_seed - .derive_keychain("") - .expect("Failed to derive keychain from seed file and passphrase."); - FileWallet::new(wallet_config.clone(), keychain) - .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)) + wallet::WalletSeed::init_file(&wallet_config).expect("Failed to create wallet seed file."); + let mut wallet = FileWallet::new(wallet_config.clone(), "") + .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 + ) + }); + wallet } diff --git a/wallet/tests/libwallet.rs b/wallet/tests/libwallet.rs index 8e1d21b21..fcaaaa4ee 100644 --- a/wallet/tests/libwallet.rs +++ b/wallet/tests/libwallet.rs @@ -25,7 +25,7 @@ use util::secp::key::{PublicKey, SecretKey}; use util::secp::pedersen::ProofMessage; use util::{kernel_sig_msg, secp}; use wallet::libtx::{aggsig, proof}; -use wallet::libwallet::sigcontext; +use wallet::libwallet::internal::sigcontext; use rand::thread_rng; diff --git a/wallet/tests/transaction.rs b/wallet/tests/transaction.rs index cdcbec8ce..225cc55d6 100644 --- a/wallet/tests/transaction.rs +++ b/wallet/tests/transaction.rs @@ -29,12 +29,12 @@ mod common; use std::fs; use std::sync::Arc; -use chain::types::*; use chain::Chain; +use chain::types::*; use core::global::ChainTypes; use core::{global, pow}; use util::LOGGER; -use wallet::libwallet::selection; +use wallet::libwallet::internal::selection; fn clean_output_dir(test_dir: &str) { let _ = fs::remove_dir_all(test_dir); @@ -100,7 +100,7 @@ fn build_transaction() { // information to the slate let _ = slate .fill_round_1( - &wallet1.keychain, + wallet1.keychain.as_ref().unwrap(), &mut sender_context.sec_key, &sender_context.sec_nonce, 0, @@ -120,7 +120,7 @@ fn build_transaction() { let _ = slate .fill_round_1( - &wallet2.keychain, + wallet2.keychain.as_ref().unwrap(), &mut recp_context.sec_key, &recp_context.sec_nonce, 1, @@ -132,7 +132,7 @@ fn build_transaction() { let _ = slate .fill_round_2( - &wallet1.keychain, + &wallet1.keychain.as_ref().unwrap(), &recp_context.sec_key, &recp_context.sec_nonce, 1, @@ -149,7 +149,7 @@ fn build_transaction() { // SENDER Part 3: Sender confirmation let _ = slate .fill_round_2( - &wallet1.keychain, + &wallet1.keychain.as_ref().unwrap(), &sender_context.sec_key, &sender_context.sec_nonce, 0, @@ -161,7 +161,7 @@ fn build_transaction() { debug!(LOGGER, "{:?}", slate); // Final transaction can be built by anyone at this stage - let res = slate.finalize(&wallet1.keychain); + let res = slate.finalize(&wallet1.keychain.as_ref().unwrap()); if let Err(e) = res { panic!("Error creating final tx: {:?}", e);