diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d75f72464..9dd5f0c6e 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -3082,6 +3082,7 @@ bool simple_wallet::show_transfers(const std::vector &args_) bool out = true; bool pending = true; bool failed = true; + bool pool = true; uint64_t min_height = 0; uint64_t max_height = (uint64_t)-1; @@ -3099,7 +3100,7 @@ bool simple_wallet::show_transfers(const std::vector &args_) local_args.erase(local_args.begin()); } else if (local_args[0] == "out" || local_args[0] == "outgoing") { - in = false; + in = pool = false; local_args.erase(local_args.begin()); } else if (local_args[0] == "pending") { @@ -3107,7 +3108,11 @@ bool simple_wallet::show_transfers(const std::vector &args_) local_args.erase(local_args.begin()); } else if (local_args[0] == "failed") { - in = out = pending = false; + in = out = pending = pool = false; + local_args.erase(local_args.begin()); + } + else if (local_args[0] == "pool") { + in = out = pending = failed = false; local_args.erase(local_args.begin()); } else if (local_args[0] == "all" || local_args[0] == "both") { @@ -3182,6 +3187,27 @@ bool simple_wallet::show_transfers(const std::vector &args_) ((unsigned long long)i->first) % (i->second.first ? tr("in") : tr("out")) % i->second.second; } + if (pool) { + try + { + m_wallet->update_pool_state(); + std::list> payments; + m_wallet->get_unconfirmed_payments(payments); + 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); + std::string note = m_wallet->get_tx_note(pd.m_tx_hash); + message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %s %s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % "-" % note).str(); + } + } + catch (...) + { + fail_msg_writer() << "Failed to get pool state"; + } + } + // print unconfirmed last if (pending || failed) { std::list> upayments; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d18681f36..201f0a1f4 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -186,7 +186,7 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp error = false; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx) +void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx, bool pool) { if (!miner_tx) process_unconfirmed(tx, height); @@ -318,16 +318,19 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ //usually we have only one transfer for user in transaction cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res); - req.txid = get_transaction_hash(tx); - m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_o_indexes.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_o_indexes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_out_indices_error, res.status); - THROW_WALLET_EXCEPTION_IF(res.o_indexes.size() != tx.vout.size(), error::wallet_internal_error, - "transactions outputs size=" + std::to_string(tx.vout.size()) + - " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response size=" + std::to_string(res.o_indexes.size())); + if (!pool) + { + req.txid = get_transaction_hash(tx); + m_daemon_rpc_mutex.lock(); + bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_o_indexes.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_o_indexes.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_out_indices_error, res.status); + THROW_WALLET_EXCEPTION_IF(res.o_indexes.size() != tx.vout.size(), error::wallet_internal_error, + "transactions outputs size=" + std::to_string(tx.vout.size()) + + " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response size=" + std::to_string(res.o_indexes.size())); + } BOOST_FOREACH(size_t o, outs) { @@ -347,18 +350,21 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ + ", m_transfers.size() is " + boost::lexical_cast(m_transfers.size())); if (kit == m_key_images.end()) { - m_transfers.push_back(boost::value_initialized()); - transfer_details& td = m_transfers.back(); - td.m_block_height = height; - td.m_internal_output_index = o; - td.m_global_output_index = res.o_indexes[o]; - td.m_tx = tx; - td.m_key_image = ki; - td.m_spent = false; - m_key_images[td.m_key_image] = m_transfers.size()-1; - LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); - if (0 != m_callback) - m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + if (!pool) + { + m_transfers.push_back(boost::value_initialized()); + transfer_details& td = m_transfers.back(); + td.m_block_height = height; + td.m_internal_output_index = o; + td.m_global_output_index = res.o_indexes[o]; + td.m_tx = tx; + td.m_key_image = ki; + td.m_spent = false; + m_key_images[td.m_key_image] = m_transfers.size()-1; + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); + if (0 != m_callback) + m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + } } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount) { @@ -375,17 +381,20 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ // The new larger output replaced a previous smaller one tx_money_got_in_outs -= tx.vout[o].amount; - transfer_details &td = m_transfers[kit->second]; - td.m_block_height = height; - td.m_internal_output_index = o; - td.m_global_output_index = res.o_indexes[o]; - td.m_tx = tx; - THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki, error::wallet_internal_error, "Inconsistent key images"); - THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); + if (!pool) + { + transfer_details &td = m_transfers[kit->second]; + td.m_block_height = height; + td.m_internal_output_index = o; + td.m_global_output_index = res.o_indexes[o]; + td.m_tx = tx; + THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki, error::wallet_internal_error, "Inconsistent key images"); + THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); - LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); - if (0 != m_callback) - m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); + if (0 != m_callback) + m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + } } } } @@ -462,8 +471,11 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ payment.m_block_height = height; payment.m_unlock_time = tx.unlock_time; payment.m_timestamp = ts; - m_payments.emplace(payment_id, payment); - LOG_PRINT_L2("Payment found: " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount); + if (pool) + m_unconfirmed_payments.emplace(payment_id, payment); + else + m_payments.emplace(payment_id, payment); + LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount); } } //---------------------------------------------------------------------------------------------------- @@ -506,7 +518,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) { TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(b.miner_tx, height, b.timestamp, true); + process_new_transaction(b.miner_tx, height, b.timestamp, true, false); TIME_MEASURE_FINISH(miner_tx_handle_time); TIME_MEASURE_START(txs_handle_time); @@ -515,7 +527,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry cryptonote::transaction tx; bool r = parse_and_validate_tx_from_blob(txblob, tx); THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); - process_new_transaction(tx, height, b.timestamp, false); + process_new_transaction(tx, height, b.timestamp, false, false); } TIME_MEASURE_FINISH(txs_handle_time); LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time <<")ms"); @@ -743,20 +755,16 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei } } //---------------------------------------------------------------------------------------------------- -void wallet2::check_pending_txes() +void wallet2::update_pool_state() { - // if we don't have any pending txes, we don't need to check anything - if (m_unconfirmed_txs.empty()) - return; - - // we have at least one pending tx, so get the pool state + // get the pool state cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::request req; cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::response res; m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/get_transaction_pool", req, res, m_http_client, 200000); m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "check_pending_txes"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool"); THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); // remove any pending tx that's not in the pool @@ -793,6 +801,120 @@ void wallet2::check_pending_txes() } } } + + // remove pool txes to us that aren't in the pool anymore + std::unordered_map::iterator uit = m_unconfirmed_payments.begin(); + while (uit != m_unconfirmed_payments.end()) + { + const std::string txid = string_tools::pod_to_hex(uit->first); + bool found = false; + for (auto it2: res.transactions) + { + if (it2.id_hash == txid) + { + found = true; + break; + } + } + auto pit = uit++; + if (!found) + { + m_unconfirmed_payments.erase(pit); + } + } + + // add new pool txes to us + for (auto it: res.transactions) + { + cryptonote::blobdata txid_data; + if(epee::string_tools::parse_hexstr_to_binbuff(it.id_hash, txid_data)) + { + const crypto::hash txid = *reinterpret_cast(txid_data.data()); + if (m_unconfirmed_payments.find(txid) == m_unconfirmed_payments.end()) + { + LOG_PRINT_L1("Found new pool tx: " << txid); + bool found = false; + for (const auto &i: m_unconfirmed_txs) + { + if (i.first == txid) + { + found = true; + break; + } + } + if (!found) + { + // not one of those we sent ourselves + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(it.id_hash); + req.decode_as_json = false; + m_daemon_rpc_mutex.lock(); + bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/gettransactions", req, res, m_http_client, 200000); + m_daemon_rpc_mutex.unlock(); + if (r && res.status == CORE_RPC_STATUS_OK) + { + if (res.txs.size() == 1) + { + // might have just been put in a block + if (res.txs[0].in_pool) + { + cryptonote::transaction tx; + cryptonote::blobdata bd; + crypto::hash tx_hash, tx_prefix_hash; + if (epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd)) + { + if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) + { + if (tx_hash == txid) + { + process_new_transaction(tx, 0, time(NULL), false, true); + } + else + { + LOG_PRINT_L0("Mismatched txids when processing unconfimed txes from pool"); + } + } + else + { + LOG_PRINT_L0("failed to validate transaction from daemon"); + } + } + else + { + LOG_PRINT_L0("Failed to parse tx " << txid); + } + } + else + { + LOG_PRINT_L1("Tx " << txid << " was in pool, but is no more"); + } + } + else + { + LOG_PRINT_L0("Expected 1 tx, got " << res.txs.size()); + } + } + else + { + LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << res.status); + } + } + else + { + LOG_PRINT_L1("We sent that one"); + } + } + else + { + LOG_PRINT_L1("Already saw that one"); + } + } + else + { + LOG_PRINT_L0("Failed to parse txid"); + } + } } //---------------------------------------------------------------------------------------------------- void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list &short_chain_history) @@ -931,7 +1053,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re try { - check_pending_txes(); + update_pool_state(); } catch (...) { @@ -1733,6 +1855,13 @@ void wallet2::get_unconfirmed_payments_out(std::list>& unconfirmed_payments) const +{ + for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.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 d004b7da9..de3580777 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -303,6 +303,8 @@ namespace tools 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; + void get_unconfirmed_payments(std::list>& unconfirmed_payments) const; + uint64_t get_blockchain_current_height() const { return m_local_bc_height; } void rescan_spent(); void rescan_blockchain(bool refresh = true); @@ -334,6 +336,9 @@ namespace tools if(ver < 12) return; a & m_tx_notes; + if(ver < 13) + return; + a & m_unconfirmed_payments; } /*! @@ -389,6 +394,8 @@ namespace tools std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; + void update_pool_state(); + private: /*! * \brief Stores wallet information to wallet file. @@ -404,7 +411,7 @@ namespace tools * \param password Password of wallet file */ bool load_keys(const std::string& keys_file_name, const std::string& password); - void process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx); + void process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx, bool pool); void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height); void detach_blockchain(uint64_t height); void get_short_chain_history(std::list& ids) const; @@ -428,7 +435,6 @@ namespace tools void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_tranaction_size_limit(); - void check_pending_txes(); std::vector get_unspent_amounts_vector(); uint64_t sanitize_fee_multiplier(uint64_t fee_multiplier) const; @@ -441,6 +447,7 @@ namespace tools std::atomic m_local_bc_height; //temporary workaround std::unordered_map m_unconfirmed_txs; std::unordered_map m_confirmed_txs; + std::unordered_map m_unconfirmed_payments; std::unordered_map m_tx_keys; transfer_container m_transfers; @@ -469,7 +476,7 @@ namespace tools uint64_t m_refresh_from_block_height; }; } -BOOST_CLASS_VERSION(tools::wallet2, 12) +BOOST_CLASS_VERSION(tools::wallet2, 13) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 3) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 2) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 5c42e1c53..dbfb880a1 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -918,8 +918,8 @@ namespace tools 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) { - res.in.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); - wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.in.back(); + res.out.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); + wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.out.back(); const tools::wallet2::confirmed_transfer_details &pd = i->second; entry.txid = string_tools::pod_to_hex(i->first); @@ -969,6 +969,29 @@ namespace tools } } + if (req.pool) + { + m_wallet.update_pool_state(); + + std::list> payments; + m_wallet.get_unconfirmed_payments(payments); + for (std::list>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + res.pool.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); + wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.pool.back(); + const tools::wallet2::payment_details &pd = i->second; + + entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); + entry.payment_id = string_tools::pod_to_hex(i->first); + if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) + entry.payment_id = entry.payment_id.substr(0,16); + entry.height = 0; + entry.timestamp = pd.m_timestamp; + entry.amount = pd.m_amount; + entry.fee = 0; // TODO + entry.note = m_wallet.get_tx_note(pd.m_tx_hash); + } + } + return true; } //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 1404340da..f4eefcd1a 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -496,6 +496,7 @@ namespace wallet_rpc bool out; bool pending; bool failed; + bool pool; bool filter_by_height; uint64_t min_height; @@ -506,6 +507,7 @@ namespace wallet_rpc KV_SERIALIZE(out); KV_SERIALIZE(pending); KV_SERIALIZE(failed); + KV_SERIALIZE(pool); KV_SERIALIZE(filter_by_height); KV_SERIALIZE(min_height); KV_SERIALIZE(max_height); @@ -541,12 +543,14 @@ namespace wallet_rpc std::list out; std::list pending; std::list failed; + std::list pool; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(in); KV_SERIALIZE(out); KV_SERIALIZE(pending); KV_SERIALIZE(failed); + KV_SERIALIZE(pool); END_KV_SERIALIZE_MAP() }; };