diff options
Diffstat (limited to 'src/wallet')
42 files changed, 474 insertions, 229 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 6095f99d5..e72f3eb20 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index af7948d8a..35ce5144b 100644 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp index c73653e37..aeffe921e 100644 --- a/src/wallet/api/address_book.cpp +++ b/src/wallet/api/address_book.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/address_book.h b/src/wallet/api/address_book.h index 5b0655000..8de7f95ff 100644 --- a/src/wallet/api/address_book.h +++ b/src/wallet/api/address_book.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 70a702796..47eb7a243 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 0a9779c07..ea2831b25 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress.cpp b/src/wallet/api/subaddress.cpp index 9e358b4c8..7a1460c8e 100644 --- a/src/wallet/api/subaddress.cpp +++ b/src/wallet/api/subaddress.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress.h b/src/wallet/api/subaddress.h index 53ece126d..bcee10577 100644 --- a/src/wallet/api/subaddress.h +++ b/src/wallet/api/subaddress.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress_account.cpp b/src/wallet/api/subaddress_account.cpp index e8153df3d..ebee80e7e 100644 --- a/src/wallet/api/subaddress_account.cpp +++ b/src/wallet/api/subaddress_account.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress_account.h b/src/wallet/api/subaddress_account.h index 94cab47fb..3934df3ef 100644 --- a/src/wallet/api/subaddress_account.h +++ b/src/wallet/api/subaddress_account.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 9f5e41156..ad797cc07 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_history.h b/src/wallet/api/transaction_history.h index 1d52f4a69..04bc8a705 100644 --- a/src/wallet/api/transaction_history.h +++ b/src/wallet/api/transaction_history.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp index 572b04316..8f5ee39a0 100644 --- a/src/wallet/api/transaction_info.cpp +++ b/src/wallet/api/transaction_info.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h index 6337f2aaa..33dc8a7f4 100644 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index 6165a2240..07cf93f59 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h index 30065a7fa..4fd1a0b28 100644 --- a/src/wallet/api/unsigned_transaction.h +++ b/src/wallet/api/unsigned_transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/utils.cpp b/src/wallet/api/utils.cpp index d8dcedc5f..d02fdcaf6 100644 --- a/src/wallet/api/utils.cpp +++ b/src/wallet/api/utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 085f4f9df..38184fb51 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 4ac672287..c44d05968 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 0ae84adb9..a363636df 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index e81b8f83a..1bb4bc27c 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index a223e1df9..46ec36297 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/message_store.cpp b/src/wallet/message_store.cpp index cf1d91d5a..1169d5269 100644 --- a/src/wallet/message_store.cpp +++ b/src/wallet/message_store.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/message_store.h b/src/wallet/message_store.h index c5421a702..202d77be6 100644 --- a/src/wallet/message_store.h +++ b/src/wallet/message_store.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/message_transporter.cpp b/src/wallet/message_transporter.cpp index c985eb583..7aa765c87 100644 --- a/src/wallet/message_transporter.cpp +++ b/src/wallet/message_transporter.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/message_transporter.h b/src/wallet/message_transporter.h index b7d3c8107..d24888333 100644 --- a/src/wallet/message_transporter.h +++ b/src/wallet/message_transporter.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 0a9ea8f7b..62a525a4b 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -392,4 +392,34 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_payment_info(bool mining, boo return boost::none; } +boost::optional<std::string> NodeRPCProxy::get_transactions(const std::vector<crypto::hash> &txids, const std::function<void(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request&, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response&, bool)> &f) +{ + const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp + for (size_t offset = 0; offset < txids.size(); offset += SLICE_SIZE) + { + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response resp_t = AUTO_VAL_INIT(resp_t); + + const size_t n_txids = std::min<size_t>(SLICE_SIZE, txids.size() - offset); + for (size_t n = offset; n < (offset + n_txids); ++n) + req_t.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids[n])); + MDEBUG("asking for " << req_t.txs_hashes.size() << " transactions"); + req_t.decode_as_json = false; + req_t.prune = true; + + bool r = false; + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key); + r = net_utils::invoke_http_json("/gettransactions", req_t, resp_t, m_http_client, rpc_timeout); + if (r && resp_t.status == CORE_RPC_STATUS_OK) + check_rpc_cost(m_rpc_payment_state, "/gettransactions", resp_t.credits, pre_call_credits, resp_t.txs.size() * COST_PER_TX); + } + + f(req_t, resp_t, r); + } + return boost::optional<std::string>(); +} + } diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index e320565ac..eb947d2b2 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -59,6 +59,7 @@ public: boost::optional<std::string> get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees); boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask); boost::optional<std::string> get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie); + boost::optional<std::string> get_transactions(const std::vector<crypto::hash> &txids, const std::function<void(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request&, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response&, bool)> &f); private: template<typename T> void handle_payment_changes(const T &res, std::true_type) { diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 7e4f12f5b..3b3a8303a 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index bdecdba37..93be362d0 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 9e99cac83..6c474abc7 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -1006,7 +1006,7 @@ gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets, double shap const size_t blocks_to_consider = std::min<size_t>(rct_offsets.size(), blocks_in_a_year); const size_t outputs_to_consider = rct_offsets.back() - (blocks_to_consider < rct_offsets.size() ? rct_offsets[rct_offsets.size() - blocks_to_consider - 1] : 0); begin = rct_offsets.data(); - end = rct_offsets.data() + rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; + end = rct_offsets.data() + rct_offsets.size() - (std::max(1, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE) - 1); num_rct_outputs = *(end - 1); THROW_WALLET_EXCEPTION_IF(num_rct_outputs == 0, error::wallet_internal_error, "No rct outputs"); average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / static_cast<double>(outputs_to_consider); // this assumes constant target over the whole rct range @@ -1225,6 +1225,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_load_deprecated_formats(false), m_credits_target(0), m_enable_multisig(false), + m_pool_info_query_time(0), m_has_ever_refreshed_from_node(false), m_allow_mismatched_daemon_version(false) { @@ -1349,6 +1350,7 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u m_rpc_payment_state.discrepancy = 0; m_rpc_version = 0; m_node_rpc_proxy.invalidate(); + m_pool_info_query_time = 0; } const std::string address = get_daemon_address(); @@ -2664,7 +2666,83 @@ void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::bl error = !cryptonote::parse_and_validate_block_from_blob(blob, bl, bl_id); } //---------------------------------------------------------------------------------------------------- -void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height) +void read_pool_txs(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request &req, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response &res, bool r, const std::vector<crypto::hash> &txids, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs) +{ + if (r && res.status == CORE_RPC_STATUS_OK) + { + MDEBUG("Reading pool txs"); + if (res.txs.size() == req.txs_hashes.size()) + { + for (const auto &tx_entry: res.txs) + { + if (tx_entry.in_pool) + { + cryptonote::transaction tx; + cryptonote::blobdata bd; + crypto::hash tx_hash; + + if (get_pruned_tx(tx_entry, tx, tx_hash)) + { + const std::vector<crypto::hash>::const_iterator i = std::find_if(txids.begin(), txids.end(), + [tx_hash](const crypto::hash &e) { return e == tx_hash; }); + if (i != txids.end()) + { + txs.push_back(std::make_tuple(tx, tx_hash, tx_entry.double_spend_seen)); + } + else + { + MERROR("Got txid " << tx_hash << " which we did not ask for"); + } + } + else + { + LOG_PRINT_L0("Failed to parse transaction from daemon"); + } + } + else + { + LOG_PRINT_L1("Transaction from daemon was in pool, but is no more"); + } + } + } + else + { + LOG_PRINT_L0("Expected " << req.txs_hashes.size() << " out of " << txids.size() << " tx(es), got " << res.txs.size()); + } + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_pool_info_extent(const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response &res, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) +{ + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> added_pool_txs; + added_pool_txs.reserve(res.added_pool_txs.size() + res.remaining_added_pool_txids.size()); + + for (const auto &pool_tx: res.added_pool_txs) + { + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_base_from_blob(pool_tx.tx_blob, tx), + error::wallet_internal_error, "Failed to validate transaction base from daemon"); + added_pool_txs.push_back(std::make_tuple(tx, pool_tx.tx_hash, pool_tx.double_spend_seen)); + } + + // getblocks.bin may return more added pool transactions than we're allowed to request in restricted mode + if (!res.remaining_added_pool_txids.empty()) + { + // request the remaining txs + m_node_rpc_proxy.get_transactions(res.remaining_added_pool_txids, + [this, &res, &added_pool_txs](const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request &req_t, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response &resp_t, bool r) + { + read_pool_txs(req_t, resp_t, r, res.remaining_added_pool_txids, added_pool_txs); + if (!r || resp_t.status != CORE_RPC_STATUS_OK) + LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << get_rpc_status(resp_t.status)); + } + ); + } + + update_pool_state_from_pool_data(res.pool_info_extent == COMMAND_RPC_GET_BLOCKS_FAST::INCREMENTAL, res.removed_pool_txids, added_pool_txs, process_txs, refreshed); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height) { cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); @@ -2676,6 +2754,10 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, req.start_height = start_height; req.no_miner_tx = m_refresh_type == RefreshNoCoinbase; + req.requested_info = first ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY; + if (try_incremental) + req.pool_info_since = m_pool_info_query_time; + { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; @@ -2685,16 +2767,36 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error, "mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" + boost::lexical_cast<std::string>(res.output_indices.size()) + ") sizes from daemon"); - check_rpc_cost("/getblocks.bin", res.credits, pre_call_credits, 1 + res.blocks.size() * COST_PER_BLOCK); + uint64_t pool_info_cost = res.added_pool_txs.size() * COST_PER_TX + (res.remaining_added_pool_txids.size() + res.removed_pool_txids.size()) * COST_PER_POOL_HASH; + check_rpc_cost("/getblocks.bin", res.credits, pre_call_credits, 1 + res.blocks.size() * COST_PER_BLOCK + pool_info_cost); } blocks_start_height = res.start_height; blocks = std::move(res.blocks); o_indices = std::move(res.output_indices); current_height = res.current_height; + if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE) + m_pool_info_query_time = res.daemon_time; MDEBUG("Pulled blocks: blocks_start_height " << blocks_start_height << ", count " << blocks.size() - << ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height); + << ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height + << ", pool info " << static_cast<unsigned int>(res.pool_info_extent)); + + if (first) + { + if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE) + { + process_pool_info_extent(res, m_process_pool_txs, true); + } + else + { + // If we did not get any pool info, neither incremental nor the whole pool, we probably talk + // to a daemon that does not yet support giving back pool info with the 'getblocks' call, + // and we have to update in the "old way" + update_pool_state_by_pool_query(m_process_pool_txs, true); + } + } + } //---------------------------------------------------------------------------------------------------- void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes) @@ -2944,7 +3046,7 @@ void check_block_hard_fork_version(cryptonote::network_type nettype, uint8_t hf_ daemon_is_outdated = height < start_height || height >= end_height; } //---------------------------------------------------------------------------------------------------- -void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception) +void wallet2::pull_and_parse_next_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception) { error = false; last = false; @@ -2966,7 +3068,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks // pull the new blocks std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> o_indices; uint64_t current_height; - pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices, current_height); + pull_blocks(first, try_incremental, start_height, blocks_start_height, short_chain_history, blocks, o_indices, current_height); THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "Mismatched sizes of blocks and o_indices"); tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); @@ -3030,9 +3132,10 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks } } -void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes) +void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes, bool remove_if_found) { - // remove pool txes to us that aren't in the pool anymore + // remove pool txes to us that aren't in the pool anymore (remove_if_found = false), + // or remove pool txes to us that were reported as removed (remove_if_found = true) std::unordered_multimap<crypto::hash, wallet2::pool_payment_details>::iterator uit = m_unconfirmed_payments.begin(); while (uit != m_unconfirmed_payments.end()) { @@ -3047,9 +3150,9 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe } } auto pit = uit++; - if (!found) + if ((!remove_if_found && !found) || (remove_if_found && found)) { - MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool"); + MDEBUG("Removing " << txid << " from unconfirmed payments"); m_unconfirmed_payments.erase(pit); if (0 != m_callback) m_callback->on_pool_tx_removed(txid); @@ -3058,9 +3161,183 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe } //---------------------------------------------------------------------------------------------------- -void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) +// Code that is common to 'update_pool_state_by_pool_query' and 'update_pool_state_from_pool_data': +// Check wether a tx in the pool is worthy of processing because we did not see it +// yet or because it is "interesting" out of special circumstances +bool wallet2::accept_pool_tx_for_processing(const crypto::hash &txid) +{ + bool txid_found_in_up = false; + for (const auto &up: m_unconfirmed_payments) + { + if (up.second.m_pd.m_tx_hash == txid) + { + txid_found_in_up = true; + break; + } + } + if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) + { + // if it's for us, we want to keep track of whether we saw a double spend, so don't bail out + if (!txid_found_in_up) + { + LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped"); + return false; + } + } + if (!txid_found_in_up) + { + LOG_PRINT_L1("Found new pool tx: " << txid); + bool found = false; + for (const auto &i: m_unconfirmed_txs) + { + if (i.first == txid) + { + found = true; + // if this is a payment to yourself at a different subaddress account, don't skip it + // so that you can see the incoming pool tx with 'show_transfers' on that receiving subaddress account + const unconfirmed_transfer_details& utd = i.second; + for (const auto& dst : utd.m_dests) + { + auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key); + if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != utd.m_subaddr_account) + { + found = false; + break; + } + } + break; + } + } + if (!found) + { + // not one of those we sent ourselves + return true; + } + else + { + LOG_PRINT_L1("We sent that one"); + return false; + } + } + else { + return false; + } +} +//---------------------------------------------------------------------------------------------------- +// Code that is common to 'update_pool_state_by_pool_query' and 'update_pool_state_from_pool_data': +// Process an unconfirmed transfer after we know whether it's in the pool or not +void wallet2::process_unconfirmed_transfer(bool incremental, const crypto::hash &txid, wallet2::unconfirmed_transfer_details &tx_details, bool seen_in_pool, std::chrono::system_clock::time_point now, bool refreshed) { - MTRACE("update_pool_state start"); + // TODO: set tx_propagation_timeout to CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE * 3 / 2 after v15 hardfork + constexpr const std::chrono::seconds tx_propagation_timeout{500}; + if (seen_in_pool) + { + if (tx_details.m_state != wallet2::unconfirmed_transfer_details::pending_in_pool) + { + tx_details.m_state = wallet2::unconfirmed_transfer_details::pending_in_pool; + MINFO("Pending txid " << txid << " seen in pool, marking as pending in pool"); + } + } + else + { + if (!incremental) + { + if (tx_details.m_state == wallet2::unconfirmed_transfer_details::pending_in_pool) + { + // For the probably unlikely case that a tx once seen in the pool vanishes + // again set back to 'pending' + tx_details.m_state = wallet2::unconfirmed_transfer_details::pending; + MINFO("Already seen txid " << txid << " vanished from pool, marking as pending"); + } + } + // If a tx is pending for a "long time" without appearing in the pool, and if + // we have refreshed and thus had a chance to really see it if it was there, + // judge it as failed; the waiting for timeout and refresh happened avoids + // false alarms with txs going to 'failed' too early + if (tx_details.m_state == wallet2::unconfirmed_transfer_details::pending && refreshed && + now > std::chrono::system_clock::from_time_t(tx_details.m_sent_time) + tx_propagation_timeout) + { + LOG_PRINT_L1("Pending txid " << txid << " not in pool after " << tx_propagation_timeout.count() << + " seconds, marking as failed"); + tx_details.m_state = wallet2::unconfirmed_transfer_details::failed; + + // the inputs aren't spent anymore, since the tx failed + for (size_t vini = 0; vini < tx_details.m_tx.vin.size(); ++vini) + { + if (tx_details.m_tx.vin[vini].type() == typeid(txin_to_key)) + { + txin_to_key &tx_in_to_key = boost::get<txin_to_key>(tx_details.m_tx.vin[vini]); + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[i]; + if (td.m_key_image == tx_in_to_key.k_image) + { + LOG_PRINT_L1("Resetting spent status for output " << vini << ": " << td.m_key_image); + set_unspent(i); + break; + } + } + } + } + } + } +} +//---------------------------------------------------------------------------------------------------- +// This public method is typically called to make sure that the wallet's pool state is up-to-date by +// clients like simplewallet and the RPC daemon. Before incremental update this was the same method +// that 'refresh' also used, but now it's more complicated because for the time being we support +// the "old" and the "new" way of updating the pool and because only the 'getblocks' call supports +// incremental update but we don't want any blocks here. +// +// simplewallet does NOT update the pool info during automatic refresh to avoid disturbing interactive +// messages and prompts. When it finally calls this method here "to catch up" so to say we can't use +// incremental update anymore, because with that we might miss some txs altogether. +void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed, bool try_incremental) +{ + bool updated = false; + if (m_pool_info_query_time != 0 && try_incremental) + { + // We are connected to a daemon that supports giving back pool data with the 'getblocks' call, + // thus use that, to get the chance to work incrementally and to keep working incrementally; + // 'POOL_ONLY' was created to support this case + cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); + + req.requested_info = COMMAND_RPC_GET_BLOCKS_FAST::POOL_ONLY; + req.pool_info_since = m_pool_info_query_time; + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + bool r = net_utils::invoke_http_bin("/getblocks.bin", req, res, *m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "getblocks.bin", error::get_blocks_error, get_rpc_status(res.status)); + uint64_t pool_info_cost = res.added_pool_txs.size() * COST_PER_TX + (res.remaining_added_pool_txids.size() + res.removed_pool_txids.size()) * COST_PER_POOL_HASH; + check_rpc_cost("/getblocks.bin", res.credits, pre_call_credits, pool_info_cost); + } + + m_pool_info_query_time = res.daemon_time; + if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE) + { + process_pool_info_extent(res, process_txs, refreshed); + updated = true; + } + // We SHOULD get pool data here, but if for some crazy reason we don't fall back to the "old" method + } + if (!updated) + { + update_pool_state_by_pool_query(process_txs, refreshed); + } +} +//---------------------------------------------------------------------------------------------------- +// This is the "old" way of updating the pool with separate queries to get the pool content, used before +// the 'getblocks' command was able to give back pool data in addition to blocks. Before this code was +// the public 'update_pool_state' method. The logic is unchanged. This is a candidate for elimination +// when it's sure that no more "old" daemons can be possibly around. +void wallet2::update_pool_state_by_pool_query(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) +{ + MTRACE("update_pool_state_by_pool_query start"); + process_txs.clear(); auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { m_encrypt_keys_after_refresh.reset(); @@ -3078,16 +3355,15 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_transaction_pool_hashes.bin", error::get_tx_pool_error); check_rpc_cost("/get_transaction_pool_hashes.bin", res.credits, pre_call_credits, 1 + res.tx_hashes.size() * COST_PER_POOL_HASH); } - MTRACE("update_pool_state got pool"); + MTRACE("update_pool_state_by_pool_query got pool"); // remove any pending tx that's not in the pool - // TODO: set tx_propagation_timeout to CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE * 3 / 2 after v15 hardfork - constexpr const std::chrono::seconds tx_propagation_timeout{500}; const auto now = std::chrono::system_clock::now(); std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin(); while (it != m_unconfirmed_txs.end()) { const crypto::hash &txid = it->first; + MDEBUG("Checking m_unconfirmed_txs entry " << txid); bool found = false; for (const auto &it2: res.tx_hashes) { @@ -3098,193 +3374,115 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, } } auto pit = it++; - if (!found) - { - // we want to avoid a false positive when we ask for the pool just after - // a tx is removed from the pool due to being found in a new block, but - // just before the block is visible by refresh. So we keep a boolean, so - // that the first time we don't see the tx, we set that boolean, and only - // delete it the second time it is checked (but only when refreshed, so - // we're sure we've seen the blockchain state first) - if (pit->second.m_state == wallet2::unconfirmed_transfer_details::pending) - { - LOG_PRINT_L1("Pending txid " << txid << " not in pool, marking as not in pool"); - pit->second.m_state = wallet2::unconfirmed_transfer_details::pending_not_in_pool; - } - else if (pit->second.m_state == wallet2::unconfirmed_transfer_details::pending_not_in_pool && refreshed && - now > std::chrono::system_clock::from_time_t(pit->second.m_sent_time) + tx_propagation_timeout) - { - LOG_PRINT_L1("Pending txid " << txid << " not in pool after " << tx_propagation_timeout.count() << - " seconds, marking as failed"); - pit->second.m_state = wallet2::unconfirmed_transfer_details::failed; - - // the inputs aren't spent anymore, since the tx failed - for (size_t vini = 0; vini < pit->second.m_tx.vin.size(); ++vini) - { - if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) - { - txin_to_key &tx_in_to_key = boost::get<txin_to_key>(pit->second.m_tx.vin[vini]); - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details &td = m_transfers[i]; - if (td.m_key_image == tx_in_to_key.k_image) - { - LOG_PRINT_L1("Resetting spent status for output " << vini << ": " << td.m_key_image); - set_unspent(i); - break; - } - } - } - } - } - } + process_unconfirmed_transfer(false, txid, pit->second, found, now, refreshed); + MDEBUG("New state of that entry: " << pit->second.m_state); } - MTRACE("update_pool_state done first loop"); + MTRACE("update_pool_state_by_pool_query done first loop"); // remove pool txes to us that aren't in the pool anymore // but only if we just refreshed, so that the tx can go in // the in transfers list instead (or nowhere if it just // disappeared without being mined) if (refreshed) - remove_obsolete_pool_txs(res.tx_hashes); + remove_obsolete_pool_txs(res.tx_hashes, false); - MTRACE("update_pool_state done second loop"); + MTRACE("update_pool_state_by_pool_query done second loop"); // gather txids of new pool txes to us - std::vector<std::pair<crypto::hash, bool>> txids; + std::vector<crypto::hash> txids; for (const auto &txid: res.tx_hashes) { - bool txid_found_in_up = false; - for (const auto &up: m_unconfirmed_payments) + if (accept_pool_tx_for_processing(txid)) + txids.push_back(txid); + } + + m_node_rpc_proxy.get_transactions(txids, + [this, &txids, &process_txs](const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request &req_t, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response &resp_t, bool r) { - if (up.second.m_pd.m_tx_hash == txid) - { - txid_found_in_up = true; - break; - } + read_pool_txs(req_t, resp_t, r, txids, process_txs); + if (!r || resp_t.status != CORE_RPC_STATUS_OK) + LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << get_rpc_status(resp_t.status)); } - if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) + ); + + MTRACE("update_pool_state_by_pool_query end"); +} +//---------------------------------------------------------------------------------------------------- +// Update pool state from pool data we got together with block data, either incremental data with +// txs that are new in the pool since the last time we queried and the ids of txs that were +// removed from the pool since then, or the whole content of the pool if incremental was not +// possible, e.g. because the server was just started or restarted. +void wallet2::update_pool_state_from_pool_data(bool incremental, const std::vector<crypto::hash> &removed_pool_txids, const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &added_pool_txs, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) +{ + MTRACE("update_pool_state_from_pool_data start"); + auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { + m_encrypt_keys_after_refresh.reset(); + }); + + if (refreshed) + { + if (incremental) { - // if it's for us, we want to keep track of whether we saw a double spend, so don't bail out - if (!txid_found_in_up) - { - LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped"); - continue; - } + // Delete from the list of unconfirmed payments what the daemon reported as tx that was removed from + // pool; do so only after refresh to not delete too early and too eagerly; maybe we will find the tx + // later in a block, or not, or find it again in the pool txs because it was first removed but then + // somehow quickly "resurrected" - that all does not matter here, we retrace the removal + remove_obsolete_pool_txs(removed_pool_txids, true); } - if (!txid_found_in_up) + else { - LOG_PRINT_L1("Found new pool tx: " << txid); - bool found = false; - for (const auto &i: m_unconfirmed_txs) + // Delete from the list of unconfirmed payments what we don't find anymore in the pool; a bit + // unfortunate that we have to build a new vector with ids first, but better than copying and + // modifying the code of 'remove_obsolete_pool_txs' here + std::vector<crypto::hash> txids; + txids.reserve(added_pool_txs.size()); + for (const auto &pool_tx: added_pool_txs) { - if (i.first == txid) - { - found = true; - // if this is a payment to yourself at a different subaddress account, don't skip it - // so that you can see the incoming pool tx with 'show_transfers' on that receiving subaddress account - const unconfirmed_transfer_details& utd = i.second; - for (const auto& dst : utd.m_dests) - { - auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key); - if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != utd.m_subaddr_account) - { - found = false; - break; - } - } - break; - } - } - if (!found) - { - // not one of those we sent ourselves - txids.push_back({txid, false}); - } - else - { - LOG_PRINT_L1("We sent that one"); + txids.push_back(std::get<1>(pool_tx)); } + remove_obsolete_pool_txs(txids, false); } } - // get_transaction_pool_hashes.bin may return more transactions than we're allowed to request in restricted mode - const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp - for (size_t offset = 0; offset < txids.size(); offset += SLICE_SIZE) + // Possibly remove any pending tx that's not in the pool + const auto now = std::chrono::system_clock::now(); + std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin(); + while (it != m_unconfirmed_txs.end()) { - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - - const size_t n_txids = std::min<size_t>(SLICE_SIZE, txids.size() - offset); - for (size_t n = offset; n < (offset + n_txids); ++n) { - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids.at(n).first)); - } - MDEBUG("asking for " << req.txs_hashes.size() << " transactions"); - req.decode_as_json = false; - req.prune = true; - - bool r; - { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); - if (r && res.status == CORE_RPC_STATUS_OK) - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX); - } - - MDEBUG("Got " << r << " and " << res.status); - if (r && res.status == CORE_RPC_STATUS_OK) + const crypto::hash &txid = it->first; + MDEBUG("Checking m_unconfirmed_txs entry " << txid); + bool found = false; + for (const auto &pool_tx: added_pool_txs) { - if (res.txs.size() == req.txs_hashes.size()) + if (std::get<1>(pool_tx) == txid) { - for (const auto &tx_entry: res.txs) - { - if (tx_entry.in_pool) - { - cryptonote::transaction tx; - cryptonote::blobdata bd; - crypto::hash tx_hash; - - if (get_pruned_tx(tx_entry, tx, tx_hash)) - { - const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(), - [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; }); - if (i != txids.end()) - { - process_txs.push_back(std::make_tuple(tx, tx_hash, tx_entry.double_spend_seen)); - } - else - { - MERROR("Got txid " << tx_hash << " which we did not ask for"); - } - } - else - { - LOG_PRINT_L0("Failed to parse transaction from daemon"); - } - } - else - { - LOG_PRINT_L1("Transaction from daemon was in pool, but is no more"); - } - } - } - else - { - LOG_PRINT_L0("Expected " << n_txids << " out of " << txids.size() << " tx(es), got " << res.txs.size()); + found = true; + break; } } - else + auto pit = it++; + process_unconfirmed_transfer(incremental, txid, pit->second, found, now, refreshed); + MDEBUG("Resulting state of that entry: " << pit->second.m_state); + } + + // Collect all pool txs that are "interesting" i.e. mostly those that we don't know about yet; + // if we work incrementally and thus see only new pool txs since last time we asked it should + // be rare that we know already about one of those, but check nevertheless + process_txs.clear(); + for (const auto &pool_tx: added_pool_txs) + { + if (accept_pool_tx_for_processing(std::get<1>(pool_tx))) { - LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << get_rpc_status(res.status)); + process_txs.push_back(pool_tx); } } - MTRACE("update_pool_state end"); + + MTRACE("update_pool_state_from_pool_data end"); } //---------------------------------------------------------------------------------------------------- void wallet2::process_pool_state(const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs) { + MTRACE("process_pool_state start"); const time_t now = time(NULL); for (const auto &e: txs) { @@ -3299,6 +3497,7 @@ void wallet2::process_pool_state(const std::vector<std::tuple<cryptonote::transa m_scanned_pool_txs[0].clear(); } } + MTRACE("process_pool_state end"); } //---------------------------------------------------------------------------------------------------- void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force) @@ -3419,7 +3618,7 @@ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> wallet2::create return cache; } //---------------------------------------------------------------------------------------------------- -void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool, uint64_t max_blocks) +void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool, bool try_incremental, uint64_t max_blocks) { if (m_offline) { @@ -3507,12 +3706,15 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo auto scope_exit_handler_hwdev = epee::misc_utils::create_scope_leave_handler([&](){hwdev.computing_key_images(false);}); + m_process_pool_txs.clear(); + // Getting and processing the pool state has moved down into method 'pull_blocks' to + // allow for "conventional" as well as "incremental" update. However the following + // principle of getting all info first (pool AND blocks) and only process txs afterwards + // still holds and is still respected: // get updated pool state first, but do not process those txes just yet, // since that might cause a password prompt, which would introduce a data // leak allowing a passive adversary with traffic analysis capability to // infer when we get an incoming output - std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_pool_txs; - update_pool_state(process_pool_txs, true); bool first = true, last = false; while(m_run.load(std::memory_order_relaxed) && blocks_fetched < max_blocks) @@ -3533,11 +3735,10 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo if (!first && blocks.empty()) { m_node_rpc_proxy.set_height(m_blockchain.size()); - refreshed = true; break; } if (!last) - tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, last, error, exception);}); + tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(first, try_incremental, start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, last, error, exception);}); if (!first) { @@ -3591,7 +3792,6 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo if(!first && blocks_start_height == next_blocks_start_height) { m_node_rpc_proxy.set_height(m_blockchain.size()); - refreshed = true; break; } @@ -3657,8 +3857,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo try { // If stop() is called we don't need to check pending transactions - if (check_pool && m_run.load(std::memory_order_relaxed) && !process_pool_txs.empty()) - process_pool_state(process_pool_txs); + if (check_pool && m_run.load(std::memory_order_relaxed) && !m_process_pool_txs.empty()) + process_pool_state(m_process_pool_txs); } catch (...) { @@ -3780,7 +3980,7 @@ void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, ui transfers_detached = std::distance(it, m_transfers.end()); m_transfers.erase(it, m_transfers.end()); - size_t blocks_detached = m_blockchain.size() - height; + const uint64_t blocks_detached = m_blockchain.size() - height; m_blockchain.crop(height); for (auto it = m_payments.begin(); it != m_payments.end(); ) @@ -3799,6 +3999,9 @@ void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, ui ++it; } + if (m_callback) + m_callback->on_reorg(height, blocks_detached, transfers_detached); + LOG_PRINT_L0("Detached blockchain on height " << height << ", transfers detached " << transfers_detached << ", blocks detached " << blocks_detached); } //---------------------------------------------------------------------------------------------------- @@ -3831,6 +4034,7 @@ bool wallet2::clear() m_subaddress_labels.clear(); m_multisig_rounds_passed = 0; m_device_last_key_image_sync = 0; + m_pool_info_query_time = 0; return true; } //---------------------------------------------------------------------------------------------------- @@ -3847,6 +4051,7 @@ void wallet2::clear_soft(bool keep_key_images) m_unconfirmed_payments.clear(); m_scanned_pool_txs[0].clear(); m_scanned_pool_txs[1].clear(); + m_pool_info_query_time = 0; cryptonote::block b; generate_genesis(b); @@ -8413,7 +8618,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> else { // the base offset of the first rct output in the first unlocked block (or the one to be if there's none) - num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE]; + num_outs = gamma->get_num_rct_outs(); LOG_PRINT_L1("" << num_outs << " unlocked rct outputs"); THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, "histogram reports no unlocked rct outputs, not even ours"); @@ -8697,7 +8902,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } bool use_histogram = amount != 0; if (!use_histogram) - num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE]; + num_outs = gamma->get_num_rct_outs(); // make sure the real outputs we asked for are really included, along // with the correct key and mask: this guards against an active attack @@ -9850,7 +10055,7 @@ void wallet2::light_wallet_get_address_txs() } } // TODO: purge old unconfirmed_txs - remove_obsolete_pool_txs(pool_txs); + remove_obsolete_pool_txs(pool_txs, false); // Calculate wallet balance m_light_wallet_balance = ires.total_received-wallet_total_sent; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 3ee40a5f0..142c8edea 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -101,6 +101,7 @@ namespace tools uint64_t pick(); gamma_picker(const std::vector<uint64_t> &rct_offsets); gamma_picker(const std::vector<uint64_t> &rct_offsets, double shape, double scale); + uint64_t get_num_rct_outs() const { return num_rct_outputs; } private: struct gamma_engine @@ -138,6 +139,7 @@ private: public: // Full wallet callbacks virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} + virtual void on_reorg(uint64_t height, uint64_t blocks_detached, size_t transfers_detached) {} virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {} virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {} @@ -475,7 +477,7 @@ private: time_t m_sent_time; std::vector<cryptonote::tx_destination_entry> m_dests; crypto::hash m_payment_id; - enum { pending, pending_not_in_pool, failed } m_state; + enum { pending, pending_in_pool, failed } m_state; uint64_t m_timestamp; uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer @@ -1023,7 +1025,7 @@ private: bool is_deprecated() const; void refresh(bool trusted_daemon); void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched); - void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true, uint64_t max_blocks = std::numeric_limits<uint64_t>::max()); + void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true, bool try_incremental = true, uint64_t max_blocks = std::numeric_limits<uint64_t>::max()); bool refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& received_money, bool& ok); void set_refresh_type(RefreshType refresh_type) { m_refresh_type = refresh_type; } @@ -1506,9 +1508,9 @@ private: bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; - void update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false); + void update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false, bool try_incremental = false); void process_pool_state(const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs); - void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes); + void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes, bool remove_if_found); std::string encrypt(const char *plaintext, size_t len, const crypto::secret_key &skey, bool authenticated = true) const; std::string encrypt(const epee::span<char> &span, const crypto::secret_key &skey, bool authenticated = true) const; @@ -1706,11 +1708,16 @@ private: void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const; bool clear(); void clear_soft(bool keep_key_images=false); - void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height); + void pull_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height); void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes); void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false); - void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception); + void pull_and_parse_next_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception); void process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); + bool accept_pool_tx_for_processing(const crypto::hash &txid); + void process_unconfirmed_transfer(bool incremental, const crypto::hash &txid, wallet2::unconfirmed_transfer_details &tx_details, bool seen_in_pool, std::chrono::system_clock::time_point now, bool refreshed); + void process_pool_info_extent(const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response &res, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed); + void update_pool_state_by_pool_query(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false); + void update_pool_state_from_pool_data(bool incremental, const std::vector<crypto::hash> &removed_pool_txids, const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &added_pool_txs, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed); uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers) const; bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height); @@ -1845,6 +1852,8 @@ private: // If m_refresh_from_block_height is explicitly set to zero we need this to differentiate it from the case that // m_refresh_from_block_height was defaulted to zero.*/ bool m_explicit_refresh_from_block_height; + uint64_t m_pool_info_query_time; + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> m_process_pool_txs; bool m_confirm_non_default_ring_size; AskPasswordType m_ask_password; uint64_t m_max_reorg_depth; diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index ce13fc573..c1f47d6a9 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index 350fce24e..2e687521f 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 0b8512163..fb43d16b4 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_light_rpc.h b/src/wallet/wallet_light_rpc.h index 743a147f6..bae3a05f7 100644 --- a/src/wallet/wallet_light_rpc.h +++ b/src/wallet/wallet_light_rpc.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_rpc_helpers.h b/src/wallet/wallet_rpc_helpers.h index 93fa6996a..e974e0a9e 100644 --- a/src/wallet/wallet_rpc_helpers.h +++ b/src/wallet/wallet_rpc_helpers.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp index 06910ebbb..3242d88b8 100644 --- a/src/wallet/wallet_rpc_payments.cpp +++ b/src/wallet/wallet_rpc_payments.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index cecf24289..2e12b3ea4 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -154,7 +154,7 @@ namespace tools uint64_t blocks_fetched = 0; try { bool received_money = false; - if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, blocks_fetched, received_money, true, REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE); + if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, blocks_fetched, received_money, true, true, REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE); } catch (const std::exception& ex) { LOG_ERROR("Exception at while refreshing, what=" << ex.what()); } diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 3088fd9c2..282035052 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 60df6296f..72719e982 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 734229380..8e3bdf650 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // |