From 427f42c5528a46434f869b83dd89b5d9077021d4 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Fri, 29 Mar 2019 16:00:02 +0000 Subject: [PATCH] Final Owner API Changes (#32) * node height return value and documentation * rustfmt * add parameter struct for initiate tx functions * rustfmt * change tx estimate to use InitTxArgs * rustfmt * transaction estimate * rustfmt * initiate tx args fixed * add send args to init * rustfmt * last owner api documentation --- api/src/foreign_rpc.rs | 22 +- api/src/lib.rs | 1 + api/src/owner.rs | 444 ++++++++++++++++++-------------- api/src/owner_rpc.rs | 149 +++-------- controller/src/command.rs | 58 +++-- controller/src/controller.rs | 35 +-- controller/tests/accounts.rs | 19 +- controller/tests/check.rs | 22 +- controller/tests/file.rs | 23 +- controller/tests/repost.rs | 41 +-- controller/tests/restore.rs | 80 +++--- controller/tests/self_send.rs | 21 +- controller/tests/transaction.rs | 101 ++++---- impls/src/test_framework/mod.rs | 21 +- libwallet/src/api_impl/owner.rs | 97 +++---- libwallet/src/types.rs | 196 ++++++++++---- 16 files changed, 698 insertions(+), 632 deletions(-) diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index d43afb977..a8721b332 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -16,7 +16,7 @@ use crate::keychain::Keychain; use crate::libwallet::slate::Slate; -use crate::libwallet::types::{BlockFees, CbData, NodeClient, WalletBackend}; +use crate::libwallet::types::{BlockFees, CbData, InitTxArgs, NodeClient, WalletBackend}; use crate::libwallet::ErrorKind; use crate::Foreign; use easy_jsonrpc; @@ -416,16 +416,16 @@ pub fn run_doctest_foreign( let amount = 60_000_000_000; let mut w = wallet1.lock(); w.open_with_credentials().unwrap(); - let slate = api_impl::owner::initiate_tx( - &mut *w, None, // account - amount, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, None, true, - ) - .unwrap(); + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate = api_impl::owner::initiate_tx(&mut *w, args, true).unwrap(); println!("INIT SLATE"); // Spit out slate for input to finalize_tx println!("{}", serde_json::to_string_pretty(&slate).unwrap()); diff --git a/api/src/lib.rs b/api/src/lib.rs index f4920b3a0..43174e8c4 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -25,6 +25,7 @@ use grin_wallet_util::grin_core as core; use grin_wallet_util::grin_keychain as keychain; use grin_wallet_util::grin_util as util; +extern crate grin_wallet_impls as impls; extern crate grin_wallet_libwallet as libwallet; extern crate failure_derive; diff --git a/api/src/owner.rs b/api/src/owner.rs index 2af815e9e..732e6444e 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -21,14 +21,15 @@ use std::sync::Arc; use uuid::Uuid; use crate::core::core::Transaction; +use crate::impls::{HTTPWalletCommAdapter, KeybaseWalletCommAdapter}; use crate::keychain::{Identifier, Keychain}; use crate::libwallet::api_impl::owner; use crate::libwallet::slate::Slate; use crate::libwallet::types::{ - AcctPathMapping, NodeClient, OutputCommitMapping, TxEstimation, TxLogEntry, WalletBackend, - WalletInfo, + AcctPathMapping, InitTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, TxLogEntry, + WalletBackend, WalletInfo, }; -use crate::libwallet::Error; +use crate::libwallet::{Error, ErrorKind}; /// Main interface into all wallet API functions. /// Wallet APIs are split into two seperate blocks of functionality @@ -431,37 +432,15 @@ where /// as via file transfer,) the lock call should happen immediately (before the file is sent /// to the recipient). /// + /// If the `send_args` [`InitTxSendArgs`](../grin_wallet_libwallet/types/struct.InitTxSendArgs.html), + /// of the [`args`](../grin_wallet_libwallet/types/struct.InitTxArgs.html), field is Some, this + /// function will attempt to perform a synchronous send to the recipient specified in the `dest` + /// field according to the `method` field, and will also finalize and post the transaction if + /// the `finalize` field is set. + /// /// # Arguments - /// * `src_acct_name` - The human readable account name from which to draw outputs - /// for the transaction, overriding whatever the active account is as set via the - /// [`set_active_account`](struct.Owner.html#method.set_active_account) method. - /// If None, the transaction will use the active account. - /// * `amount` - The amount to send, in nanogrins. (`1 G = 1_000_000_000nG`) - /// * `minimum_confirmations` - The minimum number of confirmations an output - /// should have in order to be included in the transaction. - /// * `max_outputs` - By default, the wallet selects as many inputs as possible in a - /// transaction, to reduce the Output set and the fees. The wallet will attempt to spend - /// include up to `max_outputs` in a transaction, however if this is not enough to cover - /// the whole amount, the wallet will include more outputs. This parameter should be considered - /// a soft limit. - /// * `num_change_outputs` - The target number of change outputs to create in the transaction. - /// The actual number created will be `num_change_outputs` + whatever remainder is needed. - /// * `selection_strategy_is_use_all` - If `true`, attempt to use up as many outputs as - /// possible to create the transaction, up the 'soft limit' of `max_outputs`. This helps - /// to reduce the size of the UTXO set and the amount of data stored in the wallet, and - /// minimizes fees. This will generally result in many inputs and a large change output(s), - /// usually much larger than the amount being sent. If `false`, the transaction will include - /// as many outputs as are needed to meet the amount, (and no more) starting with the smallest - /// value outputs. - /// * `message` - An optional participant message to include alongside the sender's public - /// ParticipantData within the slate. This message will include a signature created with the - /// sender's private excess value, and will be publically verifiable. Note this message is for - /// the convenience of the participants during the exchange; it is not included in the final - /// transaction sent to the chain. The message will be truncated to 256 characters. - /// Validation of this message is optional. - /// * `target_slate_version` Optionally set the output target slate version (acceptable - /// down to the minimum slate version compatible with the current. If `None` the slate - /// is generated with the latest version. + /// * `args` - [`InitTxArgs`](../grin_wallet_libwallet/types/struct.InitTxArgs.html), + /// transaction initialization arguments. See struct documentation for further detail. /// /// # Returns /// * a result containing: @@ -486,111 +465,69 @@ where /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// /// let mut api_owner = Owner::new(wallet.clone()); - /// let amount = 2_000_000_000; - /// /// // Attempt to create a transaction using the 'default' account + /// let args = InitTxArgs { + /// src_acct_name: None, + /// amount: 2_000_000_000, + /// minimum_confirmations: 2, + /// max_outputs: 500, + /// num_change_outputs: 1, + /// selection_strategy_is_use_all: true, + /// message: Some("Have some Grins. Love, Yeastplume".to_owned()), + /// ..Default::default() + /// }; /// let result = api_owner.initiate_tx( - /// None, - /// amount, // amount - /// 10, // minimum confirmations - /// 500, // max outputs - /// 1, // num change outputs - /// true, // select all outputs - /// Some("Have some Grins. Love, Yeastplume".to_owned()), - /// None, // Use the default slate version - /// ); + /// args, + /// ); /// /// if let Ok(slate) = result { - /// // Send slate somehow - /// // ... - /// // Lock our outputs if we're happy the slate was (or is being) sent - /// api_owner.tx_lock_outputs(&slate); + /// // Send slate somehow + /// // ... + /// // Lock our outputs if we're happy the slate was (or is being) sent + /// api_owner.tx_lock_outputs(&slate); /// } /// ``` - pub fn initiate_tx( - &self, - src_acct_name: Option<&str>, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, - message: Option, - target_slate_version: Option, - ) -> Result { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let res = owner::initiate_tx( - &mut *w, - src_acct_name, - amount, - minimum_confirmations, - max_outputs, - num_change_outputs, - selection_strategy_is_use_all, - message, - target_slate_version, - self.doctest_mode, - ); - w.close()?; - res - } - - /// Estimates the amount to be locked and fee for the transaction without creating one. - /// - /// # Arguments - /// * As found in [`initiate_tx`](struct.Owner.html#method.initiate_tx) above. - /// - /// # Returns - /// * a result containing a - /// [`TxEstimation`](../grin_wallet_libwallet/types/struct.TxEstimation.html) - /// - /// # Example - /// Set up as in [new](struct.Owner.html#method.new) method above. - /// ``` - /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); - /// - /// let mut api_owner = Owner::new(wallet.clone()); - /// let amount = 2_000_000_000; - /// - /// // Estimate transaction using default account - /// let result = api_owner.estimate_initiate_tx( - /// None, - /// amount, // amount - /// 10, // minimum confirmations - /// 500, // max outputs - /// 1, // num change outputs - /// true, // select all outputs - /// ); - /// - /// if let Ok(est) = result { - /// // ... - /// } - /// ``` + pub fn initiate_tx(&self, args: InitTxArgs) -> Result { + let send_args = args.send_args.clone(); + let mut slate = { + let mut w = self.wallet.lock(); + w.open_with_credentials()?; + let slate = owner::initiate_tx(&mut *w, args, self.doctest_mode)?; + w.close()?; + slate + }; + // Helper functionality. If send arguments exist, attempt to send + match send_args { + Some(sa) => { + match sa.method.as_ref() { + "http" => { + slate = HTTPWalletCommAdapter::new().send_tx_sync(&sa.dest, &slate)? + } + "keybase" => { + //TODO: in case of keybase, the response might take 60s and leave the service hanging + slate = KeybaseWalletCommAdapter::new().send_tx_sync(&sa.dest, &slate)?; + } + _ => { + error!("unsupported payment method: {}", sa.method); + return Err(ErrorKind::ClientCallback( + "unsupported payment method".to_owned(), + ))?; + } + } + self.tx_lock_outputs(&slate)?; + let slate = match sa.finalize { + true => self.finalize_tx(&slate)?, + false => slate, + }; - pub fn estimate_initiate_tx( - &self, - src_acct_name: Option<&str>, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, - ) -> Result { - let mut w = self.wallet.lock(); - w.open_with_credentials()?; - let res = owner::estimate_initiate_tx( - &mut *w, - src_acct_name, - amount, - minimum_confirmations, - max_outputs, - num_change_outputs, - selection_strategy_is_use_all, - ); - w.close()?; - res + if sa.post_tx { + self.post_tx(&slate.tx, sa.fluff)?; + } + Ok(slate) + } + None => Ok(slate), + } } /// Locks the outputs associated with the inputs to the transaction in the given @@ -621,19 +558,19 @@ where /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// /// let mut api_owner = Owner::new(wallet.clone()); - /// let amount = 2_000_000_000; - /// - /// // Attempt to create a transaction using the 'default' account + /// let args = InitTxArgs { + /// src_acct_name: None, + /// amount: 2_000_000_000, + /// minimum_confirmations: 10, + /// max_outputs: 500, + /// num_change_outputs: 1, + /// selection_strategy_is_use_all: true, + /// message: Some("Remember to lock this when we're happy this is sent".to_owned()), + /// ..Default::default() + /// }; /// let result = api_owner.initiate_tx( - /// None, - /// amount, // amount - /// 10, // minimum confirmations - /// 500, // max outputs - /// 1, // num change outputs - /// true, // select all outputs - /// Some("Remember to lock when we're happy this is sent".to_owned()), - /// None, // Use the default slate version - /// ); + /// args, + /// ); /// /// if let Ok(slate) = result { /// // Send slate somehow @@ -679,19 +616,19 @@ where /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// /// let mut api_owner = Owner::new(wallet.clone()); - /// let amount = 2_000_000_000; - /// - /// // Attempt to create a transaction using the 'default' account + /// let args = InitTxArgs { + /// src_acct_name: None, + /// amount: 2_000_000_000, + /// minimum_confirmations: 10, + /// max_outputs: 500, + /// num_change_outputs: 1, + /// selection_strategy_is_use_all: true, + /// message: Some("Finalize this tx now".to_owned()), + /// ..Default::default() + /// }; /// let result = api_owner.initiate_tx( - /// None, - /// amount, // amount - /// 10, // minimum confirmations - /// 500, // max outputs - /// 1, // num change outputs - /// true, // select all outputs - /// Some("Finalize this tx now".to_owned()), - /// None, // Use the default slate version - /// ); + /// args, + /// ); /// /// if let Ok(slate) = result { /// // Send slate somehow @@ -736,19 +673,19 @@ where /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// /// let mut api_owner = Owner::new(wallet.clone()); - /// let amount = 2_000_000_000; - /// - /// // Attempt to create a transaction using the 'default' account + /// let args = InitTxArgs { + /// src_acct_name: None, + /// amount: 2_000_000_000, + /// minimum_confirmations: 10, + /// max_outputs: 500, + /// num_change_outputs: 1, + /// selection_strategy_is_use_all: true, + /// message: Some("Post this tx".to_owned()), + /// ..Default::default() + /// }; /// let result = api_owner.initiate_tx( - /// None, - /// amount, // amount - /// 10, // minimum confirmations - /// 500, // max outputs - /// 1, // num change outputs - /// true, // select all outputs - /// Some("Finalize this tx now".to_owned()), - /// None, // Use the default slate version - /// ); + /// args, + /// ); /// /// if let Ok(slate) = result { /// // Send slate somehow @@ -797,19 +734,19 @@ where /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// /// let mut api_owner = Owner::new(wallet.clone()); - /// let amount = 2_000_000_000; - /// - /// // Attempt to create a transaction using the 'default' account + /// let args = InitTxArgs { + /// src_acct_name: None, + /// amount: 2_000_000_000, + /// minimum_confirmations: 10, + /// max_outputs: 500, + /// num_change_outputs: 1, + /// selection_strategy_is_use_all: true, + /// message: Some("Cancel this tx".to_owned()), + /// ..Default::default() + /// }; /// let result = api_owner.initiate_tx( - /// None, - /// amount, // amount - /// 10, // minimum confirmations - /// 500, // max outputs - /// 1, // num change outputs - /// true, // select all outputs - /// Some("Cancel this tx".to_owned()), - /// None, // Use the default slate version - /// ); + /// args, + /// ); /// /// if let Ok(slate) = result { /// // Send slate somehow @@ -890,19 +827,19 @@ where /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); /// /// let mut api_owner = Owner::new(wallet.clone()); - /// let amount = 2_000_000_000; - /// - /// // Attempt to create a transaction using the 'default' account + /// let args = InitTxArgs { + /// src_acct_name: None, + /// amount: 2_000_000_000, + /// minimum_confirmations: 10, + /// max_outputs: 500, + /// num_change_outputs: 1, + /// selection_strategy_is_use_all: true, + /// message: Some("Just verify messages".to_owned()), + /// ..Default::default() + /// }; /// let result = api_owner.initiate_tx( - /// None, - /// amount, // amount - /// 10, // minimum confirmations - /// 500, // max outputs - /// 1, // num change outputs - /// true, // select all outputs - /// Some("Finalize this tx now".to_owned()), - /// None, // Use the default slate version - /// ); + /// args, + /// ); /// /// if let Ok(slate) = result { /// // Send slate somehow @@ -919,8 +856,40 @@ where owner::verify_slate_messages(slate) } - /// Attempt to restore contents of wallet - /// TODO: Full docs + /// Scans the entire UTXO set from the node, creating outputs for each scanned + /// output that matches the wallet's master seed. This function is intended to be called as part + /// of a recovery process (either from BIP32 phrase or backup seed files,) and will error if the + /// wallet is non-empty, i.e. contains any outputs at all. + /// + /// This operation scans the entire chain, and is expected to be time intensive. It is imperative + /// that no other processes should be trying to use the wallet at the same time this function is + /// running. + /// + /// A single [TxLogEntry](../grin_wallet_libwallet/types/struct.TxLogEntry.html) is created for + /// all non-coinbase outputs discovered and restored during this process. A separate entry + /// is created for each coinbase output. + /// + /// # Arguments + /// + /// * None + /// + /// # Returns + /// * `Ok(())` if successful + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + + /// # Example + /// Set up as in [`new`](struct.Owner.html#method.new) method above. + /// ``` + /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); + /// + /// let mut api_owner = Owner::new(wallet.clone()); + /// let result = api_owner.restore(); + /// + /// if let Ok(_) = result { + /// // Wallet outputs should be consistent with what's on chain + /// // ... + /// } + /// ``` pub fn restore(&self) -> Result<(), Error> { let mut w = self.wallet.lock(); w.open_with_credentials()?; @@ -929,8 +898,52 @@ where res } - /// Attempt to check and fix the contents of the wallet - /// TODO: Full docs + /// Scans the entire UTXO set from the node, identify which outputs belong to the given wallet + /// update the wallet state to be consistent with what's currently in the UTXO set. + /// + /// This function can be used to repair wallet state, particularly by restoring outputs that may + /// be missing if the wallet owner has cancelled transactions locally that were then successfully + /// posted to the chain. + /// + /// This operation scans the entire chain, and is expected to be time intensive. It is imperative + /// that no other processes should be trying to use the wallet at the same time this function is + /// running. + /// + /// When an output is found that doesn't exist in the wallet, a corresponding + /// [TxLogEntry](../grin_wallet_libwallet/types/struct.TxLogEntry.html) is created. + /// + /// # Arguments + /// + /// * `delete_unconfirmed` - if `false`, the check_repair process will be non-destructive, and + /// mostly limited to restoring missing outputs. It will leave unconfirmed transaction logs entries + /// and unconfirmed outputs intact. If `true`, the process will unlock all locked outputs, + /// restore all missing outputs, and mark any outputs that have been marked 'Spent' but are still + /// in the UTXO set as 'Unspent' (as can happen during a fork). It will also attempt to cancel any + /// transaction log entries associated with any locked outputs or outputs incorrectly marked 'Spent'. + /// Note this completely removes all outstanding transactions, so users should be very aware what + /// will happen if this flag is set. Note that if transactions/outputs are removed that later + /// confirm on the chain, another call to this function will restore them. + /// + /// # Returns + /// * `Ok(())` if successful + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + + /// # Example + /// Set up as in [`new`](struct.Owner.html#method.new) method above. + /// ``` + /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); + /// + /// let mut api_owner = Owner::new(wallet.clone()); + /// let result = api_owner.check_repair( + /// false, + /// ); + /// + /// if let Ok(_) = result { + /// // Wallet outputs should be consistent with what's on chain + /// // ... + /// } + /// ``` + pub fn check_repair(&self, delete_unconfirmed: bool) -> Result<(), Error> { let mut w = self.wallet.lock(); w.open_with_credentials()?; @@ -939,9 +952,44 @@ where res } - /// Retrieve current height from node - // TODO: Should return u64 as string - pub fn node_height(&self) -> Result<(u64, bool), Error> { + /// Retrieves the last known height known by the wallet. This is determined as follows: + /// * If the wallet can successfully contact its configured node, the reported node + /// height is returned, and the `updated_from_node` field in the response is `true` + /// * If the wallet cannot contact the node, this function returns the maximum height + /// of all outputs contained within the wallet, and the `updated_from_node` fields + /// in the response is set to false. + /// + /// Clients should generally ensure the `updated_from_node` field is returned as + /// `true` before assuming the height for any operation. + /// + /// # Arguments + /// + /// * None + /// + /// # Returns + /// * Ok with a [`NodeHeightResult`](../grin_wallet_libwallet/types/struct.NodeHeightResult.html) + /// if successful. If the height result was obtained from the configured node, + /// `updated_from_node` will be set to `true` + /// * or [`libwallet::Error`](../grin_wallet_libwallet/struct.Error.html) if an error is encountered. + /// + /// # Example + /// Set up as in [`new`](struct.Owner.html#method.new) method above. + /// ``` + /// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config); + /// + /// let api_owner = Owner::new(wallet.clone()); + /// let result = api_owner.node_height(); + /// + /// if let Ok(node_height_result) = result { + /// if node_height_result.updated_from_node { + /// //we can assume node_height_result.height is relatively safe to use + /// + /// } + /// //... + /// } + /// ``` + + pub fn node_height(&self) -> Result { let mut w = self.wallet.lock(); w.open_with_credentials()?; let res = owner::node_height(&mut *w); @@ -970,7 +1018,7 @@ macro_rules! doctest_helper_setup_doc_env { use api::Owner; use config::WalletConfig; use impls::{HTTPNodeClient, LMDBBackend, WalletSeed}; - use libwallet::types::WalletBackend; + use libwallet::types::{InitTxArgs, WalletBackend}; let dir = tempdir().map_err(|e| format!("{:#?}", e)).unwrap(); let dir = dir diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index c0bf1438f..1c9cdc2b6 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -19,8 +19,8 @@ use crate::core::core::Transaction; use crate::keychain::{Identifier, Keychain}; use crate::libwallet::slate::Slate; use crate::libwallet::types::{ - AcctPathMapping, NodeClient, OutputCommitMapping, TxEstimation, TxLogEntry, WalletBackend, - WalletInfo, + AcctPathMapping, InitTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, TxLogEntry, + WalletBackend, WalletInfo, }; use crate::libwallet::ErrorKind; use crate::Owner; @@ -321,7 +321,19 @@ pub trait OwnerRpc { { "jsonrpc": "2.0", "method": "initiate_tx", - "params": [null, 6000000000, 2, 500, 1, true, "my message", null], + "params": { + "args": { + "src_acct_name": null, + "amount": "6000000000", + "minimum_confirmations": 2, + "max_outputs": 500, + "num_change_outputs": 1, + "selection_strategy_is_use_all": true, + "message": "my message", + "target_slate_version": null, + "send_args": null + } + }, "id": 1 } # "# @@ -388,62 +400,11 @@ pub trait OwnerRpc { ``` */ - fn initiate_tx( - &self, - src_acct_name: Option, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, - message: Option, - target_slate_version: Option, - ) -> Result; - - /** - Networked version of [Owner::estimate_initiate_tx](struct.Owner.html#method.estimate_initiate_tx). - - - ``` - # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( - # r#" - { - "jsonrpc": "2.0", - "method": "estimate_initiate_tx", - "params": [null, 6000000000, 2, 500, 1, true], - "id": 1 - } - # "# - # , - # r#" - { - "id": 1, - "jsonrpc": "2.0", - "result": { - "Ok": { - "total": "60000000000", - "fee": "8000000" - } - } - } - # "# - # ,4, false, false, false); - ``` - */ - fn estimate_initiate_tx( - &self, - src_acct_name: Option, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, - ) -> Result; + fn initiate_tx(&self, args: InitTxArgs) -> Result; /** Networked version of [Owner::tx_lock_outputs](struct.Owner.html#method.tx_lock_outputs). - ``` # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( # r#" @@ -1029,17 +990,17 @@ pub trait OwnerRpc { "id": 1, "jsonrpc": "2.0", "result": { - "Ok": [ - 5, - true - ] + "Ok": { + "height": "5", + "updated_from_node": true + } } } # "# # , 5, false, false, false); ``` */ - fn node_height(&self) -> Result<(u64, bool), ErrorKind>; + fn node_height(&self) -> Result; } impl OwnerRpc for Owner @@ -1087,50 +1048,8 @@ where .map_err(|e| e.kind()) } - fn initiate_tx( - &self, - src_acct_name: Option, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, - message: Option, - target_slate_version: Option, - ) -> Result { - Owner::initiate_tx( - self, - src_acct_name.as_ref().map(String::as_str), - amount, - minimum_confirmations, - max_outputs, - num_change_outputs, - selection_strategy_is_use_all, - message, - target_slate_version, - ) - .map_err(|e| e.kind()) - } - - fn estimate_initiate_tx( - &self, - src_acct_name: Option, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, - ) -> Result { - Owner::estimate_initiate_tx( - self, - src_acct_name.as_ref().map(String::as_str), - amount, - minimum_confirmations, - max_outputs, - num_change_outputs, - selection_strategy_is_use_all, - ) - .map_err(|e| e.kind()) + fn initiate_tx(&self, args: InitTxArgs) -> Result { + Owner::initiate_tx(self, args).map_err(|e| e.kind()) } fn finalize_tx(&self, mut slate: Slate) -> Result { @@ -1165,7 +1084,7 @@ where Owner::check_repair(self, delete_unconfirmed).map_err(|e| e.kind()) } - fn node_height(&self) -> Result<(u64, bool), ErrorKind> { + fn node_height(&self) -> Result { Owner::node_height(self).map_err(|e| e.kind()) } } @@ -1244,16 +1163,16 @@ pub fn run_doctest_owner( let amount = 60_000_000_000; let mut w = wallet1.lock(); w.open_with_credentials().unwrap(); - let mut slate = api_impl::owner::initiate_tx( - &mut *w, None, // account - amount, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, None, true, - ) - .unwrap(); + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api_impl::owner::initiate_tx(&mut *w, args, true).unwrap(); { let mut w2 = wallet2.lock(); w2.open_with_credentials().unwrap(); diff --git a/controller/src/command.rs b/controller/src/command.rs index dc28a2ce0..fb33470e4 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -35,7 +35,7 @@ use crate::impls::{ LMDBBackend, NullWalletCommAdapter, }; use crate::impls::{HTTPNodeClient, WalletSeed}; -use crate::libwallet::types::{NodeClient, WalletInst}; +use crate::libwallet::types::{InitTxArgs, NodeClient, WalletInst}; use crate::{controller, display}; /// Arguments common to all wallet commands @@ -247,31 +247,35 @@ pub fn send( let strategies = vec!["smallest", "all"] .into_iter() .map(|strategy| { - let est = api - .estimate_initiate_tx( - None, - args.amount, - args.minimum_confirmations, - args.max_outputs, - args.change_outputs, - strategy == "all", - ) - .unwrap(); - (strategy, est.total, est.fee) + let init_args = InitTxArgs { + src_acct_name: None, + amount: args.amount, + minimum_confirmations: args.minimum_confirmations, + max_outputs: args.max_outputs as u32, + num_change_outputs: args.change_outputs as u32, + selection_strategy_is_use_all: strategy == "all", + estimate_only: Some(true), + ..Default::default() + }; + let slate = api.initiate_tx(init_args).unwrap(); + (strategy, slate.amount, slate.fee) }) .collect(); display::estimate(args.amount, strategies, dark_scheme); } else { - let result = api.initiate_tx( - None, - args.amount, - args.minimum_confirmations, - args.max_outputs, - args.change_outputs, - args.selection_strategy == "all", - args.message.clone(), - args.target_slate_version, - ); + let init_args = InitTxArgs { + src_acct_name: None, + amount: args.amount, + minimum_confirmations: args.minimum_confirmations, + max_outputs: args.max_outputs as u32, + num_change_outputs: args.change_outputs as u32, + selection_strategy_is_use_all: args.selection_strategy == "all", + message: args.message.clone(), + target_slate_version: args.target_slate_version, + send_args: None, + ..Default::default() + }; + let result = api.initiate_tx(init_args); let mut slate = match result { Ok(s) => { info!( @@ -421,9 +425,9 @@ pub fn outputs( dark_scheme: bool, ) -> Result<(), Error> { controller::owner_single_use(wallet.clone(), |api| { - let (height, _) = api.node_height()?; + let res = api.node_height()?; let (validated, outputs) = api.retrieve_outputs(g_args.show_spent, true, None)?; - display::outputs(&g_args.account, height, validated, outputs, dark_scheme)?; + display::outputs(&g_args.account, res.height, validated, outputs, dark_scheme)?; Ok(()) })?; Ok(()) @@ -441,12 +445,12 @@ pub fn txs( dark_scheme: bool, ) -> Result<(), Error> { controller::owner_single_use(wallet.clone(), |api| { - let (height, _) = api.node_height()?; + let res = api.node_height()?; let (validated, txs) = api.retrieve_txs(true, args.id, None)?; let include_status = !args.id.is_some(); display::txs( &g_args.account, - height, + res.height, validated, &txs, include_status, @@ -456,7 +460,7 @@ pub fn txs( // inputs/outputs and messages if args.id.is_some() { let (_, outputs) = api.retrieve_outputs(true, false, args.id)?; - display::outputs(&g_args.account, height, validated, outputs, dark_scheme)?; + display::outputs(&g_args.account, res.height, validated, outputs, dark_scheme)?; // should only be one here, but just in case for tx in txs { display::tx_messages(&tx, dark_scheme)?; diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 664953bd1..1fff2e70d 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -23,7 +23,8 @@ use crate::impls::{FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCo use crate::keychain::Keychain; use crate::libwallet::slate::Slate; use crate::libwallet::types::{ - CbData, NodeClient, OutputCommitMapping, SendTXArgs, TxLogEntry, WalletBackend, WalletInfo, + CbData, InitTxArgs, NodeClient, OutputCommitMapping, SendTXArgs, TxLogEntry, WalletBackend, + WalletInfo, }; use crate::libwallet::{Error, ErrorKind}; use crate::util::to_base64; @@ -289,7 +290,8 @@ where _req: &Request, api: Owner, ) -> Result<(u64, bool), Error> { - api.node_height() + let res = api.node_height()?; + Ok((res.height, res.updated_from_node)) } fn handle_get_request(&self, req: &Request) -> Result, Error> { @@ -299,7 +301,7 @@ where match req .uri() .path() - .trim_right_matches("/") + .trim_end_matches("/") .rsplit("/") .next() .unwrap() @@ -320,16 +322,19 @@ where api: Owner, ) -> Box + Send> { Box::new(parse_body(req).and_then(move |args: SendTXArgs| { - let result = api.initiate_tx( - None, - args.amount, - args.minimum_confirmations, - args.max_outputs, - args.num_change_outputs, - args.selection_strategy_is_use_all, - args.message, - args.target_slate_version, - ); + let init_args = InitTxArgs { + src_acct_name: None, + amount: args.amount, + minimum_confirmations: args.minimum_confirmations, + max_outputs: args.max_outputs as u32, + num_change_outputs: args.num_change_outputs as u32, + selection_strategy_is_use_all: args.selection_strategy_is_use_all, + message: args.message.clone(), + target_slate_version: args.target_slate_version, + send_args: None, + ..Default::default() + }; + let result = api.initiate_tx(init_args); let mut slate = match result { Ok(s) => { info!( @@ -554,7 +559,7 @@ where match req .uri() .path() - .trim_right_matches("/") + .trim_end_matches("/") .rsplit("/") .next() .unwrap() @@ -688,7 +693,7 @@ where match req .uri() .path() - .trim_right_matches("/") + .trim_end_matches("/") .rsplit("/") .next() .unwrap() diff --git a/controller/tests/accounts.rs b/controller/tests/accounts.rs index bb5ed5463..1074a5694 100644 --- a/controller/tests/accounts.rs +++ b/controller/tests/accounts.rs @@ -26,6 +26,7 @@ use self::core::global::ChainTypes; use self::keychain::{ExtKeychain, Keychain}; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use libwallet::types::InitTxArgs; use std::fs; use std::thread; use std::time::Duration; @@ -179,14 +180,16 @@ fn accounts_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { } wallet::controller::owner_single_use(wallet1.clone(), |api| { - let mut slate = api.initiate_tx( - None, reward, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: reward, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api.initiate_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate)?; api.tx_lock_outputs(&slate)?; slate = api.finalize_tx(&slate)?; diff --git a/controller/tests/check.rs b/controller/tests/check.rs index d539b5efe..79e76f2a7 100644 --- a/controller/tests/check.rs +++ b/controller/tests/check.rs @@ -28,7 +28,7 @@ use self::keychain::ExtKeychain; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient, WalletProxy}; use impls::FileWalletCommAdapter; -use libwallet::types::WalletInst; +use libwallet::types::{InitTxArgs, WalletInst}; use std::fs; use std::thread; use std::time::Duration; @@ -178,16 +178,16 @@ fn check_repair_impl(test_dir: &str) -> Result<(), libwallet::Error> { // perform a transaction, but don't let it finish wallet::controller::owner_single_use(wallet1.clone(), |api| { // send to send - let mut slate = api.initiate_tx( - None, - reward * 2, // amount - cm, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, // optional message - None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: reward * 2, + minimum_confirmations: cm, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api.initiate_tx(args)?; // output tx file let file_adapter = FileWalletCommAdapter::new(); let send_file = format!("{}/part_tx_1.tx", test_dir); diff --git a/controller/tests/file.rs b/controller/tests/file.rs index c4474fb19..3d923925e 100644 --- a/controller/tests/file.rs +++ b/controller/tests/file.rs @@ -31,6 +31,8 @@ use std::fs; use std::thread; use std::time::Duration; +use grin_wallet_libwallet::types::InitTxArgs; + use serde_json; fn clean_output_dir(test_dir: &str) { @@ -105,16 +107,17 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { assert_eq!(wallet1_info.last_confirmed_height, bh); assert_eq!(wallet1_info.total, bh * reward); // send to send - let mut slate = api.initiate_tx( - Some("mining"), - reward * 2, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - Some(message.to_owned()), // optional message - None, - )?; + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + message: Some(message.to_owned()), + ..Default::default() + }; + let mut slate = api.initiate_tx(args)?; // output tx file let file_adapter = FileWalletCommAdapter::new(); file_adapter.send_tx_async(&send_file, &mut slate)?; diff --git a/controller/tests/repost.rs b/controller/tests/repost.rs index db624bf80..f3f5b0b18 100644 --- a/controller/tests/repost.rs +++ b/controller/tests/repost.rs @@ -26,6 +26,7 @@ use self::core::global; use self::core::global::ChainTypes; use self::keychain::ExtKeychain; use self::libwallet::slate::Slate; +use self::libwallet::types::InitTxArgs; use impls::test_framework::{self, LocalWalletClient, WalletProxy}; use impls::FileWalletCommAdapter; use std::fs; @@ -103,16 +104,16 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { assert_eq!(wallet1_info.last_confirmed_height, bh); assert_eq!(wallet1_info.total, bh * reward); // send to send - let mut slate = api.initiate_tx( - Some("mining"), - reward * 2, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, - None, - )?; + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api.initiate_tx(args)?; // output tx file let file_adapter = FileWalletCommAdapter::new(); file_adapter.send_tx_async(&send_file, &mut slate)?; @@ -200,16 +201,16 @@ fn file_repost_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - let slate_i = sender_api.initiate_tx( - None, - amount * 2, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, - None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.initiate_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&mut slate)?; diff --git a/controller/tests/restore.rs b/controller/tests/restore.rs index 265543ebe..0b3874ab4 100644 --- a/controller/tests/restore.rs +++ b/controller/tests/restore.rs @@ -26,7 +26,7 @@ use self::core::global; use self::core::global::ChainTypes; use self::keychain::{ExtKeychain, Identifier, Keychain}; use self::libwallet::slate::Slate; -use self::libwallet::types::AcctPathMapping; +use self::libwallet::types::{AcctPathMapping, InitTxArgs}; use impls::test_framework::{self, LocalWalletClient, WalletProxy}; use std::fs; use std::sync::atomic::Ordering; @@ -237,14 +237,16 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { let mut slate = Slate::blank(1); wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - let slate_i = sender_api.initiate_tx( - None, amount, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.initiate_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; @@ -258,16 +260,16 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { // Send some to wallet 3 wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - let slate_i = sender_api.initiate_tx( - None, - amount * 2, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, - None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.initiate_tx(args)?; slate = client1.send_tx_slate_direct("wallet3", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; @@ -281,16 +283,16 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { // Wallet3 to wallet 2 wallet::controller::owner_single_use(wallet3.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - let slate_i = sender_api.initiate_tx( - None, - amount * 3, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, - None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: amount * 3, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.initiate_tx(args)?; slate = client3.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; @@ -310,16 +312,16 @@ fn setup_restore(test_dir: &str) -> Result<(), libwallet::Error> { // Wallet3 to wallet 2 again (to another account) wallet::controller::owner_single_use(wallet3.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - let slate_i = sender_api.initiate_tx( - None, - amount * 3, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, - None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: amount * 3, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.initiate_tx(args)?; slate = client3.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; diff --git a/controller/tests/self_send.rs b/controller/tests/self_send.rs index 37358c249..7c1f5bc1a 100644 --- a/controller/tests/self_send.rs +++ b/controller/tests/self_send.rs @@ -26,6 +26,7 @@ use self::core::global::ChainTypes; use self::keychain::ExtKeychain; use grin_wallet_libwallet as libwallet; use impls::test_framework::{self, LocalWalletClient, WalletProxy}; +use libwallet::types::InitTxArgs; use std::fs; use std::thread; use std::time::Duration; @@ -86,16 +87,16 @@ fn self_send_test_impl(test_dir: &str) -> Result<(), libwallet::Error> { assert_eq!(wallet1_info.last_confirmed_height, bh); assert_eq!(wallet1_info.total, bh * reward); // send to send - let mut slate = api.initiate_tx( - Some("mining"), - reward * 2, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, - None, - )?; + let args = InitTxArgs { + src_acct_name: Some("mining".to_owned()), + amount: reward * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let mut slate = api.initiate_tx(args)?; api.tx_lock_outputs(&slate)?; // Send directly to self wallet::controller::foreign_single_use(wallet1.clone(), |api| { diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index db5e62fed..640f6e206 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -27,7 +27,7 @@ use self::core::global; use self::core::global::ChainTypes; use self::keychain::ExtKeychain; use self::libwallet::slate::Slate; -use self::libwallet::types::OutputStatus; +use self::libwallet::types::{InitTxArgs, OutputStatus}; use impls::test_framework::{self, LocalWalletClient, WalletProxy}; use std::fs; use std::thread; @@ -100,14 +100,16 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { let mut slate = Slate::blank(1); wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - let slate_i = sender_api.initiate_tx( - None, amount, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.initiate_tx(args)?; // Check we are creating a tx with the expected lock_height of 0. // We will check this produces a Plain kernel later. @@ -250,26 +252,32 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { // Estimate fee and locked amount for a transaction wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { - let est = sender_api.estimate_initiate_tx( - None, - amount * 2, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - )?; - assert_eq!(est.total, 600_000_000_000); + let init_args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + estimate_only: Some(true), + ..Default::default() + }; + let est = sender_api.initiate_tx(init_args)?; + assert_eq!(est.amount, 600_000_000_000); assert_eq!(est.fee, 4_000_000); - let est = sender_api.estimate_initiate_tx( - None, - amount * 2, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - false, // select the smallest amount of outputs - )?; - assert_eq!(est.total, 180_000_000_000); + let init_args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, //select smallest number + estimate_only: Some(true), + ..Default::default() + }; + let est = sender_api.initiate_tx(init_args)?; + assert_eq!(est.amount, 180_000_000_000); assert_eq!(est.fee, 6_000_000); Ok(()) @@ -279,16 +287,16 @@ fn basic_transaction_api(test_dir: &str) -> Result<(), libwallet::Error> { // the stored transaction instead wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - let slate_i = sender_api.initiate_tx( - None, - amount * 2, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, - None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: amount * 2, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = sender_api.initiate_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; @@ -378,14 +386,17 @@ fn tx_rollback(test_dir: &str) -> Result<(), libwallet::Error> { let mut slate = Slate::blank(1); wallet::controller::owner_single_use(wallet1.clone(), |sender_api| { // note this will increment the block count as part of the transaction "Posting" - let slate_i = sender_api.initiate_tx( - None, amount, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, None, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount: amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + + let slate_i = sender_api.initiate_tx(args)?; slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(&slate)?; slate = sender_api.finalize_tx(&slate)?; diff --git a/impls/src/test_framework/mod.rs b/impls/src/test_framework/mod.rs index 485d14b92..f6e5954b7 100644 --- a/impls/src/test_framework/mod.rs +++ b/impls/src/test_framework/mod.rs @@ -23,7 +23,7 @@ use crate::keychain; use crate::libwallet; use crate::libwallet::api_impl::{foreign, owner}; use crate::libwallet::types::{ - BlockFees, CbData, NodeClient, WalletBackend, WalletInfo, WalletInst, + BlockFees, CbData, InitTxArgs, NodeClient, WalletBackend, WalletInfo, WalletInst, }; use crate::lmdb_wallet::LMDBBackend; use crate::util; @@ -196,15 +196,16 @@ where let slate = { let mut w = wallet.lock(); w.open_with_credentials()?; - let slate_i = owner::initiate_tx( - &mut *w, None, // account - amount, // amount - 2, // minimum confirmations - 500, // max outputs - 1, // num change outputs - true, // select all outputs - None, None, test_mode, - )?; + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + ..Default::default() + }; + let slate_i = owner::initiate_tx(&mut *w, args, test_mode)?; let slate = client.send_tx_slate_direct(dest, &slate_i)?; owner::tx_lock_outputs(&mut *w, &slate)?; let slate = owner::finalize_tx(&mut *w, &slate)?; diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 71e9e0b4d..8b763fee9 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -25,8 +25,8 @@ use crate::grin_keychain::{Identifier, Keychain}; use crate::internal::{keys, selection, tx, updater}; use crate::slate::Slate; use crate::types::{ - AcctPathMapping, NodeClient, OutputCommitMapping, TxEstimation, TxLogEntry, TxWrapper, - WalletBackend, WalletInfo, + AcctPathMapping, InitTxArgs, NodeClient, NodeHeightResult, OutputCommitMapping, TxLogEntry, + TxWrapper, WalletBackend, WalletInfo, }; use crate::{Error, ErrorKind}; @@ -137,14 +137,7 @@ where /// Initiate tx as sender pub fn initiate_tx( w: &mut T, - src_acct_name: Option<&str>, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, - message: Option, - target_slate_version: Option, + args: InitTxArgs, use_test_rng: bool, ) -> Result where @@ -152,9 +145,9 @@ where C: NodeClient, K: Keychain, { - let parent_key_id = match src_acct_name { + let parent_key_id = match args.src_acct_name { Some(d) => { - let pm = w.get_acct_path(d.to_owned())?; + let pm = w.get_acct_path(d)?; match pm { Some(p) => p.path, None => w.parent_key_id(), @@ -163,7 +156,7 @@ where None => w.parent_key_id(), }; - let message = match message { + let message = match args.message { Some(mut m) => { m.truncate(USER_MESSAGE_MAX_LEN); Some(m) @@ -171,15 +164,32 @@ where None => None, }; - let mut slate = tx::new_tx_slate(&mut *w, amount, 2, use_test_rng)?; + let mut slate = tx::new_tx_slate(&mut *w, args.amount, 2, use_test_rng)?; + + // if we just want to estimate, don't save a context, just send the results + // back + if let Some(true) = args.estimate_only { + let (total, fee) = tx::estimate_send_tx( + &mut *w, + args.amount, + args.minimum_confirmations, + args.max_outputs as usize, + args.num_change_outputs as usize, + args.selection_strategy_is_use_all, + &parent_key_id, + )?; + slate.amount = total; + slate.fee = fee; + return Ok(slate); + } let context = tx::add_inputs_to_slate( &mut *w, &mut slate, - minimum_confirmations, - max_outputs, - num_change_outputs, - selection_strategy_is_use_all, + args.minimum_confirmations, + args.max_outputs as usize, + args.num_change_outputs as usize, + args.selection_strategy_is_use_all, &parent_key_id, 0, message, @@ -193,49 +203,12 @@ where batch.save_private_context(slate.id.as_bytes(), &context)?; batch.commit()?; } - if let Some(v) = target_slate_version { + if let Some(v) = args.target_slate_version { slate.version_info.orig_version = v; } Ok(slate) } -/// Estimate -pub fn estimate_initiate_tx( - w: &mut T, - src_acct_name: Option<&str>, - amount: u64, - minimum_confirmations: u64, - max_outputs: usize, - num_change_outputs: usize, - selection_strategy_is_use_all: bool, -) -> Result -where - T: WalletBackend, - C: NodeClient, - K: Keychain, -{ - let parent_key_id = match src_acct_name { - Some(d) => { - let pm = w.get_acct_path(d.to_owned())?; - match pm { - Some(p) => p.path, - None => w.parent_key_id(), - } - } - None => w.parent_key_id(), - }; - let (total, fee) = tx::estimate_send_tx( - &mut *w, - amount, - minimum_confirmations, - max_outputs, - num_change_outputs, - selection_strategy_is_use_all, - &parent_key_id, - )?; - Ok(TxEstimation { total, fee }) -} - /// Lock sender outputs pub fn tx_lock_outputs(w: &mut T, slate: &Slate) -> Result<(), Error> where @@ -348,7 +321,7 @@ where } /// node height -pub fn node_height(w: &mut T) -> Result<(u64, bool), Error> +pub fn node_height(w: &mut T) -> Result where T: WalletBackend, C: NodeClient, @@ -356,14 +329,20 @@ where { let res = w.w2n_client().get_chain_height(); match res { - Ok(height) => Ok((height, true)), + Ok(height) => Ok(NodeHeightResult { + height, + updated_from_node: true, + }), Err(_) => { let outputs = retrieve_outputs(w, true, false, None)?; let height = match outputs.1.iter().map(|m| m.output.height).max() { Some(height) => height, None => 0, }; - Ok((height, false)) + Ok(NodeHeightResult { + height, + updated_from_node: false, + }) } } } diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 2e41d448f..95cdeb369 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -373,29 +373,6 @@ impl fmt::Display for OutputStatus { } } -/// Map Outputdata to commits -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct OutputCommitMapping { - /// Output Data - pub output: OutputData, - /// The commit - #[serde( - serialize_with = "secp_ser::as_hex", - deserialize_with = "secp_ser::commitment_from_hex" - )] - pub commit: pedersen::Commitment, -} - -/// Transaction Estimate -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct TxEstimation { - /// Total amount to be locked - #[serde(with = "secp_ser::string_or_u64")] - pub total: u64, - /// Transaction Fee - #[serde(with = "secp_ser::string_or_u64")] - pub fee: u64, -} #[derive(Serialize, Deserialize, Clone, Debug)] /// Holds the context for a single aggsig transaction pub struct Context { @@ -545,37 +522,6 @@ impl<'de> serde::de::Visitor<'de> for BlockIdentifierVisitor { } } -/// Fees in block to use for coinbase amount calculation -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct BlockFees { - /// fees - #[serde(with = "secp_ser::string_or_u64")] - pub fees: u64, - /// height - #[serde(with = "secp_ser::string_or_u64")] - pub height: u64, - /// key id - pub key_id: Option, -} - -impl BlockFees { - /// return key id - pub fn key_id(&self) -> Option { - self.key_id.clone() - } -} - -/// Response to build a coinbase output. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct CbData { - /// Output - pub output: Output, - /// Kernel - pub kernel: TxKernel, - /// Key Id - pub key_id: Option, -} - /// a contained wallet info struct, so automated tests can parse wallet info /// can add more fields here over time as needed #[derive(Serialize, Eq, PartialEq, Deserialize, Debug, Clone)] @@ -752,7 +698,11 @@ pub struct TxWrapper { pub tx_hex: String, } +// Types to facilitate API arguments and serialization + /// Send TX API Args +// TODO: This is here to ensure the legacy V1 API remains intact +// remove this when v1 api is removed #[derive(Clone, Serialize, Deserialize)] pub struct SendTXArgs { /// amount to send @@ -774,3 +724,141 @@ pub struct SendTXArgs { /// Optional slate version to target when sending pub target_slate_version: Option, } + +/// V2 Init / Send TX API Args +#[derive(Clone, Serialize, Deserialize)] +pub struct InitTxArgs { + /// The human readable account name from which to draw outputs + /// for the transaction, overriding whatever the active account is as set via the + /// [`set_active_account`](../grin_wallet_api/owner/struct.Owner.html#method.set_active_account) method. + pub src_acct_name: Option, + #[serde(with = "secp_ser::string_or_u64")] + /// The amount to send, in nanogrins. (`1 G = 1_000_000_000nG`) + pub amount: u64, + #[serde(with = "secp_ser::string_or_u64")] + /// The minimum number of confirmations an output + /// should have in order to be included in the transaction. + pub minimum_confirmations: u64, + /// By default, the wallet selects as many inputs as possible in a + /// transaction, to reduce the Output set and the fees. The wallet will attempt to spend + /// include up to `max_outputs` in a transaction, however if this is not enough to cover + /// the whole amount, the wallet will include more outputs. This parameter should be considered + /// a soft limit. + pub max_outputs: u32, + /// The target number of change outputs to create in the transaction. + /// The actual number created will be `num_change_outputs` + whatever remainder is needed. + pub num_change_outputs: u32, + /// If `true`, attempt to use up as many outputs as + /// possible to create the transaction, up the 'soft limit' of `max_outputs`. This helps + /// to reduce the size of the UTXO set and the amount of data stored in the wallet, and + /// minimizes fees. This will generally result in many inputs and a large change output(s), + /// usually much larger than the amount being sent. If `false`, the transaction will include + /// as many outputs as are needed to meet the amount, (and no more) starting with the smallest + /// value outputs. + pub selection_strategy_is_use_all: bool, + /// An optional participant message to include alongside the sender's public + /// ParticipantData within the slate. This message will include a signature created with the + /// sender's private excess value, and will be publically verifiable. Note this message is for + /// the convenience of the participants during the exchange; it is not included in the final + /// transaction sent to the chain. The message will be truncated to 256 characters. + pub message: Option, + /// Optionally set the output target slate version (acceptable + /// down to the minimum slate version compatible with the current. If `None` the slate + /// is generated with the latest version. + pub target_slate_version: Option, + /// If true, just return an estimate of the resulting slate, containing fees and amounts + /// locked without actually locking outputs or creating the transaction. Note if this is set to + /// 'true', the amount field in the slate will contain the total amount locked, not the provided + /// transaction amount + pub estimate_only: Option, + /// Sender arguments. If present, the underlying function will also attempt to send the + /// transaction to a destination and optionally finalize the result + pub send_args: Option, +} + +/// Send TX API Args, for convenience functionality that inits the transaction and sends +/// in one go +#[derive(Clone, Serialize, Deserialize)] +pub struct InitTxSendArgs { + /// The transaction method. Can currently be 'http' or 'keybase'. + pub method: String, + /// The destination, contents will depend on the particular method + pub dest: String, + /// Whether to finalize the result immediately if the send was successful + pub finalize: bool, + /// Whether to post the transasction if the send and finalize were successful + pub post_tx: bool, + /// Whether to use dandelion when posting. If false, skip the dandelion relay + pub fluff: bool, +} + +impl Default for InitTxArgs { + fn default() -> InitTxArgs { + InitTxArgs { + src_acct_name: None, + amount: 0, + minimum_confirmations: 10, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: true, + message: None, + target_slate_version: None, + estimate_only: Some(false), + send_args: None, + } + } +} + +/// Fees in block to use for coinbase amount calculation +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct BlockFees { + /// fees + #[serde(with = "secp_ser::string_or_u64")] + pub fees: u64, + /// height + #[serde(with = "secp_ser::string_or_u64")] + pub height: u64, + /// key id + pub key_id: Option, +} + +impl BlockFees { + /// return key id + pub fn key_id(&self) -> Option { + self.key_id.clone() + } +} + +/// Response to build a coinbase output. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CbData { + /// Output + pub output: Output, + /// Kernel + pub kernel: TxKernel, + /// Key Id + pub key_id: Option, +} + +/// Map Outputdata to commits +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct OutputCommitMapping { + /// Output Data + pub output: OutputData, + /// The commit + #[serde( + serialize_with = "secp_ser::as_hex", + deserialize_with = "secp_ser::commitment_from_hex" + )] + pub commit: pedersen::Commitment, +} + +/// Node height result +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct NodeHeightResult { + /// Last known height + #[serde(with = "secp_ser::string_or_u64")] + pub height: u64, + /// Whether this height was updated from the node + pub updated_from_node: bool, +}