Skip to content

Commit f6ebd47

Browse files
committed
bootstrap mode: ensure we bootstrap with compatible daemon
1 parent dbe95f3 commit f6ebd47

9 files changed

Lines changed: 155 additions & 99 deletions

File tree

src/hardforks/hardforks.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,59 @@ const hardfork_t stagenet_hard_forks[] = {
130130
{ 16, 1151720, 0, 1656629118 },
131131
};
132132
const size_t num_stagenet_hard_forks = sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]);
133+
134+
bool check_fork_version_compatibility(
135+
const cryptonote::network_type &nettype,
136+
const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks,
137+
const uint64_t height,
138+
const uint64_t target_height,
139+
bool *client_is_outdated,
140+
bool *daemon_is_outdated)
141+
{
142+
const size_t client_num_hard_forks = nettype == cryptonote::network_type::TESTNET ? num_testnet_hard_forks
143+
: nettype == cryptonote::network_type::STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks;
144+
const hardfork_t *client_hard_forks = cryptonote::network_type::TESTNET ? testnet_hard_forks
145+
: nettype == cryptonote::network_type::STAGENET ? stagenet_hard_forks : mainnet_hard_forks;
146+
147+
// Make sure we're pointing to an FCMP++ compatible daemon in order for client
148+
// to sync the FCMP++ tree.
149+
if (daemon_hard_forks.size() < HF_VERSION_FCMP_PLUS_PLUS)
150+
{
151+
if (daemon_is_outdated)
152+
*daemon_is_outdated = true;
153+
return false;
154+
}
155+
156+
// Check if client or daemon is outdated (whether either are unaware of a hard
157+
// fork). Then check if fork has passed rendering versions incompatible.
158+
const bool daemon_outdated = daemon_hard_forks.size() < client_num_hard_forks;
159+
const bool client_outdated = daemon_hard_forks.size() > client_num_hard_forks;
160+
161+
if (daemon_is_outdated)
162+
*daemon_is_outdated = daemon_outdated;
163+
if (client_is_outdated)
164+
*client_is_outdated = client_outdated;
165+
166+
if (daemon_outdated)
167+
{
168+
uint64_t daemon_missed_fork_height = client_hard_forks[daemon_hard_forks.size()].height;
169+
170+
// If the daemon missed the fork, then technically it is no longer part of
171+
// the Monero network. Don't connect.
172+
bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height;
173+
if (daemon_missed_fork)
174+
return false;
175+
}
176+
else if (client_outdated)
177+
{
178+
uint64_t client_missed_fork_height = daemon_hard_forks[client_num_hard_forks].second;
179+
180+
// If the client missed the fork, then technically it is no longer able
181+
// to communicate with the Monero network. Don't connect.
182+
bool client_missed_fork = height >= client_missed_fork_height || target_height >= client_missed_fork_height;
183+
if (client_missed_fork)
184+
return false;
185+
}
186+
187+
return true;
188+
}

src/hardforks/hardforks.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@
2828

2929
#pragma once
3030

31+
#include "cryptonote_config.h"
32+
3133
#include <stdint.h>
3234
#include <time.h>
35+
#include <utility>
36+
#include <vector>
3337

