diff --git a/llarp/dht/messages/gotrouter.cpp b/llarp/dht/messages/gotrouter.cpp index fef4666ca..17ea95bfb 100644 --- a/llarp/dht/messages/gotrouter.cpp +++ b/llarp/dht/messages/gotrouter.cpp @@ -127,6 +127,10 @@ namespace llarp auto* router = dht.GetRouter(); router->NotifyRouterEvent(router->pubkey(), rc); router->GossipRCIfNeeded(rc); + + auto peerDb = router->peerDb(); + if (peerDb) + peerDb->handleGossipedRC(rc); } } return true; diff --git a/llarp/peerstats/peer_db.cpp b/llarp/peerstats/peer_db.cpp index 77cb817a6..9f1966ce8 100644 --- a/llarp/peerstats/peer_db.cpp +++ b/llarp/peerstats/peer_db.cpp @@ -121,6 +121,31 @@ namespace llarp return itr->second; } + void + PeerDb::handleGossipedRC(const RouterContact& rc, llarp_time_t now) + { + std::lock_guard gaurd(m_statsLock); + + RouterID id(rc.pubkey); + auto& stats = m_peerStats[id]; + + if (stats.lastRCUpdated < rc.last_updated.count()) + { + // we track max expiry as the delta between (time received - last expiration time), + // and this value will often be negative for a healthy router + // TODO: handle case where new RC is also expired? just ignore? + int64_t expiry = (now.count() - (stats.lastRCUpdated + RouterContact::Lifetime.count())); + + if (stats.numDistinctRCsReceived == 0) + stats.mostExpiredRCMs = expiry; + else + stats.mostExpiredRCMs = std::max(stats.mostExpiredRCMs, expiry); + + stats.numDistinctRCsReceived++; + stats.lastRCUpdated = rc.last_updated.count(); + } + } + void PeerDb::configure(const RouterConfig& routerConfig) { diff --git a/llarp/peerstats/peer_db.hpp b/llarp/peerstats/peer_db.hpp index 9b668d65b..3280447ce 100644 --- a/llarp/peerstats/peer_db.hpp +++ b/llarp/peerstats/peer_db.hpp @@ -81,6 +81,16 @@ namespace llarp std::optional getCurrentPeerStats(const RouterID& routerId) const; + /// Handles a new gossiped RC, updating stats as needed. The database tracks the last + /// advertised update time, so it knows whether this is a new RC or not. + /// + /// The given RC is assumed to be valid. + /// + /// @param rc is the RouterContact to handle + /// @param now is an optional time representing the current time + void + handleGossipedRC(const RouterContact& rc, llarp_time_t now = time_now_ms()); + /// Configures the PeerDb based on RouterConfig /// /// @param routerConfig diff --git a/llarp/peerstats/types.hpp b/llarp/peerstats/types.hpp index 3b4c3783f..ffab7afdc 100644 --- a/llarp/peerstats/types.hpp +++ b/llarp/peerstats/types.hpp @@ -32,6 +32,7 @@ namespace llarp double peakBandwidthBytesPerSec = 0; int64_t longestRCReceiveIntervalMs = 0; int64_t mostExpiredRCMs = 0; + int64_t lastRCUpdated = 0; PeerStats(); PeerStats(const RouterID& routerId); diff --git a/llarp/router/abstractrouter.hpp b/llarp/router/abstractrouter.hpp index 90ae1e952..f53c2917a 100644 --- a/llarp/router/abstractrouter.hpp +++ b/llarp/router/abstractrouter.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef LOKINET_HIVE #include "tooling/router_hive.hpp" @@ -151,6 +152,9 @@ namespace llarp virtual I_RCLookupHandler& rcLookupHandler() = 0; + virtual std::shared_ptr + peerDb() = 0; + virtual bool Sign(Signature& sig, const llarp_buffer_t& buf) const = 0; diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index 1d77721d4..1b040d7c1 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -582,7 +582,7 @@ namespace llarp if (conf->router.m_enablePeerStats) { LogInfo("Initializing peerdb..."); - m_peerDb = std::make_unique(); + m_peerDb = std::make_shared(); m_peerDb->configure(conf->router); } diff --git a/llarp/router/router.hpp b/llarp/router/router.hpp index 3a3f16445..275679ca0 100644 --- a/llarp/router/router.hpp +++ b/llarp/router/router.hpp @@ -307,6 +307,12 @@ namespace llarp return _rcLookupHandler; } + std::shared_ptr + peerDb() override + { + return m_peerDb; + } + void GossipRCIfNeeded(const RouterContact rc) override; @@ -496,7 +502,7 @@ namespace llarp llarp_time_t m_LastStatsReport = 0s; std::shared_ptr m_keyManager; - std::unique_ptr m_peerDb; + std::shared_ptr m_peerDb; uint32_t path_build_count = 0; diff --git a/test/peerstats/test_peer_db.cpp b/test/peerstats/test_peer_db.cpp index 18bc9ecd5..bab8c5cbb 100644 --- a/test/peerstats/test_peer_db.cpp +++ b/test/peerstats/test_peer_db.cpp @@ -3,6 +3,8 @@ #include #include +#include "router_contact.hpp" +#include "util/time.hpp" TEST_CASE("Test PeerDb PeerStats memory storage", "[PeerDb]") { @@ -111,3 +113,42 @@ TEST_CASE("Test PeerDb modifyPeerStats", "[PeerDb]") CHECK(stats.has_value()); CHECK(stats.value().numPathBuilds == 42); } + +TEST_CASE("Test PeerDb handleGossipedRC", "[PeerDb]") +{ + const llarp::RouterID id = llarp::test::makeBuf(0xCA); + + auto rcLifetime = llarp::RouterContact::Lifetime; + llarp_time_t now = 0s; + + llarp::RouterContact rc; + rc.pubkey = llarp::PubKey(id); + rc.last_updated = 10s; + + llarp::PeerDb db; + db.handleGossipedRC(rc, now); + + auto stats = db.getCurrentPeerStats(id); + CHECK(stats.has_value()); + CHECK(stats.value().mostExpiredRCMs == (0s - rcLifetime).count()); + CHECK(stats.value().numDistinctRCsReceived == 1); + CHECK(stats.value().lastRCUpdated == 10000); + + now = 9s; + db.handleGossipedRC(rc, now); + stats = db.getCurrentPeerStats(id); + CHECK(stats.has_value()); + // these values should remain unchanged, this is not a new RC + CHECK(stats.value().mostExpiredRCMs == (0s - rcLifetime).count()); + CHECK(stats.value().numDistinctRCsReceived == 1); + CHECK(stats.value().lastRCUpdated == 10000); + + rc.last_updated = 11s; + + db.handleGossipedRC(rc, now); + stats = db.getCurrentPeerStats(id); + // new RC received at 9sec, making it (expiration time - 9 sec) expired (a negative number) + CHECK(stats.value().mostExpiredRCMs == (9s - (now + rcLifetime)).count()); + CHECK(stats.value().numDistinctRCsReceived == 2); + CHECK(stats.value().lastRCUpdated == 11000); +}