Skip to content

Commit

Permalink
Merge pull request #8566
Browse files Browse the repository at this point in the history
65e13db wallet2: fix rescanning tx via scan_tx (j-berman)
  • Loading branch information
luigi1111 committed Jun 27, 2023
2 parents 1ce32d8 + 65e13db commit 60e9426
Show file tree
Hide file tree
Showing 12 changed files with 631 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ jobs:
- name: install monero dependencies
run: ${{env.APT_INSTALL_LINUX}}
- name: install Python dependencies
run: pip install requests psutil monotonic zmq
run: pip install requests psutil monotonic zmq deepdiff
- name: tests
env:
CTEST_OUTPUT_ON_FAILURE: ON
Expand Down
5 changes: 3 additions & 2 deletions src/simplewallet/simplewallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3215,7 +3215,6 @@ bool simple_wallet::scan_tx(const std::vector<std::string> &args)
}
txids.insert(txid);
}
std::vector<crypto::hash> txids_v(txids.begin(), txids.end());

if (!m_wallet->is_trusted_daemon()) {
message_writer(console_color_red, true) << tr("WARNING: this operation may reveal the txids to the remote node and affect your privacy");
Expand All @@ -3228,7 +3227,9 @@ bool simple_wallet::scan_tx(const std::vector<std::string> &args)
LOCK_IDLE_SCOPE();
m_in_manual_refresh.store(true);
try {
m_wallet->scan_tx(txids_v);
m_wallet->scan_tx(txids);
} catch (const tools::error::wont_reprocess_recent_txs_via_untrusted_daemon &e) {
fail_msg_writer() << e.what() << ". Either connect to a trusted daemon by passing --trusted-daemon when starting the wallet, or use rescan_bc to rescan the chain.";
} catch (const std::exception &e) {
fail_msg_writer() << e.what();
}
Expand Down
8 changes: 6 additions & 2 deletions src/wallet/api/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1302,11 +1302,15 @@ bool WalletImpl::scanTransactions(const std::vector<std::string> &txids)
}
txids_u.insert(txid);
}
std::vector<crypto::hash> txids_v(txids_u.begin(), txids_u.end());

try
{
m_wallet->scan_tx(txids_v);
m_wallet->scan_tx(txids_u);
}
catch (const tools::error::wont_reprocess_recent_txs_via_untrusted_daemon &e)
{
setStatusError(e.what());
return false;
}
catch (const std::exception &e)
{
Expand Down
22 changes: 22 additions & 0 deletions src/wallet/node_rpc_proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,4 +392,26 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_payment_info(bool mining, boo
return boost::none;
}

boost::optional<std::string> NodeRPCProxy::get_block_header_by_height(uint64_t height, cryptonote::block_header_response &block_header)
{
if (m_offline)
return boost::optional<std::string>("offline");

cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response resp_t = AUTO_VAL_INIT(resp_t);
req_t.height = height;

{
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);
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req_t, resp_t, m_http_client, rpc_timeout);
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "getblockheaderbyheight");
check_rpc_cost(m_rpc_payment_state, "getblockheaderbyheight", resp_t.credits, pre_call_credits, COST_PER_BLOCK_HEADER);
}

block_header = std::move(resp_t.block_header);
return boost::optional<std::string>();
}

}
1 change: 1 addition & 0 deletions src/wallet/node_rpc_proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class NodeRPCProxy
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_block_header_by_height(uint64_t height, cryptonote::block_header_response &block_header);

private:
template<typename T> void handle_payment_changes(const T &res, std::true_type) {
Expand Down
366 changes: 315 additions & 51 deletions src/wallet/wallet2.cpp

Large diffs are not rendered by default.

37 changes: 34 additions & 3 deletions src/wallet/wallet2.h
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,30 @@ namespace tools
bool empty() const { return tx_extra_fields.empty() && primary.empty() && additional.empty(); }
};

struct detached_blockchain_data
{
hashchain detached_blockchain;
size_t original_chain_size;
std::unordered_set<crypto::hash> detached_tx_hashes;
std::unordered_map<crypto::hash, std::vector<cryptonote::tx_destination_entry>> detached_confirmed_txs_dests;
};

struct process_tx_entry_t
{
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry tx_entry;
cryptonote::transaction tx;
crypto::hash tx_hash;
};

struct tx_entry_data
{
std::vector<process_tx_entry_t> tx_entries;
uint64_t lowest_height;
uint64_t highest_height;

tx_entry_data(): lowest_height((uint64_t)-1), highest_height(0) {}
};

