RPC: +peerlist, -publicnodes

Updates GET_PEER_LIST to new RPC.

Removes GET_PUBLIC_NODES because we don't use this option at all in
oxend.  (At the point where we need a decentralized public node
repository we'll almost certainly want to use the service node network
to do it, not the p2p layer).
This commit is contained in:
Jason Rhinelander 2021-08-27 10:39:38 -03:00 committed by Thomas Winget
parent e1ef4a44e4
commit ff0c76d7f3
8 changed files with 105 additions and 276 deletions

View file

@ -225,6 +225,7 @@ namespace net_utils
virtual std::string str() const = 0;
virtual std::string host_str() const = 0;
virtual uint16_t port() const = 0;
virtual bool is_loopback() const = 0;
virtual bool is_local() const = 0;
virtual address_type get_type_id() const = 0;
@ -255,6 +256,7 @@ namespace net_utils
virtual std::string str() const override { return value.str(); }
virtual std::string host_str() const override { return value.host_str(); }
virtual uint16_t port() const override { return value.port(); }
virtual bool is_loopback() const override { return value.is_loopback(); }
virtual bool is_local() const override { return value.is_local(); }
virtual address_type get_type_id() const override { return value.get_type_id(); }
@ -303,6 +305,7 @@ namespace net_utils
bool is_same_host(const network_address &other) const;
std::string str() const { return self ? self->str() : "<none>"; }
std::string host_str() const { return self ? self->host_str() : "<none>"; }
uint16_t port() const { return self ? self->port() : 0; }
bool is_loopback() const { return self ? self->is_loopback() : false; }
bool is_local() const { return self ? self->is_local() : false; }
address_type get_type_id() const { return self ? self->get_type_id() : address_type::invalid; }

View file

