aboutsummaryrefslogtreecommitdiff
path: root/src/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc')
-rw-r--r--src/rpc/CMakeLists.txt4
-rw-r--r--src/rpc/bootstrap_daemon.cpp95
-rw-r--r--src/rpc/bootstrap_daemon.h67
-rw-r--r--src/rpc/core_rpc_server.cpp299
-rw-r--r--src/rpc/core_rpc_server.h12
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h68
-rw-r--r--src/rpc/daemon_handler.cpp5
-rw-r--r--src/rpc/rpc_args.cpp37
-rw-r--r--src/rpc/rpc_args.h6
-rw-r--r--src/rpc/rpc_handler.cpp4
-rw-r--r--src/rpc/zmq_server.cpp2
11 files changed, 524 insertions, 75 deletions
diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt
index cffe8e1eb..d98670e05 100644
--- a/src/rpc/CMakeLists.txt
+++ b/src/rpc/CMakeLists.txt
@@ -30,6 +30,7 @@ set(rpc_base_sources
rpc_args.cpp)
set(rpc_sources
+ bootstrap_daemon.cpp
core_rpc_server.cpp
rpc_handler.cpp
instanciations)
@@ -47,12 +48,13 @@ set(rpc_base_headers
rpc_args.h)
set(rpc_headers
- rpc_handler.cpp)
+ rpc_handler.h)
set(daemon_rpc_server_headers)
set(rpc_daemon_private_headers
+ bootstrap_daemon.h
core_rpc_server.h
core_rpc_server_commands_defs.h
core_rpc_server_error_codes.h)
diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp
new file mode 100644
index 000000000..6f8426ee7
--- /dev/null
+++ b/src/rpc/bootstrap_daemon.cpp
@@ -0,0 +1,95 @@
+#include "bootstrap_daemon.h"
+
+#include <stdexcept>
+
+#include "crypto/crypto.h"
+#include "cryptonote_core/cryptonote_core.h"
+#include "misc_log_ex.h"
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc.bootstrap_daemon"
+
+namespace cryptonote
+{
+
+ bootstrap_daemon::bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node) noexcept
+ : m_get_next_public_node(get_next_public_node)
+ {
+ }
+
+ bootstrap_daemon::bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials)
+ : bootstrap_daemon(nullptr)
+ {
+ if (!set_server(address, credentials))
+ {
+ throw std::runtime_error("invalid bootstrap daemon address or credentials");
+ }
+ }
+
+ std::string bootstrap_daemon::address() const noexcept
+ {
+ const auto& host = m_http_client.get_host();
+ if (host.empty())
+ {
+ return std::string();
+ }
+ return host + ":" + m_http_client.get_port();
+ }
+
+ boost::optional<uint64_t> bootstrap_daemon::get_height()
+ {
+ cryptonote::COMMAND_RPC_GET_HEIGHT::request req;
+ cryptonote::COMMAND_RPC_GET_HEIGHT::response res;
+
+ if (!invoke_http_json("/getheight", req, res))
+ {
+ return boost::none;
+ }
+
+ if (res.status != CORE_RPC_STATUS_OK)
+ {
+ return boost::none;
+ }
+
+ return res.height;
+ }
+
+ bool bootstrap_daemon::handle_result(bool success)
+ {
+ if (!success && m_get_next_public_node)
+ {
+ m_http_client.disconnect();
+ }
+
+ return success;
+ }
+
+ bool bootstrap_daemon::set_server(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials /* = boost::none */)
+ {
+ if (!m_http_client.set_server(address, credentials))
+ {
+ MERROR("Failed to set bootstrap daemon address " << address);
+ return false;
+ }
+
+ MINFO("Changed bootstrap daemon address to " << address);
+ return true;
+ }
+
+
+ bool bootstrap_daemon::switch_server_if_needed()
+ {
+ if (!m_get_next_public_node || m_http_client.is_connected())
+ {
+ return true;
+ }
+
+ const boost::optional<std::string> address = m_get_next_public_node();
+ if (address) {
+ return set_server(*address);
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h
new file mode 100644
index 000000000..130a6458d
--- /dev/null
+++ b/src/rpc/bootstrap_daemon.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <functional>
+#include <vector>
+
+#include <boost/optional/optional.hpp>
+#include <boost/utility/string_ref.hpp>
+
+#include "net/http_client.h"
+#include "storages/http_abstract_invoke.h"
+
+namespace cryptonote
+{
+
+ class bootstrap_daemon
+ {
+ public:
+ bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node) noexcept;
+ bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials);
+
+ std::string address() const noexcept;
+ boost::optional<uint64_t> get_height();
+ bool handle_result(bool success);
+
+ template <class t_request, class t_response>
+ bool invoke_http_json(const boost::string_ref uri, const t_request &out_struct, t_response &result_struct)
+ {
+ if (!switch_server_if_needed())
+ {
+ return false;
+ }
+
+ return handle_result(epee::net_utils::invoke_http_json(uri, out_struct, result_struct, m_http_client));
+ }
+
+ template <class t_request, class t_response>
+ bool invoke_http_bin(const boost::string_ref uri, const t_request &out_struct, t_response &result_struct)
+ {
+ if (!switch_server_if_needed())
+ {
+ return false;
+ }
+
+ return handle_result(epee::net_utils::invoke_http_bin(uri, out_struct, result_struct, m_http_client));
+ }
+
+ template <class t_request, class t_response>
+ bool invoke_http_json_rpc(const boost::string_ref command_name, const t_request &out_struct, t_response &result_struct)
+ {
+ if (!switch_server_if_needed())
+ {
+ return false;
+ }
+
+ return handle_result(epee::net_utils::invoke_http_json_rpc("/json_rpc", std::string(command_name.begin(), command_name.end()), out_struct, result_struct, m_http_client));
+ }
+
+ private:
+ bool set_server(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials = boost::none);
+ bool switch_server_if_needed();
+
+ private:
+ epee::net_utils::http::http_simple_client m_http_client;
+ std::function<boost::optional<std::string>()> m_get_next_public_node;
+ };
+
+}
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 27d90c768..7706d2cf7 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -117,20 +117,59 @@ namespace cryptonote
return set_bootstrap_daemon(address, credentials);
}
//------------------------------------------------------------------------------------------------------------------------------
+ boost::optional<std::string> core_rpc_server::get_random_public_node()
+ {
+ COMMAND_RPC_GET_PUBLIC_NODES::request request;
+ COMMAND_RPC_GET_PUBLIC_NODES::response response;
+
+ request.gray = true;
+ request.white = true;
+ if (!on_get_public_nodes(request, response) || response.status != CORE_RPC_STATUS_OK)
+ {
+ return boost::none;
+ }
+
+ const auto get_random_node_address = [](const std::vector<public_node>& public_nodes) -> std::string {
+ const auto& random_node = public_nodes[crypto::rand_idx(public_nodes.size())];
+ const auto address = random_node.host + ":" + std::to_string(random_node.rpc_port);
+ return address;
+ };
+
+ if (!response.white.empty())
+ {
+ return get_random_node_address(response.white);
+ }
+
+ MDEBUG("No white public node found, checking gray peers");
+
+ if (!response.gray.empty())
+ {
+ return get_random_node_address(response.gray);
+ }
+
+ MERROR("Failed to find any suitable public node");
+
+ return boost::none;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::set_bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials)
{
boost::unique_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex);
- if (!address.empty())
+ if (address.empty())
{
- if (!m_http_client.set_server(address, credentials, epee::net_utils::ssl_support_t::e_ssl_support_autodetect))
- {
- return false;
- }
+ m_bootstrap_daemon.reset(nullptr);
+ }
+ else if (address == "auto")
+ {
+ m_bootstrap_daemon.reset(new bootstrap_daemon([this]{ return get_random_public_node(); }));
+ }
+ else
+ {
+ m_bootstrap_daemon.reset(new bootstrap_daemon(address, credentials));
}
- m_bootstrap_daemon_address = address;
- m_should_use_bootstrap_daemon = !m_bootstrap_daemon_address.empty();
+ m_should_use_bootstrap_daemon = m_bootstrap_daemon.get() != nullptr;
return true;
}
@@ -143,6 +182,7 @@ namespace cryptonote
{
m_restricted = restricted;
m_net_server.set_threads_prefix("RPC");
+ m_net_server.set_connection_filter(&m_p2p);
auto rpc_config = cryptonote::rpc_args::process(vm, true);
if (!rpc_config)
@@ -162,7 +202,9 @@ namespace cryptonote
auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); };
return epee::http_server_impl_base<core_rpc_server, connection_context>::init(
- rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options)
+ rng, std::move(port), std::move(rpc_config->bind_ip),
+ std::move(rpc_config->bind_ipv6_address), std::move(rpc_config->use_ipv6), std::move(rpc_config->require_ipv4),
+ std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options)
);
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -174,6 +216,24 @@ namespace cryptonote
}
return true;
}
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool core_rpc_server::add_host_fail(const connection_context *ctx)
+ {
+ if(!ctx || !ctx->m_remote_address.is_blockable())
+ return false;
+
+ CRITICAL_REGION_LOCAL(m_host_fails_score_lock);
+ uint64_t fails = ++m_host_fails_score[ctx->m_remote_address.host_str()];
+ MDEBUG("Host " << ctx->m_remote_address.host_str() << " fail score=" << fails);
+ if(fails > RPC_IP_FAILS_BEFORE_BLOCK)
+ {
+ auto it = m_host_fails_score.find(ctx->m_remote_address.host_str());
+ CHECK_AND_ASSERT_MES(it != m_host_fails_score.end(), false, "internal error");
+ it->second = RPC_IP_FAILS_BEFORE_BLOCK/2;
+ m_p2p.block_host(ctx->m_remote_address);
+ }
+ return true;
+ }
#define CHECK_CORE_READY() do { if(!check_core_ready()){res.status = CORE_RPC_STATUS_BUSY;return true;} } while(0)
//------------------------------------------------------------------------------------------------------------------------------
@@ -200,7 +260,10 @@ namespace cryptonote
{
{
boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex);
- res.bootstrap_daemon_address = m_bootstrap_daemon_address;
+ if (m_bootstrap_daemon.get() != nullptr)
+ {
+ res.bootstrap_daemon_address = m_bootstrap_daemon->address();
+ }
}
crypto::hash top_hash;
m_core.get_blockchain_top(res.height_without_bootstrap, top_hash);
@@ -249,7 +312,10 @@ namespace cryptonote
else
{
boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex);
- res.bootstrap_daemon_address = m_bootstrap_daemon_address;
+ if (m_bootstrap_daemon.get() != nullptr)
+ {
+ res.bootstrap_daemon_address = m_bootstrap_daemon->address();
+ }
res.was_bootstrap_ever_used = m_was_bootstrap_ever_used;
}
res.database_size = m_core.get_blockchain_storage().get_db().get_database_size();
@@ -301,6 +367,7 @@ namespace cryptonote
if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT))
{
res.status = "Failed";
+ add_host_fail(ctx);
return false;
}
@@ -424,6 +491,7 @@ namespace cryptonote
if(!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, res.m_block_ids, res.start_height, res.current_height, false))
{
res.status = "Failed";
+ add_host_fail(ctx);
return false;
}
@@ -604,7 +672,8 @@ namespace cryptonote
return true;
}
const cryptonote::blobdata pruned = ss.str();
- sorted_txs.push_back(std::make_tuple(h, pruned, get_transaction_prunable_hash(tx), std::string(i->tx_blob, pruned.size())));
+ const crypto::hash prunable_hash = tx.version == 1 ? crypto::null_hash : get_transaction_prunable_hash(tx);
+ sorted_txs.push_back(std::make_tuple(h, pruned, prunable_hash, std::string(i->tx_blob, pruned.size())));
missed_txs.erase(std::find(missed_txs.begin(), missed_txs.end(), h));
pool_tx_hashes.insert(h);
const std::string hash_string = epee::string_tools::pod_to_hex(h);
@@ -863,6 +932,8 @@ namespace cryptonote
add_reason(reason, "fee too low");
if ((res.not_rct = tvc.m_not_rct))
add_reason(reason, "tx is not ringct");
+ if ((res.too_few_outputs = tvc.m_too_few_outputs))
+ add_reason(reason, "too few outputs");
const std::string punctuation = reason.empty() ? "" : ": ";
if (tvc.m_verifivation_failed)
{
@@ -1019,24 +1090,36 @@ namespace cryptonote
PERF_TIMER(on_get_peer_list);
std::vector<nodetool::peerlist_entry> white_list;
std::vector<nodetool::peerlist_entry> gray_list;
- m_p2p.get_public_peerlist(gray_list, white_list);
- res.white_list.reserve(white_list.size());
+ if (req.public_only)
+ {
+ m_p2p.get_public_peerlist(gray_list, white_list);
+ }
+ else
+ {
+ m_p2p.get_peerlist(gray_list, white_list);
+ }
+
for (auto & entry : white_list)
{
if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id())
res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(),
entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port);
+ else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id())
+ res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv6_network_address>().host_str(),
+ entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port);
else
res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port);
}
- res.gray_list.reserve(gray_list.size());
for (auto & entry : gray_list)
{
if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id())
res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(),
entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port);
+ else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id())
+ res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv6_network_address>().host_str(),
+ entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port);
else
res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port);
}
@@ -1045,6 +1128,45 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool core_rpc_server::on_get_public_nodes(const COMMAND_RPC_GET_PUBLIC_NODES::request& req, COMMAND_RPC_GET_PUBLIC_NODES::response& res, const connection_context *ctx)
+ {
+ PERF_TIMER(on_get_public_nodes);
+
+ COMMAND_RPC_GET_PEER_LIST::response peer_list_res;
+ const bool success = on_get_peer_list(COMMAND_RPC_GET_PEER_LIST::request(), peer_list_res, ctx);
+ res.status = peer_list_res.status;
+ if (!success)
+ {
+ return false;
+ }
+ if (res.status != CORE_RPC_STATUS_OK)
+ {
+ return true;
+ }
+
+ const auto collect = [](const std::vector<peer> &peer_list, std::vector<public_node> &public_nodes)
+ {
+ for (const auto &entry : peer_list)
+ {
+ if (entry.rpc_port != 0)
+ {
+ public_nodes.emplace_back(entry);
+ }
+ }
+ };
+
+ if (req.white)
+ {
+ collect(peer_list_res.white_list, res.white);
+ }
+ if (req.gray)
+ {
+ collect(peer_list_res.gray_list, res.gray);
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res, const connection_context *ctx)
{
PERF_TIMER(on_set_log_hash_rate);
@@ -1257,6 +1379,20 @@ namespace cryptonote
return false;
}
+ if(req.reserve_size && !req.extra_nonce.empty())
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
+ error_resp.message = "Cannot specify both a reserve_size and an extra_nonce";
+ return false;
+ }
+
+ if(req.extra_nonce.size() > 510)
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_RESERVE_SIZE;
+ error_resp.message = "Too big extra_nonce size, maximum 510 hex chars";
+ return false;
+ }
+
cryptonote::address_parse_info info;
if(!req.wallet_address.size() || !cryptonote::get_account_address_from_str(info, nettype(), req.wallet_address))
@@ -1274,7 +1410,17 @@ namespace cryptonote
block b;
cryptonote::blobdata blob_reserve;
- blob_reserve.resize(req.reserve_size, 0);
+ if(!req.extra_nonce.empty())
+ {
+ if(!string_tools::parse_hexstr_to_binbuff(req.extra_nonce, blob_reserve))
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
+ error_resp.message = "Parameter extra_nonce should be a hex string";
+ return false;
+ }
+ }
+ else
+ blob_reserve.resize(req.reserve_size, 0);
cryptonote::difficulty_type wdiff;
crypto::hash prev_block;
if (!req.prev_block.empty())
@@ -1493,8 +1639,10 @@ namespace cryptonote
boost::upgrade_lock<boost::shared_mutex> upgrade_lock(m_bootstrap_daemon_mutex);
- if (m_bootstrap_daemon_address.empty())
+ if (m_bootstrap_daemon.get() == nullptr)
+ {
return false;
+ }
if (!m_should_use_bootstrap_daemon)
{
@@ -1510,42 +1658,38 @@ namespace cryptonote
m_bootstrap_height_check_time = current_time;
}
- uint64_t top_height;
- crypto::hash top_hash;
- m_core.get_blockchain_top(top_height, top_hash);
- ++top_height; // turn top block height into blockchain height
+ boost::optional<uint64_t> bootstrap_daemon_height = m_bootstrap_daemon->get_height();
+ if (!bootstrap_daemon_height)
+ {
+ MERROR("Failed to fetch bootstrap daemon height");
+ return false;
+ }
- // query bootstrap daemon's height
- cryptonote::COMMAND_RPC_GET_HEIGHT::request getheight_req;
- cryptonote::COMMAND_RPC_GET_HEIGHT::response getheight_res;
- bool ok = epee::net_utils::invoke_http_json("/getheight", getheight_req, getheight_res, m_http_client);
- ok = ok && getheight_res.status == CORE_RPC_STATUS_OK;
+ uint64_t target_height = m_core.get_target_blockchain_height();
+ if (*bootstrap_daemon_height < target_height)
+ {
+ MINFO("Bootstrap daemon is out of sync");
+ return m_bootstrap_daemon->handle_result(false);
+ }
- m_should_use_bootstrap_daemon = ok && top_height + 10 < getheight_res.height;
- MINFO((m_should_use_bootstrap_daemon ? "Using" : "Not using") << " the bootstrap daemon (our height: " << top_height << ", bootstrap daemon's height: " << (ok ? getheight_res.height : 0) << ")");
+ uint64_t top_height = m_core.get_current_blockchain_height();
+ m_should_use_bootstrap_daemon = top_height + 10 < *bootstrap_daemon_height;
+ MINFO((m_should_use_bootstrap_daemon ? "Using" : "Not using") << " the bootstrap daemon (our height: " << top_height << ", bootstrap daemon's height: " << *bootstrap_daemon_height << ")");
}
if (!m_should_use_bootstrap_daemon)
return false;
if (mode == invoke_http_mode::JON)
{
- r = epee::net_utils::invoke_http_json(command_name, req, res, m_http_client);
+ r = m_bootstrap_daemon->invoke_http_json(command_name, req, res);
}
else if (mode == invoke_http_mode::BIN)
{
- r = epee::net_utils::invoke_http_bin(command_name, req, res, m_http_client);
+ r = m_bootstrap_daemon->invoke_http_bin(command_name, req, res);
}
else if (mode == invoke_http_mode::JON_RPC)
{
- epee::json_rpc::request<typename COMMAND_TYPE::request> json_req = AUTO_VAL_INIT(json_req);
- epee::json_rpc::response<typename COMMAND_TYPE::response, std::string> json_resp = AUTO_VAL_INIT(json_resp);
- json_req.jsonrpc = "2.0";
- json_req.id = epee::serialization::storage_entry(0);
- json_req.method = command_name;
- json_req.params = req;
- r = net_utils::invoke_http_json("/json_rpc", json_req, json_resp, m_http_client);
- if (r)
- res = json_resp.result;
+ r = m_bootstrap_daemon->invoke_http_json_rpc(command_name, req, res);
}
else
{
@@ -1601,38 +1745,55 @@ namespace cryptonote
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH>(invoke_http_mode::JON_RPC, "getblockheaderbyhash", req, res, r))
return r;
- crypto::hash block_hash;
- bool hash_parsed = parse_hash256(req.hash, block_hash);
- if(!hash_parsed)
- {
- error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
- error_resp.message = "Failed to parse hex representation of block hash. Hex = " + req.hash + '.';
- return false;
- }
- block blk;
- bool orphan = false;
- bool have_block = m_core.get_block_by_hash(block_hash, blk, &orphan);
- if (!have_block)
- {
- error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
- error_resp.message = "Internal error: can't get block by hash. Hash = " + req.hash + '.';
- return false;
- }
- if (blk.miner_tx.vin.size() != 1 || blk.miner_tx.vin.front().type() != typeid(txin_gen))
+ auto get = [this](const std::string &hash, bool fill_pow_hash, block_header_response &block_header, bool restricted, epee::json_rpc::error& error_resp) -> bool {
+ crypto::hash block_hash;
+ bool hash_parsed = parse_hash256(hash, block_hash);
+ if(!hash_parsed)
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
+ error_resp.message = "Failed to parse hex representation of block hash. Hex = " + hash + '.';
+ return false;
+ }
+ block blk;
+ bool orphan = false;
+ bool have_block = m_core.get_block_by_hash(block_hash, blk, &orphan);
+ if (!have_block)
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
+ error_resp.message = "Internal error: can't get block by hash. Hash = " + hash + '.';
+ return false;
+ }
+ if (blk.miner_tx.vin.size() != 1 || blk.miner_tx.vin.front().type() != typeid(txin_gen))
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
+ error_resp.message = "Internal error: coinbase transaction in the block has the wrong type";
+ return false;
+ }
+ uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height;
+ bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, block_header, fill_pow_hash && !restricted);
+ if (!response_filled)
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
+ error_resp.message = "Internal error: can't produce valid response.";
+ return false;
+ }
+ return true;
+ };
+
+ const bool restricted = m_restricted && ctx;
+ if (!req.hash.empty())
{
- error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
- error_resp.message = "Internal error: coinbase transaction in the block has the wrong type";
- return false;
+ if (!get(req.hash, req.fill_pow_hash, res.block_header, restricted, error_resp))
+ return false;
}
- uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height;
- const bool restricted = m_restricted && ctx;
- bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header, req.fill_pow_hash && !restricted);
- if (!response_filled)
+ res.block_headers.reserve(req.hashes.size());
+ for (const std::string &hash: req.hashes)
{
- error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
- error_resp.message = "Internal error: can't produce valid response.";
- return false;
+ res.block_headers.push_back({});
+ if (!get(hash, req.fill_pow_hash, res.block_headers.back(), restricted, error_resp))
+ return false;
}
+
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -2030,6 +2191,7 @@ namespace cryptonote
return r;
res.version = CORE_RPC_VERSION;
+ res.release = MONERO_VERSION_IS_RELEASE;
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -2062,7 +2224,7 @@ namespace cryptonote
PERF_TIMER(on_get_alternate_chains);
try
{
- std::list<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains();
+ std::vector<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains();
for (const auto &i: chains)
{
difficulty_type wdiff = i.first.cumulative_difficulty;
@@ -2500,7 +2662,8 @@ namespace cryptonote
const command_line::arg_descriptor<std::string> core_rpc_server::arg_bootstrap_daemon_address = {
"bootstrap-daemon-address"
- , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced"
+ , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced.\n"
+ "Use 'auto' to enable automatic public nodes discovering and bootstrap daemon switching"
, ""
};
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index 663975617..379f6ed28 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -30,9 +30,12 @@
#pragma once
+#include <memory>
+
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
+#include "bootstrap_daemon.h"
#include "net/http_server_impl_base.h"
#include "net/http_client.h"
#include "core_rpc_server_commands_defs.h"
@@ -108,6 +111,7 @@ namespace cryptonote
MAP_URI_AUTO_JON2_IF("/mining_status", on_mining_status, COMMAND_RPC_MINING_STATUS, !m_restricted)
MAP_URI_AUTO_JON2_IF("/save_bc", on_save_bc, COMMAND_RPC_SAVE_BC, !m_restricted)
MAP_URI_AUTO_JON2_IF("/get_peer_list", on_get_peer_list, COMMAND_RPC_GET_PEER_LIST, !m_restricted)
+ MAP_URI_AUTO_JON2_IF("/get_public_nodes", on_get_public_nodes, COMMAND_RPC_GET_PUBLIC_NODES, !m_restricted)
MAP_URI_AUTO_JON2_IF("/set_log_hash_rate", on_set_log_hash_rate, COMMAND_RPC_SET_LOG_HASH_RATE, !m_restricted)
MAP_URI_AUTO_JON2_IF("/set_log_level", on_set_log_level, COMMAND_RPC_SET_LOG_LEVEL, !m_restricted)
MAP_URI_AUTO_JON2_IF("/set_log_categories", on_set_log_categories, COMMAND_RPC_SET_LOG_CATEGORIES, !m_restricted)
@@ -186,6 +190,7 @@ namespace cryptonote
bool on_get_net_stats(const COMMAND_RPC_GET_NET_STATS::request& req, COMMAND_RPC_GET_NET_STATS::response& res, const connection_context *ctx = NULL);
bool on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res, const connection_context *ctx = NULL);
bool on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res, const connection_context *ctx = NULL);
+ bool on_get_public_nodes(const COMMAND_RPC_GET_PUBLIC_NODES::request& req, COMMAND_RPC_GET_PUBLIC_NODES::response& res, const connection_context *ctx = NULL);
bool on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res, const connection_context *ctx = NULL);
bool on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res, const connection_context *ctx = NULL);
bool on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res, const connection_context *ctx = NULL);
@@ -236,10 +241,12 @@ namespace cryptonote
private:
bool check_core_busy();
bool check_core_ready();
+ bool add_host_fail(const connection_context *ctx);
//utils
uint64_t get_block_reward(const block& blk);
bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash);
+ boost::optional<std::string> get_random_public_node();
bool set_bootstrap_daemon(const std::string &address, const std::string &username_password);
bool set_bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials);
enum invoke_http_mode { JON, BIN, JON_RPC };
@@ -248,14 +255,15 @@ private:
core& m_core;
nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& m_p2p;
- std::string m_bootstrap_daemon_address;
- epee::net_utils::http::http_simple_client m_http_client;
boost::shared_mutex m_bootstrap_daemon_mutex;
+ std::unique_ptr<bootstrap_daemon> m_bootstrap_daemon;
bool m_should_use_bootstrap_daemon;
std::chrono::system_clock::time_point m_bootstrap_height_check_time;
bool m_was_bootstrap_ever_used;
network_type m_nettype;
bool m_restricted;
+ epee::critical_section m_host_fails_score_lock;
+ std::map<std::string, uint64_t> m_host_fails_score;
};
}
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 571a71207..325ac4343 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -87,7 +87,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 2
-#define CORE_RPC_VERSION_MINOR 7
+#define CORE_RPC_VERSION_MINOR 10
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@@ -612,6 +612,7 @@ namespace cryptonote
bool overspend;
bool fee_too_low;
bool not_rct;
+ bool too_few_outputs;
bool sanity_check_failed;
bool untrusted;
@@ -627,6 +628,7 @@ namespace cryptonote
KV_SERIALIZE(overspend)
KV_SERIALIZE(fee_too_low)
KV_SERIALIZE(not_rct)
+ KV_SERIALIZE(too_few_outputs)
KV_SERIALIZE(sanity_check_failed)
KV_SERIALIZE(untrusted)
END_KV_SERIALIZE_MAP()
@@ -922,11 +924,13 @@ namespace cryptonote
uint64_t reserve_size; //max 255 bytes
std::string wallet_address;
std::string prev_block;
+ std::string extra_nonce;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(reserve_size)
KV_SERIALIZE(wallet_address)
KV_SERIALIZE(prev_block)
+ KV_SERIALIZE(extra_nonce)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
@@ -1094,10 +1098,12 @@ namespace cryptonote
struct request_t
{
std::string hash;
+ std::vector<std::string> hashes;
bool fill_pow_hash;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(hash)
+ KV_SERIALIZE(hashes)
KV_SERIALIZE_OPT(fill_pow_hash, false);
END_KV_SERIALIZE_MAP()
};
@@ -1107,10 +1113,12 @@ namespace cryptonote
{
std::string status;
block_header_response block_header;
+ std::vector<block_header_response> block_headers;
bool untrusted;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(block_header)
+ KV_SERIALIZE(block_headers)
KV_SERIALIZE(status)
KV_SERIALIZE(untrusted)
END_KV_SERIALIZE_MAP()
@@ -1200,6 +1208,9 @@ namespace cryptonote
peer(uint64_t id, const std::string &host, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port)
: id(id), host(host), ip(0), port(0), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed)
{}
+ peer(uint64_t id, const std::string &host, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port)
+ : id(id), host(host), ip(0), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed)
+ {}
peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port)
: id(id), host(epee::string_tools::get_ip_string_from_int32(ip)), ip(ip), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed)
{}
@@ -1219,7 +1230,10 @@ namespace cryptonote
{
struct request_t
{
+ bool public_only;
+
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(public_only, true)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
@@ -1239,6 +1253,54 @@ namespace cryptonote
typedef epee::misc_utils::struct_init<response_t> response;
};
+ struct public_node
+ {
+ std::string host;
+ uint64_t last_seen;
+ uint16_t rpc_port;
+
+ public_node() = delete;
+
+ public_node(const peer &peer)
+ : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port)
+ {}
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(host)
+ KV_SERIALIZE(last_seen)
+ KV_SERIALIZE(rpc_port)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct COMMAND_RPC_GET_PUBLIC_NODES
+ {
+ struct request_t
+ {
+ bool gray;
+ bool white;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(gray, false)
+ KV_SERIALIZE_OPT(white, true)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct response_t
+ {
+ std::string status;
+ std::vector<public_node> gray;
+ std::vector<public_node> white;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(gray)
+ KV_SERIALIZE(white)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+
struct COMMAND_RPC_SET_LOG_HASH_RATE
{
struct request_t
@@ -1991,11 +2053,13 @@ namespace cryptonote
{
std::string status;
uint32_t version;
+ bool release;
bool untrusted;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(version)
+ KV_SERIALIZE(release)
KV_SERIALIZE(untrusted)
END_KV_SERIALIZE_MAP()
};
@@ -2095,7 +2159,7 @@ namespace cryptonote
struct response_t
{
std::string status;
- std::list<chain_info> chains;
+ std::vector<chain_info> chains;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp
index 612b2cab6..890380dc8 100644
--- a/src/rpc/daemon_handler.cpp
+++ b/src/rpc/daemon_handler.cpp
@@ -343,6 +343,11 @@ namespace rpc
if (!res.error_details.empty()) res.error_details += " and ";
res.error_details = "tx is not ringct";
}
+ if (tvc.m_too_few_outputs)
+ {
+ if (!res.error_details.empty()) res.error_details += " and ";
+ res.error_details = "too few outputs";
+ }
if (res.error_details.empty())
{
res.error_details = "an unknown issue was found with the transaction";
diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp
index 4479bd1f1..68b33cb8c 100644
--- a/src/rpc/rpc_args.cpp
+++ b/src/rpc/rpc_args.cpp
@@ -90,6 +90,9 @@ namespace cryptonote
rpc_args::descriptors::descriptors()
: rpc_bind_ip({"rpc-bind-ip", rpc_args::tr("Specify IP to bind RPC server"), "127.0.0.1"})
+ , rpc_bind_ipv6_address({"rpc-bind-ipv6-address", rpc_args::tr("Specify IPv6 address to bind RPC server"), "::1"})
+ , rpc_use_ipv6({"rpc-use-ipv6", rpc_args::tr("Allow IPv6 for RPC"), false})
+ , rpc_require_ipv4({"rpc-require-ipv4", rpc_args::tr("Require successful IPv4 bind for RPC"), true})
, rpc_login({"rpc-login", rpc_args::tr("Specify username[:password] required for RPC server"), "", true})
, confirm_external_bind({"confirm-external-bind", rpc_args::tr("Confirm rpc-bind-ip value is NOT a loopback (local) IP")})
, rpc_access_control_origins({"rpc-access-control-origins", rpc_args::tr("Specify a comma separated list of origins to allow cross origin resource sharing"), ""})
@@ -108,6 +111,9 @@ namespace cryptonote
{
const descriptors arg{};
command_line::add_arg(desc, arg.rpc_bind_ip);
+ command_line::add_arg(desc, arg.rpc_bind_ipv6_address);
+ command_line::add_arg(desc, arg.rpc_use_ipv6);
+ command_line::add_arg(desc, arg.rpc_require_ipv4);
command_line::add_arg(desc, arg.rpc_login);
command_line::add_arg(desc, arg.confirm_external_bind);
command_line::add_arg(desc, arg.rpc_access_control_origins);
@@ -127,6 +133,9 @@ namespace cryptonote
rpc_args config{};
config.bind_ip = command_line::get_arg(vm, arg.rpc_bind_ip);
+ config.bind_ipv6_address = command_line::get_arg(vm, arg.rpc_bind_ipv6_address);
+ config.use_ipv6 = command_line::get_arg(vm, arg.rpc_use_ipv6);
+ config.require_ipv4 = command_line::get_arg(vm, arg.rpc_require_ipv4);
if (!config.bind_ip.empty())
{
// always parse IP here for error consistency
@@ -148,6 +157,34 @@ namespace cryptonote
return boost::none;
}
}
+ if (!config.bind_ipv6_address.empty())
+ {
+ // allow square braces, but remove them here if present
+ if (config.bind_ipv6_address.find('[') != std::string::npos)
+ {
+ config.bind_ipv6_address = config.bind_ipv6_address.substr(1, config.bind_ipv6_address.size() - 2);
+ }
+
+
+ // always parse IP here for error consistency
+ boost::system::error_code ec{};
+ const auto parsed_ip = boost::asio::ip::address::from_string(config.bind_ipv6_address, ec);
+ if (ec)
+ {
+ LOG_ERROR(tr("Invalid IP address given for --") << arg.rpc_bind_ipv6_address.name);
+ return boost::none;
+ }
+
+ if (!parsed_ip.is_loopback() && !command_line::get_arg(vm, arg.confirm_external_bind))
+ {
+ LOG_ERROR(
+ "--" << arg.rpc_bind_ipv6_address.name <<
+ tr(" permits inbound unencrypted external connections. Consider SSH tunnel or SSL proxy instead. Override with --") <<
+ arg.confirm_external_bind.name
+ );
+ return boost::none;
+ }
+ }
const char *env_rpc_login = nullptr;
const bool has_rpc_arg = command_line::has_arg(vm, arg.rpc_login);
diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h
index 619f02b42..cd154a4d0 100644
--- a/src/rpc/rpc_args.h
+++ b/src/rpc/rpc_args.h
@@ -52,6 +52,9 @@ namespace cryptonote
descriptors& operator=(descriptors&&) = delete;
const command_line::arg_descriptor<std::string> rpc_bind_ip;
+ const command_line::arg_descriptor<std::string> rpc_bind_ipv6_address;
+ const command_line::arg_descriptor<bool> rpc_use_ipv6;
+ const command_line::arg_descriptor<bool> rpc_require_ipv4;
const command_line::arg_descriptor<std::string> rpc_login;
const command_line::arg_descriptor<bool> confirm_external_bind;
const command_line::arg_descriptor<std::string> rpc_access_control_origins;
@@ -76,6 +79,9 @@ namespace cryptonote
static boost::optional<epee::net_utils::ssl_options_t> process_ssl(const boost::program_options::variables_map& vm, const bool any_cert_option = false);
std::string bind_ip;
+ std::string bind_ipv6_address;
+ bool use_ipv6;
+ bool require_ipv4;
std::vector<std::string> access_control_origins;
boost::optional<tools::login> login; // currently `boost::none` if unspecified by user
epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled;
diff --git a/src/rpc/rpc_handler.cpp b/src/rpc/rpc_handler.cpp
index af5cb98a3..d528ffef3 100644
--- a/src/rpc/rpc_handler.cpp
+++ b/src/rpc/rpc_handler.cpp
@@ -63,7 +63,9 @@ namespace rpc
d.cached_to -= 10;
d.cached_top_hash = hash10;
d.cached_m10_hash = crypto::null_hash;
- d.cached_distribution.resize(d.cached_distribution.size() - 10);
+ CHECK_AND_ASSERT_MES(d.cached_distribution.size() >= 10, boost::none, "Cached distribution size does not match cached bounds");
+ for (int p = 0; p < 10; ++p)
+ d.cached_distribution.pop_back();
can_extend = true;
}
}
diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp
index ae748e052..668a2e5cd 100644
--- a/src/rpc/zmq_server.cpp
+++ b/src/rpc/zmq_server.cpp
@@ -59,7 +59,7 @@ void ZmqServer::serve()
{
throw std::runtime_error("ZMQ RPC server reply socket is null");
}
- while (rep_socket->recv(&message))
+ while (rep_socket->recv(&message, 0))
{
std::string message_string(reinterpret_cast<const char *>(message.data()), message.size());