From 3f611bc3dc2c9e0a991bd9375fd0d936555956b2 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 15 Nov 2015 21:59:40 +0000 Subject: [PATCH] wallet: track outgoing payments and add a show_transfers command It's a user friendly display of incoming and outgoing transfers, listed by height, within an optional height range. --- src/simplewallet/simplewallet.cpp | 99 +++++++++++++++++++++++++++++++ src/simplewallet/simplewallet.h | 1 + src/wallet/wallet2.cpp | 44 +++++++++++--- src/wallet/wallet2.h | 33 ++++++++++- 4 files changed, 166 insertions(+), 11 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 92fbba023..3d7a23d5e 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -453,6 +453,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs")); m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given tx")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to a given address in a partcular tx")); + m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out] [ []] - show incoming/outgoing transfers within an optional height range")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- @@ -1992,6 +1993,104 @@ bool simple_wallet::check_tx_key(const std::vector &args_) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::show_transfers(const std::vector &args_) +{ + std::vector local_args = args_; + bool in = true; + bool out = true; + uint64_t min_height = 0; + uint64_t max_height = (uint64_t)-1; + + if(local_args.size() > 3) { + fail_msg_writer() << tr("Usage: show_transfers [in|out] [ []]"); + return true; + } + + // optional in/out selector + if (local_args.size() > 0) { + if (local_args[0] == "in" || local_args[0] == "incoming") { + out = false; + local_args.erase(local_args.begin()); + } + else if (local_args[0] == "out" || local_args[0] == "outgoing") { + in = false; + local_args.erase(local_args.begin()); + } + else if (local_args[0] == "all" || local_args[0] == "both") { + local_args.erase(local_args.begin()); + } + } + + // min height + if (local_args.size() > 0) { + try { + min_height = boost::lexical_cast(local_args[0]); + } + catch (boost::bad_lexical_cast &) { + fail_msg_writer() << "Bad min_height parameter: " << local_args[0]; + return true; + } + local_args.erase(local_args.begin()); + } + + // max height + if (local_args.size() > 0) { + try { + max_height = boost::lexical_cast(local_args[0]); + } + catch (boost::bad_lexical_cast &) { + fail_msg_writer() << "Bad max_height parameter: " << local_args[0]; + return true; + } + local_args.erase(local_args.begin()); + } + + std::multimap> output; + + if (in) { + std::list> payments; + m_wallet->get_payments(payments, min_height, max_height); + for (std::list>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + const tools::wallet2::payment_details &pd = i->second; + std::string payment_id = string_tools::pod_to_hex(i->first); + if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) + payment_id = payment_id.substr(0,16); + output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%18.18s %s %s") % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id).str()))); + } + } + + if (out) { + std::list> payments; + m_wallet->get_payments_out(payments, min_height, max_height); + for (std::list>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + const tools::wallet2::confirmed_transfer_details &pd = i->second; + uint64_t fee = pd.m_amount_in - pd.m_amount_out; + output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%18.18s %s") % print_money(pd.m_amount_in - pd.m_change - fee) % "-").str()))); + } + } + + // print in and out sorted by height + for (std::map>::const_iterator i = output.begin(); i != output.end(); ++i) { + message_writer(i->second.first ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, false) << + boost::format("[%8.8llu] %4.4s %s") % + ((unsigned long long)i->first) % (i->second.first ? tr("in") : tr("out")) % i->second.second; + } + + // print unconfirmed last + if (out) { + std::list> upayments; + m_wallet->get_unconfirmed_payments_out(upayments); + for (std::list>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { + const tools::wallet2::unconfirmed_transfer_details &pd = i->second; + uint64_t amount = 0; + cryptonote::get_inputs_money_amount(pd.m_tx, amount); + message_writer() << (boost::format("[%8.8s] %4.4s %18.18s") % tr("pending") % tr("out") % print_money(amount - pd.m_change)).str(); + } + } + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::run() { std::string addr_start = m_wallet->get_account().get_public_address_str(m_wallet->testnet()).substr(0, 6); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index c4edd98d0..8636c1c7b 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -126,6 +126,7 @@ namespace cryptonote bool set_log(const std::vector &args); bool get_tx_key(const std::vector &args); bool check_tx_key(const std::vector &args); + bool show_transfers(const std::vector &args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 834095539..788f96415 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -150,7 +150,7 @@ bool wallet2::is_deprecated() const //---------------------------------------------------------------------------------------------------- void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height) { - process_unconfirmed(tx); + process_unconfirmed(tx, height); std::vector outs; uint64_t tx_money_got_in_outs = 0; crypto::public_key tx_pub_key = null_pkey; @@ -283,11 +283,22 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ } } //---------------------------------------------------------------------------------------------------- -void wallet2::process_unconfirmed(const cryptonote::transaction& tx) -{ - auto unconf_it = m_unconfirmed_txs.find(get_transaction_hash(tx)); - if(unconf_it != m_unconfirmed_txs.end()) +void wallet2::process_unconfirmed(const cryptonote::transaction& tx, uint64_t height) +{ + crypto::hash txid = get_transaction_hash(tx); + auto unconf_it = m_unconfirmed_txs.find(txid); + if(unconf_it != m_unconfirmed_txs.end()) { + if (store_tx_keys()) { + try { + m_confirmed_txs.insert(std::make_pair(txid, confirmed_transfer_details(unconf_it->second, height))); + } + catch (...) { + // can fail if the tx has unexpected input types + LOG_PRINT_L0("Failed to add outgoing transaction to confirmed transaction map"); + } + } m_unconfirmed_txs.erase(unconf_it); + } } //---------------------------------------------------------------------------------------------------- void wallet2::process_new_blockchain_entry(const cryptonote::block& b, cryptonote::block_complete_entry& bche, crypto::hash& bl_id, uint64_t height) @@ -1021,17 +1032,34 @@ void wallet2::get_payments(const crypto::hash& payment_id, std::list>& payments, uint64_t min_height) const +void wallet2::get_payments(std::list>& payments, uint64_t min_height, uint64_t max_height) const { auto range = std::make_pair(m_payments.begin(), m_payments.end()); - std::for_each(range.first, range.second, [&payments, &min_height](const payment_container::value_type& x) { - if (min_height < x.second.m_block_height) + std::for_each(range.first, range.second, [&payments, &min_height, &max_height](const payment_container::value_type& x) { + if (min_height < x.second.m_block_height && max_height >= x.second.m_block_height) { payments.push_back(x); } }); } //---------------------------------------------------------------------------------------------------- +void wallet2::get_payments_out(std::list>& confirmed_payments, + uint64_t min_height, uint64_t max_height) const +{ + for (auto i = m_confirmed_txs.begin(); i != m_confirmed_txs.end(); ++i) { + if (i->second.m_block_height > min_height && i->second.m_block_height <= max_height) { + confirmed_payments.push_back(*i); + } + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::get_unconfirmed_payments_out(std::list>& unconfirmed_payments) const +{ + for (auto i = m_unconfirmed_txs.begin(); i != m_unconfirmed_txs.end(); ++i) { + unconfirmed_payments.push_back(*i); + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::rescan_spent() { std::vector key_images; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index c9732cdd6..dbd305a02 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -109,6 +109,17 @@ namespace tools time_t m_sent_time; }; + struct confirmed_transfer_details + { + uint64_t m_amount_in; + uint64_t m_amount_out; + uint64_t m_change; + uint64_t m_block_height; + confirmed_transfer_details() {} + confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height): + m_amount_out(get_outs_money_amount(utd.m_tx)), m_change(utd.m_change), m_block_height(height) { get_inputs_money_amount(utd.m_tx, m_amount_in); } + }; + typedef std::vector transfer_container; typedef std::unordered_multimap payment_container; @@ -244,7 +255,10 @@ namespace tools bool check_connection(); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list& payments, uint64_t min_height = 0) const; - void get_payments(std::list>& payments, uint64_t min_height) const; + void get_payments(std::list>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1) const; + void get_payments_out(std::list>& confirmed_payments, + uint64_t min_height, uint64_t max_height = (uint64_t)-1) const; + void get_unconfirmed_payments_out(std::list>& unconfirmed_payments) const; uint64_t get_blockchain_current_height() const { return m_local_bc_height; } void rescan_spent(); template @@ -265,6 +279,9 @@ namespace tools if(ver < 8) return; a & m_tx_keys; + if(ver < 9) + return; + a & m_confirmed_txs; } /*! @@ -323,7 +340,7 @@ namespace tools void pull_blocks(uint64_t start_height, uint64_t& blocks_added); uint64_t select_transfers(uint64_t needed_money, bool add_dust, uint64_t dust, std::list& selected_transfers); bool prepare_file_names(const std::string& file_path); - void process_unconfirmed(const cryptonote::transaction& tx); + void process_unconfirmed(const cryptonote::transaction& tx, uint64_t height); void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t change_amount); void generate_genesis(cryptonote::block& b); void check_genesis(const crypto::hash& genesis_hash) const; //throws @@ -337,6 +354,7 @@ namespace tools std::vector m_blockchain; std::atomic m_local_bc_height; //temporary workaround std::unordered_map m_unconfirmed_txs; + std::unordered_map m_confirmed_txs; std::unordered_map m_tx_keys; transfer_container m_transfers; @@ -358,7 +376,7 @@ namespace tools uint32_t m_default_mixin; }; } -BOOST_CLASS_VERSION(tools::wallet2, 8) +BOOST_CLASS_VERSION(tools::wallet2, 9) namespace boost { @@ -383,6 +401,15 @@ namespace boost a & x.m_tx; } + template + inline void serialize(Archive &a, tools::wallet2::confirmed_transfer_details &x, const boost::serialization::version_type ver) + { + a & x.m_amount_in; + a & x.m_amount_out; + a & x.m_change; + a & x.m_block_height; + } + template inline void serialize(Archive& a, tools::wallet2::payment_details& x, const boost::serialization::version_type ver) {