@ -112,24 +112,6 @@ namespace {
return input_line_result::yes;
}
void print_peer(std::string const & prefix, GET_PEER_LIST::peer const & peer, bool pruned_only, bool publicrpc_only)
{
if (pruned_only && peer.pruning_seed == 0)
return;
if (publicrpc_only && peer.rpc_port == 0)
return;
time_t now = std::time(nullptr);
time_t last_seen = static_cast<time_t>(peer.last_seen);
std::string elapsed = peer.last_seen == 0 ? "never" : epee::misc_utils::get_time_interval_string(now - last_seen);
std::string id_str = epee::string_tools::pad_string(epee::string_tools::to_string_hex(peer.id), 16, '0', true);
std::string addr_str = peer.host + ":" + std::to_string(peer.port);
std::string rpc_port = peer.rpc_port ? std::to_string(peer.rpc_port) : "-";
std::string pruning_seed = epee::string_tools::to_string_hex(peer.pruning_seed);
tools::msg_writer() << fmt::format("{:<10} {:<25} {:<25} {:<5} %{:-4} {}", prefix, id_str, addr_str, rpc_port, pruning_seed, elapsed);
}
void print_block_header(block_header_response const & header)
{
tools::success_msg_writer()
@ -178,6 +160,36 @@ namespace {
std::string get_human_time_ago(std::time_t t, std::time_t now, bool abbreviate = false) {
return get_human_time_ago(std::chrono::seconds{now - t}, abbreviate);
}
bool print_peer(std::string_view prefix, const json& peer, bool pruned_only, bool publicrpc_only)
{
auto pruning_seed = peer.value<uint64_t>("pruning_seed", 0);
if (pruned_only && pruning_seed == 0)
return false;
auto rpc_port = peer.value<uint16_t>("rpc_port", 0);
if (publicrpc_only && rpc_port == 0)
return false;
time_t now = std::time(nullptr);
time_t last_seen = peer.value<time_t>("last_seen", 0);
tools::msg_writer() << fmt::format("{:<10} {:016x} {:<30} {:<5} {:<4x} {}",
prefix,
peer["id"].get<uint64_t>(),
fmt::format("{}:{}", peer["host"].get<std::string_view>(), peer["ip"].get<uint16_t>()),
rpc_port == 0 ? "-" : tools::int_to_string(rpc_port),
pruning_seed,
last_seen == 0 ? "never" : get_human_time_ago(last_seen, now));
return true;
}
template <typename... Args>
void print_peers(std::string_view prefix, const json& peers, size_t& limit, Args&&... args) {
for (auto it = peers.begin(); it != peers.end() && limit > 0; it++)
if (print_peer(prefix, *it, std::forward<Args>(args)...))
limit--;
}
}
rpc_command_executor::rpc_command_executor(
@ -334,43 +346,35 @@ bool rpc_command_executor::print_sn_state_changes(uint64_t start_height, uint64_
}
bool rpc_command_executor::print_peer_list(bool white, bool gray, size_t limit, bool pruned_only, bool publicrpc_only) {
GET_PEER_LIST::response res{};
if (!invoke<GET_PEER_LIST>({}, res, "Couldn't retrieve peer list"))
auto maybe_pl = try_running([this] { return invoke<GET_PEER_LIST>(); }, "Failed to retrieve peer list");
if (!maybe_pl)
return false;
auto& pl = *maybe_pl;
if (white)
{
auto peer = res.white_list.cbegin();
const auto end = limit ? peer + std::min(limit, res.white_list.size()) : res.white_list.cend();
for (; peer != end; ++peer)
{
print_peer("white", *peer, pruned_only, publicrpc_only);
}
}
print_peers("white", pl["white_list"], limit, pruned_only, publicrpc_only);
if (gray)
{
auto peer = res.gray_list.cbegin();
const auto end = limit ? peer + std::min(limit, res.gray_list.size()) : res.gray_list.cend();
for (; peer != end; ++peer)
{
print_peer("gray", *peer, pruned_only, publicrpc_only);
}
}
print_peers("gray", pl["gray_list"], limit, pruned_only, publicrpc_only);
return true;
}
bool rpc_command_executor::print_peer_list_stats() {
GET_PEER_LIST::response res{};
if (!invoke<GET_PEER_LIST>({}, res, "Couldn't retrieve peer list"))
auto maybe_info = try_running([this] { return invoke<GET_INFO>(); }, "Failed to retrieve node info");
if (!maybe_info)
return false;
auto& info = *maybe_info;
auto wls = info.find("white_peerlist_size");
auto gls = info.find("grey_peerlist_size");
if (wls == info.end() || gls == info.end()) {
tools::fail_msg_writer() << "Failed to retrieve whitelist info";
return false;
}
tools::msg_writer()
<< "White list size: " << res.white_list.size() << "/" << P2P_LOCAL_WHITE_PEERLIST_LIMIT << " (" << res.white_list.size() * 100.0 / P2P_LOCAL_WHITE_PEERLIST_LIMIT << "%)" << std::endl
<< "Gray list size: " << res.gray_list.size() << "/" << P2P_LOCAL_GRAY_PEERLIST_LIMIT << " (" << res.gray_list.size() * 100.0 / P2P_LOCAL_GRAY_PEERLIST_LIMIT << "%)";
<< "White list size: " << wls->get<int>() << "/" << P2P_LOCAL_WHITE_PEERLIST_LIMIT << " (" << wls->get<int>() * 100.0 / P2P_LOCAL_WHITE_PEERLIST_LIMIT << "%)\n"
<< "Gray list size: " << gls->get<int>() << "/" << P2P_LOCAL_GRAY_PEERLIST_LIMIT << " (" << gls->get<int>() * 100.0 / P2P_LOCAL_GRAY_PEERLIST_LIMIT << "%)";
return true;
}

View file

@ -260,8 +260,6 @@ namespace cryptonote::rpc {
if (address.empty())
m_bootstrap_daemon.reset();
else if (address == "auto")
m_bootstrap_daemon = std::make_unique<bootstrap_daemon>([this]{ return get_random_public_node(); });
else
m_bootstrap_daemon = std::make_unique<bootstrap_daemon>(address, credentials);
@ -270,46 +268,6 @@ namespace cryptonote::rpc {
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
std::optional<std::string> core_rpc_server::get_random_public_node()
{
GET_PUBLIC_NODES::response response{};
try
{
GET_PUBLIC_NODES::request request{};
request.gray = true;
request.white = true;
rpc_context context = {};
context.admin = true;
response = invoke(std::move(request), context);
}
catch(const std::exception &e)
{
return std::nullopt;
}
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 std::nullopt;
}
//------------------------------------------------------------------------------------------------------------------------------
void core_rpc_server::init(const boost::program_options::variables_map& vm)
{
if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address),
@ -1397,81 +1355,35 @@ namespace cryptonote::rpc {
}
save_bc.response["status"] = STATUS_OK;
}
//------------------------------------------------------------------------------------------------------------------------------
GET_PEER_LIST::response core_rpc_server::invoke(GET_PEER_LIST::request&& req, rpc_context context)
{
GET_PEER_LIST::response res{};
PERF_TIMER(on_get_peer_list);
std::vector<nodetool::peerlist_entry> white_list;
std::vector<nodetool::peerlist_entry> gray_list;
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);
}
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);
}
res.status = STATUS_OK;
return res;
}
//------------------------------------------------------------------------------------------------------------------------------
GET_PUBLIC_NODES::response core_rpc_server::invoke(GET_PUBLIC_NODES::request&& req, rpc_context context)
{
PERF_TIMER(on_get_public_nodes);
GET_PEER_LIST::response peer_list_res = invoke(GET_PEER_LIST::request{}, context);
GET_PUBLIC_NODES::response res{};
res.status = std::move(peer_list_res.status);
const auto collect = [](const std::vector<GET_PEER_LIST::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);
}
}
static nlohmann::json json_peer_info(const nodetool::peerlist_entry& peer) {
auto addr_type = peer.adr.get_type_id();
nlohmann::json p{
{"id", peer.id},
{"host", peer.adr.host_str()},
{"port", peer.adr.port()},
{"last_seen", peer.last_seen}
};
if (peer.pruning_seed) p["pruning_seed"] = peer.pruning_seed;
if (peer.rpc_port) p["rpc_port"] = peer.rpc_port;
return p;
}
if (req.white)
{
collect(peer_list_res.white_list, res.white);
}
if (req.gray)
{
collect(peer_list_res.gray_list, res.gray);
}
//------------------------------------------------------------------------------------------------------------------------------
void core_rpc_server::invoke(GET_PEER_LIST& pl, rpc_context context)
{
PERF_TIMER(on_get_peer_list);
std::vector<nodetool::peerlist_entry> white_list, gray_list;
return res;
if (pl.request.public_only)
m_p2p.get_public_peerlist(gray_list, white_list);
else
m_p2p.get_peerlist(gray_list, white_list);
std::transform(white_list.begin(), white_list.end(), std::back_inserter(pl.response["white_list"]), json_peer_info);
std::transform(gray_list.begin(), gray_list.end(), std::back_inserter(pl.response["gray_list"]), json_peer_info);
pl.response["status"] = STATUS_OK;
}
//------------------------------------------------------------------------------------------------------------------------------
SET_LOG_LEVEL::response core_rpc_server::invoke(SET_LOG_LEVEL::request&& req, rpc_context context)

