aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
authorj-berman <justinberman@protonmail.com>2022-08-26 16:13:19 -0700
committerj-berman <justinberman@protonmail.com>2022-09-12 21:24:24 -0600
commitb8271ab88154730149d54a31b2b165f210ac5b47 (patch)
tree46749b58928bbaa2b4405a3ac47cfa85f0768ddc /src/wallet
parentMerge pull request #8554 (diff)
downloadmonero-b8271ab88154730149d54a31b2b165f210ac5b47.tar.xz
wallet2: check wallet compatibility with daemon's hard fork version
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/api/wallet.cpp10
-rw-r--r--src/wallet/node_rpc_proxy.cpp32
-rw-r--r--src/wallet/node_rpc_proxy.h4
-rw-r--r--src/wallet/wallet2.cpp153
-rw-r--r--src/wallet/wallet2.h7
-rw-r--r--src/wallet/wallet_errors.h10
6 files changed, 199 insertions, 17 deletions
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 807dba33b..470206bc5 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -2173,9 +2173,15 @@ bool WalletImpl::connectToDaemon()
Wallet::ConnectionStatus WalletImpl::connected() const
{
uint32_t version = 0;
- m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS);
+ bool wallet_is_outdated, daemon_is_outdated = false;
+ m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS, &wallet_is_outdated, &daemon_is_outdated);
if (!m_is_connected)
- return Wallet::ConnectionStatus_Disconnected;
+ {
+ if (!m_wallet->light_wallet() && (wallet_is_outdated || daemon_is_outdated))
+ return Wallet::ConnectionStatus_WrongVersion;
+ else
+ return Wallet::ConnectionStatus_Disconnected;
+ }
// Version check is not implemented in light wallets nodes/wallets
if (!m_wallet->light_wallet() && (version >> 16) != CORE_RPC_VERSION_MAJOR)
return Wallet::ConnectionStatus_WrongVersion;
diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp
index 7810abdd2..0a9ea8f7b 100644
--- a/src/wallet/node_rpc_proxy.cpp
+++ b/src/wallet/node_rpc_proxy.cpp
@@ -82,18 +82,21 @@ void NodeRPCProxy::invalidate()
m_rpc_payment_seed_hash = crypto::null_hash;
m_rpc_payment_next_seed_hash = crypto::null_hash;
m_height_time = 0;
+ m_target_height_time = 0;
m_rpc_payment_diff = 0;
m_rpc_payment_credits_per_hash_found = 0;
m_rpc_payment_height = 0;
m_rpc_payment_cookie = 0;
+ m_daemon_hard_forks.clear();
}
-boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version)
+boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height)
{
if (m_offline)
return boost::optional<std::string>("offline");
if (m_rpc_version == 0)
{
+ const time_t now = time(NULL);
cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t);
{
@@ -101,9 +104,28 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout);
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_version");
}
+
m_rpc_version = resp_t.version;
+ m_daemon_hard_forks.clear();
+ for (const auto &hf : resp_t.hard_forks)
+ m_daemon_hard_forks.push_back(std::make_pair(hf.hf_version, hf.height));
+ if (resp_t.current_height > 0 || resp_t.target_height > 0)
+ {
+ m_height = resp_t.current_height;
+ m_target_height = resp_t.target_height;
+ m_height_time = now;
+ m_target_height_time = now;
+ }
}
+
rpc_version = m_rpc_version;
+ daemon_hard_forks = m_daemon_hard_forks;
+ boost::optional<std::string> result = get_height(height);
+ if (result)
+ return result;
+ result = get_target_height(target_height);
+ if (result)
+ return result;
return boost::optional<std::string>();
}
@@ -138,6 +160,7 @@ boost::optional<std::string> NodeRPCProxy::get_info()
m_adjusted_time = resp_t.adjusted_time;
m_get_info_time = now;
m_height_time = now;
+ m_target_height_time = now;
}
return boost::optional<std::string>();
}
@@ -160,6 +183,13 @@ boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height)
boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height)
{
+ const time_t now = time(NULL);
+ if (now < m_target_height_time + 30) // re-cache every 30 seconds
+ {
+ height = m_target_height;
+ return boost::optional<std::string>();
+ }
+
auto res = get_info();
if (res)
return res;
diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h
index 07675cdb0..e320565ac 100644
--- a/src/wallet/node_rpc_proxy.h
+++ b/src/wallet/node_rpc_proxy.h
@@ -48,7 +48,7 @@ public:
void invalidate();
void set_offline(bool offline) { m_offline = offline; }
- boost::optional<std::string> get_rpc_version(uint32_t &version);
+ boost::optional<std::string> get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height);
boost::optional<std::string> get_height(uint64_t &height);
void set_height(uint64_t h);
boost::optional<std::string> get_target_height(uint64_t &height);
@@ -103,6 +103,8 @@ private:
crypto::hash m_rpc_payment_next_seed_hash;
uint32_t m_rpc_payment_cookie;
time_t m_height_time;
+ time_t m_target_height_time;
+ std::vector<std::pair<uint8_t, uint64_t>> m_daemon_hard_forks;
};
}
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index b6d95b6b7..2328350d2 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -47,6 +47,7 @@
using namespace epee;
#include "cryptonote_config.h"
+#include "hardforks/hardforks.h"
#include "cryptonote_core/tx_sanity_check.h"
#include "wallet_rpc_helpers.h"
#include "wallet2.h"
@@ -275,6 +276,7 @@ struct options {
const command_line::arg_descriptor<bool> no_dns = {"no-dns", tools::wallet2::tr("Do not use DNS"), false};
const command_line::arg_descriptor<bool> offline = {"offline", tools::wallet2::tr("Do not connect to a daemon, nor use DNS"), false};
const command_line::arg_descriptor<std::string> extra_entropy = {"extra-entropy", tools::wallet2::tr("File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)")};
+ const command_line::arg_descriptor<bool> allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", tools::wallet2::tr("Allow communicating with a daemon that uses a different version"), false};
};
void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file, std::string &mms_file)
@@ -484,6 +486,9 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
add_extra_entropy_thread_safe(data.data(), data.size());
}
+ if (command_line::has_arg(vm, opts.allow_mismatched_daemon_version))
+ wallet->allow_mismatched_daemon_version(true);
+
try
{
if (!command_line::is_arg_defaulted(vm, opts.tx_notify))
@@ -1219,7 +1224,8 @@ 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_has_ever_refreshed_from_node(false)
+ m_has_ever_refreshed_from_node(false),
+ m_allow_mismatched_daemon_version(false)
{
set_rpc_client_secret_key(rct::rct2sk(rct::skGen()));
}
@@ -1279,6 +1285,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par
command_line::add_arg(desc_params, opts.no_dns);
command_line::add_arg(desc_params, opts.offline);
command_line::add_arg(desc_params, opts.extra_entropy);
+ command_line::add_arg(desc_params, opts.allow_mismatched_daemon_version);
}
std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
@@ -2915,6 +2922,26 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
refresh(trusted_daemon, start_height, blocks_fetched, received_money);
}
//----------------------------------------------------------------------------------------------------
+void check_block_hard_fork_version(cryptonote::network_type nettype, uint8_t hf_version, uint64_t height, bool &wallet_is_outdated, bool &daemon_is_outdated)
+{
+ const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks
+ : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks;
+ const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks
+ : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks;
+
+ wallet_is_outdated = static_cast<size_t>(hf_version) > wallet_num_hard_forks;
+ if (wallet_is_outdated)
+ return;
+
+ // check block's height falls within wallet's expected range for block's given version
+ uint64_t start_height = hf_version == 1 ? 0 : wallet_hard_forks[hf_version - 1].height;
+ uint64_t end_height = static_cast<size_t>(hf_version) + 1 > wallet_num_hard_forks
+ ? std::numeric_limits<uint64_t>::max()
+ : wallet_hard_forks[hf_version].height;
+
+ 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)
{
error = false;
@@ -2956,6 +2983,23 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks
error = true;
break;
}
+
+ if (!m_allow_mismatched_daemon_version)
+ {
+ // make sure block's hard fork version is expected at the block's height
+ uint8_t hf_version = parsed_blocks[i].block.major_version;
+ uint64_t height = blocks_start_height + i;
+ bool wallet_is_outdated = false;
+ bool daemon_is_outdated = false;
+ check_block_hard_fork_version(m_nettype, hf_version, height, wallet_is_outdated, daemon_is_outdated);
+ THROW_WALLET_EXCEPTION_IF(wallet_is_outdated || daemon_is_outdated, error::incorrect_fork_version,
+ "Unexpected hard fork version v" + std::to_string(hf_version) + " at height " + std::to_string(height) + ". " +
+ (wallet_is_outdated
+ ? "Make sure your wallet is up to date"
+ : "Make sure the node you are connected to is running the latest version")
+ );
+ }
+
parsed_blocks[i].o_indices = std::move(o_indices[i]);
}
@@ -3578,6 +3622,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool");
throw;
}
+ catch (const error::incorrect_fork_version&)
+ {
+ THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool");
+ throw;
+ }
catch (const std::exception&)
{
blocks_fetched += added_blocks;
@@ -4188,6 +4237,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_auto_mine_for_rpc_payment_threshold = -1.0f;
m_credits_target = 0;
m_enable_multisig = false;
+ m_allow_mismatched_daemon_version = false;
}
else if(json.IsObject())
{
@@ -5331,7 +5381,7 @@ bool wallet2::prepare_file_names(const std::string& file_path)
return true;
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout)
+bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, bool *wallet_is_outdated, bool *daemon_is_outdated)
{
THROW_WALLET_EXCEPTION_IF(!m_is_initialized, error::wallet_not_initialized);
@@ -5368,20 +5418,99 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout)
}
}
- if (!m_rpc_version)
+ if (!m_rpc_version && !check_version(version, wallet_is_outdated, daemon_is_outdated))
+ return false;
+ if (version)
+ *version = m_rpc_version;
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated)
+{
+ uint32_t rpc_version;
+ std::vector<std::pair<uint8_t, uint64_t>> daemon_hard_forks;
+ uint64_t height;
+ uint64_t target_height;
+ if (m_node_rpc_proxy.get_rpc_version(rpc_version, daemon_hard_forks, height, target_height))
{
- cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t);
- cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t);
- bool r = invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t);
- if(!r || resp_t.status != CORE_RPC_STATUS_OK) {
- if(version)
- *version = 0;
+ if(version)
+ *version = 0;
+ return false;
+ }
+
+ // check wallet compatibility with daemon's hard fork version
+ if (!m_allow_mismatched_daemon_version)
+ if (!check_hard_fork_version(m_nettype, daemon_hard_forks, height, target_height, wallet_is_outdated, daemon_is_outdated))
return false;
+
+ m_rpc_version = rpc_version;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated)
+{
+ const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks
+ : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks;
+ const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks
+ : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks;
+
+ // First check if wallet or daemon is outdated (whether either are unaware of
+ // a hard fork). Then check if fork has passed rendering versions incompatible
+ if (daemon_hard_forks.size() > 0)
+ {
+ bool daemon_outdated = daemon_hard_forks.size() < wallet_num_hard_forks;
+ bool wallet_outdated = daemon_hard_forks.size() > wallet_num_hard_forks;
+
+ if (daemon_is_outdated)
+ *daemon_is_outdated = daemon_outdated;
+ if (wallet_is_outdated)
+ *wallet_is_outdated = wallet_outdated;
+
+ if (daemon_outdated)
+ {
+ uint64_t daemon_missed_fork_height = wallet_hard_forks[daemon_hard_forks.size()].height;
+
+ // If the daemon missed the fork, then technically it is no longer part of
+ // the Monero network. Don't connect.
+ bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height;
+ if (daemon_missed_fork)
+ return false;
+ }
+ else if (wallet_outdated)
+ {
+ uint64_t wallet_missed_fork_height = daemon_hard_forks[wallet_num_hard_forks].second;
+
+ // If the wallet missed the fork, then technically it is no longer able
+ // to communicate with the Monero network. Don't connect.
+ bool wallet_missed_fork = height >= wallet_missed_fork_height || target_height >= wallet_missed_fork_height;
+ if (wallet_missed_fork)
+ return false;
}
- m_rpc_version = resp_t.version;
}
- if (version)
- *version = m_rpc_version;
+ else
+ {
+ // Non-updated daemons won't return daemon_hard_forks in response to
+ // get_version. Fall back to extra call to get_hard_fork_info by version.
+ uint64_t daemon_fork_height;
+ get_hard_fork_info(wallet_num_hard_forks-1/* wallet expects "double fork" pattern */, daemon_fork_height);
+ bool daemon_outdated = daemon_fork_height == std::numeric_limits<uint64_t>::max();
+
+ if (daemon_is_outdated)
+ *daemon_is_outdated = daemon_outdated;
+
+ if (daemon_outdated)
+ {
+ uint64_t daemon_missed_fork_height = wallet_hard_forks[wallet_num_hard_forks-2].height;
+ bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height;
+ if (daemon_missed_fork)
+ return false;
+ }
+
+ // Don't need to check if wallet is outdated here because the daemons updated
+ // for a future hard fork will serve daemon_hard_forks above. The check for
+ // an outdated wallet is done above using daemon_hard_forks.
+ }
return true;
}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 1f84458a6..83c22d5f8 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -1091,7 +1091,9 @@ private:
bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids);
std::vector<pending_tx> create_unmixable_sweep_transactions();
void discard_unmixable_outputs();
- bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000);
+ bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL);
+ bool check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated);
+ bool check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated);
void get_transfers(wallet2::transfer_container& incoming_transfers) const;
void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
@@ -1358,6 +1360,8 @@ private:
void credits_target(uint64_t threshold) { m_credits_target = threshold; }
bool is_multisig_enabled() const { return m_enable_multisig; }
void enable_multisig(bool enable) { m_enable_multisig = enable; }
+ bool is_mismatched_daemon_version_allowed() const { return m_allow_mismatched_daemon_version; }
+ void allow_mismatched_daemon_version(bool allow_mismatch) { m_allow_mismatched_daemon_version = allow_mismatch; }
bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none);
@@ -1875,6 +1879,7 @@ private:
rpc_payment_state_t m_rpc_payment_state;
uint64_t m_credits_target;
bool m_enable_multisig;
+ bool m_allow_mismatched_daemon_version;
// Aux transaction data from device
serializable_unordered_map<crypto::hash, std::string> m_tx_device;
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index df594aa21..0b8512163 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -439,6 +439,16 @@ namespace tools
std::string to_string() const { return refresh_error::to_string(); }
};
//----------------------------------------------------------------------------------------------------
+ struct incorrect_fork_version : public refresh_error
+ {
+ explicit incorrect_fork_version(std::string&& loc, const std::string& message)
+ : refresh_error(std::move(loc), message)
+ {
+ }
+
+ std::string to_string() const { return refresh_error::to_string(); }
+ };
+ //----------------------------------------------------------------------------------------------------
struct signature_check_failed : public wallet_logic_error
{
explicit signature_check_failed(std::string&& loc, const std::string& message)