diff --git a/servers/tests/simulnet.rs b/servers/tests/simulnet.rs index 849875c050..a7c9a757d2 100644 --- a/servers/tests/simulnet.rs +++ b/servers/tests/simulnet.rs @@ -973,7 +973,7 @@ fn replicate_tx_fluff_failure() { slate = client1_w.send_tx_sync(dest, &slate)?; api.finalize_tx(&mut slate)?; api.tx_lock_outputs(&slate, lock_fn)?; - api.post_tx(&slate, false)?; + api.post_tx(&slate.tx, false)?; Ok(()) }).unwrap(); diff --git a/src/bin/cmd/wallet.rs b/src/bin/cmd/wallet.rs index 7fa7b6b69d..7329d18bea 100644 --- a/src/bin/cmd/wallet.rs +++ b/src/bin/cmd/wallet.rs @@ -15,10 +15,14 @@ use clap::ArgMatches; use rpassword; use std::collections::HashMap; +use std::fs::File; +use std::io::Write; use std::path::{Path, PathBuf}; use std::thread; use std::time::Duration; +use serde_json as json; + use api::TLSConfig; use config::GlobalWalletConfig; use core::{core, global}; @@ -328,7 +332,7 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i } else { let label = create.unwrap(); let res = controller::owner_single_use(wallet, |api| { - api.new_account_path(label)?; + api.create_account_path(label)?; thread::sleep(Duration::from_millis(200)); println!("Account: '{}' Created!", label); Ok(()) @@ -460,7 +464,7 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i api.tx_lock_outputs(&slate, lock_fn)?; } if adapter.supports_sync() { - let result = api.post_tx(&slate, fluff); + let result = api.post_tx(&slate.tx, fluff); match result { Ok(_) => { info!("Tx sent",); @@ -513,7 +517,7 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i 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); + let result = api.post_tx(&slate.tx, fluff); match result { Ok(_) => { info!("Transaction sent successfully, check the wallet again for confirmation."); @@ -525,24 +529,6 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i } } } - ("burn", Some(send_args)) => { - let amount = send_args - .value_of("amount") - .expect("Amount to burn required"); - let amount = 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(args)) => { let minimum_confirmations: u64 = args .value_of("minimum_confirmations") @@ -651,32 +637,33 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i let dump_file = repost_args.value_of("dumpfile"); let fluff = repost_args.is_present("fluff"); + let (_, txs) = api.retrieve_txs(true, Some(tx_id), None)?; + let stored_tx = txs[0].get_stored_tx(); + if stored_tx.is_none() { + println!( + "Transaction with id {} does not have transaction data. Not reposting.", + tx_id + ); + std::process::exit(0); + } match dump_file { None => { - let result = api.post_stored_tx(tx_id, fluff); - match result { - Ok(_) => { - info!("Reposted transaction at {}", tx_id); - Ok(()) - } - Err(e) => { - error!("Transaction reposting failed: {}", e); - Err(e) - } + if txs[0].confirmed { + println!("Transaction with id {} is confirmed. Not reposting.", tx_id); + std::process::exit(0); } + api.post_tx(&stored_tx.unwrap(), fluff)?; + info!("Reposted transaction at {}", tx_id); + println!("Reposted transaction at {}", tx_id); + Ok(()) } Some(f) => { - let result = api.dump_stored_tx(tx_id, true, f); - match result { - Ok(_) => { - warn!("Dumped transaction data for tx {} to {}", tx_id, f); - Ok(()) - } - Err(e) => { - error!("Transaction reposting failed: {}", e); - Err(e) - } - } + let mut tx_file = File::create(f)?; + tx_file.write_all(json::to_string(&stored_tx).unwrap().as_bytes())?; + tx_file.sync_all()?; + info!("Dumped transaction data for tx {} to {}", tx_id, f); + println!("Dumped transaction data for tx {} to {}", tx_id, f); + Ok(()) } } } diff --git a/wallet/src/controller.rs b/wallet/src/controller.rs index 5c783e052f..0028cbcd2b 100644 --- a/wallet/src/controller.rs +++ b/wallet/src/controller.rs @@ -222,31 +222,31 @@ where api.retrieve_txs(update_from_node, tx_id, tx_slate_id) } - fn dump_stored_tx( + fn retrieve_stored_tx( &self, req: &Request, api: APIOwner, - ) -> Result { + ) -> Result<(bool, Option), Error> { let params = parse_params(req); if let Some(id_string) = params.get("id") { match id_string[0].parse() { - Ok(id) => match api.dump_stored_tx(id, false, "") { - Ok(tx) => Ok(tx), + Ok(id) => match api.retrieve_txs(true, Some(id), None) { + Ok((_, txs)) => Ok((txs[0].confirmed, txs[0].get_stored_tx())), Err(e) => { - error!("dump_stored_tx: failed with error: {}", e); + error!("retrieve_stored_tx: failed with error: {}", e); Err(e) } }, Err(e) => { - error!("dump_stored_tx: could not parse id: {}", e); + error!("retrieve_stored_tx: could not parse id: {}", e); Err(ErrorKind::TransactionDumpError( - "dump_stored_tx: cannot dump transaction. Could not parse id in request.", + "retrieve_stored_tx: cannot dump transaction. Could not parse id in request.", ).into()) } } } else { Err(ErrorKind::TransactionDumpError( - "dump_stored_tx: Cannot dump transaction. Missing id param in request.", + "retrieve_stored_tx: Cannot retrieve transaction. Missing id param in request.", ).into()) } } @@ -292,7 +292,7 @@ where "retrieve_summary_info" => json_response(&self.retrieve_summary_info(req, api)?), "node_height" => json_response(&self.node_height(req, api)?), "retrieve_txs" => json_response(&self.retrieve_txs(req, api)?), - "dump_stored_tx" => json_response(&self.dump_stored_tx(req, api)?), + "retrieve_stored_tx" => json_response(&self.retrieve_stored_tx(req, api)?), _ => response(StatusCode::BAD_REQUEST, ""), }) } @@ -439,18 +439,6 @@ where ) } - fn issue_burn_tx( - &self, - _req: Request, - mut api: APIOwner, - ) -> Box + Send> { - // TODO: Args - Box::new(match api.issue_burn_tx(60, 10, 1000) { - Ok(_) => ok(()), - Err(e) => err(e), - }) - } - fn handle_post_request(&self, req: Request) -> WalletResponseFuture { let api = APIOwner::new(self.wallet.clone()); match req @@ -477,10 +465,6 @@ where self.post_tx(req, api) .and_then(|_| ok(response(StatusCode::OK, ""))), ), - "issue_burn_tx" => Box::new( - self.issue_burn_tx(req, api) - .and_then(|_| ok(response(StatusCode::OK, ""))), - ), _ => Box::new(err(ErrorKind::GenericError( "Unknown error handling post request".to_owned(), ).into())), diff --git a/wallet/src/libwallet/api.rs b/wallet/src/libwallet/api.rs index 145f508db3..8e839157fd 100644 --- a/wallet/src/libwallet/api.rs +++ b/wallet/src/libwallet/api.rs @@ -12,20 +12,28 @@ // 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 +//! Main interface into all wallet API functions. +//! Wallet APIs are split into two seperate blocks of functionality +//! called the 'Owner' and 'Foreign' APIs: +//! * The 'Owner' API is intended to expose methods that are to be +//! used by the wallet owner only. It is vital that this API is not +//! exposed to anyone other than the owner of the wallet (i.e. the +//! person with access to the seed and password. +//! * The 'Foreign' API contains methods that other wallets will +//! use to interact with the owner's wallet. This API can be exposed +//! to the outside world, with the consideration as to how that can +//! be done securely up to the implementor. +//! +//! Methods in both APIs are intended to be 'single use', that is to say each +//! method will 'open' the wallet (load the keychain with its master seed), perform +//! its operation, then 'close' the wallet (unloading references to the keychain and master +//! seed). -use std::fs::File; -use std::io::Write; use std::marker::PhantomData; use std::sync::Arc; use util::Mutex; use uuid::Uuid; -use serde_json as json; - use core::core::hash::Hashed; use core::core::Transaction; use core::ser; @@ -40,16 +48,15 @@ use libwallet::{Error, ErrorKind}; use util; use util::secp::pedersen; -/// Wrapper around internal API functions, containing a reference to -/// the wallet/keychain that they're acting upon +/// Functions intended for use by the owner (e.g. master seed holder) of the wallet. pub struct APIOwner where W: WalletBackend, C: NodeClient, K: Keychain, { - /// Wallet, contains its keychain (TODO: Split these up into 2 traits - /// perhaps) + /// A reference-counted mutex to an implementation of the + /// [WalletBackend](../types/trait.WalletBackend.html) trait. pub wallet: Arc>, phantom: PhantomData, phantom_c: PhantomData, @@ -61,7 +68,52 @@ where C: NodeClient, K: Keychain, { - /// Create new API instance + /// Create a new API instance with the given wallet instance. All subsequent + /// API calls will operate on this instance of the wallet. + /// + /// Each method will call the [WalletBackend](../types/trait.WalletBackend.html)'s + /// [open_with_credentials](../types/trait.WalletBackend.html#tymethod.open_with_credentials) + /// (initialising a keychain with the master seed,) perform its operation, then close the keychain + /// with a call to [close](../types/trait.WalletBackend.html#tymethod.close) + /// + /// # Arguments + /// * `wallet_in` - A reference-counted mutex containing an implementation of the + /// [WalletBackend](../types/trait.WalletBackend.html) trait. + /// + /// # Returns + /// * An instance of the OwnerAPI holding a reference to the provided wallet + /// + /// # Example + /// ``` + /// # extern crate grin_wallet as wallet; + /// # extern crate grin_keychain as keychain; + /// # extern crate grin_util as util; + /// + /// use std::sync::Arc; + /// use util::Mutex; + /// + /// use keychain::ExtKeychain; + /// use wallet::libwallet::api::APIOwner; + /// + /// // These contain sample implementations of each part needed for a wallet + /// use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig}; + /// + /// let mut wallet_config = WalletConfig::default(); + /// + /// // A NodeClient must first be created to handle communication between + /// // the wallet and the node. + /// + /// let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); + /// let mut wallet:Arc>> = + /// Arc::new(Mutex::new( + /// LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap() + /// )); + /// + /// let api_owner = APIOwner::new(wallet.clone()); + /// // .. perform wallet operations + /// + /// ``` + pub fn new(wallet_in: Arc>) -> Self { APIOwner { wallet: wallet_in, @@ -70,9 +122,214 @@ where } } - /// Attempt to update and retrieve outputs - /// Return (whether the outputs were validated against a node, OutputData) - /// if tx_id is some then only retrieve outputs for associated transaction + /// Returns a list of accounts stored in the wallet (i.e. mappings between + /// user-specified labels and BIP32 derivation paths. + /// + /// # Returns + /// * Result Containing: + /// * A Vector of [AcctPathMapping](../types/struct.AcctPathMapping.html) data + /// * or [libwallet::Error](../struct.Error.html) if an error is encountered. + /// + /// # Remarks + /// + /// * A wallet should always have the path with the label 'default' path defined, + /// with path m/0/0 + /// * This method does not need to use the wallet seed or keychain. + /// + /// # Example + /// Set up as in [new](struct.APIOwner.html#method.new) method above. + /// ``` + /// # extern crate grin_wallet as wallet; + /// # extern crate grin_keychain as keychain; + /// # extern crate grin_util as util; + /// # use std::sync::Arc; + /// # use util::Mutex; + /// # use keychain::ExtKeychain; + /// # use wallet::libwallet::api::APIOwner; + /// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig}; + /// # let mut wallet_config = WalletConfig::default(); + /// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); + /// # let mut wallet:Arc>> = + /// # Arc::new(Mutex::new( + /// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap() + /// # )); + /// + /// let api_owner = APIOwner::new(wallet.clone()); + /// + /// let result = api_owner.accounts(); + /// + /// if let Ok(accts) = result { + /// //... + /// } + /// ``` + + pub fn accounts(&self) -> Result, Error> { + let mut w = self.wallet.lock(); + keys::accounts(&mut *w) + } + + /// Creates a new 'account', which is a mapping of a user-specified + /// label to a BIP32 path + /// + /// # Arguments + /// * `label` - A human readable label to which to map the new BIP32 Path + /// + /// # Returns + /// * Result Containing: + /// * A [Keychain Identifier](#) for the new path + /// * or [libwallet::Error](../struct.Error.html) if an error is encountered. + /// + /// # Remarks + /// + /// * Wallets should be initialised with the 'default' path mapped to `m/0/0` + /// * Each call to this function will increment the first element of the path + /// so the first call will create an account at `m/1/0` and the second at + /// `m/2/0` etc. . . + /// * The account path is used throughout as the parent key for most key-derivation + /// operations. See [set_active_account](struct.APIOwner.html#method.set_active_account) for + /// further details. + /// + /// * This function does not need to use the root wallet seed or keychain. + /// + /// # Example + /// Set up as in [new](struct.APIOwner.html#method.new) method above. + /// ``` + /// # extern crate grin_wallet as wallet; + /// # extern crate grin_keychain as keychain; + /// # extern crate grin_util as util; + /// # use std::sync::Arc; + /// # use util::Mutex; + /// # use keychain::ExtKeychain; + /// # use wallet::libwallet::api::APIOwner; + /// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig}; + /// # let mut wallet_config = WalletConfig::default(); + /// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); + /// # let mut wallet:Arc>> = + /// # Arc::new(Mutex::new( + /// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap() + /// # )); + /// + /// let api_owner = APIOwner::new(wallet.clone()); + /// + /// let result = api_owner.create_account_path("account1"); + /// + /// if let Ok(identifier) = result { + /// //... + /// } + /// ``` + + pub fn create_account_path(&self, label: &str) -> Result { + let mut w = self.wallet.lock(); + keys::new_acct_path(&mut *w, label) + } + + /// Sets the wallet's currently active account. This sets the + /// BIP32 parent path used for most key-derivation operations. + /// + /// # Arguments + /// * `label` - The human readable label for the account. Accounts can be retrieved via + /// the [account](struct.APIOwner.html#method.accounts) method + /// # Returns + /// * Result Containing: + /// * `Ok(())` if the path was correctly set + /// * or [libwallet::Error](../struct.Error.html) if an error is encountered. + /// + /// # Remarks + /// + /// * Wallet parent paths are 2 path elements long, e.g. `m/0/0` is the path + /// labelled 'default'. Keys derived from this parent path are 3 elements long, + /// e.g. the secret keys derived from the `m/0/0` path will be at paths `m/0/0/0`, + /// `m/0/0/1` etc... + /// + /// * This function does not need to use the root wallet seed or keychain. + /// + /// # Example + /// Set up as in [new](struct.APIOwner.html#method.new) method above. + /// ``` + /// # extern crate grin_wallet as wallet; + /// # extern crate grin_keychain as keychain; + /// # extern crate grin_util as util; + /// # use std::sync::Arc; + /// # use util::Mutex; + /// # use keychain::ExtKeychain; + /// # use wallet::libwallet::api::APIOwner; + /// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig}; + /// # let mut wallet_config = WalletConfig::default(); + /// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); + /// # let mut wallet:Arc>> = + /// # Arc::new(Mutex::new( + /// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap() + /// # )); + /// + /// let api_owner = APIOwner::new(wallet.clone()); + /// + /// let result = api_owner.create_account_path("account1"); + /// + /// if let Ok(identifier) = result { + /// // set the account active + /// let result2 = api_owner.set_active_account("account1"); + /// } + /// ``` + + pub fn set_active_account(&self, label: &str) -> Result<(), Error> { + let mut w = self.wallet.lock(); + w.set_parent_key_id_by_name(label)?; + Ok(()) + } + + /// Returns a list of outputs from the active account in the wallet. + /// + /// # Arguments + /// * `include_spent` - If `true`, outputs that have been marked as 'spent' + /// in the wallet will be returned. If `false`, spent outputs will omitted + /// from the results. + /// * `refresh_from_node` - If true, the wallet will attempt to contact + /// a node (via the [NodeClient](../types/trait.NodeClient.html) + /// provided during wallet instantiation). If `false`, the results will + /// contain output information that may be out-of-date (from the last time + /// the wallet's output set was refreshed against the node). + /// * `tx_id` - If `Some(i)`, only return the outputs associated with + /// the transaction log entry of id `i`. + /// + /// # Returns + /// * (`bool`, `Vec`) - A tuple: + /// * The first `bool` element indicates whether the data was successfully + /// refreshed from the node (note this may be false even if the `refresh_from_node` + /// argument was set to `true`. + /// * The second element contains the result set, of which each element is + /// a mapping between the wallet's internal [OutputData](../types/struct.OutputData.html) + /// and the Output commitment as identified in the chain's UTXO set + /// + /// # Example + /// Set up as in [new](struct.APIOwner.html#method.new) method above. + /// ``` + /// # extern crate grin_wallet as wallet; + /// # extern crate grin_keychain as keychain; + /// # extern crate grin_util as util; + /// # use std::sync::Arc; + /// # use util::Mutex; + /// # use keychain::ExtKeychain; + /// # use wallet::libwallet::api::APIOwner; + /// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig}; + /// # let mut wallet_config = WalletConfig::default(); + /// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); + /// # let mut wallet:Arc>> = + /// # Arc::new(Mutex::new( + /// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap() + /// # )); + /// + /// let api_owner = APIOwner::new(wallet.clone()); + /// let show_spent = false; + /// let update_from_node = true; + /// let tx_id = None; + /// + /// let result = api_owner.retrieve_outputs(show_spent, update_from_node, tx_id); + /// + /// if let Ok((was_updated, output_mapping)) = result { + /// //... + /// } + /// ``` + pub fn retrieve_outputs( &self, include_spent: bool, @@ -97,8 +354,59 @@ where res } - /// Attempt to update outputs and retrieve transactions - /// Return (whether the outputs were validated against a node, OutputData) + /// Returns a list of [Transaction Log Entries](../types/struct.TxLogEntry.html) + /// from the active account in the wallet. + /// + /// # Arguments + /// * `refresh_from_node` - If true, the wallet will attempt to contact + /// a node (via the [NodeClient](../types/trait.NodeClient.html) + /// provided during wallet instantiation). If `false`, the results will + /// contain transaction information that may be out-of-date (from the last time + /// the wallet's output set was refreshed against the node). + /// * `tx_id` - If `Some(i)`, only return the transactions associated with + /// the transaction log entry of id `i`. + /// * `tx_slate_id` - If `Some(uuid)`, only return transactions associated with + /// the given [Slate](../../libtx/slate/struct.Slate.html) uuid. + /// + /// # Returns + /// * (`bool`, `Vec<[TxLogEntry](../types/struct.TxLogEntry.html)>`) - A tuple: + /// * The first `bool` element indicates whether the data was successfully + /// refreshed from the node (note this may be false even if the `refresh_from_node` + /// argument was set to `true`. + /// * The second element contains the set of retrieved + /// [TxLogEntries](../types/struct/TxLogEntry.html) + /// + /// # Example + /// Set up as in [new](struct.APIOwner.html#method.new) method above. + /// ``` + /// # extern crate grin_wallet as wallet; + /// # extern crate grin_keychain as keychain; + /// # extern crate grin_util as util; + /// # use std::sync::Arc; + /// # use util::Mutex; + /// # use keychain::ExtKeychain; + /// # use wallet::libwallet::api::APIOwner; + /// # use wallet::{LMDBBackend, HTTPNodeClient, WalletBackend, WalletConfig}; + /// # let mut wallet_config = WalletConfig::default(); + /// # let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None); + /// # let mut wallet:Arc>> = + /// # Arc::new(Mutex::new( + /// # LMDBBackend::new(wallet_config.clone(), "", node_client).unwrap() + /// # )); + /// + /// let api_owner = APIOwner::new(wallet.clone()); + /// let update_from_node = true; + /// let tx_id = None; + /// let tx_slate_id = None; + /// + /// // Return all TxLogEntries + /// let result = api_owner.retrieve_txs(update_from_node, tx_id, tx_slate_id); + /// + /// if let Ok((was_updated, tx_log_entries)) = result { + /// //... + /// } + /// ``` + pub fn retrieve_txs( &self, refresh_from_node: bool, @@ -145,18 +453,6 @@ where res } - /// Return list of existing account -> Path mappings - pub fn accounts(&mut self) -> Result, Error> { - let mut w = self.wallet.lock(); - keys::accounts(&mut *w) - } - - /// Create a new account path - pub fn new_account_path(&mut self, label: &str) -> Result { - let mut w = self.wallet.lock(); - keys::new_acct_path(&mut *w, label) - } - /// Creates a new partial transaction for the given amount pub fn initiate_tx( &mut self, @@ -260,33 +556,9 @@ where Ok(()) } - /// Issue a burn TX - pub fn issue_burn_tx( - &mut self, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - ) -> Result<(), Error> { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let parent_key_id = w.parent_key_id(); - let tx_burn = tx::issue_burn_tx( - &mut *w, - amount, - minimum_confirmations, - max_outputs, - &parent_key_id, - )?; - let tx_hex = util::to_hex(ser::ser_vec(&tx_burn).unwrap()); - w.w2n_client() - .post_tx(&TxWrapper { tx_hex: tx_hex }, false)?; - w.close()?; - Ok(()) - } - /// Posts a transaction to the chain - pub fn post_tx(&self, slate: &Slate, fluff: bool) -> Result<(), Error> { - let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap()); + pub fn post_tx(&self, tx: &Transaction, fluff: bool) -> Result<(), Error> { + let tx_hex = util::to_hex(ser::ser_vec(tx).unwrap()); let client = { let mut w = self.wallet.lock(); w.w2n_client().clone() @@ -298,86 +570,13 @@ where } else { debug!( "api: post_tx: successfully posted tx: {}, fluff? {}", - slate.tx.hash(), + tx.hash(), fluff ); Ok(()) } } - /// Writes stored transaction data to a given file - pub fn dump_stored_tx( - &self, - tx_id: u32, - write_to_disk: bool, - dest: &str, - ) -> Result { - let (confirmed, tx_hex) = { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let parent_key_id = w.parent_key_id(); - let res = tx::retrieve_tx_hex(&mut *w, &parent_key_id, tx_id)?; - w.close()?; - res - }; - if confirmed { - warn!( - "api: dump_stored_tx: transaction at {} is already confirmed.", - tx_id - ); - } - if tx_hex.is_none() { - error!( - "api: dump_stored_tx: completed transaction at {} does not exist.", - tx_id - ); - return Err(ErrorKind::TransactionBuildingNotCompleted(tx_id))?; - } - let tx_bin = util::from_hex(tx_hex.unwrap()).unwrap(); - let tx = ser::deserialize::(&mut &tx_bin[..])?; - if write_to_disk { - let mut tx_file = File::create(dest)?; - tx_file.write_all(json::to_string(&tx).unwrap().as_bytes())?; - tx_file.sync_all()?; - } - Ok(tx) - } - - /// (Re)Posts a transaction that's already been stored to the chain - pub fn post_stored_tx(&self, tx_id: u32, fluff: bool) -> Result<(), Error> { - let client; - let (confirmed, tx_hex) = { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let parent_key_id = w.parent_key_id(); - client = w.w2n_client().clone(); - let res = tx::retrieve_tx_hex(&mut *w, &parent_key_id, tx_id)?; - w.close()?; - res - }; - if confirmed { - error!( - "api: repost_tx: transaction at {} is confirmed. NOT resending.", - tx_id - ); - return Err(ErrorKind::TransactionAlreadyConfirmed)?; - } - if tx_hex.is_none() { - error!( - "api: repost_tx: completed transaction at {} does not exist.", - tx_id - ); - return Err(ErrorKind::TransactionBuildingNotCompleted(tx_id))?; - } - client.post_tx( - &TxWrapper { - tx_hex: tx_hex.unwrap(), - }, - fluff, - )?; - Ok(()) - } - /// Attempt to restore contents of wallet pub fn restore(&mut self) -> Result<(), Error> { let mut w = self.wallet.lock(); diff --git a/wallet/src/libwallet/internal/tx.rs b/wallet/src/libwallet/internal/tx.rs index 0884452edd..deae53479d 100644 --- a/wallet/src/libwallet/internal/tx.rs +++ b/wallet/src/libwallet/internal/tx.rs @@ -14,16 +14,12 @@ //! Transaction building functions -use std::sync::Arc; -use util::{self, RwLock}; +use util; use uuid::Uuid; -use core::core::verifier_cache::LruVerifierCache; -use core::core::Transaction; use core::ser; use keychain::{Identifier, Keychain}; use libtx::slate::Slate; -use libtx::{build, tx_fee}; use libwallet::internal::{selection, updater}; use libwallet::types::{Context, NodeClient, TxLogEntryType, WalletBackend}; use libwallet::{Error, ErrorKind}; @@ -231,58 +227,6 @@ where Ok(()) } -/// Issue a burn tx -pub fn issue_burn_tx( - wallet: &mut T, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - parent_key_id: &Identifier, -) -> Result -where - T: WalletBackend, - C: NodeClient, - K: Keychain, -{ - // TODO - // let keychain = &Keychain::burn_enabled(wallet.keychain(), - // &Identifier::zero()); - let keychain = wallet.keychain().clone(); - - let current_height = wallet.w2n_client().get_chain_height()?; - - let _ = updater::refresh_outputs(wallet, parent_key_id); - - // select some spendable coins from the wallet - let (_, coins) = selection::select_coins( - wallet, - amount, - current_height, - minimum_confirmations, - max_outputs, - false, - parent_key_id, - ); - - debug!("selected some coins - {}", coins.len()); - - let fee = tx_fee(coins.len(), 2, 1, None); - let num_change_outputs = 1; - let (mut parts, _) = - selection::inputs_and_change(&coins, wallet, amount, fee, num_change_outputs)?; - - //TODO: If we end up using this, create change output here - - // add burn output and fees - parts.push(build::output(amount - fee, Identifier::zero())); - - // finalize the burn transaction and send - let tx_burn = build::transaction(parts, &keychain)?; - let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new())); - tx_burn.validate(verifier_cache)?; - Ok(tx_burn) -} - #[cfg(test)] mod test { use keychain::{ExtKeychain, ExtKeychainPath, Keychain}; diff --git a/wallet/src/libwallet/types.rs b/wallet/src/libwallet/types.rs index 4696df6a91..aa050a34ee 100644 --- a/wallet/src/libwallet/types.rs +++ b/wallet/src/libwallet/types.rs @@ -26,6 +26,7 @@ use failure::ResultExt; use uuid::Uuid; use core::core::hash::Hash; +use core::core::Transaction; use core::ser; use keychain::{Identifier, Keychain}; @@ -33,6 +34,7 @@ use keychain::{Identifier, Keychain}; use libtx::aggsig; use libwallet::error::{Error, ErrorKind}; +use util; use util::secp::key::{PublicKey, SecretKey}; use util::secp::{self, pedersen, Secp256k1}; @@ -639,6 +641,17 @@ impl TxLogEntry { pub fn update_confirmation_ts(&mut self) { self.confirmation_ts = Some(Utc::now()); } + + /// Retrieve the stored transaction, if any + pub fn get_stored_tx(&self) -> Option { + match self.tx_hex.as_ref() { + None => None, + Some(t) => { + let tx_bin = util::from_hex(t.clone()).unwrap(); + Some(ser::deserialize::(&mut &tx_bin[..]).unwrap()) + } + } + } } /// Map of named accounts to BIP32 paths diff --git a/wallet/tests/accounts.rs b/wallet/tests/accounts.rs index 58cecded22..40c6fce9c9 100644 --- a/wallet/tests/accounts.rs +++ b/wallet/tests/accounts.rs @@ -86,21 +86,21 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // add some accounts wallet::controller::owner_single_use(wallet1.clone(), |api| { - let new_path = api.new_account_path("account1").unwrap(); + let new_path = api.create_account_path("account1").unwrap(); assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); - let new_path = api.new_account_path("account2").unwrap(); + let new_path = api.create_account_path("account2").unwrap(); assert_eq!(new_path, ExtKeychain::derive_key_id(2, 2, 0, 0, 0)); - let new_path = api.new_account_path("account3").unwrap(); + let new_path = api.create_account_path("account3").unwrap(); assert_eq!(new_path, ExtKeychain::derive_key_id(2, 3, 0, 0, 0)); // trying to add same label again should fail - let res = api.new_account_path("account1"); + let res = api.create_account_path("account1"); assert!(res.is_err()); Ok(()) })?; // add account to wallet 2 wallet::controller::owner_single_use(wallet2.clone(), |api| { - let new_path = api.new_account_path("listener_account").unwrap(); + let new_path = api.create_account_path("listener_account").unwrap(); assert_eq!(new_path, ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); Ok(()) })?; @@ -194,7 +194,7 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { slate = client1.send_tx_slate_direct("wallet2", &slate)?; api.tx_lock_outputs(&slate, lock_fn)?; api.finalize_tx(&mut slate)?; - api.post_tx(&slate, false)?; + api.post_tx(&slate.tx, false)?; Ok(()) })?; diff --git a/wallet/tests/common/testclient.rs b/wallet/tests/common/testclient.rs index 4ef9cb3d5c..9a309144d9 100644 --- a/wallet/tests/common/testclient.rs +++ b/wallet/tests/common/testclient.rs @@ -376,11 +376,11 @@ impl WalletCommAdapter for LocalWalletClient { fn listen( &self, - params: HashMap, - config: WalletConfig, - passphrase: &str, - account: &str, - node_api_secret: Option, + _params: HashMap, + _config: WalletConfig, + _passphrase: &str, + _account: &str, + _node_api_secret: Option, ) -> Result<(), libwallet::Error> { unimplemented!(); } diff --git a/wallet/tests/file.rs b/wallet/tests/file.rs index 5b65aa77ac..03c0d6eac7 100644 --- a/wallet/tests/file.rs +++ b/wallet/tests/file.rs @@ -74,15 +74,15 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // add some accounts wallet::controller::owner_single_use(wallet1.clone(), |api| { - api.new_account_path("mining")?; - api.new_account_path("listener")?; + api.create_account_path("mining")?; + api.create_account_path("listener")?; Ok(()) })?; // add some accounts wallet::controller::owner_single_use(wallet2.clone(), |api| { - api.new_account_path("account1")?; - api.new_account_path("account2")?; + api.create_account_path("account1")?; + api.create_account_path("account2")?; Ok(()) })?; @@ -141,7 +141,7 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { let adapter = FileWalletCommAdapter::new(); let mut slate = adapter.receive_tx_async(&receive_file)?; api.finalize_tx(&mut slate)?; - api.post_tx(&slate, false); + api.post_tx(&slate.tx, false)?; bh += 1; Ok(()) })?; diff --git a/wallet/tests/repost.rs b/wallet/tests/repost.rs index 7288fa58d7..0647ca0d53 100644 --- a/wallet/tests/repost.rs +++ b/wallet/tests/repost.rs @@ -75,15 +75,15 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // add some accounts wallet::controller::owner_single_use(wallet1.clone(), |api| { - api.new_account_path("mining")?; - api.new_account_path("listener")?; + api.create_account_path("mining")?; + api.create_account_path("listener")?; Ok(()) })?; // add some accounts wallet::controller::owner_single_use(wallet2.clone(), |api| { - api.new_account_path("account1")?; - api.new_account_path("account2")?; + api.create_account_path("account1")?; + api.create_account_path("account2")?; Ok(()) })?; @@ -155,7 +155,7 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // Now repost from cached wallet::controller::owner_single_use(wallet1.clone(), |api| { let (_, txs) = api.retrieve_txs(true, None, Some(slate.id))?; - api.post_stored_tx(txs[0].id, false)?; + api.post_tx(&txs[0].get_stored_tx().unwrap(), false)?; bh += 1; Ok(()) })?; @@ -220,7 +220,7 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // Now repost from cached wallet::controller::owner_single_use(wallet1.clone(), |api| { let (_, txs) = api.retrieve_txs(true, None, Some(slate.id))?; - api.post_stored_tx(txs[0].id, false)?; + api.post_tx(&txs[0].get_stored_tx().unwrap(), false)?; bh += 1; Ok(()) })?; diff --git a/wallet/tests/restore.rs b/wallet/tests/restore.rs index 323de93f3d..ea782445e1 100644 --- a/wallet/tests/restore.rs +++ b/wallet/tests/restore.rs @@ -198,8 +198,8 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { // wallet 2 will use another account wallet::controller::owner_single_use(wallet2.clone(), |api| { - api.new_account_path("account1")?; - api.new_account_path("account2")?; + api.create_account_path("account1")?; + api.create_account_path("account2")?; Ok(()) })?; @@ -240,7 +240,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate, lock_fn)?; sender_api.finalize_tx(&mut slate)?; - sender_api.post_tx(&slate, false)?; + sender_api.post_tx(&slate.tx, false)?; Ok(()) })?; @@ -261,7 +261,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { slate = client1.send_tx_slate_direct("wallet3", &slate_i)?; sender_api.tx_lock_outputs(&slate, lock_fn)?; sender_api.finalize_tx(&mut slate)?; - sender_api.post_tx(&slate, false)?; + sender_api.post_tx(&slate.tx, false)?; Ok(()) })?; @@ -282,7 +282,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { slate = client3.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate, lock_fn)?; sender_api.finalize_tx(&mut slate)?; - sender_api.post_tx(&slate, false)?; + sender_api.post_tx(&slate.tx, false)?; Ok(()) })?; @@ -309,7 +309,7 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { slate = client3.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate, lock_fn)?; sender_api.finalize_tx(&mut slate)?; - sender_api.post_tx(&slate, false)?; + sender_api.post_tx(&slate.tx, false)?; Ok(()) })?; diff --git a/wallet/tests/self_send.rs b/wallet/tests/self_send.rs index 5b4f09745d..5194fad34a 100644 --- a/wallet/tests/self_send.rs +++ b/wallet/tests/self_send.rs @@ -72,8 +72,8 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { // add some accounts wallet::controller::owner_single_use(wallet1.clone(), |api| { - api.new_account_path("mining")?; - api.new_account_path("listener")?; + api.create_account_path("mining")?; + api.create_account_path("listener")?; Ok(()) })?; @@ -109,7 +109,7 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { })?; api.finalize_tx(&mut slate)?; api.tx_lock_outputs(&slate, lock_fn)?; - api.post_tx(&slate, false)?; // mines a block + api.post_tx(&slate.tx, false)?; // mines a block bh += 1; Ok(()) })?; diff --git a/wallet/tests/transaction.rs b/wallet/tests/transaction.rs index 11ffa3d5bd..30cc39f926 100644 --- a/wallet/tests/transaction.rs +++ b/wallet/tests/transaction.rs @@ -157,7 +157,7 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { // post transaction wallet::controller::owner_single_use(wallet1.clone(), |api| { - api.post_tx(&slate, false)?; + api.post_tx(&slate.tx, false)?; Ok(()) })?; @@ -255,13 +255,12 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { let (refreshed, _wallet1_info) = sender_api.retrieve_summary_info(true, 1)?; assert!(refreshed); let (_, txs) = sender_api.retrieve_txs(true, None, None)?; - // find the transaction let tx = txs .iter() .find(|t| t.tx_slate_id == Some(slate.id)) .unwrap(); - sender_api.post_stored_tx(tx.id, false)?; + sender_api.post_tx(&tx.get_stored_tx().unwrap(), false)?; let (_, wallet1_info) = sender_api.retrieve_summary_info(true, 1)?; // should be mined now assert_eq!(