View file

@ -225,6 +225,7 @@ namespace cryptonote::rpc {
void invoke(IS_KEY_IMAGE_SPENT& spent, rpc_context context);
void invoke(SUBMIT_TRANSACTION& tx, rpc_context context);
void invoke(GET_BLOCK_HASH& req, rpc_context context);
void invoke(GET_PEER_LIST& pl, rpc_context context);
// Deprecated Monero NIH binary endpoints:
GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context);
@ -238,8 +239,6 @@ namespace cryptonote::rpc {
GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context);
// FIXME: unconverted JSON RPC endpoints:
GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context);
GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context);
SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context);
SET_LOG_CATEGORIES::response invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context);
SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context);
@ -293,7 +292,6 @@ private:
//utils
uint64_t get_block_reward(const block& blk);
std::optional<std::string> get_random_public_node();
bool set_bootstrap_daemon(const std::string &address, std::string_view username_password);
bool set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password);
void 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, bool get_tx_hashes);

View file

@ -393,4 +393,8 @@ namespace cryptonote::rpc {
if (bh.request.heights.size() > bh.MAX_HEIGHTS)
throw std::domain_error{"Error: too many block heights requested at once"};
}
void parse_request(GET_PEER_LIST& pl, rpc_input in) {
get_values(in, "public_only", pl.request.public_only);
}
}

View file

@ -21,4 +21,5 @@ namespace cryptonote::rpc {
void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in);
void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in);
void parse_request(GET_BLOCK_HASH& bh, rpc_input in);
void parse_request(GET_PEER_LIST& bh, rpc_input in);
}

View file

