rippled
Loading...
Searching...
No Matches
overlay/Slot.h
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#ifndef RIPPLE_OVERLAY_SLOT_H_INCLUDED
21#define RIPPLE_OVERLAY_SLOT_H_INCLUDED
22
23#include <xrpld/core/Config.h>
24#include <xrpld/overlay/Peer.h>
25#include <xrpld/overlay/ReduceRelayCommon.h>
26
27#include <xrpl/basics/Log.h>
28#include <xrpl/basics/chrono.h>
29#include <xrpl/basics/random.h>
30#include <xrpl/beast/container/aged_unordered_map.h>
31#include <xrpl/beast/utility/Journal.h>
32#include <xrpl/protocol/PublicKey.h>
33#include <xrpl/protocol/messages.h>
34
35#include <algorithm>
36#include <optional>
37#include <set>
38#include <tuple>
39#include <unordered_map>
40#include <unordered_set>
41
42namespace ripple {
43
44namespace reduce_relay {
45
46template <typename clock_type>
47class Slots;
48
50enum class PeerState : uint8_t {
51 Counting, // counting messages
52 Selected, // selected to relay, counting if Slot in Counting
53 Squelched, // squelched, doesn't relay
54};
56enum class SlotState : uint8_t {
57 Counting, // counting messages
58 Selected, // peers selected, stop counting
59};
60
61template <typename Unit, typename TP>
62Unit
63epoch(TP const& t)
64{
65 return std::chrono::duration_cast<Unit>(t.time_since_epoch());
66}
67
73{
74public:
76 {
77 }
83 virtual void
84 squelch(PublicKey const& validator, Peer::id_t id, std::uint32_t duration)
85 const = 0;
90 virtual void
91 unsquelch(PublicKey const& validator, Peer::id_t id) const = 0;
92};
93
104template <typename clock_type>
105class Slot final
106{
107private:
108 friend class Slots<clock_type>;
110 using time_point = typename clock_type::time_point;
111
112 // a callback to report ignored squelches
114
122 SquelchHandler const& handler,
123 beast::Journal journal,
124 uint16_t maxSelectedPeers)
126 , lastSelected_(clock_type::now())
128 , handler_(handler)
129 , journal_(journal)
130 , maxSelectedPeers_(maxSelectedPeers)
131 {
132 }
133
153 void
155 PublicKey const& validator,
156 id_t id,
157 protocol::MessageType type,
158 ignored_squelch_callback callback);
159
170 void
171 deletePeer(PublicKey const& validator, id_t id, bool erase);
172
174 time_point const&
176 {
177 return lastSelected_;
178 }
179
182 inState(PeerState state) const;
183
186 notInState(PeerState state) const;
187
190 getState() const
191 {
192 return state_;
193 }
194
197 getSelected() const;
198
202 std::
204 getPeers() const;
205
212 void
213 deleteIdlePeer(PublicKey const& validator);
214
222
223private:
225 void
227
229 void
231
233 struct PeerInfo
234 {
235 PeerState state; // peer's state
236 std::size_t count; // message count
237 time_point expire; // squelch expiration time
238 time_point lastMessage; // time last message received
239 };
240
242
243 // pool of peers considered as the source of messages
244 // from validator - peers that reached MIN_MESSAGE_THRESHOLD
246
247 // number of peers that reached MAX_MESSAGE_THRESHOLD
249
250 // last time peers were selected, used to age the slot
251 typename clock_type::time_point lastSelected_;
252
253 SlotState state_; // slot's state
254 SquelchHandler const& handler_; // squelch/unsquelch handler
255 beast::Journal const journal_; // logging
256
257 // the maximum number of peers that should be selected as a validator
258 // message source
259 uint16_t const maxSelectedPeers_;
260};
261
262template <typename clock_type>
263void
265{
266 using namespace std::chrono;
267 auto now = clock_type::now();
268 for (auto it = peers_.begin(); it != peers_.end();)
269 {
270 auto& peer = it->second;
271 auto id = it->first;
272 ++it;
273 if (now - peer.lastMessage > IDLED)
274 {
275 JLOG(journal_.trace())
276 << "deleteIdlePeer: " << Slice(validator) << " " << id
277 << " idled "
278 << duration_cast<seconds>(now - peer.lastMessage).count()
279 << " selected " << (peer.state == PeerState::Selected);
280 deletePeer(validator, id, false);
281 }
282 }
283}
284
285template <typename clock_type>
286void
288 PublicKey const& validator,
289 id_t id,
290 protocol::MessageType type,
292{
293 using namespace std::chrono;
294 auto now = clock_type::now();
295 auto it = peers_.find(id);
296 // First message from this peer
297 if (it == peers_.end())
298 {
299 JLOG(journal_.trace())
300 << "update: adding peer " << Slice(validator) << " " << id;
301 peers_.emplace(
302 std::make_pair(id, PeerInfo{PeerState::Counting, 0, now, now}));
303 initCounting();
304 return;
305 }
306 // Message from a peer with expired squelch
307 if (it->second.state == PeerState::Squelched && now > it->second.expire)
308 {
309 JLOG(journal_.trace())
310 << "update: squelch expired " << Slice(validator) << " " << id;
311 it->second.state = PeerState::Counting;
312 it->second.lastMessage = now;
313 initCounting();
314 return;
315 }
316
317 auto& peer = it->second;
318
319 JLOG(journal_.trace())
320 << "update: existing peer " << Slice(validator) << " " << id
321 << " slot state " << static_cast<int>(state_) << " peer state "
322 << static_cast<int>(peer.state) << " count " << peer.count << " last "
323 << duration_cast<milliseconds>(now - peer.lastMessage).count()
324 << " pool " << considered_.size() << " threshold " << reachedThreshold_
325 << " " << (type == protocol::mtVALIDATION ? "validation" : "proposal");
326
327 peer.lastMessage = now;
328
329 // report if we received a message from a squelched peer
330 if (peer.state == PeerState::Squelched)
331 callback();
332
333 if (state_ != SlotState::Counting || peer.state == PeerState::Squelched)
334 return;
335
336 if (++peer.count > MIN_MESSAGE_THRESHOLD)
337 considered_.insert(id);
338 if (peer.count == (MAX_MESSAGE_THRESHOLD + 1))
339 ++reachedThreshold_;
340
341 if (now - lastSelected_ > 2 * MAX_UNSQUELCH_EXPIRE_DEFAULT)
342 {
343 JLOG(journal_.trace())
344 << "update: resetting due to inactivity " << Slice(validator) << " "
345 << id << " " << duration_cast<seconds>(now - lastSelected_).count();
346 initCounting();
347 return;
348 }
349
350 if (reachedThreshold_ == maxSelectedPeers_)
351 {
352 // Randomly select maxSelectedPeers_ peers from considered.
353 // Exclude peers that have been idling > IDLED -
354 // it's possible that deleteIdlePeer() has not been called yet.
355 // If number of remaining peers != maxSelectedPeers_
356 // then reset the Counting state and let deleteIdlePeer() handle
357 // idled peers.
359 auto const consideredPoolSize = considered_.size();
360 while (selected.size() != maxSelectedPeers_ && considered_.size() != 0)
361 {
362 auto i =
363 considered_.size() == 1 ? 0 : rand_int(considered_.size() - 1);
364 auto it = std::next(considered_.begin(), i);
365 auto id = *it;
366 considered_.erase(it);
367 auto const& itpeers = peers_.find(id);
368 if (itpeers == peers_.end())
369 {
370 JLOG(journal_.error()) << "update: peer not found "
371 << Slice(validator) << " " << id;
372 continue;
373 }
374 if (now - itpeers->second.lastMessage < IDLED)
375 selected.insert(id);
376 }
377
378 if (selected.size() != maxSelectedPeers_)
379 {
380 JLOG(journal_.trace())
381 << "update: selection failed " << Slice(validator) << " " << id;
382 initCounting();
383 return;
384 }
385
386 lastSelected_ = now;
387
388 auto s = selected.begin();
389 JLOG(journal_.trace())
390 << "update: " << Slice(validator) << " " << id << " pool size "
391 << consideredPoolSize << " selected " << *s << " "
392 << *std::next(s, 1) << " " << *std::next(s, 2);
393
394 XRPL_ASSERT(
395 peers_.size() >= maxSelectedPeers_,
396 "ripple::reduce_relay::Slot::update : minimum peers");
397
398 // squelch peers which are not selected and
399 // not already squelched
401 for (auto& [k, v] : peers_)
402 {
403 v.count = 0;
404
405 if (selected.find(k) != selected.end())
406 v.state = PeerState::Selected;
407 else if (v.state != PeerState::Squelched)
408 {
409 if (journal_.trace())
410 str << k << " ";
411 v.state = PeerState::Squelched;
413 getSquelchDuration(peers_.size() - maxSelectedPeers_);
414 v.expire = now + duration;
415 handler_.squelch(validator, k, duration.count());
416 }
417 }
418 JLOG(journal_.trace()) << "update: squelching " << Slice(validator)
419 << " " << id << " " << str.str();
420 considered_.clear();
421 reachedThreshold_ = 0;
422 state_ = SlotState::Selected;
423 }
424}
425
426template <typename clock_type>
429{
430 using namespace std::chrono;
431 auto m = std::max(
434 {
436 JLOG(journal_.warn())
437 << "getSquelchDuration: unexpected squelch duration " << npeers;
438 }
439 return seconds{ripple::rand_int(MIN_UNSQUELCH_EXPIRE / 1s, m / 1s)};
440}
441
442template <typename clock_type>
443void
445{
446 auto it = peers_.find(id);
447 if (it != peers_.end())
448 {
449 std::vector<Peer::id_t> toUnsquelch;
450
451 JLOG(journal_.trace())
452 << "deletePeer: " << Slice(validator) << " " << id << " selected "
453 << (it->second.state == PeerState::Selected) << " considered "
454 << (considered_.find(id) != considered_.end()) << " erase "
455 << erase;
456 auto now = clock_type::now();
457 if (it->second.state == PeerState::Selected)
458 {
459 for (auto& [k, v] : peers_)
460 {
461 if (v.state == PeerState::Squelched)
462 toUnsquelch.push_back(k);
463 v.state = PeerState::Counting;
464 v.count = 0;
465 v.expire = now;
466 }
467
468 considered_.clear();
469 reachedThreshold_ = 0;
470 state_ = SlotState::Counting;
471 }
472 else if (considered_.find(id) != considered_.end())
473 {
474 if (it->second.count > MAX_MESSAGE_THRESHOLD)
475 --reachedThreshold_;
476 considered_.erase(id);
477 }
478
479 it->second.lastMessage = now;
480 it->second.count = 0;
481
482 if (erase)
483 peers_.erase(it);
484
485 // Must be after peers_.erase(it)
486 for (auto const& k : toUnsquelch)
487 handler_.unsquelch(validator, k);
488 }
489}
490
491template <typename clock_type>
492void
494{
495 for (auto& [_, peer] : peers_)
496 {
497 (void)_;
498 peer.count = 0;
499 }
500}
501
502template <typename clock_type>
503void
505{
506 state_ = SlotState::Counting;
507 considered_.clear();
508 reachedThreshold_ = 0;
509 resetCounts();
510}
511
512template <typename clock_type>
515{
516 return std::count_if(peers_.begin(), peers_.end(), [&](auto const& it) {
517 return (it.second.state == state);
518 });
519}
520
521template <typename clock_type>
524{
525 return std::count_if(peers_.begin(), peers_.end(), [&](auto const& it) {
526 return (it.second.state != state);
527 });
528}
529
530template <typename clock_type>
533{
535 for (auto const& [id, info] : peers_)
536 if (info.state == PeerState::Selected)
537 r.insert(id);
538 return r;
539}
540
541template <typename clock_type>
543 typename Peer::id_t,
546{
547 using namespace std::chrono;
548 auto r = std::unordered_map<
549 id_t,
551
552 for (auto const& [id, info] : peers_)
553 r.emplace(std::make_pair(
554 id,
555 std::move(std::make_tuple(
556 info.state,
557 info.count,
558 epoch<milliseconds>(info.expire).count(),
559 epoch<milliseconds>(info.lastMessage).count()))));
560
561 return r;
562}
563
568template <typename clock_type>
569class Slots final
570{
571 using time_point = typename clock_type::time_point;
572 using id_t = typename Peer::id_t;
574 uint256,
576 clock_type,
578
579public:
585 Slots(Logs& logs, SquelchHandler const& handler, Config const& config)
586 : handler_(handler)
587 , logs_(logs)
588 , journal_(logs.journal("Slots"))
589 , baseSquelchEnabled_(config.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE)
590 , maxSelectedPeers_(config.VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS)
591 {
592 }
593 ~Slots() = default;
594
596 bool
601
603 bool
605 {
608 reduce_relay::epoch<std::chrono::minutes>(clock_type::now()) >
610
611 return reduceRelayReady_;
612 }
613
621 void
623 uint256 const& key,
624 PublicKey const& validator,
625 id_t id,
626 protocol::MessageType type)
627 {
628 updateSlotAndSquelch(key, validator, id, type, []() {});
629 }
630
638 void
640 uint256 const& key,
641 PublicKey const& validator,
642 id_t id,
643 protocol::MessageType type,
645
649 void
651
654 inState(PublicKey const& validator, PeerState state) const
655 {
656 auto const& it = slots_.find(validator);
657 if (it != slots_.end())
658 return it->second.inState(state);
659 return {};
660 }
661
664 notInState(PublicKey const& validator, PeerState state) const
665 {
666 auto const& it = slots_.find(validator);
667 if (it != slots_.end())
668 return it->second.notInState(state);
669 return {};
670 }
671
673 bool
674 inState(PublicKey const& validator, SlotState state) const
675 {
676 auto const& it = slots_.find(validator);
677 if (it != slots_.end())
678 return it->second.state_ == state;
679 return false;
680 }
681
684 getSelected(PublicKey const& validator)
685 {
686 auto const& it = slots_.find(validator);
687 if (it != slots_.end())
688 return it->second.getSelected();
689 return {};
690 }
691
696 typename Peer::id_t,
698 getPeers(PublicKey const& validator)
699 {
700 auto const& it = slots_.find(validator);
701 if (it != slots_.end())
702 return it->second.getPeers();
703 return {};
704 }
705
708 getState(PublicKey const& validator)
709 {
710 auto const& it = slots_.find(validator);
711 if (it != slots_.end())
712 return it->second.getState();
713 return {};
714 }
715
722 void
724
725private:
729 bool
730 addPeerMessage(uint256 const& key, id_t id);
731
733
735 SquelchHandler const& handler_; // squelch/unsquelch handler
738
741
742 // Maintain aged container of message/peers. This is required
743 // to discard duplicate message from the same peer. A message
744 // is aged after IDLED seconds. A message received IDLED seconds
745 // after it was relayed is ignored by PeerImp.
747 beast::get_abstract_clock<clock_type>()};
748};
749
750template <typename clock_type>
751bool
753{
754 beast::expire(peersWithMessage_, reduce_relay::IDLED);
755
756 if (key.isNonZero())
757 {
758 auto it = peersWithMessage_.find(key);
759 if (it == peersWithMessage_.end())
760 {
761 JLOG(journal_.trace())
762 << "addPeerMessage: new " << to_string(key) << " " << id;
763 peersWithMessage_.emplace(key, std::unordered_set<id_t>{id});
764 return true;
765 }
766
767 if (it->second.find(id) != it->second.end())
768 {
769 JLOG(journal_.trace()) << "addPeerMessage: duplicate message "
770 << to_string(key) << " " << id;
771 return false;
772 }
773
774 JLOG(journal_.trace())
775 << "addPeerMessage: added " << to_string(key) << " " << id;
776
777 it->second.insert(id);
778 }
779
780 return true;
781}
782
783template <typename clock_type>
784void
786 uint256 const& key,
787 PublicKey const& validator,
788 id_t id,
789 protocol::MessageType type,
791{
792 if (!addPeerMessage(key, id))
793 return;
794
795 auto it = slots_.find(validator);
796 if (it == slots_.end())
797 {
798 JLOG(journal_.trace())
799 << "updateSlotAndSquelch: new slot " << Slice(validator);
800 auto it =
801 slots_
802 .emplace(std::make_pair(
803 validator,
805 handler_, logs_.journal("Slot"), maxSelectedPeers_)))
806 .first;
807 it->second.update(validator, id, type, callback);
808 }
809 else
810 it->second.update(validator, id, type, callback);
811}
812
813template <typename clock_type>
814void
816{
817 for (auto& [validator, slot] : slots_)
818 slot.deletePeer(validator, id, erase);
819}
820
821template <typename clock_type>
822void
824{
825 auto now = clock_type::now();
826
827 for (auto it = slots_.begin(); it != slots_.end();)
828 {
829 it->second.deleteIdlePeer(it->first);
830 if (now - it->second.getLastSelected() > MAX_UNSQUELCH_EXPIRE_DEFAULT)
831 {
832 JLOG(journal_.trace())
833 << "deleteIdlePeers: deleting idle slot " << Slice(it->first);
834 it = slots_.erase(it);
835 }
836 else
837 ++it;
838 }
839}
840
841} // namespace reduce_relay
842
843} // namespace ripple
844
845#endif // RIPPLE_OVERLAY_SLOT_H_INCLUDED
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:60
Associative container where each element is also indexed by time.
Manages partitions for logging.
Definition Log.h:52
std::uint32_t id_t
Uniquely identifies a peer.
A public key.
Definition PublicKey.h:61
An immutable linear range of bytes.
Definition Slice.h:46
std::size_t size() const noexcept
Returns the number of bytes in the storage.
Definition Slice.h:81
bool isNonZero() const
Definition base_uint.h:545
Seed functor once per construction.
Slot is associated with a specific validator via validator's public key.
std::unordered_map< id_t, std::tuple< PeerState, uint16_t, uint32_t, uint32_t > > getPeers() const
Get peers info.
Slot(SquelchHandler const &handler, beast::Journal journal, uint16_t maxSelectedPeers)
Constructor.
void deletePeer(PublicKey const &validator, id_t id, bool erase)
Handle peer deletion when a peer disconnects.
std::uint16_t reachedThreshold_
uint16_t const maxSelectedPeers_
std::uint16_t notInState(PeerState state) const
Return number of peers not in state.
typename clock_type::time_point time_point
SquelchHandler const & handler_
void initCounting()
Initialize slot to Counting state.
time_point const & getLastSelected() const
Get the time of the last peer selection round.
clock_type::time_point lastSelected_
void update(PublicKey const &validator, id_t id, protocol::MessageType type, ignored_squelch_callback callback)
Update peer info.
std::set< id_t > getSelected() const
Return selected peers.
SlotState getState() const
Return Slot's state.
std::uint16_t inState(PeerState state) const
Return number of peers in state.
void deleteIdlePeer(PublicKey const &validator)
Check if peers stopped relaying messages.
std::unordered_set< id_t > considered_
std::chrono::seconds getSquelchDuration(std::size_t npeers)
Get random squelch duration between MIN_UNSQUELCH_EXPIRE and min(max(MAX_UNSQUELCH_EXPIRE_DEFAULT,...
std::unordered_map< id_t, PeerInfo > peers_
void resetCounts()
Reset counts of peers in Selected or Counting state.
beast::Journal const journal_
Slots is a container for validator's Slot and handles Slot update when a message is received from a v...
typename clock_type::time_point time_point
bool reduceRelayReady()
Check if reduce_relay::WAIT_ON_BOOTUP time passed since startup.
static messages peersWithMessage_
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.
beast::Journal const journal_
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 baseSquelchReady()
Check if base squelching feature is enabled and ready.
hash_map< PublicKey, Slot< clock_type > > slots_
std::optional< std::uint16_t > inState(PublicKey const &validator, PeerState state) const
Return number of peers in state.
bool inState(PublicKey const &validator, SlotState state) const
Return true if Slot is in state.
std::unordered_map< typename Peer::id_t, std::tuple< PeerState, uint16_t, uint32_t, std::uint32_t > > getPeers(PublicKey const &validator)
Get peers info.
uint16_t const maxSelectedPeers_
std::optional< SlotState > getState(PublicKey const &validator)
Get Slot's state.
void updateSlotAndSquelch(uint256 const &key, PublicKey const &validator, id_t id, protocol::MessageType type, typename Slot< clock_type >::ignored_squelch_callback callback)
Calls Slot::update of Slot associated with the validator.
std::atomic_bool reduceRelayReady_
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.
Slots(Logs &logs, SquelchHandler const &handler, Config const &config)
SquelchHandler const & handler_
typename Peer::id_t id_t
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 end(T... args)
T find(T... args)
T insert(T... args)
T is_same_v
T make_pair(T... args)
T make_tuple(T... args)
T max(T... args)
std::enable_if< is_aged_container< AgedContainer >::value, std::size_t >::type expire(AgedContainer &c, std::chrono::duration< Rep, Period > const &age)
Expire aged container items past the specified age.
static constexpr auto SQUELCH_PER_PEER
Unit epoch(TP const &t)
static constexpr auto MIN_UNSQUELCH_EXPIRE
SlotState
Slot's State.
static constexpr auto IDLED
PeerState
Peer's State.
static constexpr uint16_t MAX_MESSAGE_THRESHOLD
static constexpr auto WAIT_ON_BOOTUP
static constexpr auto MAX_UNSQUELCH_EXPIRE_DEFAULT
static constexpr uint16_t MIN_MESSAGE_THRESHOLD
static constexpr auto MAX_UNSQUELCH_EXPIRE_PEERS
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
std::enable_if_t< std::is_integral< Integral >::value, Integral > rand_int()
base_uint< 256 > uint256
Definition base_uint.h:558
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
Definition STExchange.h:172
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
T next(T... args)
T push_back(T... args)
T size(T... args)
T str(T... args)
Data maintained for each peer.