xrpld
Loading...
Searching...
No Matches
src/xrpld/overlay/Slot.h
1#pragma once
2
3#include <xrpld/core/Config.h>
4#include <xrpld/overlay/Peer.h>
5#include <xrpld/overlay/ReduceRelayCommon.h>
6
7#include <xrpl/basics/Log.h>
8#include <xrpl/basics/Slice.h>
9#include <xrpl/basics/UnorderedContainers.h>
10#include <xrpl/basics/base_uint.h>
11#include <xrpl/basics/hardened_hash.h>
12#include <xrpl/basics/random.h>
13#include <xrpl/beast/container/aged_unordered_map.h>
14#include <xrpl/beast/utility/Journal.h>
15#include <xrpl/beast/utility/instrumentation.h>
16#include <xrpl/core/ServiceRegistry.h>
17#include <xrpl/protocol/PublicKey.h>
18
19#include <xrpl.pb.h>
20
21#include <algorithm>
22#include <atomic>
23#include <chrono>
24#include <cstddef>
25#include <cstdint>
26#include <functional>
27#include <iterator>
28#include <optional>
29#include <set>
30#include <sstream>
31#include <tuple>
32#include <unordered_map>
33#include <unordered_set>
34#include <vector>
35
36namespace xrpl::reduce_relay {
37
38template <typename ClockType>
39class Slots;
40
44enum class PeerState : uint8_t {
45 Counting, // counting messages
46 Selected, // selected to relay, counting if Slot in Counting
47 Squelched, // squelched, doesn't relay
48};
49
52enum class SlotState : uint8_t {
53 Counting, // counting messages
54 Selected, // peers selected, stop counting
55};
56
57template <typename Unit, typename TP>
58Unit
59epoch(TP const& t)
60{
61 return std::chrono::duration_cast<Unit>(t.time_since_epoch());
62}
63
71{
72public:
73 virtual ~SquelchHandler() = default;
80 virtual void
81 squelch(PublicKey const& validator, Peer::id_t id, std::uint32_t duration) const = 0;
87 virtual void
88 unsquelch(PublicKey const& validator, Peer::id_t id) const = 0;
89};
90
101template <typename ClockType>
102class Slot final
103{
104private:
105 friend class Slots<ClockType>;
107 using time_point = ClockType::time_point;
108
109 // a callback to report ignored squelches
111
119 Slot(SquelchHandler const& handler, beast::Journal journal, uint16_t maxSelectedPeers)
120 : lastSelected_(ClockType::now())
121 , handler_(handler)
122 , journal_(journal)
123 , maxSelectedPeers_(maxSelectedPeers)
124 {
125 }
126
147 void
149 PublicKey const& validator,
150 id_t id,
151 protocol::MessageType type,
152 ignored_squelch_callback callback);
153
165 void
166 deletePeer(PublicKey const& validator, id_t id, bool erase);
167
171 [[nodiscard]] time_point const&
173 {
174 return lastSelected_;
175 }
176
180 [[nodiscard]] std::uint16_t
181 inState(PeerState state) const;
182
186 [[nodiscard]] std::uint16_t
187 notInState(PeerState state) const;
188
192 [[nodiscard]] SlotState
193 getState() const
194 {
195 return state_;
196 }
197
201 [[nodiscard]] std::set<id_t>
202 getSelected() const;
203
209 getPeers() const;
210
218 void
219 deleteIdlePeer(PublicKey const& validator);
220
229
230private:
234 void
236
240 void
242
246 struct PeerInfo
247 {
248 PeerState state; // peer's state
249 std::size_t count; // message count
250 time_point expire; // squelch expiration time
251 time_point lastMessage; // time last message received
252 };
253
255
256 // pool of peers considered as the source of messages
257 // from validator - peers that reached kMinMessageThreshold
259
260 // number of peers that reached kMaxMessageThreshold
262
263 // last time peers were selected, used to age the slot
264 ClockType::time_point lastSelected_;
265
267 SquelchHandler const& handler_; // squelch/unsquelch handler
268 beast::Journal const journal_; // logging
269
270 // the maximum number of peers that should be selected as a validator
271 // message source
272 uint16_t const maxSelectedPeers_;
273};
274
275template <typename ClockType>
276void
278{
279 using namespace std::chrono;
280 auto now = ClockType::now();
281 for (auto it = peers_.begin(); it != peers_.end();)
282 {
283 auto& peer = it->second;
284 auto id = it->first;
285 ++it;
286 if (now - peer.lastMessage > kIdled)
287 {
288 JLOG(journal_.trace())
289 << "deleteIdlePeer: " << Slice(validator) << " " << id << " idled "
290 << duration_cast<seconds>(now - peer.lastMessage).count() << " selected "
291 << (peer.state == PeerState::Selected);
292 deletePeer(validator, id, false);
293 }
294 }
295}
296
297template <typename ClockType>
298void
300 PublicKey const& validator,
301 id_t id,
302 protocol::MessageType type,
304{
305 using namespace std::chrono;
306 auto now = ClockType::now();
307 auto it = peers_.find(id);
308 // First message from this peer
309 if (it == peers_.end())
310 {
311 JLOG(journal_.trace()) << "update: adding peer " << Slice(validator) << " " << id;
312 peers_.emplace(std::make_pair(id, PeerInfo{PeerState::Counting, 0, now, now}));
313 initCounting();
314 return;
315 }
316 // Message from a peer with expired squelch
317 if (it->second.state == PeerState::Squelched && now > it->second.expire)
318 {
319 JLOG(journal_.trace()) << "update: squelch expired " << Slice(validator) << " " << id;
320 it->second.state = PeerState::Counting;
321 it->second.lastMessage = now;
322 initCounting();
323 return;
324 }
325
326 auto& peer = it->second;
327
328 JLOG(journal_.trace()) << "update: existing peer " << Slice(validator) << " " << id
329 << " slot state " << static_cast<int>(state_) << " peer state "
330 << static_cast<int>(peer.state) << " count " << peer.count << " last "
331 << duration_cast<milliseconds>(now - peer.lastMessage).count()
332 << " pool " << considered_.size() << " threshold " << reachedThreshold_
333 << " " << (type == protocol::mtVALIDATION ? "validation" : "proposal");
334
335 peer.lastMessage = now;
336
337 // report if we received a message from a squelched peer
338 if (peer.state == PeerState::Squelched)
339 callback();
340
341 if (state_ != SlotState::Counting || peer.state == PeerState::Squelched)
342 return;
343
344 if (++peer.count > kMinMessageThreshold)
345 considered_.insert(id);
346 if (peer.count == (kMaxMessageThreshold + 1))
348
350 {
351 JLOG(journal_.trace()) << "update: resetting due to inactivity " << Slice(validator) << " "
352 << id << " " << duration_cast<seconds>(now - lastSelected_).count();
353 initCounting();
354 return;
355 }
356
358 {
359 // Randomly select maxSelectedPeers_ peers from considered.
360 // Exclude peers that have been idling > IDLED -
361 // it's possible that deleteIdlePeer() has not been called yet.
362 // If number of remaining peers != maxSelectedPeers_
363 // then reset the Counting state and let deleteIdlePeer() handle
364 // idled peers.
366 auto const consideredPoolSize = considered_.size();
367 while (selected.size() != maxSelectedPeers_ && !considered_.empty())
368 {
369 auto i = considered_.size() == 1 ? 0 : randInt(considered_.size() - 1);
370 auto it = std::next(considered_.begin(), i);
371 auto id = *it;
372 considered_.erase(it);
373 auto const& itPeers = peers_.find(id);
374 if (itPeers == peers_.end())
375 {
376 JLOG(journal_.error())
377 << "update: peer not found " << Slice(validator) << " " << id;
378 continue;
379 }
380 if (now - itPeers->second.lastMessage < kIdled)
381 selected.insert(id);
382 }
383
384 if (selected.size() != maxSelectedPeers_)
385 {
386 JLOG(journal_.trace()) << "update: selection failed " << Slice(validator) << " " << id;
387 initCounting();
388 return;
389 }
390
391 lastSelected_ = now;
392
393 auto s = selected.begin();
394 JLOG(journal_.trace()) << "update: " << Slice(validator) << " " << id << " pool size "
395 << consideredPoolSize << " selected " << *s << " "
396 << *std::next(s, 1) << " " << *std::next(s, 2);
397
398 XRPL_ASSERT(
399 peers_.size() >= maxSelectedPeers_, "xrpl::reduce_relay::Slot::update : minimum peers");
400
401 // squelch peers which are not selected and
402 // not already squelched
404 for (auto& [k, v] : peers_)
405 {
406 v.count = 0;
407
408 if (selected.find(k) != selected.end())
409 {
410 v.state = PeerState::Selected;
411 }
412 else if (v.state != PeerState::Squelched)
413 {
414 if (journal_.trace())
415 str << k << " ";
416 v.state = PeerState::Squelched;
419 v.expire = now + duration;
420 handler_.squelch(validator, k, duration.count());
421 }
422 }
423 JLOG(journal_.trace()) << "update: squelching " << Slice(validator) << " " << id << " "
424 << str.str();
425 considered_.clear();
428 }
429}
430
431template <typename ClockType>
434{
435 using namespace std::chrono;
438 {
440 JLOG(journal_.warn()) << "getSquelchDuration: unexpected squelch duration " << npeers;
441 }
442 return seconds{xrpl::randInt(kMinUnsquelchExpire / 1s, m / 1s)};
443}
444
445template <typename ClockType>
446void
448{
449 auto it = peers_.find(id);
450 if (it != peers_.end())
451 {
452 std::vector<Peer::id_t> toUnsquelch;
453
454 JLOG(journal_.trace()) << "deletePeer: " << Slice(validator) << " " << id << " selected "
455 << (it->second.state == PeerState::Selected) << " considered "
456 << (considered_.contains(id)) << " erase " << erase;
457 auto now = ClockType::now();
458 if (it->second.state == PeerState::Selected)
459 {
460 for (auto& [k, v] : peers_)
461 {
462 if (v.state == PeerState::Squelched)
463 toUnsquelch.push_back(k);
464 v.state = PeerState::Counting;
465 v.count = 0;
466 v.expire = now;
467 }
468
469 considered_.clear();
472 }
473 else if (considered_.contains(id))
474 {
475 if (it->second.count > kMaxMessageThreshold)
477 considered_.erase(id);
478 }
479
480 it->second.lastMessage = now;
481 it->second.count = 0;
482
483 if (erase)
484 peers_.erase(it);
485
486 // Must be after peers_.erase(it)
487 for (auto const& k : toUnsquelch)
488 handler_.unsquelch(validator, k);
489 }
490}
491
492template <typename ClockType>
493void
495{
496 for (auto& [_, peer] : peers_)
497 {
498 (void)_;
499 peer.count = 0;
500 }
501}
502
503template <typename ClockType>
504void
512
513template <typename ClockType>
516{
517 return std::count_if(
518 peers_.begin(), peers_.end(), [&](auto const& it) { return (it.second.state == state); });
519}
520
521template <typename ClockType>
524{
525 return std::count_if(
526 peers_.begin(), peers_.end(), [&](auto const& it) { return (it.second.state != state); });
527}
528
529template <typename ClockType>
532{
534 for (auto const& [id, info] : peers_)
535 {
536 if (info.state == PeerState::Selected)
537 r.insert(id);
538 }
539 return r;
540}
541
542template <typename ClockType>
545{
546 using namespace std::chrono;
547 auto r = std::
548 unordered_map<id_t, std::tuple<PeerState, std::uint16_t, std::uint32_t, std::uint32_t>>();
549
550 for (auto const& [id, info] : peers_)
551 {
552 r.emplace(
554 id,
555 std::move(
557 info.state,
558 info.count,
559 epoch<milliseconds>(info.expire).count(),
560 epoch<milliseconds>(info.lastMessage).count()))));
561 }
562
563 return r;
564}
565
571template <typename ClockType>
572class Slots final
573{
574 using time_point = ClockType::time_point;
577 uint256,
579 ClockType,
581
582public:
588 Slots(ServiceRegistry& registry, SquelchHandler const& handler, Config const& config)
589 : handler_(handler)
590 , logs_(registry.getLogs())
591 , journal_(registry.getJournal("Slots"))
592 , baseSquelchEnabled_(config.vpReduceRelayBaseSquelchEnable)
593 , maxSelectedPeers_(config.vpReduceRelaySquelchMaxSelectedPeers)
594 {
595 }
596 ~Slots() = default;
597
601 bool
606
610 bool
621
630 void
632 uint256 const& key,
633 PublicKey const& validator,
634 id_t id,
635 protocol::MessageType type)
636 {
637 updateSlotAndSquelch(key, validator, id, type, []() {});
638 }
639
648 void
650 uint256 const& key,
651 PublicKey const& validator,
652 id_t id,
653 protocol::MessageType type,
655
660 void
662
666 [[nodiscard]] std::optional<std::uint16_t>
667 inState(PublicKey const& validator, PeerState state) const
668 {
669 auto const& it = slots_.find(validator);
670 if (it != slots_.end())
671 return it->second.inState(state);
672 return {};
673 }
674
678 [[nodiscard]] std::optional<std::uint16_t>
679 notInState(PublicKey const& validator, PeerState state) const
680 {
681 auto const& it = slots_.find(validator);
682 if (it != slots_.end())
683 return it->second.notInState(state);
684 return {};
685 }
686
690 [[nodiscard]] bool
691 inState(PublicKey const& validator, SlotState state) const
692 {
693 auto const& it = slots_.find(validator);
694 if (it != slots_.end())
695 return it->second.state_ == state;
696 return false;
697 }
698
703 getSelected(PublicKey const& validator)
704 {
705 auto const& it = slots_.find(validator);
706 if (it != slots_.end())
707 return it->second.getSelected();
708 return {};
709 }
710
716 getPeers(PublicKey const& validator)
717 {
718 auto const& it = slots_.find(validator);
719 if (it != slots_.end())
720 return it->second.getPeers();
721 return {};
722 }
723
728 getState(PublicKey const& validator)
729 {
730 auto const& it = slots_.find(validator);
731 if (it != slots_.end())
732 return it->second.getState();
733 return {};
734 }
735
743 void
745
746private:
752 bool
753 addPeerMessage(uint256 const& key, id_t id);
754
756
758 SquelchHandler const& handler_; // squelch/unsquelch handler
761
764
765 // Maintain aged container of message/peers. This is required
766 // to discard duplicate message from the same peer. A message
767 // is aged after IDLED seconds. A message received IDLED seconds
768 // after it was relayed is ignored by PeerImp.
770};
771
772template <typename ClockType>
773bool
775{
777
778 if (key.isNonZero())
779 {
780 auto it = peersWithMessage.find(key);
781 if (it == peersWithMessage.end())
782 {
783 JLOG(journal_.trace()) << "addPeerMessage: new " << to_string(key) << " " << id;
785 return true;
786 }
787
788 if (it->second.find(id) != it->second.end())
789 {
790 JLOG(journal_.trace())
791 << "addPeerMessage: duplicate message " << to_string(key) << " " << id;
792 return false;
793 }
794
795 JLOG(journal_.trace()) << "addPeerMessage: added " << to_string(key) << " " << id;
796
797 it->second.insert(id);
798 }
799
800 return true;
801}
802
803template <typename ClockType>
804void
806 uint256 const& key,
807 PublicKey const& validator,
808 id_t id,
809 protocol::MessageType type,
811{
812 if (!addPeerMessage(key, id))
813 return;
814
815 auto it = slots_.find(validator);
816 if (it == slots_.end())
817 {
818 JLOG(journal_.trace()) << "updateSlotAndSquelch: new slot " << Slice(validator);
819 auto it = slots_
820 .emplace(
822 validator,
824 .first;
825 it->second.update(validator, id, type, callback);
826 }
827 else
828 {
829 it->second.update(validator, id, type, callback);
830 }
831}
832
833template <typename ClockType>
834void
836{
837 for (auto& [validator, slot] : slots_)
838 slot.deletePeer(validator, id, erase);
839}
840
841template <typename ClockType>
842void
844{
845 auto now = ClockType::now();
846
847 for (auto it = slots_.begin(); it != slots_.end();)
848 {
849 it->second.deleteIdlePeer(it->first);
850 if (now - it->second.getLastSelected() > kMaxUnsquelchExpireDefault)
851 {
852 JLOG(journal_.trace()) << "deleteIdlePeers: deleting idle slot " << Slice(it->first);
853 it = slots_.erase(it);
854 }
855 else
856 {
857 ++it;
858 }
859 }
860}
861
862} // namespace xrpl::reduce_relay
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
bool isNonZero() const
Definition base_uint.h:567
Seed functor once per construction.
Manages partitions for logging.
Definition Log.h:23
std::uint32_t id_t
Uniquely identifies a peer.
A public key.
Definition PublicKey.h:53
Service registry for dependency injection.
An immutable linear range of bytes.
Definition Slice.h:28
Slot is associated with a specific validator via validator's public key.
time_point const & getLastSelected() const
Get the time of the last peer selection round.
std::unordered_map< id_t, std::tuple< PeerState, uint16_t, uint32_t, uint32_t > > getPeers() const
Get peers info.
std::uint16_t inState(PeerState state) const
Return number of peers in state.
std::set< id_t > getSelected() const
Return selected peers.
SlotState getState() const
Return Slot's state.
std::uint16_t notInState(PeerState state) const
Return number of peers not in state.
Slot(SquelchHandler const &handler, beast::Journal journal, uint16_t maxSelectedPeers)
Constructor.
void resetCounts()
Reset counts of peers in Selected or Counting state.
std::chrono::seconds getSquelchDuration(std::size_t npeers)
Get random squelch duration between kMinUnsquelchExpire and min(max(kMaxUnsquelchExpireDefault,...
void initCounting()
Initialize slot to Counting state.
void update(PublicKey const &validator, id_t id, protocol::MessageType type, ignored_squelch_callback callback)
Update peer info.
void deleteIdlePeer(PublicKey const &validator)
Check if peers stopped relaying messages.
void deletePeer(PublicKey const &validator, id_t id, bool erase)
Handle peer deletion when a peer disconnects.
std::unordered_map< id_t, PeerInfo > peers_
Slots is a container for validator's Slot and handles Slot update when a message is received from a v...
std::optional< SlotState > getState(PublicKey const &validator)
Get Slot's state.
Slots(ServiceRegistry &registry, SquelchHandler const &handler, Config const &config)
void deletePeer(id_t id, bool erase)
Called when a peer is deleted.
void deleteIdlePeers()
Check if peers stopped relaying messages and if slots stopped receiving messages from the validator.
bool addPeerMessage(uint256 const &key, id_t id)
Add message/peer if have not seen this message from the peer.
bool reduceRelayReady()
Check if reduce_relay::kWaitOnBootup time passed since startup.
std::optional< std::uint16_t > inState(PublicKey const &validator, PeerState state) const
Return number of peers in state.
hash_map< PublicKey, Slot< ClockType > > slots_
std::optional< std::uint16_t > notInState(PublicKey const &validator, PeerState state) const
Return number of peers not in state.
std::set< id_t > getSelected(PublicKey const &validator)
Get selected peers.
bool inState(PublicKey const &validator, SlotState state) const
Return true if Slot is in state.
beast::aged_unordered_map< uint256, std::unordered_set< Peer::id_t >, ClockType, HardenedHash< strong_hash > > messages
SquelchHandler const & handler_
bool baseSquelchReady()
Check if base squelching feature is enabled and ready.
void updateSlotAndSquelch(uint256 const &key, PublicKey const &validator, id_t id, protocol::MessageType type)
Calls Slot::update of Slot associated with the validator, with a noop callback.
std::unordered_map< Peer::id_t, std::tuple< PeerState, uint16_t, uint32_t, std::uint32_t > > getPeers(PublicKey const &validator)
Get peers info.
virtual void unsquelch(PublicKey const &validator, Peer::id_t id) const =0
Unsquelch handler.
virtual void squelch(PublicKey const &validator, Peer::id_t id, std::uint32_t duration) const =0
Squelch handler.
T duration_cast(T... args)
T end(T... args)
T find(T... args)
T insert(T... args)
T make_pair(T... args)
T make_tuple(T... args)
T max(T... args)
AbstractClock< Facade > & getAbstractClock()
Returns a global instance of an abstract clock.
std::size_t expire(AgedContainer &c, std::chrono::duration< Rep, Period > const &age)
Expire aged container items past the specified age.
detail::AgedUnorderedContainer< false, true, Key, T, Clock, Hash, KeyEqual, Allocator > aged_unordered_map
static constexpr uint16_t kMinMessageThreshold
static constexpr auto kSquelchPerPeer
static constexpr auto kMinUnsquelchExpire
static constexpr uint16_t kMaxMessageThreshold
static constexpr auto kWaitOnBootup
static constexpr auto kMaxUnsquelchExpireDefault
static constexpr auto kMaxUnsquelchExpirePeers
static constexpr auto kIdled
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
std::unordered_map< Key, Value, Hash, Pred, Allocator > hash_map
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
Definition STExchange.h:161
BaseUInt< 256 > uint256
Definition base_uint.h:580
T next(T... args)
T push_back(T... args)
T size(T... args)
T str(T... args)