3438
struct hardfork_t
3539
{
@@ -50,3 +54,11 @@ extern const size_t num_testnet_hard_forks;
5054

5155
extern const hardfork_t stagenet_hard_forks[];
5256
extern const size_t num_stagenet_hard_forks;
57+
58+
bool check_fork_version_compatibility(
59+
const cryptonote::network_type &nettype,
60+
const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks,
61+
const uint64_t height,
62+
const uint64_t target_height,
63+
bool *client_is_outdated,
64+
bool *daemon_is_outdated);

src/rpc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ target_link_libraries(daemon_rpc_server
164164
rpc_pub
165165
cryptonote_core
166166
cryptonote_protocol
167+
hardforks
167168
version
168169
daemon_messages
169170
serialization

src/rpc/bootstrap_daemon.cpp

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "crypto/crypto.h"
88
#include "cryptonote_core/cryptonote_core.h"
9+
#include "hardforks/hardforks.h"
910
#include "misc_log_ex.h"
1011
#include "net/parse.h"
1112

@@ -16,21 +17,25 @@ namespace cryptonote
1617
{
1718

1819
bootstrap_daemon::bootstrap_daemon(
20+
const cryptonote::network_type &nettype,
1921
std::function<std::map<std::string, bool>()> get_public_nodes,
2022
bool rpc_payment_enabled,
2123
const std::string &proxy)
22-
: m_selector(new bootstrap_node::selector_auto(std::move(get_public_nodes)))
24+
: m_nettype(nettype)
25+
, m_selector(new bootstrap_node::selector_auto(std::move(get_public_nodes)))
2326
, m_rpc_payment_enabled(rpc_payment_enabled)
2427
{
2528
set_proxy(proxy);
2629
}
2730

2831
bootstrap_daemon::bootstrap_daemon(
32+
const cryptonote::network_type &nettype,
2933
const std::string &address,
3034
boost::optional<epee::net_utils::http::login> credentials,
3135
bool rpc_payment_enabled,
3236
const std::string &proxy)
33-
: m_selector(nullptr)
37+
: m_nettype(nettype)
38+
, m_selector(nullptr)
3439
, m_rpc_payment_enabled(rpc_payment_enabled)
3540
{
3641
set_proxy(proxy);
@@ -68,6 +73,25 @@ namespace cryptonote
6873
return {{res.height, res.target_height}};
6974
}
7075

76+
boost::optional<cryptonote::COMMAND_RPC_GET_VERSION::response> bootstrap_daemon::get_version()
77+
{
78+
cryptonote::COMMAND_RPC_GET_VERSION::request req;
79+
cryptonote::COMMAND_RPC_GET_VERSION::response res;
80+
81+
const bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req, res, m_http_client, std::chrono::seconds(10));
82+
if (!handle_result(r, res.status))
83+
{
84+
return boost::none;
85+
}
86+
87+
if (res.status != CORE_RPC_STATUS_OK)
88+
{
89+
return boost::none;
90+
}
91+
92+
return boost::optional<cryptonote::COMMAND_RPC_GET_VERSION::response>(res);
93+
}
94+
7195
bool bootstrap_daemon::handle_result(bool success, const std::string &status)
7296
{
7397
const bool failed = !success || (!m_rpc_payment_enabled && status == CORE_RPC_STATUS_PAYMENT_REQUIRED);
@@ -115,15 +139,50 @@ namespace cryptonote
115139
return true;
116140
}
117141

118-
boost::optional<bootstrap_node::node_info> node;
142+
// We want to make sure we connect to a compatible bootstrap daemon
143+
MINFO("Attempting to switch bootstrap daemon address");
144+
std::size_t n_attempts = 0;
145+
while (n_attempts++ < 5)
119146
{
120-
const boost::unique_lock<boost::mutex> lock(m_selector_mutex);
121-
node = m_selector->next_node();
122-
}
123-
if (node) {
124-
return set_server(node->address, node->credentials);
147+
MDEBUG("Bootstrap daemon switch attempt " << n_attempts);
148+
std::string address;
149+
{
150+
boost::unique_lock<boost::mutex> lock(m_selector_mutex);
151+
const auto node = m_selector->next_node();
152+
if (!node)
153+
continue;
154+
if (!this->set_server(node->address, node->credentials))
155+
continue;
156+
address = node->address;
157+
}
158+
159+
const auto res = this->get_version();
160+
if (!res)
161+
continue;
162+
std::vector<std::pair<uint8_t, uint64_t>> bootstrap_daemon_hfs;
163+
bootstrap_daemon_hfs.reserve(res->hard_forks.size());
164+
for (const auto &hf : res->hard_forks)
165+
bootstrap_daemon_hfs.push_back({hf.hf_version, hf.height});
166+
167+
// Only connect to a compatible bootstrap daemon
168+
bool client_is_outdated = false, daemon_is_outdated = false;
169+
if (!check_fork_version_compatibility(m_nettype, bootstrap_daemon_hfs, res->current_height, res->target_height, &client_is_outdated, &daemon_is_outdated))
170+
{
171+
MWARNING("Bootstrap daemon " << address << " is incompatible with our daemon");
172+
this->handle_result(false, "");
173+
continue;
174+
}
175+
176+
if (client_is_outdated)
177+
MWARNING("We are connected to a bootstrap daemon that knows of a future hard fork(s) that we do not");
178+
else if (daemon_is_outdated)
179+
MWARNING("We are connected to a bootstrap daemon that has not yet updated for the upcoming fork");
180+
181+
MGINFO("Successfully switched bootstrap daemon to " << address);
182+
return true;
125183
}
126184

185+
MERROR("Could not find compatible bootstrap daemon");
127186
return false;
128187
}
129188

src/rpc/bootstrap_daemon.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <boost/utility/string_ref.hpp>
1010

1111
#include "net/http.h"
12+
#include "core_rpc_server_commands_defs.h"
1213
#include "storages/http_abstract_invoke.h"
1314

1415
#include "bootstrap_node_selector.h"
@@ -20,10 +21,12 @@ namespace cryptonote
2021
{
2122
public:
2223
bootstrap_daemon(
24+
const cryptonote::network_type &nettype,
2325
std::function<std::map<std::string, bool>()> get_public_nodes,
2426
bool rpc_payment_enabled,
2527
const std::string &proxy);
2628
bootstrap_daemon(
29+
const cryptonote::network_type &nettype,
2730
const std::string &address,
2831
boost::optional<epee::net_utils::http::login> credentials,
2932
bool rpc_payment_enabled,
@@ -78,9 +81,11 @@ namespace cryptonote
7881

7982
private:
8083
bool set_server(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials = boost::none);
84+
boost::optional<cryptonote::COMMAND_RPC_GET_VERSION::response> get_version();
8185
bool switch_server_if_needed();
8286

8387
private:
88+
const cryptonote::network_type m_nettype;
8489
net::http::client m_http_client;
8590
const bool m_rpc_payment_enabled;
8691
const std::unique_ptr<bootstrap_node::selector> m_selector;

src/rpc/core_rpc_server.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,11 @@ namespace cryptonote
250250
auto get_nodes = [this]() {
251251
return get_public_nodes(credits_per_hash_threshold);
252252
};
253-
m_bootstrap_daemon.reset(new bootstrap_daemon(std::move(get_nodes), rpc_payment_enabled, m_bootstrap_daemon_proxy.empty() ? proxy : m_bootstrap_daemon_proxy));
253+
m_bootstrap_daemon.reset(new bootstrap_daemon(nettype(), std::move(get_nodes), rpc_payment_enabled, m_bootstrap_daemon_proxy.empty() ? proxy : m_bootstrap_daemon_proxy));
254254
}
255255
else
256256
{
257-
m_bootstrap_daemon.reset(new bootstrap_daemon(address, credentials, rpc_payment_enabled, m_bootstrap_daemon_proxy.empty() ? proxy : m_bootstrap_daemon_proxy));
257+
m_bootstrap_daemon.reset(new bootstrap_daemon(nettype(), address, credentials, rpc_payment_enabled, m_bootstrap_daemon_proxy.empty() ? proxy : m_bootstrap_daemon_proxy));
258258
}
259259

260260
m_should_use_bootstrap_daemon = m_bootstrap_daemon.get() != nullptr;

src/wallet/wallet2.cpp

Lines changed: 9 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3437,23 +3437,23 @@ bool wallet2::bump_refresh_start_height(const uint64_t init_start_height, const
34373437
// Get the corresponding hash from the daemon
34383438
crypto::hash granularized_init_hash;
34393439
{
3440-
cryptonote::COMMAND_RPC_GETBLOCKHASH::request req = AUTO_VAL_INIT(req);
3441-
cryptonote::COMMAND_RPC_GETBLOCKHASH::response res = AUTO_VAL_INIT(res);
3440+
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req = AUTO_VAL_INIT(req);
3441+
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response res = AUTO_VAL_INIT(res);
34423442
epee::json_rpc::error error;
34433443
error.code = 0;
3444-
req.push_back(granularized_init_block_idx);
3444+
req.height = granularized_init_block_idx;
34453445

34463446
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
3447-
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "on_get_block_hash", req, res, error, *m_http_client, rpc_timeout);
3447+
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, error, *m_http_client, rpc_timeout);
34483448
if (error.code == CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT) {
34493449
// We don't need to sync if start height is higher than the daemon height
34503450
MWARNING("Refresh start height is higher than the current chain tip, not syncing");
34513451
return false;
34523452
}
3453-
THROW_WALLET_EXCEPTION_IF(!r || error.code != 0, error::get_block_hash_error, error.message);
3454-
THROW_WALLET_EXCEPTION_IF(res.size() != 64, error::get_block_hash_error, "Hash is not 32 bytes as expected");
3455-
r = epee::string_tools::hex_to_pod(res, granularized_init_hash);
3456-
THROW_WALLET_EXCEPTION_IF(!r, error::get_block_hash_error, "Failed to parse getblockhash hash");
3453+
THROW_WALLET_EXCEPTION_IF(!r || error.code != 0, error::block_header_by_height, error.message);
3454+
THROW_WALLET_EXCEPTION_IF(res.block_header.hash.size() != 64, error::block_header_by_height, "Hash is not 32 bytes as expected");
3455+
r = epee::string_tools::hex_to_pod(res.block_header.hash, granularized_init_hash);
3456+
THROW_WALLET_EXCEPTION_IF(!r, error::block_header_by_height, "Failed to parse getblockheaderbyheight hash");
34573457
}
34583458

34593459
MINFO("Starting scanner on top of block " << granularized_init_block_idx << " , hash " << granularized_init_hash);
@@ -6660,90 +6660,14 @@ bool wallet2::check_version(uint32_t *version, bool *wallet_is_outdated, bool *d
66606660
// check wallet compatibility with daemon's hard fork version
66616661
if (!m_allow_mismatched_daemon_version)
66626662
{
6663-
if (rpc_version < MAKE_CORE_RPC_VERSION(3, 17))
6664-
{
6665-
// Wallet cannot init FCMP++ tree while syncing if pointing to an older daemon
6666-
if (daemon_is_outdated)
6667-
*daemon_is_outdated = true;
6668-
return false;
6669-
}
6670-
6671-
// check wallet compatibility with daemon's hard fork version
6672-
if (!check_hard_fork_version(m_nettype, daemon_hard_forks, height, target_height, wallet_is_outdated, daemon_is_outdated))
6663+
if (!check_fork_version_compatibility(m_nettype, daemon_hard_forks, height, target_height, wallet_is_outdated, daemon_is_outdated))
66736664
return false;
66746665
}
66756666

66766667
m_rpc_version = rpc_version;
66776668
return true;
66786669
}
66796670
//----------------------------------------------------------------------------------------------------
6680-
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)
6681-
{
6682-
const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks
6683-
: nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks;
6684-
const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks
6685-
: nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks;
6686-
6687-
// First check if wallet or daemon is outdated (whether either are unaware of
6688-
// a hard fork). Then check if fork has passed rendering versions incompatible
6689-
if (daemon_hard_forks.size() > 0)
6690-
{
6691-
bool daemon_outdated = daemon_hard_forks.size() < wallet_num_hard_forks;
6692-
bool wallet_outdated = daemon_hard_forks.size() > wallet_num_hard_forks;
6693-
6694-
if (daemon_is_outdated)
6695-
*daemon_is_outdated = daemon_outdated;
6696-
if (wallet_is_outdated)
6697-
*wallet_is_outdated = wallet_outdated;
6698-
6699-
if (daemon_outdated)
6700-
{
6701-
uint64_t daemon_missed_fork_height = wallet_hard_forks[daemon_hard_forks.size()].height;
6702-
6703-
// If the daemon missed the fork, then technically it is no longer part of
6704-
// the Monero network. Don't connect.
6705-
bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height;
6706-
if (daemon_missed_fork)
6707-
return false;
6708-
}
6709-
else if (wallet_outdated)
6710-
{
6711-
uint64_t wallet_missed_fork_height = daemon_hard_forks[wallet_num_hard_forks].second;
6712-
6713-
// If the wallet missed the fork, then technically it is no longer able
6714-
// to communicate with the Monero network. Don't connect.
6715-
bool wallet_missed_fork = height >= wallet_missed_fork_height || target_height >= wallet_missed_fork_height;
6716-
if (wallet_missed_fork)
6717-
return false;
6718-
}
6719-
}
6720-
else
6721-
{
6722-
// Non-updated daemons won't return daemon_hard_forks in response to
6723-
// get_version. Fall back to extra call to get_hard_fork_info by version.
6724-
uint64_t daemon_fork_height;
6725-
get_hard_fork_info(wallet_num_hard_forks-1/* wallet expects "double fork" pattern */, daemon_fork_height);
6726-
bool daemon_outdated = daemon_fork_height == std::numeric_limits<uint64_t>::max();
6727-
6728-
if (daemon_is_outdated)
6729-
*daemon_is_outdated = daemon_outdated;
6730-
6731-
if (daemon_outdated)
6732-
{
6733-
uint64_t daemon_missed_fork_height = wallet_hard_forks[wallet_num_hard_forks-2].height;
6734-
bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height;
6735-
if (daemon_missed_fork)
6736-
return false;
6737-
}
6738-
6739-
// Don't need to check if wallet is outdated here because the daemons updated
6740-
// for a future hard fork will serve daemon_hard_forks above. The check for
6741-
// an outdated wallet is done above using daemon_hard_forks.
6742-
}
6743-
6744-
return true;
6745-
}
6746-
//----------------------------------------------------------------------------------------------------
67476671
void wallet2::set_offline(bool offline)
67486672
{
67496673
m_offline = offline;

src/wallet/wallet2.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,6 @@ namespace tools
772772
void discard_unmixable_outputs();
773773
bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL);
774774
bool check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated);
775-
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);
776775
void get_transfers(wallet2::transfer_container& incoming_transfers, const bool include_all = true) const;
777776
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;
778777
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;

0 commit comments

Comments
 (0)