diff --git a/api/src/owner.rs b/api/src/owner.rs index 92b6680da..b4a38ccd8 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -446,6 +446,8 @@ where /// the transaction log entry of id `i`. /// * `tx_slate_id` - If `Some(uuid)`, only return transactions associated with /// the given [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html) uuid. + /// * `confirmed_height` - If `Some(block_height)`, only return transactions confirmed at + /// the given block height. /// /// # Returns /// * `(bool, Vec, tx_slate_id: Option, + confirmed_height: Option, ) -> Result<(bool, Vec), Error> { let tx = { let t = self.status_tx.lock(); @@ -495,6 +499,7 @@ where refresh_from_node, tx_id, tx_slate_id, + confirmed_height, )?; if self.doctest_mode { res.1 = res @@ -1158,9 +1163,10 @@ where /// let update_from_node = true; /// let tx_id = None; /// let tx_slate_id = None; + /// let confirmed_height = None; /// /// // Return all TxLogEntries - /// let result = api_owner.retrieve_txs(None, update_from_node, tx_id, tx_slate_id); + /// let result = api_owner.retrieve_txs(None, update_from_node, tx_id, tx_slate_id, confirmed_height); /// /// if let Ok((was_updated, tx_log_entries)) = result { /// let stored_tx = api_owner.get_stored_tx(None, Some(tx_log_entries[0].id), None).unwrap(); diff --git a/api/src/owner_rpc.rs b/api/src/owner_rpc.rs index 6e61195e3..41dedef85 100644 --- a/api/src/owner_rpc.rs +++ b/api/src/owner_rpc.rs @@ -236,7 +236,8 @@ pub trait OwnerRpc { "token": "d202964900000000d302964900000000d402964900000000d502964900000000", "refresh_from_node": true, "tx_id": null, - "tx_slate_id": null + "tx_slate_id": null, + "confirmed_height": null }, "id": 1 } @@ -255,6 +256,7 @@ pub trait OwnerRpc { "amount_debited": "0", "confirmation_ts": "2019-01-15T16:01:26Z", "confirmed": true, + "confirmed_height": 1, "creation_ts": "2019-01-15T16:01:26Z", "fee": null, "id": 0, @@ -275,6 +277,7 @@ pub trait OwnerRpc { "amount_debited": "0", "confirmation_ts": "2019-01-15T16:01:26Z", "confirmed": true, + "confirmed_height": 2, "creation_ts": "2019-01-15T16:01:26Z", "fee": null, "id": 1, @@ -305,6 +308,7 @@ pub trait OwnerRpc { refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, + confirmed_height: Option, ) -> Result<(bool, Vec), ErrorKind>; /** @@ -1767,6 +1771,7 @@ where refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, + confirmed_height: Option, ) -> Result<(bool, Vec), ErrorKind> { Owner::retrieve_txs( self, @@ -1774,6 +1779,7 @@ where refresh_from_node, tx_id, tx_slate_id, + confirmed_height, ) .map_err(|e| e.kind()) } diff --git a/controller/src/command.rs b/controller/src/command.rs index 722e344e7..dad83cf5c 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -987,6 +987,7 @@ where pub struct TxsArgs { pub id: Option, pub tx_slate_id: Option, + pub confirmed_height: Option, } pub fn txs( @@ -1004,8 +1005,10 @@ where let updater_running = owner_api.updater_running.load(Ordering::Relaxed); controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { let res = api.node_height(m)?; - let (validated, txs) = api.retrieve_txs(m, true, args.id, args.tx_slate_id)?; - let include_status = !args.id.is_some() && !args.tx_slate_id.is_some(); + let (validated, txs) = + api.retrieve_txs(m, true, args.id, args.tx_slate_id, args.confirmed_height)?; + let include_status = + !args.id.is_some() && !args.tx_slate_id.is_some() && !args.confirmed_height.is_some(); display::txs( &g_args.account, res.height, @@ -1030,6 +1033,13 @@ where None }; + if args.confirmed_height.is_some() && txs.is_empty() { + println!( + "Could not find any transactions confirmed at the block height {}.\n", + args.confirmed_height.unwrap() + ) + } + if id.is_some() { let (_, outputs) = api.retrieve_outputs(m, true, false, id)?; display::outputs( @@ -1112,7 +1122,7 @@ where } Some(s) => s, }; - let (_, txs) = api.retrieve_txs(m, true, Some(args.id), None)?; + let (_, txs) = api.retrieve_txs(m, true, Some(args.id), None, None)?; match args.dump_file { None => { if txs[0].confirmed { diff --git a/controller/src/display.rs b/controller/src/display.rs index 1f28ad987..df6dd0f2e 100644 --- a/controller/src/display.rs +++ b/controller/src/display.rs @@ -157,6 +157,7 @@ pub fn txs( bMG->"Creation Time", bMG->"TTL Cutoff Height", bMG->"Confirmed?", + bMG->"Confirmed Height", bMG->"Confirmation Time", bMG->"Num. \nInputs", bMG->"Num. \nOutputs", @@ -186,6 +187,10 @@ pub fn txs( None => "None".to_owned(), }; let confirmed = format!("{}", t.confirmed); + let confirmed_height = match t.confirmed_height { + Some(ch) => format!("{}", ch), + None => "None".to_owned(), + }; let num_inputs = format!("{}", t.num_inputs); let num_outputs = format!("{}", t.num_outputs); let amount_debited_str = core::amount_to_hr_string(t.amount_debited, true); @@ -225,6 +230,7 @@ pub fn txs( bFB->creation_ts, bFB->ttl_cutoff_height, bFC->confirmed, + bFC->confirmed_height, bFB->confirmation_ts, bFC->num_inputs, bFC->num_outputs, @@ -244,6 +250,7 @@ pub fn txs( bFD->slate_id, bFB->creation_ts, bFg->confirmed, + bFg->confirmed_height, bFB->confirmation_ts, bFD->num_inputs, bFD->num_outputs, @@ -262,6 +269,7 @@ pub fn txs( bFD->slate_id, bFB->creation_ts, bFR->confirmed, + bFR->confirmed_height, bFB->confirmation_ts, bFD->num_inputs, bFD->num_outputs, diff --git a/controller/tests/accounts.rs b/controller/tests/accounts.rs index 9c1bb7944..67f587d00 100644 --- a/controller/tests/accounts.rs +++ b/controller/tests/accounts.rs @@ -135,7 +135,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(wallet1_info.total, 5 * reward); assert_eq!(wallet1_info.amount_currently_spendable, (5 - cm) * reward); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 5); Ok(()) })?; @@ -159,7 +159,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(wallet1_info.total, 7 * reward); assert_eq!(wallet1_info.amount_currently_spendable, 7 * reward); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 7); Ok(()) })?; @@ -178,7 +178,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(wallet1_info.total, 0,); assert_eq!(wallet1_info.amount_currently_spendable, 0,); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 0); Ok(()) })?; @@ -210,7 +210,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; assert!(wallet1_refreshed); assert_eq!(wallet1_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 9); Ok(()) })?; @@ -225,7 +225,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(wallet1_info.last_confirmed_height, 12); let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; assert_eq!(wallet1_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; println!("{:?}", txs); assert_eq!(txs.len(), 5); Ok(()) @@ -236,7 +236,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; assert!(wallet2_refreshed); assert_eq!(wallet2_info.last_confirmed_height, 13); - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 1); Ok(()) })?; @@ -254,7 +254,7 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(wallet2_info.total, 0,); assert_eq!(wallet2_info.amount_currently_spendable, 0,); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 0); Ok(()) })?; diff --git a/controller/tests/check.rs b/controller/tests/check.rs index d275fa682..bd1cfd894 100644 --- a/controller/tests/check.rs +++ b/controller/tests/check.rs @@ -119,7 +119,7 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(wallet1_info.total, bh * reward); assert_eq!(wallet1_info.amount_currently_spendable, (bh - cm) * reward); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); assert_eq!(wallet1_info.total, c); assert_eq!(txs.len(), bh as usize); @@ -150,7 +150,7 @@ fn scan_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // check we have a problem now wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); assert!(wallet1_info.total != c); Ok(()) diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index 0972ad5ae..31b4f8797 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -146,7 +146,7 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // Check transaction log for wallet 2 wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { let (_, wallet2_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); assert!(txs.len() == 1); println!( @@ -161,7 +161,7 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // exists wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); assert_eq!(txs.len() as u64, bh + 1); println!( diff --git a/controller/tests/no_change.rs b/controller/tests/no_change.rs index d171e195c..555de7eef 100644 --- a/controller/tests/no_change.rs +++ b/controller/tests/no_change.rs @@ -104,7 +104,7 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // Refresh and check transaction log for wallet 1 wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; assert!(refreshed); let tx = txs[0].clone(); println!("SIMPLE SEND - SENDING WALLET"); @@ -117,7 +117,7 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // Refresh and check transaction log for wallet 2 wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; assert!(refreshed); let tx = txs[0].clone(); println!("SIMPLE SEND - RECEIVING WALLET"); @@ -170,7 +170,7 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // check wallet 2's version wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; assert!(refreshed); for tx in txs { stored_excess = tx.kernel_excess; @@ -184,7 +184,7 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // Refresh and check transaction log for wallet 1 wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; assert!(refreshed); for tx in txs { println!("Wallet 1: {:?}", tx); diff --git a/controller/tests/payment_proofs.rs b/controller/tests/payment_proofs.rs index 1176643db..c7e496d16 100644 --- a/controller/tests/payment_proofs.rs +++ b/controller/tests/payment_proofs.rs @@ -116,7 +116,7 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err sender_api.tx_lock_outputs(m, &slate)?; // Ensure what's stored in TX log for payment proof is correct - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; assert!(txs[0].payment_proof.is_some()); let pp = txs[0].clone().payment_proof.unwrap(); assert_eq!( diff --git a/controller/tests/repost.rs b/controller/tests/repost.rs index 41bd29603..b309c0989 100644 --- a/controller/tests/repost.rs +++ b/controller/tests/repost.rs @@ -154,7 +154,7 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> // Now repost from cached wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; + let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; println!("TXS[0]: {:?}", txs[0]); let stored_tx = api.get_stored_tx(m, None, Some(&txs[0].tx_slate_id.unwrap()))?; println!("Stored tx: {:?}", stored_tx); @@ -224,7 +224,7 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> // Now repost from cached wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id))?; + let (_, txs) = api.retrieve_txs(m, true, None, Some(slate.id), None)?; let stored_tx_slate = api.get_stored_tx(m, Some(txs[0].id), None)?.unwrap(); api.post_tx(m, &stored_tx_slate, false)?; bh += 1; diff --git a/controller/tests/revert.rs b/controller/tests/revert.rs index 899a3bad0..8f2475f4a 100644 --- a/controller/tests/revert.rs +++ b/controller/tests/revert.rs @@ -133,7 +133,7 @@ fn revert( assert_eq!(info.amount_currently_spendable, (bh - cm) * reward); assert_eq!(info.amount_reverted, 0); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; let (c, _) = libwallet::TxLogEntry::sum_confirmed(&txs); assert_eq!(info.total, c); assert_eq!(txs.len(), bh as usize); @@ -148,7 +148,7 @@ fn revert( assert_eq!(info.amount_currently_spendable, 0); assert_eq!(info.amount_reverted, 0); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 0); Ok(()) })?; @@ -188,7 +188,7 @@ fn revert( assert_eq!(info.amount_currently_spendable, 0); assert_eq!(info.amount_reverted, 0); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 1); let tx = &txs[0]; assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived); @@ -230,11 +230,12 @@ fn revert( assert_eq!(info.amount_currently_spendable, sent); assert_eq!(info.amount_reverted, 0); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 1); let tx = &txs[0]; assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived); assert!(tx.confirmed); + assert!(tx.confirmed_height.is_some()); assert!(tx.kernel_excess.is_some()); assert!(tx.reverted_after.is_none()); Ok(()) @@ -266,11 +267,12 @@ fn revert( assert_eq!(info.amount_currently_spendable, 0); assert_eq!(info.amount_reverted, sent); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 1); let tx = &txs[0]; assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReverted); assert!(!tx.confirmed); + assert!(tx.confirmed_height.is_none()); assert!(tx.reverted_after.is_some()); Ok(()) })?; @@ -300,11 +302,12 @@ fn revert_reconfirm_impl(test_dir: &'static str) -> Result<(), libwallet::Error> assert_eq!(info.amount_currently_spendable, sent); assert_eq!(info.amount_reverted, 0); // check tx log as well - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 1); let tx = &txs[0]; assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceived); assert!(tx.confirmed); + assert!(tx.confirmed_height.is_some()); assert!(tx.reverted_after.is_none()); Ok(()) })?; @@ -329,7 +332,7 @@ fn revert_cancel_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(info.amount_currently_spendable, 0); assert_eq!(info.amount_reverted, sent); - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 1); let tx = &txs[0]; @@ -345,7 +348,7 @@ fn revert_cancel_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { assert_eq!(info.amount_reverted, 0); // Check updated tx log - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; assert_eq!(txs.len(), 1); let tx = &txs[0]; assert_eq!(tx.tx_type, libwallet::TxLogEntryType::TxReceivedCancelled); diff --git a/controller/tests/transaction.rs b/controller/tests/transaction.rs index 6eb3eb7b0..8c054212a 100644 --- a/controller/tests/transaction.rs +++ b/controller/tests/transaction.rs @@ -145,7 +145,7 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> // Check transaction log for wallet 1 wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); let fee = core::libtx::tx_fee( wallet1_info.last_confirmed_height as usize - cm as usize, @@ -157,6 +157,7 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> assert!(tx.is_some()); let tx = tx.unwrap(); assert!(!tx.confirmed); + assert!(tx.confirmed_height.is_none()); assert!(tx.confirmation_ts.is_none()); assert_eq!(tx.amount_debited - tx.amount_credited, fee + amount); println!("tx: {:?}", tx); @@ -166,13 +167,14 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> // Check transaction log for wallet 2 wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); // we should have a transaction entry for this slate let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); assert!(tx.is_some()); let tx = tx.unwrap(); assert!(!tx.confirmed); + assert!(tx.confirmed_height.is_none()); assert!(tx.confirmation_ts.is_none()); assert_eq!(amount, tx.amount_credited); assert_eq!(0, tx.amount_debited); @@ -211,12 +213,13 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> assert_eq!(wallet1_info.amount_immature, cm * reward + fee); // check tx log entry is confirmed - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); assert!(tx.is_some()); let tx = tx.unwrap(); assert!(tx.confirmed); + assert!(tx.confirmed_height.is_some()); assert!(tx.confirmation_ts.is_some()); Ok(()) @@ -247,12 +250,13 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> assert_eq!(wallet2_info.amount_currently_spendable, amount); // check tx log entry is confirmed - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); assert!(tx.is_some()); let tx = tx.unwrap(); assert!(tx.confirmed); + assert!(tx.confirmed_height.is_some()); assert!(tx.confirmation_ts.is_some()); Ok(()) })?; @@ -315,7 +319,7 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { let (refreshed, _wallet1_info) = sender_api.retrieve_summary_info(m, true, 1)?; assert!(refreshed); - let (_, txs) = sender_api.retrieve_txs(m, true, None, None)?; + let (_, txs) = sender_api.retrieve_txs(m, true, None, None, None)?; // find the transaction let tx = txs .iter() @@ -344,12 +348,13 @@ fn basic_transaction_api(test_dir: &'static str) -> Result<(), libwallet::Error> assert_eq!(wallet2_info.amount_currently_spendable, amount * 3); // check tx log entry is confirmed - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); assert!(tx.is_some()); let tx = tx.unwrap(); assert!(tx.confirmed); + assert!(tx.confirmed_height.is_some()); assert!(tx.confirmation_ts.is_some()); Ok(()) })?; @@ -433,7 +438,7 @@ fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> { wallet1_info.last_confirmed_height ); assert!(refreshed); - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; // we should have a transaction entry for this slate let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); assert!(tx.is_some()); @@ -458,7 +463,7 @@ fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> { // Check transaction log for wallet 2 wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); let mut unconfirmed_count = 0; let tx = txs.iter().find(|t| t.tx_slate_id == Some(slate.id)); @@ -488,7 +493,7 @@ fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> { // can't roll back coinbase let res = api.cancel_tx(m, Some(1), None); assert!(res.is_err()); - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; let tx = txs .iter() .find(|t| t.tx_slate_id == Some(slate.id)) @@ -515,7 +520,7 @@ fn tx_rollback(test_dir: &'static str) -> Result<(), libwallet::Error> { // Wallet 2 rolls back wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; let tx = txs .iter() .find(|t| t.tx_slate_id == Some(slate.id)) diff --git a/controller/tests/ttl_cutoff.rs b/controller/tests/ttl_cutoff.rs index 428caaa44..4c324fa9d 100644 --- a/controller/tests/ttl_cutoff.rs +++ b/controller/tests/ttl_cutoff.rs @@ -95,7 +95,7 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> slate = client1.send_tx_slate_direct("wallet2", &slate_i)?; sender_api.tx_lock_outputs(m, &slate)?; - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; let tx = txs[0].clone(); assert_eq!(tx.ttl_cutoff_height, Some(12)); @@ -106,7 +106,7 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 2, false); wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |sender_api, m| { - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; let tx = txs[0].clone(); assert_eq!(tx.ttl_cutoff_height, Some(12)); @@ -116,7 +116,7 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> // Should also be gone in wallet 2, and output gone wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |sender_api, m| { - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; let tx = txs[0].clone(); let outputs = sender_api.retrieve_outputs(m, false, true, None)?.1; assert_eq!(outputs.len(), 0); @@ -144,7 +144,7 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> sender_api.tx_lock_outputs(m, &slate_i)?; slate = slate_i; - let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; + let (_, txs) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; let tx = txs[0].clone(); assert_eq!(tx.ttl_cutoff_height, Some(14)); @@ -156,7 +156,7 @@ fn ttl_cutoff_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> // Wallet 2 will need to have updated past the TTL wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |sender_api, m| { - let (_, _) = sender_api.retrieve_txs(m, true, None, Some(slate.id))?; + let (_, _) = sender_api.retrieve_txs(m, true, None, Some(slate.id), None)?; Ok(()) })?; diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index d8cfdaac3..b6bc90f68 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -84,6 +84,7 @@ where Some(ret_slate.id), Some(&parent_key_id), use_test_rng, + None, )?; for t in &tx { if t.tx_type == TxLogEntryType::TxReceived { diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index 2eac3cc84..4930dc8b0 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -294,6 +294,7 @@ pub fn retrieve_txs<'a, L, C, K>( refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, + confirmed_height: Option, ) -> Result<(bool, Vec), Error> where L: WalletLCProvider<'a, C, K>, @@ -313,7 +314,14 @@ where wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); - let txs = updater::retrieve_txs(&mut **w, tx_id, tx_slate_id, Some(&parent_key_id), false)?; + let txs = updater::retrieve_txs( + &mut **w, + tx_id, + tx_slate_id, + Some(&parent_key_id), + false, + confirmed_height, + )?; Ok((validated, txs)) } @@ -385,6 +393,7 @@ where refresh_from_node, tx_id, tx_slate_id, + None, )?; if txs.1.len() != 1 { return Err(ErrorKind::PaymentProofRetrieval("Transaction doesn't exist".into()).into()); @@ -645,6 +654,7 @@ where Some(ret_slate.id), Some(&parent_key_id), use_test_rng, + None, )?; for t in &tx { if t.tx_type == TxLogEntryType::TxSent { @@ -1063,7 +1073,7 @@ where // Step 2: Update outstanding transactions with no change outputs by kernel let mut txs = { wallet_lock!(wallet_inst, w); - updater::retrieve_txs(&mut **w, None, None, Some(&parent_key_id), true)? + updater::retrieve_txs(&mut **w, None, None, Some(&parent_key_id), true, None)? }; result = update_txs_via_kernel(wallet_inst.clone(), keychain_mask, &mut txs)?; if !result { @@ -1291,10 +1301,10 @@ where }; for tx in txs.iter_mut() { - if tx.confirmed { + if tx.confirmed && (tx.confirmed_height.is_some() || tx.kernel_excess.is_none()) { continue; } - if tx.amount_debited != 0 && tx.amount_credited != 0 { + if tx.amount_debited != 0 && tx.amount_credited != 0 && tx.confirmed_height.is_some() { continue; } if let Some(e) = tx.kernel_excess { @@ -1308,6 +1318,7 @@ where wallet_lock!(wallet_inst, w); let mut batch = w.batch(keychain_mask)?; tx.confirmed = true; + tx.confirmed_height = Some(k.1); tx.update_confirmation_ts(); batch.save_tx_log_entry(tx.clone(), &parent_key_id)?; batch.commit()?; diff --git a/libwallet/src/internal/scan.rs b/libwallet/src/internal/scan.rs index d8cb34968..26fbfd1f6 100644 --- a/libwallet/src/internal/scan.rs +++ b/libwallet/src/internal/scan.rs @@ -233,6 +233,7 @@ where }; let mut t = TxLogEntry::new(parent_key_id.clone(), entry_type, log_id); t.confirmed = true; + t.confirmed_height = Some(output.height); t.amount_credited = output.value; t.num_outputs = 1; t.update_confirmation_ts(); @@ -296,6 +297,7 @@ where None, Some(&parent_key_id), false, + None, )?; if !entries.is_empty() { let mut entry = entries[0].clone(); diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index 505b554db..adf3d0030 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -346,7 +346,14 @@ where } else if let Some(tx_slate_id) = tx_slate_id { tx_id_string = tx_slate_id.to_string(); } - let tx_vec = updater::retrieve_txs(wallet, tx_id, tx_slate_id, Some(&parent_key_id), false)?; + let tx_vec = updater::retrieve_txs( + wallet, + tx_id, + tx_slate_id, + Some(&parent_key_id), + false, + None, + )?; if tx_vec.len() != 1 { return Err(ErrorKind::TransactionDoesntExist(tx_id_string).into()); } @@ -385,7 +392,7 @@ where K: Keychain + 'a, { // finalize command - let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false)?; + let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false, None)?; let mut tx = None; // don't want to assume this is the right tx, in case of self-sending for t in tx_vec { @@ -506,7 +513,14 @@ where C: NodeClient + 'a, K: Keychain + 'a, { - let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), Some(parent_key_id), false)?; + let tx_vec = updater::retrieve_txs( + wallet, + None, + Some(slate.id), + Some(parent_key_id), + false, + None, + )?; if tx_vec.is_empty() { return Err(ErrorKind::PaymentProof( "TxLogEntry with original proof info not found (is account correct?)".to_owned(), diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index da2209e3f..8d7ca605a 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -96,6 +96,7 @@ pub fn retrieve_txs<'a, T: ?Sized, C, K>( tx_slate_id: Option, parent_key_id: Option<&Identifier>, outstanding_only: bool, + confirmed_height: Option, ) -> Result, Error> where T: WalletBackend<'a, C, K>, @@ -117,16 +118,20 @@ where Some(t) => tx_entry.tx_slate_id == Some(t), None => true, }; + let f_confirmed_height = match confirmed_height { + Some(t) => tx_entry.confirmed_height == Some(t), + None => true, + }; let f_outstanding = match outstanding_only { true => { - !tx_entry.confirmed + (!tx_entry.confirmed || tx_entry.confirmed_height.is_none()) && (tx_entry.tx_type == TxLogEntryType::TxReceived || tx_entry.tx_type == TxLogEntryType::TxSent || tx_entry.tx_type == TxLogEntryType::TxReverted) } false => true, }; - f_pk && f_tx_id && f_txs && f_outstanding + f_pk && f_tx_id && f_txs && f_confirmed_height && f_outstanding }) .collect(); txs.sort_by_key(|tx| tx.creation_ts); @@ -171,7 +176,7 @@ where .filter(|x| x.root_key_id == *parent_key_id && x.status != OutputStatus::Spent) .collect(); - let tx_entries = retrieve_txs(wallet, None, None, Some(&parent_key_id), true)?; + let tx_entries = retrieve_txs(wallet, None, None, Some(&parent_key_id), true, None)?; // Only select outputs that are actually involved in an outstanding transaction let unspents = match update_all { @@ -284,6 +289,7 @@ where log_id, ); t.confirmed = true; + t.confirmed_height = Some(o.1); t.amount_credited = output.value; t.amount_debited = 0; t.num_outputs = 1; @@ -318,6 +324,7 @@ where } t.update_confirmation_ts(); t.confirmed = true; + t.confirmed_height = Some(o.1); batch.save_tx_log_entry(t, &parent_key_id)?; } } @@ -349,6 +356,7 @@ where (now - t).to_std().ok() }); tx.confirmed = false; + tx.confirmed_height = None; batch.save_tx_log_entry(tx, &parent_key_id)?; } } diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index 6f1ee585b..5ca41cb47 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -808,6 +808,8 @@ pub struct TxLogEntry { /// confirmed (In all cases either all outputs involved in a tx should be /// confirmed, or none should be; otherwise there's a deeper problem) pub confirmed: bool, + /// confirmed block height of the transaction + pub confirmed_height: Option, /// number of inputs involved in TX pub num_inputs: usize, /// number of outputs involved in TX @@ -866,6 +868,7 @@ impl TxLogEntry { creation_ts: Utc::now(), confirmation_ts: None, confirmed: false, + confirmed_height: None, amount_credited: 0, amount_debited: 0, num_inputs: 0, diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index 59b227b7b..ac1a7ea69 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -272,6 +272,11 @@ subcommands: short: t long: txid takes_value: true + - confirmed_height: + help: If specified, display all transactions confirmed at a specific block height + short: h + long: confirmed_height + takes_value: true - post: about: Posts a finalized transaction to the chain args: diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 70d542cd6..d2f8f2637 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -776,13 +776,20 @@ pub fn parse_txs_args(args: &ArgMatches) -> Result } }, }; + let confirmed_height = match args.value_of("confirmed_height") { + None => None, + Some(tx) => Some(parse_u64(tx, "confirmed_height")? as u64), + }; if tx_id.is_some() && tx_slate_id.is_some() { - let msg = format!("At most one of 'id' (-i) or 'txid' (-t) may be provided."); + let msg = format!( + "At most one of 'id' (-i),'txid' (-t) or 'confirmed_height' (-h) may be provided." + ); return Err(ParseError::ArgumentError(msg)); } Ok(command::TxsArgs { id: tx_id, tx_slate_id: tx_slate_id, + confirmed_height: confirmed_height, }) } diff --git a/tests/cmd_line_basic.rs b/tests/cmd_line_basic.rs index 78f0ec2ca..424e569e7 100644 --- a/tests/cmd_line_basic.rs +++ b/tests/cmd_line_basic.rs @@ -238,7 +238,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: None, |api, m| { api.set_active_account(m, "mining")?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); assert_eq!(txs.len(), bh as usize); for t in txs { @@ -357,7 +357,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: None, |api, m| { api.set_active_account(m, "mining")?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); assert_eq!(txs.len(), bh as usize); Ok(()) @@ -430,7 +430,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: None, |api, m| { api.set_active_account(m, "mining")?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?; assert!(refreshed); assert_eq!(txs.len(), bh as usize + 1); Ok(()) @@ -555,7 +555,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: None, |api, m| { api.set_active_account(m, "default")?; - let (_, txs) = api.retrieve_txs(m, true, None, None)?; + let (_, txs) = api.retrieve_txs(m, true, None, None, None)?; let some_tx_id = txs[0].tx_slate_id.clone(); assert!(some_tx_id.is_some()); tx_id = some_tx_id.unwrap().to_hyphenated().to_string().clone();