@ -106,44 +106,6 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::response)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::peer)
KV_SERIALIZE(id)
KV_SERIALIZE(host)
KV_SERIALIZE(ip)
KV_SERIALIZE(port)
KV_SERIALIZE_OPT(rpc_port, (uint16_t)0)
KV_SERIALIZE(last_seen)
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::request)
KV_SERIALIZE_OPT(public_only, true)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::response)
KV_SERIALIZE(status)
KV_SERIALIZE(white_list)
KV_SERIALIZE(gray_list)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(public_node)
KV_SERIALIZE(host)
KV_SERIALIZE(last_seen)
KV_SERIALIZE(rpc_port)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_PUBLIC_NODES::request)
KV_SERIALIZE_OPT(gray, false)
KV_SERIALIZE_OPT(white, true)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(GET_PUBLIC_NODES::response)
KV_SERIALIZE(status)
KV_SERIALIZE(gray)
KV_SERIALIZE(white)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_LEVEL::request)
KV_SERIALIZE(level)
KV_SERIALIZE_MAP_CODE_END()

View file

@ -871,88 +871,34 @@ namespace cryptonote::rpc {
};
};
OXEN_RPC_DOC_INTROSPECT
// Get the known peers list.
/// Get the list of current network peers known to this node.
///
/// Inputs: none
///
/// Output values (requires a restricted/admin RPC endpoint):
///
/// - \p status General RPC status string. `"OK"` means everything looks good.
/// - \p white_list list of "whitelist" peers (see below), that is, peers that were recorded
/// reachable the last time this node connected to them. Peers that are unreachable or not
/// synchronized with the network are moved to the graylist.
/// - \p gray_list list of peers (see below) that this node knows of but has not (recently) tried
/// to connect to.
///
/// Each peer list is an array of dicts containing the following fields:
/// - \p id a unique integer locally identifying the peer
/// - \p host the peer's IP address (as a string)
/// - \p port the port on which the peer is reachable
/// - \p last_seen unix timestamp when this node last connected to the peer. Will be omitted if
/// never connected (e.g. for a peer we received from another node but haven't yet tried).
struct GET_PEER_LIST : LEGACY
{
static constexpr auto names() { return NAMES("get_peer_list"); }
struct request
struct request_parameters
{
bool public_only;
KV_MAP_SERIALIZABLE
};
bool public_only = false; // Hidden option: can be set to false to also include non-public-zone peers (Tor, I2P), but since Oxen currently only really exists in public zones, we don't put this in the RPC docs.
} request;
struct peer
{
uint64_t id; // Peer id.
std::string host; // IP address in string format.
uint32_t ip; // IP address in integer format.
uint16_t port; // TCP port the peer is using to connect to oxen network.
uint16_t rpc_port; // RPC port the peer is using
uint64_t last_seen; // Unix time at which the peer has been seen for the last time
uint32_t pruning_seed; //
peer() = default;
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)
{}
KV_MAP_SERIALIZABLE
};
struct response
{
std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
std::vector<peer> white_list; // Array of online peer structure.
std::vector<peer> gray_list; // Array of offline peer structure.
KV_MAP_SERIALIZABLE
};
};
OXEN_RPC_DOC_INTROSPECT
struct public_node
{
std::string host;
uint64_t last_seen;
uint16_t rpc_port;
public_node() = default;
public_node(const GET_PEER_LIST::peer &peer) : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port) {}
KV_MAP_SERIALIZABLE
};
OXEN_RPC_DOC_INTROSPECT
// Query the daemon's peerlist and retrieve peers who have set their public rpc port.
struct GET_PUBLIC_NODES : PUBLIC
{
static constexpr auto names() { return NAMES("get_public_nodes"); }
struct request
{
bool gray; // Get peers that have recently gone offline.
bool white; // Get peers that are online
KV_MAP_SERIALIZABLE
};
struct response
{
std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
std::vector<public_node> gray; // Graylist peers
std::vector<public_node> white; // Whitelist peers
KV_MAP_SERIALIZABLE
};
};
OXEN_RPC_DOC_INTROSPECT
@ -2432,7 +2378,8 @@ namespace cryptonote::rpc {
GET_SERVICE_NODES,
GET_SERVICE_NODE_STATUS,
SUBMIT_TRANSACTION,
GET_BLOCK_HASH
GET_BLOCK_HASH,
GET_PEER_LIST
>;
using FIXME_old_rpc_types = tools::type_list<
@ -2441,8 +2388,6 @@ namespace cryptonote::rpc {
GET_BLOCK_HEADER_BY_HASH,
GET_BLOCK_HEADER_BY_HEIGHT,
GET_BLOCK,
GET_PEER_LIST,
GET_PUBLIC_NODES,
SET_LOG_LEVEL,
SET_LOG_CATEGORIES,
GET_BLOCK_HEADERS_RANGE,