From 2ec5add6034262fa30d4ee21b1605305123c676f Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:35:54 +0200 Subject: [PATCH] adds squelchStore --- src/test/overlay/base_squelch_test.cpp | 28 ++-- src/test/overlay/squelch_store_test.cpp | 124 ++++++++++++++++++ .../overlay/{Squelch.h => SquelchStore.h} | 52 +++++--- src/xrpld/overlay/detail/OverlayImpl.cpp | 3 + src/xrpld/overlay/detail/OverlayImpl.h | 4 +- src/xrpld/overlay/detail/PeerImp.cpp | 9 +- src/xrpld/overlay/detail/PeerImp.h | 6 +- .../detail/{Squelch.cpp => SquelchStore.cpp} | 72 ++++++---- 8 files changed, 232 insertions(+), 66 deletions(-) create mode 100644 src/test/overlay/squelch_store_test.cpp rename src/xrpld/overlay/{Squelch.h => SquelchStore.h} (67%) rename src/xrpld/overlay/detail/{Squelch.cpp => SquelchStore.cpp} (53%) diff --git a/src/test/overlay/base_squelch_test.cpp b/src/test/overlay/base_squelch_test.cpp index 67ff24ebd2..b1679506db 100644 --- a/src/test/overlay/base_squelch_test.cpp +++ b/src/test/overlay/base_squelch_test.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include @@ -458,7 +458,7 @@ class PeerSim : public PeerPartial, public std::enable_shared_from_this { public: PeerSim(Overlay& overlay, beast::Journal journal) - : overlay_(overlay), squelch_(journal, overlay_.clock()) + : overlay_(overlay), squelchStore_(journal, overlay_.clock()) { id_ = sid_++; } @@ -483,7 +483,7 @@ public: { auto validator = m->getValidatorKey(); assert(validator); - if (!squelch_.expireSquelch(*validator)) + if (squelchStore_.expireAndIsSquelched(*validator)) return; overlay_.updateSlotAndSquelch({}, *validator, id(), f); @@ -495,18 +495,17 @@ public: { auto validator = squelch.validatorpubkey(); PublicKey key(Slice(validator.data(), validator.size())); - if (squelch.squelch()) - squelch_.addSquelch( - key, std::chrono::seconds{squelch.squelchduration()}); - else - squelch_.removeSquelch(key); + squelchStore_.handleSquelch( + key, + squelch.squelch(), + std::chrono::seconds{squelch.squelchduration()}); } private: inline static Peer::id_t sid_ = 0; Peer::id_t id_; Overlay& overlay_; - reduce_relay::Squelch squelch_; + reduce_relay::SquelchStore squelchStore_; }; class OverlaySim : public Overlay, public reduce_relay::SquelchHandler @@ -1159,7 +1158,6 @@ protected: checkCounting(PublicKey const& validator, bool isCountingState) { auto countingState = network_.overlay().isCountingState(validator); - BEAST_EXPECT(countingState == isCountingState); return countingState == isCountingState; } @@ -1210,7 +1208,7 @@ protected: bool propagateAndSquelch(bool log, bool purge = true) { - int n = 0; + int squelchEvents = 0; network_.propagate( [&](Link& link, MessageSPtr message) { std::uint16_t squelched = 0; @@ -1230,7 +1228,7 @@ protected: env_.app() .config() .VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS); - n++; + squelchEvents++; } }, 1, @@ -1240,10 +1238,11 @@ protected: BEAST_EXPECT( selected.size() == env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS); - BEAST_EXPECT(n == 1); // only one selection round + + BEAST_EXPECT(squelchEvents == 1); // only one selection round auto res = checkCounting(network_.validator(0), false); BEAST_EXPECT(res); - return n == 1 && res; + return squelchEvents == 1 && res; } /** Send fewer message so that squelch event is not generated */ @@ -1270,6 +1269,7 @@ protected: nMessages, purge); auto res = checkCounting(network_.validator(0), countingState); + BEAST_EXPECT(res); return !squelched && res; } diff --git a/src/test/overlay/squelch_store_test.cpp b/src/test/overlay/squelch_store_test.cpp new file mode 100644 index 0000000000..3ca7d7c9ee --- /dev/null +++ b/src/test/overlay/squelch_store_test.cpp @@ -0,0 +1,124 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +#include +#include + +#include "xrpld/overlay/ReduceRelayCommon.h" + +#include + +namespace ripple { + +namespace test { + +class squelch_store_test : public beast::unit_test::suite +{ + using seconds = std::chrono::seconds; + +public: + jtx::Env env_; + + squelch_store_test() : env_(*this) + { + } + + void + testHandleSquelch() + { + testcase("SquelchStore handleSquelch"); + + TestStopwatch clock; + auto store = reduce_relay::SquelchStore(env_.journal, clock); + + auto const validator = randomKeyPair(KeyType::ed25519).first; + + // attempt to squelch the peer with a too small duration + store.handleSquelch( + validator, true, reduce_relay::MIN_UNSQUELCH_EXPIRE - seconds{1}); + + // the peer must not be squelched + BEAST_EXPECTS( + !store.expireAndIsSquelched(validator), "peer is squelched"); + + // attempt to squelch the peer with a too big duration + store.handleSquelch( + validator, + true, + reduce_relay::MAX_UNSQUELCH_EXPIRE_PEERS + seconds{1}); + + // the peer must not be squelched + BEAST_EXPECTS( + !store.expireAndIsSquelched(validator), "peer is squelched"); + + // squelch the peer with a good duration + store.handleSquelch( + validator, true, reduce_relay::MIN_UNSQUELCH_EXPIRE + seconds{1}); + + // the peer for the validator should be squelched + BEAST_EXPECTS( + store.expireAndIsSquelched(validator), + "peer and validator are not squelched"); + + // unsquelch the validator + store.handleSquelch(validator, false, seconds{0}); + + BEAST_EXPECTS( + !store.expireAndIsSquelched(validator), "peer is squelched"); + } + + void + testExpireAndIsSquelched() + { + testcase("SquelchStore expireAndIsSquelched"); + TestStopwatch clock; + auto store = reduce_relay::SquelchStore(env_.journal, clock); + + auto const validator = randomKeyPair(KeyType::ed25519).first; + auto const duration = reduce_relay::MIN_UNSQUELCH_EXPIRE + seconds{1}; + + store.handleSquelch( + validator, true, reduce_relay::MIN_UNSQUELCH_EXPIRE + seconds{1}); + BEAST_EXPECTS( + store.expireAndIsSquelched(validator), + "peer and validator are not squelched"); + + clock.advance(duration + seconds{1}); + + // the peer with short squelch duration must be not squelched + BEAST_EXPECTS( + !store.expireAndIsSquelched(validator), + "peer and validator are squelched"); + } + void + run() override + { + testHandleSquelch(); + testExpireAndIsSquelched(); + } +}; + +BEAST_DEFINE_TESTSUITE(squelch_store, ripple_data, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/xrpld/overlay/Squelch.h b/src/xrpld/overlay/SquelchStore.h similarity index 67% rename from src/xrpld/overlay/Squelch.h rename to src/xrpld/overlay/SquelchStore.h index 25985451af..006b9ee83d 100644 --- a/src/xrpld/overlay/Squelch.h +++ b/src/xrpld/overlay/SquelchStore.h @@ -20,6 +20,8 @@ #ifndef RIPPLE_OVERLAY_SQUELCH_H_INCLUDED #define RIPPLE_OVERLAY_SQUELCH_H_INCLUDED +#include + #include #include #include @@ -30,45 +32,57 @@ namespace ripple { namespace reduce_relay { -/** Maintains squelching of relaying messages from validators */ -class Squelch +/** Tracks which validators were squelched. + */ +class SquelchStore { using clock_type = beast::abstract_clock; using time_point = typename clock_type::time_point; public: - explicit Squelch(beast::Journal journal, clock_type& clock) + explicit SquelchStore(beast::Journal journal, clock_type& clock) : journal_(journal), clock_(clock) { } - virtual ~Squelch() = default; + virtual ~SquelchStore() = default; + /** Handle a squelch request. + * @param validator The validator's public key. + * @param squelch Indicate if the validator should be squelched. + * @param duration Duration in seconds for how long to squelch the + * validator. Duration can be zero when squelch is false. + */ + void + handleSquelch( + PublicKey const& validator, + bool squelch, + std::chrono::seconds duration); + + /** Check if a given validator is squelched. If the validator is no longer + * squelched, clear the squelch entry. + * @param validator Validator's public key + * @return true if squelch exists and it is not expired. False otherwise. + */ + bool + expireAndIsSquelched(PublicKey const& validator); + +private: /** Squelch validation/proposal relaying for the validator * @param validator The validator's public key * @param squelchDuration Squelch duration in seconds * @return false if invalid squelch duration */ - bool - addSquelch( - PublicKey const& validator, + void + add(PublicKey const& validator, std::chrono::seconds const& squelchDuration); - /** Remove the squelch + /** Remove the squelch for a validator * @param validator The validator's public key */ void - removeSquelch(PublicKey const& validator); + remove(PublicKey const& validator); - /** Remove expired squelch - * @param validator Validator's public key - * @return true if removed or doesn't exist, false if still active - */ - bool - expireSquelch(PublicKey const& validator); - -private: - /** Maintains the list of squelched relaying to downstream peers. - * Expiration time is included in the TMSquelch message. */ + // holds a squelch expiration time_point for each validator hash_map squelched_; beast::Journal const journal_; clock_type& clock_; diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index cd9f5ea2ad..e1bcc726d5 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -17,6 +17,8 @@ */ //============================================================================== +#include "xrpld/overlay/detail/OverlayImpl.h" + #include #include #include @@ -41,6 +43,7 @@ #include +#include "xrpld/overlay/Peer.h" #include "xrpld/overlay/detail/TrafficCount.h" #include diff --git a/src/xrpld/overlay/detail/OverlayImpl.h b/src/xrpld/overlay/detail/OverlayImpl.h index 8bcb45be4a..57f935c171 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.h +++ b/src/xrpld/overlay/detail/OverlayImpl.h @@ -44,6 +44,8 @@ #include #include +#include "xrpld/overlay/Peer.h" + #include #include #include @@ -104,7 +106,7 @@ private: boost::asio::io_service& io_service_; std::optional work_; boost::asio::io_service::strand strand_; - mutable std::recursive_mutex mutex_; // VFALCO use std::mutex + std::recursive_mutex mutable mutex_; // VFALCO use std::mutex std::condition_variable_any cond_; std::weak_ptr timer_; boost::container::flat_map> list_; diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index d47faa8d45..6c0610aa6f 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -45,6 +45,7 @@ #include #include +#include #include #include #include @@ -96,7 +97,7 @@ PeerImp::PeerImp( , publicKey_(publicKey) , lastPingTime_(clock_type::now()) , creationTime_(clock_type::now()) - , squelch_(app_.journal("Squelch"), stopwatch()) + , squelchStore_(app_.journal("SquelchStore"), stopwatch()) , usage_(consumer) , fee_{Resource::feeTrivialPeer, ""} , slot_(slot) @@ -247,8 +248,8 @@ PeerImp::send(std::shared_ptr const& m) if (detaching_) return; - auto validator = m->getValidatorKey(); - if (validator && !squelch_.expireSquelch(*validator)) + auto const validator = m->getValidatorKey(); + if (validator && squelchStore_.expireAndIsSquelched(*validator)) { overlay_.reportOutboundTraffic( TrafficCount::category::squelch_suppressed, @@ -266,7 +267,7 @@ PeerImp::send(std::shared_ptr const& m) TrafficCount::category::total, static_cast(m->getBuffer(compressionEnabled_).size())); - auto sendq_size = send_queue_.size(); + auto const sendq_size = send_queue_.size(); if (sendq_size < Tuning::targetSendQueue) { diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index d53307d1cc..be6378ba9e 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -116,7 +116,7 @@ private: clock_type::time_point lastPingTime_; clock_type::time_point const creationTime_; - reduce_relay::Squelch squelch_; + reduce_relay::SquelchStore squelchStore_; // Notes on thread locking: // @@ -680,7 +680,7 @@ PeerImp::PeerImp( , publicKey_(publicKey) , lastPingTime_(clock_type::now()) , creationTime_(clock_type::now()) - , squelch_(app_.journal("Squelch"), stopwatch()) + , squelchStore_(app_.journal("SquelchStore"), stopwatch()) , usage_(usage) , fee_{Resource::feeTrivialPeer} , slot_(std::move(slot)) diff --git a/src/xrpld/overlay/detail/Squelch.cpp b/src/xrpld/overlay/detail/SquelchStore.cpp similarity index 53% rename from src/xrpld/overlay/detail/Squelch.cpp rename to src/xrpld/overlay/detail/SquelchStore.cpp index ca562f7dba..6d4bd7c868 100644 --- a/src/xrpld/overlay/detail/Squelch.cpp +++ b/src/xrpld/overlay/detail/SquelchStore.cpp @@ -17,61 +17,83 @@ */ //============================================================================== +#include #include -#include +#include #include #include #include #include +#include +#include namespace ripple { namespace reduce_relay { -bool -Squelch::addSquelch( +void +SquelchStore::handleSquelch( PublicKey const& validator, - std::chrono::seconds const& squelchDuration) + bool squelch, + std::chrono::seconds duration) { - if (squelchDuration >= MIN_UNSQUELCH_EXPIRE && - squelchDuration <= MAX_UNSQUELCH_EXPIRE_PEERS) + if (squelch) { - squelched_[validator] = clock_.now() + squelchDuration; - return true; + // This should never trigger. The squelh duration is validated in + // PeerImp.onMessage(TMSquelch). However, if somehow invalid duration is + // passed, log is as an error + if ((duration < reduce_relay::MIN_UNSQUELCH_EXPIRE || + duration > reduce_relay::MAX_UNSQUELCH_EXPIRE_PEERS)) + { + JLOG(journal_.error()) + << "SquelchStore: invalid squelch duration validator: " + << Slice(validator) << " duration: " << duration.count(); + return; + } + + add(validator, duration); + return; } - JLOG(journal_.error()) << "squelch: invalid squelch duration " - << squelchDuration.count(); + remove(validator); +} - // unsquelch if invalid duration - removeSquelch(validator); +bool +SquelchStore::expireAndIsSquelched(PublicKey const& validator) +{ + auto const now = clock_.now(); + + auto const it = squelched_.find(validator); + if (it == squelched_.end()) + return false; + + if (it->second > now) + return true; + + // erase the entry if the squelch expired + squelched_.erase(it); return false; } void -Squelch::removeSquelch(PublicKey const& validator) +SquelchStore::add( + PublicKey const& validator, + std::chrono::seconds const& duration) { - squelched_.erase(validator); + squelched_[validator] = clock_.now() + duration; } -bool -Squelch::expireSquelch(PublicKey const& validator) +void +SquelchStore::remove(PublicKey const& validator) { - auto const now = clock_.now(); - - auto const& it = squelched_.find(validator); + auto const it = squelched_.find(validator); if (it == squelched_.end()) - return true; - else if (it->second > now) - return false; + return; - // squelch expired squelched_.erase(it); - - return true; } } // namespace reduce_relay