diff options
author | xiphon <xiphon@protonmail.com> | 2020-02-26 12:37:28 +0000 |
---|---|---|
committer | xiphon <xiphon@protonmail.com> | 2020-02-28 08:06:43 +0000 |
commit | 42a7a4dd320f002457843b4418314f43a70c0c8f (patch) | |
tree | 9ed6650745da931923f93ca16f12fc2043b2bda0 /src | |
parent | Merge pull request #6048 (diff) | |
download | monero-42a7a4dd320f002457843b4418314f43a70c0c8f.tar.xz |
daemon: auto public nodes - cache and prioritize most stable nodes
Diffstat (limited to 'src')
-rw-r--r-- | src/rpc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/rpc/bootstrap_daemon.cpp | 40 | ||||
-rw-r--r-- | src/rpc/bootstrap_daemon.h | 34 | ||||
-rw-r--r-- | src/rpc/bootstrap_node_selector.cpp | 117 | ||||
-rw-r--r-- | src/rpc/bootstrap_node_selector.h | 103 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.cpp | 50 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.h | 2 |
7 files changed, 304 insertions, 43 deletions
diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 65d88b57e..fe5e5a85b 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -35,6 +35,7 @@ set(rpc_base_sources set(rpc_sources bootstrap_daemon.cpp + bootstrap_node_selector.cpp core_rpc_server.cpp rpc_payment.cpp rpc_version_str.cpp diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp index c97b2c95a..6a0833f19 100644 --- a/src/rpc/bootstrap_daemon.cpp +++ b/src/rpc/bootstrap_daemon.cpp @@ -2,6 +2,8 @@ #include <stdexcept> +#include <boost/thread/locks.hpp> + #include "crypto/crypto.h" #include "cryptonote_core/cryptonote_core.h" #include "misc_log_ex.h" @@ -12,15 +14,22 @@ namespace cryptonote { - bootstrap_daemon::bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node) - : m_get_next_public_node(get_next_public_node) + bootstrap_daemon::bootstrap_daemon( + std::function<std::map<std::string, bool>()> get_public_nodes, + bool rpc_payment_enabled) + : m_selector(new bootstrap_node::selector_auto(std::move(get_public_nodes))) + , m_rpc_payment_enabled(rpc_payment_enabled) { } - bootstrap_daemon::bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials) - : bootstrap_daemon(nullptr) + bootstrap_daemon::bootstrap_daemon( + const std::string &address, + boost::optional<epee::net_utils::http::login> credentials, + bool rpc_payment_enabled) + : m_selector(nullptr) + , m_rpc_payment_enabled(rpc_payment_enabled) { - if (!set_server(address, credentials)) + if (!set_server(address, std::move(credentials))) { throw std::runtime_error("invalid bootstrap daemon address or credentials"); } @@ -54,11 +63,16 @@ namespace cryptonote return res.height; } - bool bootstrap_daemon::handle_result(bool success) + bool bootstrap_daemon::handle_result(bool success, const std::string &status) { - if (!success && m_get_next_public_node) + const bool failed = !success || (!m_rpc_payment_enabled && status == CORE_RPC_STATUS_PAYMENT_REQUIRED); + if (failed && m_selector) { + const std::string current_address = address(); m_http_client.disconnect(); + + const boost::unique_lock<boost::mutex> lock(m_selector_mutex); + m_selector->handle_result(current_address, !failed); } return success; @@ -79,14 +93,18 @@ namespace cryptonote bool bootstrap_daemon::switch_server_if_needed() { - if (!m_get_next_public_node || m_http_client.is_connected()) + if (m_http_client.is_connected() || !m_selector) { return true; } - const boost::optional<std::string> address = m_get_next_public_node(); - if (address) { - return set_server(*address); + boost::optional<bootstrap_node::node_info> node; + { + const boost::unique_lock<boost::mutex> lock(m_selector_mutex); + node = m_selector->next_node(); + } + if (node) { + return set_server(node->address, node->credentials); } return false; diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h index 6276b1b21..bedc255b5 100644 --- a/src/rpc/bootstrap_daemon.h +++ b/src/rpc/bootstrap_daemon.h @@ -1,26 +1,34 @@ #pragma once #include <functional> -#include <vector> +#include <map> #include <boost/optional/optional.hpp> +#include <boost/thread/mutex.hpp> #include <boost/utility/string_ref.hpp> #include "net/http_client.h" #include "storages/http_abstract_invoke.h" +#include "bootstrap_node_selector.h" + namespace cryptonote { class bootstrap_daemon { public: - bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node); - bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials); + bootstrap_daemon( + std::function<std::map<std::string, bool>()> get_public_nodes, + bool rpc_payment_enabled); + bootstrap_daemon( + const std::string &address, + boost::optional<epee::net_utils::http::login> credentials, + bool rpc_payment_enabled); std::string address() const noexcept; boost::optional<uint64_t> get_height(); - bool handle_result(bool success); + bool handle_result(bool success, const std::string &status); 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) @@ -30,7 +38,8 @@ namespace cryptonote return false; } - return handle_result(epee::net_utils::invoke_http_json(uri, out_struct, result_struct, m_http_client)); + const bool result = epee::net_utils::invoke_http_json(uri, out_struct, result_struct, m_http_client); + return handle_result(result, result_struct.status); } template <class t_request, class t_response> @@ -41,7 +50,8 @@ namespace cryptonote return false; } - return handle_result(epee::net_utils::invoke_http_bin(uri, out_struct, result_struct, m_http_client)); + const bool result = epee::net_utils::invoke_http_bin(uri, out_struct, result_struct, m_http_client); + return handle_result(result, result_struct.status); } template <class t_request, class t_response> @@ -52,7 +62,13 @@ namespace cryptonote 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)); + const bool 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); + return handle_result(result, result_struct.status); } private: @@ -61,7 +77,9 @@ namespace cryptonote private: epee::net_utils::http::http_simple_client m_http_client; - std::function<boost::optional<std::string>()> m_get_next_public_node; + const bool m_rpc_payment_enabled; + const std::unique_ptr<bootstrap_node::selector> m_selector; + boost::mutex m_selector_mutex; }; } diff --git a/src/rpc/bootstrap_node_selector.cpp b/src/rpc/bootstrap_node_selector.cpp new file mode 100644 index 000000000..34845060e --- /dev/null +++ b/src/rpc/bootstrap_node_selector.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "bootstrap_node_selector.h" + +#include "crypto/crypto.h" + +namespace cryptonote +{ +namespace bootstrap_node +{ + + void selector_auto::node::handle_result(bool success) + { + if (!success) + { + fails = std::min(std::numeric_limits<size_t>::max() - 2, fails) + 2; + } + else + { + fails = std::max(std::numeric_limits<size_t>::min() + 2, fails) - 2; + } + } + + void selector_auto::handle_result(const std::string &address, bool success) + { + auto &nodes_by_address = m_nodes.get<by_address>(); + const auto it = nodes_by_address.find(address); + if (it != nodes_by_address.end()) + { + nodes_by_address.modify(it, [success](node &entry) { + entry.handle_result(success); + }); + } + } + + boost::optional<node_info> selector_auto::next_node() + { + if (!has_at_least_one_good_node()) + { + append_new_nodes(); + } + + if (m_nodes.empty()) + { + return {}; + } + + auto node = m_nodes.get<by_fails>().begin(); + const size_t count = std::distance(node, m_nodes.get<by_fails>().upper_bound(node->fails)); + std::advance(node, crypto::rand_idx(count)); + + return {{node->address, {}}}; + } + + bool selector_auto::has_at_least_one_good_node() const + { + return !m_nodes.empty() && m_nodes.get<by_fails>().begin()->fails == 0; + } + + void selector_auto::append_new_nodes() + { + bool updated = false; + + for (const auto &node : m_get_nodes()) + { + const auto &address = node.first; + const auto &white = node.second; + const size_t initial_score = white ? 0 : 1; + updated |= m_nodes.get<by_address>().insert({address, initial_score}).second; + } + + if (updated) + { + truncate(); + } + } + + void selector_auto::truncate() + { + const size_t total = m_nodes.size(); + if (total > m_max_nodes) + { + auto &nodes_by_fails = m_nodes.get<by_fails>(); + auto from = nodes_by_fails.rbegin(); + std::advance(from, total - m_max_nodes); + nodes_by_fails.erase(from.base(), nodes_by_fails.end()); + } + } + +} +} diff --git a/src/rpc/bootstrap_node_selector.h b/src/rpc/bootstrap_node_selector.h new file mode 100644 index 000000000..fc993719b --- /dev/null +++ b/src/rpc/bootstrap_node_selector.h @@ -0,0 +1,103 @@ +// Copyright (c) 2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <functional> +#include <limits> +#include <map> +#include <string> +#include <utility> + +#include <boost/multi_index_container.hpp> +#include <boost/multi_index/member.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/optional/optional.hpp> + +#include "net/http_client.h" + +namespace cryptonote +{ +namespace bootstrap_node +{ + + struct node_info + { + std::string address; + boost::optional<epee::net_utils::http::login> credentials; + }; + + struct selector + { + virtual void handle_result(const std::string &address, bool success) = 0; + virtual boost::optional<node_info> next_node() = 0; + }; + + class selector_auto : public selector + { + public: + selector_auto(std::function<std::map<std::string, bool>()> get_nodes, size_t max_nodes = 1000) + : m_get_nodes(std::move(get_nodes)) + , m_max_nodes(max_nodes) + {} + + void handle_result(const std::string &address, bool success) final; + boost::optional<node_info> next_node() final; + + private: + bool has_at_least_one_good_node() const; + void append_new_nodes(); + void truncate(); + + private: + struct node + { + std::string address; + size_t fails; + + void handle_result(bool success); + }; + + struct by_address {}; + struct by_fails {}; + + typedef boost::multi_index_container< + node, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique<boost::multi_index::tag<by_address>, boost::multi_index::member<node, std::string, &node::address>>, + boost::multi_index::ordered_non_unique<boost::multi_index::tag<by_fails>, boost::multi_index::member<node, size_t, &node::fails>> + > + > nodes_list; + + const std::function<std::map<std::string, bool>()> m_get_nodes; + const size_t m_max_nodes; + nodes_list m_nodes; + }; + +} +} diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index e92ae7c08..eaded5726 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -170,7 +170,7 @@ namespace cryptonote return set_bootstrap_daemon(address, credentials); } //------------------------------------------------------------------------------------------------------------------------------ - boost::optional<std::string> core_rpc_server::get_random_public_node() + std::map<std::string, bool> core_rpc_server::get_public_nodes(uint32_t credits_per_hash_threshold/* = 0*/) { COMMAND_RPC_GET_PUBLIC_NODES::request request; COMMAND_RPC_GET_PUBLIC_NODES::response response; @@ -179,47 +179,51 @@ namespace cryptonote request.white = true; if (!on_get_public_nodes(request, response) || response.status != CORE_RPC_STATUS_OK) { - return boost::none; + return {}; } - 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); - } + std::map<std::string, bool> result; - MDEBUG("No white public node found, checking gray peers"); - - if (!response.gray.empty()) - { - return get_random_node_address(response.gray); - } + const auto append = [&result, &credits_per_hash_threshold](const std::vector<public_node> &nodes, bool white) { + for (const auto &node : nodes) + { + const bool rpc_payment_enabled = credits_per_hash_threshold > 0; + const bool node_rpc_payment_enabled = node.rpc_credits_per_hash > 0; + if (!node_rpc_payment_enabled || + (rpc_payment_enabled && node.rpc_credits_per_hash >= credits_per_hash_threshold)) + { + result.insert(std::make_pair(node.host + ":" + std::to_string(node.rpc_port), white)); + } + } + }; - MERROR("Failed to find any suitable public node"); + append(response.white, true); + append(response.gray, false); - return boost::none; + return result; } //------------------------------------------------------------------------------------------------------------------------------ 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); + constexpr const uint32_t credits_per_hash_threshold = 0; + constexpr const bool rpc_payment_enabled = credits_per_hash_threshold != 0; + if (address.empty()) { m_bootstrap_daemon.reset(nullptr); } else if (address == "auto") { - m_bootstrap_daemon.reset(new bootstrap_daemon([this]{ return get_random_public_node(); })); + auto get_nodes = [this, credits_per_hash_threshold]() { + return get_public_nodes(credits_per_hash_threshold); + }; + m_bootstrap_daemon.reset(new bootstrap_daemon(std::move(get_nodes), rpc_payment_enabled)); } else { - m_bootstrap_daemon.reset(new bootstrap_daemon(address, credentials)); + m_bootstrap_daemon.reset(new bootstrap_daemon(address, credentials, rpc_payment_enabled)); } m_should_use_bootstrap_daemon = m_bootstrap_daemon.get() != nullptr; @@ -1931,7 +1935,7 @@ namespace cryptonote if (*bootstrap_daemon_height < target_height) { MINFO("Bootstrap daemon is out of sync"); - return m_bootstrap_daemon->handle_result(false); + return m_bootstrap_daemon->handle_result(false, {}); } uint64_t top_height = m_core.get_current_blockchain_height(); diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 23c611470..218e92ca8 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -265,7 +265,7 @@ private: //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(); + std::map<std::string, bool> get_public_nodes(uint32_t credits_per_hash_threshold = 0); 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 }; |