Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/cryptonote_core/cryptonote_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2080,6 +2080,16 @@ namespace cryptonote
m_service_node_list.copy_active_x25519_pubkeys(std::inserter(active_sns, active_sns.end()));
m_lmq->set_active_sns(std::move(active_sns));
}

void core::request_peer_stats(std::function<void(bool success, std::vector<std::string> data)> results_handler)
{
if (not m_lokinet_lmq_connection)
throw std::runtime_error("cannot request peer stats without a lokinet connected");

// TODO: pass desired peer pubkeys to lokinet
m_lmq->request(*m_lokinet_lmq_connection, "lokid.get_peer_stats", std::move(results_handler));
}

//-----------------------------------------------------------------------------------------------
crypto::hash core::get_tail_id() const
{
Expand Down
8 changes: 8 additions & 0 deletions src/cryptonote_core/cryptonote_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ namespace cryptonote
/// active SNs.
void update_lmq_sns();

/// Called (from service_node_quorum_cop) to request peer stats from the connected lokinet daemon
void request_peer_stats(std::function<void(bool success, std::vector<std::string> data)> results_handler);

/**
* @brief get the cryptonote protocol instance
*
Expand Down Expand Up @@ -992,6 +995,11 @@ namespace cryptonote
uint16_t storage_port() const { return m_storage_port; }
uint16_t quorumnet_port() const { return m_quorumnet_port; }

// when lokinet connects via lokimq, we keep up with the connection so that we can make
// bi-directional requests. TODO: this is a messy place to put this, but currently the
// lmq server is not accessible to quorum_cop where this connection is needed
std::optional<lokimq::ConnectionID> m_lokinet_lmq_connection = std::nullopt;

/**
* @brief attempts to relay any transactions in the mempool which need it
*
Expand Down
45 changes: 45 additions & 0 deletions src/cryptonote_core/service_node_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

#include <boost/endian/conversion.hpp>

#include <lokimq/bt_serialize.h>

extern "C" {
#include <sodium.h>
}
Expand Down Expand Up @@ -1957,6 +1959,49 @@ namespace service_nodes
return result;
}

void lokinet_peer_stats::bt_decode(std::string_view data)
{
constexpr auto NumConnectionAttemptsKey = "numConnectionAttempts";
constexpr auto NumConnectionSuccessesKey = "numConnectionSuccesses";
constexpr auto NumConnectionRejectionsKey = "numConnectionRejections";
constexpr auto NumConnectionTimeoutsKey = "numConnectionTimeouts";
constexpr auto NumPathBuildsKey = "numPathBuilds";
constexpr auto NumPacketsAttemptedKey = "numPacketsAttempted";
constexpr auto NumPacketsSentKey = "numPacketsSent";
constexpr auto NumPacketsDroppedKey = "numPacketsDropped";
constexpr auto NumPacketsResentKey = "numPacketsResent";
constexpr auto NumDistinctRCsReceivedKey = "numDistinctRCsReceived";
constexpr auto NumLateRCsKey = "numLateRCs";
constexpr auto PeakBandwidthBytesPerSecKey = "peakBandwidthBytesPerSec";
constexpr auto LongestRCReceiveIntervalKey = "longestRCReceiveInterval";
constexpr auto LeastRCRemainingLifetimeKey = "leastRCRemainingLifetime";
constexpr auto LastRCUpdatedKey = "lastRCUpdated";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearly Jeff did not name these.


auto dict = lokimq::bt_deserialize<lokimq::bt_dict>(data);

// TODO: router_id
// TODO: validation -- do we reject if we don't get a full message? or types are wrong?

num_connection_attempts = lokimq::get_int<int32_t>(dict.at(NumConnectionAttemptsKey));
num_connection_successes = lokimq::get_int<int32_t>(dict.at(NumConnectionSuccessesKey));
num_connection_rejections = lokimq::get_int<int32_t>(dict.at(NumConnectionRejectionsKey));
num_connection_timeouts = lokimq::get_int<int32_t>(dict.at(NumConnectionTimeoutsKey));

num_path_builds = lokimq::get_int<int32_t>(dict.at(NumPathBuildsKey));
num_packets_attempted = lokimq::get_int<int64_t>(dict.at(NumPacketsAttemptedKey));
num_packets_sent = lokimq::get_int<int64_t>(dict.at(NumPacketsSentKey));
num_packets_dropped = lokimq::get_int<int64_t>(dict.at(NumPacketsDroppedKey));
num_packets_resent = lokimq::get_int<int64_t>(dict.at(NumPacketsResentKey));

num_distinct_rcs_received = lokimq::get_int<int32_t>(dict.at(NumDistinctRCsReceivedKey));
num_late_rcs = lokimq::get_int<int32_t>(dict.at(NumLateRCsKey));

peak_bandwidth_bytes_per_sec = lokimq::get_int<int64_t>(dict.at(PeakBandwidthBytesPerSecKey));
longest_rc_receive_interval = std::chrono::milliseconds(lokimq::get_int<int64_t>(dict.at(LongestRCReceiveIntervalKey)));
least_rc_remaining_lifetime = std::chrono::milliseconds(lokimq::get_int<int64_t>(dict.at(LeastRCRemainingLifetimeKey)));
last_rc_updated = std::chrono::milliseconds(lokimq::get_int<int64_t>(dict.at(LastRCUpdatedKey)));
}

#ifdef __cpp_lib_erase_if // # (C++20)
using std::erase_if;
#else
Expand Down
30 changes: 30 additions & 0 deletions src/cryptonote_core/service_node_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#pragma once

#include <chrono>
#include <mutex>
#include <shared_mutex>
#include <string_view>
Expand All @@ -38,6 +39,8 @@
#include "cryptonote_core/service_node_quorum_cop.h"
#include "common/util.h"

using namespace std::chrono_literals;

namespace cryptonote
{
class Blockchain;
Expand All @@ -60,6 +63,33 @@ namespace service_nodes
END_KV_SERIALIZE_MAP()
};

// tracks lokinet's PeerStats struct and is used to reflect the behavior of a service node's peers on the lokinet network
struct lokinet_peer_stats
{
crypto::ed25519_public_key router_id = crypto::ed25519_public_key::null();

int32_t num_connection_attempts = 0;
int32_t num_connection_successes = 0;
int32_t num_connection_rejections = 0;
int32_t num_connection_timeouts = 0;

int32_t num_path_builds = 0;
int64_t num_packets_attempted = 0;
int64_t num_packets_sent = 0;
int64_t num_packets_dropped = 0;
int64_t num_packets_resent = 0;

int32_t num_distinct_rcs_received = 0;
int32_t num_late_rcs = 0;

int64_t peak_bandwidth_bytes_per_sec = 0;
std::chrono::milliseconds longest_rc_receive_interval = 0ms;
std::chrono::milliseconds least_rc_remaining_lifetime = 0ms;
std::chrono::milliseconds last_rc_updated = 0ms;

void bt_decode(std::string_view data);
};

struct proof_info
{
uint64_t timestamp = 0; // The actual time we last received an uptime proof (serialized)
Expand Down
16 changes: 16 additions & 0 deletions src/cryptonote_core/service_node_quorum_cop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,22 @@ namespace service_nodes

bool quorum_cop::block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, cryptonote::checkpoint_t const * /*checkpoint*/)
{

try
{
m_core.request_peer_stats([](bool success, std::vector<std::string> data){
// TODO: how should we handle this?
if (not success)
MWARNING("Failed to request peer stats from lokinet");

// TODO: parse and inspect peer stats
});
}
catch (const std::exception& e)
{
MERROR("caught exception while trying to request peer stats: " << e.what());
}

process_quorums(block);
uint64_t const height = cryptonote::get_block_height(block) + 1; // chain height = new top block height + 1
m_vote_pool.remove_expired_votes(height);
Expand Down
7 changes: 6 additions & 1 deletion src/rpc/core_rpc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3145,7 +3145,12 @@ namespace cryptonote { namespace rpc {
return handle_ping<LOKINET_PING>(
req.version, service_nodes::MIN_LOKINET_VERSION,
"Lokinet", m_core.m_last_lokinet_ping, LOKINET_PING_LIFETIME,
[this](bool significant) { if (significant) m_core.reset_proof_interval(); });
[this, &context](bool significant) {
if (significant)
m_core.reset_proof_interval();

m_core.m_lokinet_lmq_connection = context.lmq_connection_id;
});
}
//------------------------------------------------------------------------------------------------------------------------------
GET_STAKING_REQUIREMENT::response core_rpc_server::invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context)
Expand Down
3 changes: 3 additions & 0 deletions src/rpc/core_rpc_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ namespace cryptonote { namespace rpc {
// A free-form identifier identifiying the remote address of the request; this might be IP:PORT,
// or could contain a pubkey, or ...
std::string_view remote;

// If RPC source is rpc_source::lmq, this stores the ConnectionID associated with the request.
std::optional<lokimq::ConnectionID> lmq_connection_id = std::nullopt;
};

struct rpc_request {
Expand Down
1 change: 1 addition & 0 deletions src/rpc/lmq_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ lmq_rpc::lmq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog
request.context.admin = m.access.auth >= AuthLevel::admin;
request.context.source = rpc_source::lmq;
request.context.remote = m.remote;
request.context.lmq_connection_id = m.conn;
request.body = m.data.empty() ? ""sv : m.data[0];

try {
Expand Down
44 changes: 44 additions & 0 deletions tests/unit_tests/service_nodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -557,3 +557,47 @@ TEST(service_nodes, service_node_get_locked_key_image_unlock_height)
ASSERT_EQ(unlock_height, expected);
}
}

TEST(service_nodes, lokinet_peer_stats_deserialization)
{
constexpr std::string_view data =
"d"
"21:numConnectionAttempts" "i1e"
"22:numConnectionSuccesses" "i2e"
"23:numConnectionRejections" "i3e"
"21:numConnectionTimeouts" "i4e"
"13:numPathBuilds" "i5e"
"19:numPacketsAttempted" "i6e"
"14:numPacketsSent" "i7e"
"17:numPacketsDropped" "i8e"
"16:numPacketsResent" "i9e"
"22:numDistinctRCsReceived" "i10e"
"10:numLateRCs" "i11e"
"24:peakBandwidthBytesPerSec" "i12e"
"24:longestRCReceiveInterval" "i13e"
"24:leastRCRemainingLifetime" "i14e"
"13:lastRCUpdated" "i15e"
"e";

service_nodes::lokinet_peer_stats stats;

ASSERT_NO_THROW(stats.bt_decode(data));

// TODO: router_id

ASSERT_EQ(1, stats.num_connection_attempts);
ASSERT_EQ(2, stats.num_connection_successes);
ASSERT_EQ(3, stats.num_connection_rejections);
ASSERT_EQ(4, stats.num_connection_timeouts);
ASSERT_EQ(5, stats.num_path_builds);
ASSERT_EQ(6, stats.num_packets_attempted);
ASSERT_EQ(7, stats.num_packets_sent);
ASSERT_EQ(8, stats.num_packets_dropped);
ASSERT_EQ(9, stats.num_packets_resent);
ASSERT_EQ(10, stats.num_distinct_rcs_received);
ASSERT_EQ(11, stats.num_late_rcs);
ASSERT_EQ(12, stats.peak_bandwidth_bytes_per_sec);
ASSERT_EQ(13, stats.longest_rc_receive_interval.count());
ASSERT_EQ(14, stats.least_rc_remaining_lifetime.count());
ASSERT_EQ(15, stats.last_rc_updated.count());
}