diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 1f12815b3..6655d621e 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -116,7 +116,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("/get_public_nodes", on_get_public_nodes, COMMAND_RPC_GET_PUBLIC_NODES) 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) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1f8286951..89c88a835 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1216,17 +1216,19 @@ namespace cryptonote std::string host; uint64_t last_seen; uint16_t rpc_port; + uint32_t rpc_credits_per_hash; - public_node() = delete; + public_node(): last_seen(0), rpc_port(0), rpc_credits_per_hash(0) {} public_node(const peer &peer) - : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port) + : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port), rpc_credits_per_hash(peer.rpc_credits_per_hash) {} BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(host) KV_SERIALIZE(last_seen) KV_SERIALIZE(rpc_port) + KV_SERIALIZE(rpc_credits_per_hash) END_KV_SERIALIZE_MAP() }; diff --git a/src/rpc/rpc_payment_costs.h b/src/rpc/rpc_payment_costs.h index 066557f42..3b27bf286 100644 --- a/src/rpc/rpc_payment_costs.h +++ b/src/rpc/rpc_payment_costs.h @@ -47,3 +47,4 @@ #define COST_PER_FEE_ESTIMATE 1 #define COST_PER_SYNC_INFO 2 #define COST_PER_HARD_FORK_INFO 1 +#define COST_PER_PEER_LIST 2 diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d203f905d..5c01e99fb 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -262,6 +262,7 @@ namespace const char* USAGE_FROZEN("frozen "); const char* USAGE_LOCK("lock"); const char* USAGE_NET_STATS("net_stats"); + const char* USAGE_PUBLIC_NODES("public_nodes"); const char* USAGE_WELCOME("welcome"); const char* USAGE_RPC_PAYMENT_INFO("rpc_payment_info"); const char* USAGE_START_MINING_FOR_RPC("start_mining_for_rpc"); @@ -2247,6 +2248,45 @@ bool simple_wallet::net_stats(const std::vector &args) return true; } +bool simple_wallet::public_nodes(const std::vector &args) +{ + try + { + auto nodes = m_wallet->get_public_nodes(false); + m_claimed_cph.clear(); + if (nodes.empty()) + { + fail_msg_writer() << tr("No known public nodes"); + return true; + } + std::sort(nodes.begin(), nodes.end(), [](const public_node &node0, const public_node &node1) { + if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash == 0) + return true; + if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash) + return node0.rpc_credits_per_hash < node1.rpc_credits_per_hash; + return false; + }); + + const uint64_t now = time(NULL); + message_writer() << boost::format("%32s %12s %16s") % tr("address") % tr("credits/hash") % tr("last_seen"); + for (const auto &node: nodes) + { + const float cph = node.rpc_credits_per_hash / RPC_CREDITS_PER_HASH_SCALE; + char cphs[9]; + snprintf(cphs, sizeof(cphs), "%.3f", cph); + const std::string last_seen = node.last_seen == 0 ? tr("never") : get_human_readable_timespan(std::chrono::seconds(now - node.last_seen)); + std::string host = node.host + ":" + std::to_string(node.rpc_port); + message_writer() << boost::format("%32s %12s %16s") % host % cphs % last_seen; + m_claimed_cph[host] = node.rpc_credits_per_hash; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Error retrieving public node list: ") << e.what(); + } + return true; +} + bool simple_wallet::welcome(const std::vector &args) { message_writer() << tr("Welcome to Monero, the private cryptocurrency."); @@ -3525,6 +3565,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::on_command, this, &simple_wallet::net_stats, _1), tr(USAGE_NET_STATS), tr("Prints simple network stats")); + m_cmd_binder.set_handler("public_nodes", + boost::bind(&simple_wallet::public_nodes, this, _1), + tr(USAGE_PUBLIC_NODES), + tr("Lists known public nodes")); m_cmd_binder.set_handler("welcome", boost::bind(&simple_wallet::on_command, this, &simple_wallet::welcome, _1), tr(USAGE_WELCOME), @@ -5260,24 +5304,7 @@ bool simple_wallet::check_daemon_rpc_prices(const std::string &daemon_url, uint3 } else { - const std::string host = node.host + ":" + std::to_string(node.rpc_port); - if (host == daemon_url) - { - claimed_cph = node.rpc_credits_per_hash; - bool payment_required; - uint64_t credits, diff, credits_per_hash_found, height; - uint32_t cookie; - cryptonote::blobdata hashing_blob; - if (m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, cookie) && payment_required) - { - actual_cph = RPC_CREDITS_PER_HASH_SCALE * (credits_per_hash_found / (float)diff); - return true; - } - else - { - fail_msg_writer() << tr("Error checking daemon RPC access prices"); - } - } + fail_msg_writer() << tr("Error checking daemon RPC access prices"); } } catch (const std::exception &e) @@ -5313,7 +5340,7 @@ bool simple_wallet::set_daemon(const std::vector& args) // If no port has been provided, use the default from config if (!match[3].length()) { - int daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT; + uint16_t daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT; daemon_url = match[1] + match[2] + std::string(":") + std::to_string(daemon_port); } else { daemon_url = args[0]; @@ -5346,8 +5373,27 @@ bool simple_wallet::set_daemon(const std::vector& args) } catch (const std::exception &e) { } } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("Failed to connect to daemon"); + return true; + } + success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (m_wallet->is_trusted_daemon() ? tr("trusted") : tr("untrusted")); + // check whether the daemon's prices match the claim, and disconnect if not, to disincentivize daemons lying + uint32_t actual_cph, claimed_cph; + if (check_daemon_rpc_prices(daemon_url, actual_cph, claimed_cph)) + { + if (actual_cph < claimed_cph) + { + fail_msg_writer() << tr("Daemon RPC credits/hash is less than was claimed. Either this daemon is cheating, or it changed its setup recently."); + fail_msg_writer() << tr("Claimed: ") << claimed_cph / (float)RPC_CREDITS_PER_HASH_SCALE; + fail_msg_writer() << tr("Actual: ") << actual_cph / (float)RPC_CREDITS_PER_HASH_SCALE; + } + } + m_daemon_rpc_payment_message_displayed = false; } else { fail_msg_writer() << tr("This does not seem to be a valid daemon URL."); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 1ce135287..e8f96ad54 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -257,6 +257,7 @@ namespace cryptonote bool start_mining_for_rpc(const std::vector &args); bool stop_mining_for_rpc(const std::vector &args); bool net_stats(const std::vector& args); + bool public_nodes(const std::vector& args); bool welcome(const std::vector& args); bool version(const std::vector& args); bool on_unknown_command(const std::vector& args); @@ -335,6 +336,8 @@ namespace cryptonote void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon); + bool check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph); + //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, uint64_t unlock_time); @@ -457,6 +460,8 @@ namespace cryptonote float m_rpc_payment_hash_rate; std::atomic m_suspend_rpc_payment_mining; + std::unordered_map m_claimed_cph; + // MMS mms::message_store& get_message_store() const { return m_wallet->get_message_store(); }; mms::multisig_wallet_state get_multisig_wallet_state() const { return m_wallet->get_multisig_wallet_state(); }; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index aecd01dec..5cebc7c23 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -13549,4 +13549,25 @@ uint64_t wallet2::get_bytes_received() const { return m_http_client.get_bytes_received(); } +//---------------------------------------------------------------------------------------------------- +std::vector wallet2::get_public_nodes(bool white_only) +{ + cryptonote::COMMAND_RPC_GET_PUBLIC_NODES::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_PUBLIC_NODES::response res = AUTO_VAL_INIT(res); + + req.white = true; + req.gray = !white_only; + + { + const boost::lock_guard lock{m_daemon_rpc_mutex}; + bool r = epee::net_utils::invoke_http_json("/get_public_nodes", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/get_public_nodes"); + } + + std::vector nodes; + nodes = res.white; + nodes.reserve(nodes.size() + res.gray.size()); + std::copy(res.gray.begin(), res.gray.end(), std::back_inserter(nodes)); + return nodes; +} } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 3635cf227..640565a4e 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -886,6 +886,8 @@ private: uint64_t get_last_block_reward() const { return m_last_block_reward; } uint64_t get_device_last_key_image_sync() const { return m_device_last_key_image_sync; } + std::vector get_public_nodes(bool white_only = true); + template inline void serialize(t_archive &a, const unsigned int ver) {