diff options
Diffstat (limited to 'src/rpc')
-rw-r--r-- | src/rpc/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/rpc/bootstrap_daemon.cpp | 95 | ||||
-rw-r--r-- | src/rpc/bootstrap_daemon.h | 67 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.cpp | 299 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.h | 12 | ||||
-rw-r--r-- | src/rpc/core_rpc_server_commands_defs.h | 68 | ||||
-rw-r--r-- | src/rpc/daemon_handler.cpp | 5 | ||||
-rw-r--r-- | src/rpc/rpc_args.cpp | 37 | ||||
-rw-r--r-- | src/rpc/rpc_args.h | 6 | ||||
-rw-r--r-- | src/rpc/rpc_handler.cpp | 4 | ||||
-rw-r--r-- | src/rpc/zmq_server.cpp | 2 |
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()); |