/*!
* \brief Generates a wallet or restores one. Assumes the multisig setup
* has already completed for the provided multisig info.
Expand Down Expand Up @@ -1381,7 +1405,7 @@ namespace tools
std::string get_spend_proof(const crypto::hash &txid, const std::string &message);
bool check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str);

void scan_tx(const std::vector<crypto::hash> &txids);
void scan_tx(const std::unordered_set<crypto::hash> &txids);

/*!
* \brief Generates a proof that proves the reserve of unspent funds
Expand Down Expand Up @@ -1700,10 +1724,11 @@ namespace tools
*/
bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password);
bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional<crypto::chacha_key>& keys_to_encrypt);
void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL, bool ignore_callbacks = false);
bool should_skip_block(const cryptonote::block &b, uint64_t height) const;
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
void detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
detached_blockchain_data detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
void handle_reorg(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
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);
Expand Down Expand Up @@ -1754,6 +1779,9 @@ namespace tools
crypto::chacha_key get_ringdb_key();
void setup_keys(const epee::wipeable_string &password);
size_t get_transfer_details(const crypto::key_image &ki) const;
tx_entry_data get_tx_entries(const std::unordered_set<crypto::hash> &txids);
void sort_scan_tx_entries(std::vector<process_tx_entry_t> &unsorted_tx_entries);
void process_scan_txs(const tx_entry_data &txs_to_scan, const tx_entry_data &txs_to_reprocess, const std::unordered_set<crypto::hash> &tx_hashes_to_reprocess, detached_blockchain_data &dbd);

void register_devices();
hw::device& lookup_device(const std::string & device_descriptor);
Expand Down Expand Up @@ -1846,6 +1874,9 @@ namespace tools
// 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_skip_to_height;
// m_skip_to_height is useful when we don't want to modify the wallet's restore height.
// m_refresh_from_block_height is also a wallet's restore height which should remain constant unless explicitly modified by the user.
bool m_confirm_non_default_ring_size;
AskPasswordType m_ask_password;
uint64_t m_max_reorg_depth;
Expand Down
19 changes: 19 additions & 0 deletions src/wallet/wallet_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ namespace tools
// get_output_distribution
// payment_required
// wallet_files_doesnt_correspond
// scan_tx_error *
// wont_reprocess_recent_txs_via_untrusted_daemon
//
// * - class with protected ctor

Expand Down Expand Up @@ -915,6 +917,23 @@ namespace tools
}
};
//----------------------------------------------------------------------------------------------------
struct scan_tx_error : public wallet_logic_error
{
protected:
explicit scan_tx_error(std::string&& loc, const std::string& message)
: wallet_logic_error(std::move(loc), message)
{
}
};
//----------------------------------------------------------------------------------------------------
struct wont_reprocess_recent_txs_via_untrusted_daemon : public scan_tx_error
{
explicit wont_reprocess_recent_txs_via_untrusted_daemon(std::string&& loc)
: scan_tx_error(std::move(loc), "The wallet has already seen 1 or more recent transactions than the scanned tx")
{
}
};
//----------------------------------------------------------------------------------------------------

#if !defined(_MSC_VER)

Expand Down
8 changes: 6 additions & 2 deletions src/wallet/wallet_rpc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3167,7 +3167,7 @@ namespace tools
return false;
}

std::vector<crypto::hash> txids;
std::unordered_set<crypto::hash> txids;
std::list<std::string>::const_iterator i = req.txids.begin();
while (i != req.txids.end())
{
Expand All @@ -3180,11 +3180,15 @@ namespace tools
}

crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_blob.data());
txids.push_back(txid);
txids.insert(txid);
}

try {
m_wallet->scan_tx(txids);
} catch (const tools::error::wont_reprocess_recent_txs_via_untrusted_daemon &e) {
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = e.what() + std::string(". Either connect to a trusted daemon or rescan the chain.");
return false;
} catch (const std::exception &e) {
handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
Expand Down
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Functional tests are located under the `tests/functional_tests` directory.

Building all the tests requires installing the following dependencies:
```bash
pip install requests psutil monotonic zmq
pip install requests psutil monotonic zmq deepdiff
```

First, run a regtest daemon in the offline mode and with a fixed difficulty:
Expand Down
4 changes: 2 additions & 2 deletions tests/functional_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ target_link_libraries(make_test_signature
monero_add_minimal_executable(cpu_power_test cpu_power_test.cpp)
find_program(PYTHON3_FOUND python3 REQUIRED)

execute_process(COMMAND ${PYTHON3_FOUND} "-c" "import requests; import psutil; import monotonic; import zmq; print('OK')" OUTPUT_VARIABLE REQUESTS_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${PYTHON3_FOUND} "-c" "import requests; import psutil; import monotonic; import zmq; import deepdiff; print('OK')" OUTPUT_VARIABLE REQUESTS_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE)
if (REQUESTS_OUTPUT STREQUAL "OK")
add_test(
NAME functional_tests_rpc
Expand All @@ -76,6 +76,6 @@ if (REQUESTS_OUTPUT STREQUAL "OK")
NAME check_missing_rpc_methods
COMMAND ${PYTHON3_FOUND} "${CMAKE_CURRENT_SOURCE_DIR}/check_missing_rpc_methods.py" "${CMAKE_SOURCE_DIR}")
else()
message(WARNING "functional_tests_rpc and check_missing_rpc_methods skipped, needs the 'requests', 'psutil', 'monotonic', and 'zmq' python modules")
message(WARNING "functional_tests_rpc and check_missing_rpc_methods skipped, needs the 'requests', 'psutil', 'monotonic', 'zmq', and 'deepdiff' python modules")
set(CTEST_CUSTOM_TESTS_IGNORE ${CTEST_CUSTOM_TESTS_IGNORE} functional_tests_rpc check_missing_rpc_methods)
endif()

0 comments on commit 60e9426

Please sign in to comment.