xrpld
Loading...
Searching...
No Matches
reduce_relay_test.cpp
1#include <test/jtx/Env.h>
2#include <test/jtx/PeerStub.h>
3#include <test/jtx/envconfig.h>
4
5#include <xrpld/app/main/Application.h>
6#include <xrpld/overlay/Message.h>
7#include <xrpld/overlay/Peer.h>
8#include <xrpld/overlay/ReduceRelayCommon.h>
9#include <xrpld/overlay/Slot.h>
10#include <xrpld/overlay/Squelch.h>
11#include <xrpld/overlay/detail/Handshake.h>
12
13#include <xrpl/basics/base_uint.h>
14#include <xrpl/basics/random.h>
15#include <xrpl/beast/net/IPAddress.h>
16#include <xrpl/beast/unit_test/suite.h>
17#include <xrpl/beast/utility/Journal.h>
18#include <xrpl/protocol/KeyType.h>
19#include <xrpl/protocol/PublicKey.h>
20#include <xrpl/protocol/SecretKey.h>
21
22#include <boost/asio/ip/address.hpp>
23
24#include <xrpl.pb.h>
25
26#include <algorithm>
27#include <cassert>
28#include <chrono>
29#include <cstdint>
30#include <functional>
31#include <iostream>
32#include <iterator>
33#include <memory>
34#include <numeric>
35#include <optional>
36#include <random>
37#include <ratio>
38#include <set>
39#include <sstream>
40#include <stdexcept>
41#include <string>
42#include <tuple>
43#include <unordered_map>
44#include <utility>
45#include <vector>
46
47namespace xrpl::test {
48
49using namespace std::chrono;
50
51class Link;
52
58using UnsquelchCB = std::function<void(PublicKey const&, PeerWPtr const&)>;
60
61static constexpr std::uint32_t kMaxPeers = 10;
62static constexpr std::uint32_t kMaxValidators = 10;
63static constexpr std::uint32_t kMaxMessages = 200000;
64
71class PeerPartial : public PeerStub
72{
73public:
75 // Keep the base overload visible; the one below would otherwise hide it.
76 using PeerStub::send;
77
78 virtual void
80 virtual void
81 onMessage(protocol::TMSquelch const& squelch) = 0;
82 void
83 send(protocol::TMSquelch const& squelch)
84 {
86 }
87};
88
93{
94public:
95 using rep = uint64_t;
99 inline static bool const is_steady = false; // NOLINT(readability-identifier-naming)
100
101 static void
102 advance(duration d) noexcept
103 {
104 kNow += d;
105 }
106
107 static void
112
113 static void
114 reset() noexcept
115 {
116 kNow = time_point(seconds(0));
117 }
118
119 static time_point
120 now() noexcept
121 {
122 return kNow;
123 }
124
125 static duration
127 {
128 return duration(milliseconds(randInt(min.count(), max.count())));
129 }
130
131 explicit ManualClock() = default;
132
133private:
134 inline static time_point kNow = time_point(seconds(0));
135};
136
141{
142public:
143 Overlay() = default;
144 virtual ~Overlay() = default;
145
146 virtual void
148 UInt256 const& key,
149 PublicKey const& validator,
150 Peer::ID id,
151 SquelchCB f,
152 protocol::MessageType type = protocol::mtVALIDATION) = 0;
153
154 virtual void deleteIdlePeers(UnsquelchCB) = 0;
155
156 virtual void deletePeer(Peer::ID, UnsquelchCB) = 0;
157};
158
159class Validator;
160
165class Link
166{
168
169public:
171 : validator_(validator), peer_(peer), latency_(std::move(latency))
172 {
173 auto sp = peer_.lock();
174 assert(sp);
175 }
176 ~Link() = default;
177 void
179 {
180 if (!up_)
181 return;
182 auto sp = peer_.lock();
183 assert(sp);
185 peer->onMessage(m, f);
186 }
187 Validator&
189 {
190 return validator_;
191 }
192 void
193 up(bool linkUp)
194 {
195 up_ = linkUp;
196 }
199 {
200 auto p = peer_.lock();
201 assert(p);
202 return p->id();
203 }
206 {
207 auto p = peer_.lock();
208 assert(p);
209 return p;
210 }
211
212private:
216 bool up_{true};
217};
218
223{
225
226public:
228 {
229 protocol::TMValidation v;
230 v.set_validation("validation");
231 message_ = std::make_shared<Message>(v, protocol::mtVALIDATION, pkey_);
232 }
233 Validator(Validator const&) = default;
234 Validator(Validator&&) = default;
235 Validator&
236 operator=(Validator const&) = default;
237 Validator&
238 operator=(Validator&&) = default;
240 {
241 clear();
242 }
243
244 void
246 {
247 links_.clear();
248 }
249
250 static void
252 {
253 sid = 0;
254 }
255
256 PublicKey const&
258 {
259 return pkey_;
260 }
261
262 operator PublicKey() const
263 {
264 return pkey_;
265 }
266
267 void
269 {
270 links_.emplace(std::make_pair(peer->id(), std::make_shared<Link>(*this, peer)));
271 }
272
273 void
275 {
276 links_.erase(id);
277 }
278
279 void
281 {
282 for (auto id : peers)
283 {
284 assert(links_.contains(id));
285 f(*links_[id], message_);
286 }
287 }
288
289 void
290 forLinks(LinkIterCB f, bool simulateSlow = false)
291 {
293 std::ranges::transform(links_, std::back_inserter(v), [](auto& kv) { return kv.second; });
295 std::mt19937 g(d());
296 std::shuffle(v.begin(), v.end(), g);
297
298 for (auto& link : v)
299 {
300 f(*link, message_);
301 }
302 }
303
307 void
309 {
310 forLinks(peers, [&](Link& link, MessageSPtr m) { link.send(m, f); });
311 }
312
316 void
318 {
319 forLinks([&](Link& link, MessageSPtr m) { link.send(m, f); });
320 }
321
324 {
325 return message_;
326 }
327
328 [[nodiscard]] std::uint16_t
329 id() const
330 {
331 return id_;
332 }
333
334 void
336 {
337 auto it = links_.find(id);
338 assert(it != links_.end());
339 it->second->up(true);
340 }
341
342 void
344 {
345 auto it = links_.find(id);
346 assert(it != links_.end());
347 it->second->up(false);
348 }
349
350private:
354 inline static std::uint16_t sid = 0;
356};
357
358class PeerSim : public PeerPartial, public std::enable_shared_from_this<PeerSim>
359{
360public:
361 using ID = Peer::ID;
362 PeerSim(Overlay& overlay, beast::Journal journal)
363 : PeerPartial(sid++), overlay_(overlay), squelch_(journal)
364 {
365 }
366
367 ~PeerSim() override = default;
368
369 static void
371 {
372 sid = 0;
373 }
374
378 void
379 onMessage(MessageSPtr const& m, SquelchCB f) override
380 {
381 auto validator = m->getValidatorKey();
382 assert(validator);
383 if (!squelch_.expireSquelch(*validator)) // NOLINT(bugprone-unchecked-optional-access)
384 return;
385
386 overlay_.updateSlotAndSquelch(
387 {}, *validator, id(), f); // NOLINT(bugprone-unchecked-optional-access)
388 }
389
393 void
394 onMessage(protocol::TMSquelch const& squelch) override
395 {
396 auto validator = squelch.validatorpubkey();
397 PublicKey const key(Slice(validator.data(), validator.size()));
398 if (squelch.squelch())
399 {
400 squelch_.addSquelch(key, std::chrono::seconds{squelch.squelchduration()});
401 }
402 else
403 {
404 squelch_.removeSquelch(key);
405 }
406 }
407
408private:
409 inline static ID sid = 0;
412};
413
415{
417
418public:
419 using ID = Peer::ID;
421 OverlaySim(Application& app) : slots_(app, *this, app.config()), registry_(app)
422 {
423 }
424
425 ~OverlaySim() override = default;
426
427 void
429 {
430 peers_.clear();
432 slots_.deleteIdlePeers();
433 }
434
437 {
438 auto res = slots_.inState(validator, state);
439 return res ? *res : 0;
440 }
441
442 void
444 UInt256 const& key,
445 PublicKey const& validator,
446 Peer::ID id,
447 SquelchCB f,
448 protocol::MessageType type = protocol::mtVALIDATION) override
449 {
450 squelch_ = f;
451 slots_.updateSlotAndSquelch(key, validator, id, type);
452 }
453
454 void
455 deletePeer(ID id, UnsquelchCB f) override
456 {
457 unsquelch_ = f;
458 slots_.deletePeer(id, true);
459 }
460
461 void
463 {
464 unsquelch_ = f;
465 slots_.deleteIdlePeers();
466 }
467
469 addPeer(bool useCache = true)
470 {
471 PeerSPtr peer{};
472 Peer::ID id = 0;
473 if (peersCache_.empty() || !useCache)
474 {
475 peer = std::make_shared<PeerSim>(*this, registry_.getJournal("Squelch"));
476 id = peer->id();
477 }
478 else
479 {
480 auto it = peersCache_.begin();
481 peer = it->second;
482 id = it->first;
483 peersCache_.erase(it);
484 }
485 peers_.emplace(std::make_pair(id, peer));
486 return peer;
487 }
488
489 void
490 deletePeer(Peer::ID id, bool useCache = true)
491 {
492 auto it = peers_.find(id);
493 assert(it != peers_.end());
494 deletePeer(id, [&](PublicKey const&, PeerWPtr) {});
495 if (useCache)
496 peersCache_.emplace(std::make_pair(id, it->second));
497 peers_.erase(it);
498 }
499
500 void
502 {
503 while (!peers_.empty())
504 deletePeer(peers_.begin()->first);
505 while (!peersCache_.empty())
506 addPeer();
507 }
508
511 {
512 if (peers_.empty())
513 return {};
514
515 std::uint8_t maxId = 0;
516
517 for (auto& [id, _] : peers_)
518 {
519 (void)_;
520 maxId = std::max<unsigned int>(id, maxId);
521 }
522
523 deletePeer(maxId, false);
524
525 return maxId;
526 }
527
528 bool
533
536 {
537 return slots_.getSelected(validator);
538 }
539
540 bool
542 {
543 auto selected = slots_.getSelected(validator);
544 return selected.contains(peer);
545 }
546
547 ID
549 {
550 auto selected = slots_.getSelected(validator);
551 assert(!selected.empty());
552 return *selected.begin();
553 }
554
556 ID,
559 {
560 return slots_.getPeers(validator);
561 }
562
565 {
566 return peers_.size();
567 }
568
569private:
570 void
571 squelch(PublicKey const& validator, Peer::ID id, std::uint32_t squelchDuration) const override
572 {
573 if (auto it = peers_.find(id); it != peers_.end())
574 squelch_(validator, it->second, squelchDuration);
575 }
576 void
577 unsquelch(PublicKey const& validator, Peer::ID id) const override
578 {
579 if (auto it = peers_.find(id); it != peers_.end())
580 unsquelch_(validator, it->second);
581 }
588};
589
591{
592public:
594 {
595 init();
596 }
597
598 void
600 {
602 for (int p = 0; p < kMaxPeers; p++)
603 {
604 auto peer = overlay_.addPeer();
605 for (auto& v : validators_)
606 v.addPeer(peer);
607 }
608 }
609
610 ~Network() = default;
611
612 void
614 {
615 validators_.clear();
616 overlay_.clear();
619 init();
620 }
621
624 {
625 auto peer = overlay_.addPeer();
626 for (auto& v : validators_)
627 v.addPeer(peer);
628 return peer->id();
629 }
630
631 void
633 {
634 auto id = overlay_.deleteLastPeer();
635
636 if (!id)
637 return;
638
639 for (auto& validator : validators_)
640 validator.deletePeer(*id);
641 }
642
643 void
645 {
646 while (overlay_.getNumPeers() > kMaxPeers)
648 }
649
650 Validator&
652 {
653 assert(v < validators_.size());
654 return validators_[v];
655 }
656
659 {
660 return overlay_;
661 }
662
663 void
664 enableLink(std::uint16_t validatorId, Peer::ID peer, bool enable)
665 {
666 auto it = std::ranges::find_if(validators_, [&](auto& v) { return v.id() == validatorId; });
667 assert(it != validators_.end());
668 if (enable)
669 {
670 it->linkUp(peer);
671 }
672 else
673 {
674 it->linkDown(peer);
675 }
676 }
677
678 void
680 {
681 // Send unsquelch to the Peer on all links. This way when
682 // the Peer "reconnects" it starts sending messages on the link.
683 // We expect that if a Peer disconnects and then reconnects, it's
684 // unsquelched.
685 protocol::TMSquelch squelch;
686 squelch.set_squelch(false);
687 for (auto& v : validators_)
688 {
689 PublicKey const key = v;
690 squelch.clear_validatorpubkey();
691 squelch.set_validatorpubkey(key.data(), key.size());
692 v.forLinks({peer}, [&](Link& l, MessageSPtr) {
694 });
695 }
696 }
697
698 static void
700 {
701 auto size = max - min;
703 std::iota(s.begin(), s.end(), min); // NOLINT(modernize-use-ranges)
705 std::mt19937 g(d());
706 std::shuffle(s.begin(), s.end(), g);
707 for (auto v : s)
708 f(v);
709 }
710
711 void
713 LinkIterCB link,
714 std::uint16_t nValidators = kMaxValidators,
715 std::uint32_t nMessages = kMaxMessages,
716 bool purge = true,
717 bool resetClock = true)
718 {
719 if (resetClock)
721
722 if (purge)
723 {
724 purgePeers();
725 overlay_.resetPeers();
726 }
727
728 for (int m = 0; m < nMessages; ++m)
729 {
731 forRand(0, nValidators, [&](std::uint32_t v) { validators_[v].forLinks(link); });
732 }
733 }
734
738 bool
740 {
741 return std::ranges::any_of(
742 validators_, [&](auto& v) { return overlay_.isSelected(v, id); });
743 }
744
750 bool
752 {
753 for (auto& v : validators_)
754 {
755 if (!overlay_.isSelected(v, peer))
756 continue;
757 auto peers = overlay_.getPeers(v);
758 for (auto& [_, v] : peers)
759 {
760 (void)_;
761 if (std::get<reduce_relay::PeerState>(v) == reduce_relay::PeerState::Squelched)
762 return false;
763 }
764 }
765 return true;
766 }
767
768private:
771};
772
774{
776 using ID = Peer::ID;
777
778protected:
779 void
781 {
782 auto peers = network_.overlay().getPeers(network_.validator(validator));
783 std::cout << msg << " "
784 << "num peers " << (int)network_.overlay().getNumPeers() << std::endl;
785 for (auto& [k, v] : peers)
786 std::cout << k << ":" << (int)std::get<reduce_relay::PeerState>(v) << " ";
788 }
789
793 static Peer::ID
795 PublicKey const& validator,
796 PeerWPtr const& peerPtr,
798 {
799 protocol::TMSquelch squelch;
800 bool const res = static_cast<bool>(duration);
801 squelch.set_squelch(res);
802 squelch.set_validatorpubkey(validator.data(), validator.size());
803 if (res)
804 squelch.set_squelchduration(*duration);
805 auto sp = peerPtr.lock();
806 assert(sp);
808 return sp->id();
809 }
810
811 enum class State { On, Off, WaitReset };
812 enum class EventType { LinkDown = 0, PeerDisconnected = 1 };
813 // Link down or Peer disconnect event
814 // TBD - add new peer event
815 // TBD - add overlapping type of events at any
816 // time in any quantity
829
834 void
836 {
840
841 network_.reset();
842 network_.propagate([&](Link& link, MessageSPtr m) {
843 auto& validator = link.validator();
844 auto now = ManualClock::now();
845
846 bool squelched = false;
848
849 link.send(
850 m, [&](PublicKey const& key, PeerWPtr const& peerPtr, std::uint32_t duration) {
851 assert(key == validator);
852 auto p = sendSquelch(key, peerPtr, duration);
853 squelched = true;
854 str << p << " ";
855 });
856
857 if (squelched)
858 {
859 auto selected = network_.overlay().getSelected(validator);
860 str << " selected: ";
861 for (auto s : selected)
862 str << s << " ";
863 if (log)
864 {
865 std::cout << (double)reduce_relay::epoch<milliseconds>(now).count() / 1000.
866 << " random, squelched, validator: " << validator.id()
867 << " peers: " << str.str() << std::endl;
868 }
869 auto countingState = network_.overlay().isCountingState(validator);
870 BEAST_EXPECT(
871 countingState == false &&
872 selected.size() == env_.app().config().vpReduceRelaySquelchMaxSelectedPeers);
873 }
874
875 // Trigger Link Down or Peer Disconnect event
876 // Only one Link Down at a time
877 if (events[EventType::LinkDown].state == State::Off)
878 {
879 auto update = [&](EventType event) {
880 events[event].cnt++;
881 events[event].validator = validator.id();
882 events[event].key = validator;
883 events[event].peer = link.peerId();
884 events[event].state = State::On;
885 events[event].time = now;
886 if (event == EventType::LinkDown)
887 {
888 network_.enableLink(validator.id(), link.peerId(), false);
889 events[event].isSelected =
890 network_.overlay().isSelected(validator, link.peerId());
891 }
892 else
893 {
894 events[event].isSelected = network_.isSelected(link.peerId());
895 }
896 };
897 auto r = randInt(0, 1000);
898 if (r == (int)EventType::LinkDown || r == (int)EventType::PeerDisconnected)
899 {
900 update(static_cast<EventType>(r));
901 }
902 }
903
904 if (events[EventType::PeerDisconnected].state == State::On)
905 {
906 auto& event = events[EventType::PeerDisconnected];
907 bool const allCounting = network_.allCounting(event.peer);
908 network_.overlay().deletePeer(
909 event.peer, [&](PublicKey const& v, PeerWPtr const& peerPtr) {
910 if (event.isSelected)
911 sendSquelch(v, peerPtr, {});
912 event.handled = true;
913 });
914 // Should only be unsquelched if the peer is in Selected state
915 // If in Selected state it's possible unsquelching didn't
916 // take place because there is no peers in Squelched state in
917 // any of the slots where the peer is in Selected state
918 // (allCounting is true)
919 bool const handled = (!event.isSelected && !event.handled) ||
920 (event.isSelected && (event.handled || allCounting));
921 BEAST_EXPECT(handled);
922 event.state = State::Off;
923 event.isSelected = false;
924 event.handledCnt += handled;
925 event.handled = false;
926 network_.onDisconnectPeer(event.peer);
927 }
928
929 auto& event = events[EventType::LinkDown];
930 // Check every sec for idled peers. Idled peers are
931 // created by Link Down event.
932 if (now - lastCheck > milliseconds(1000))
933 {
934 lastCheck = now;
935 // Check if Link Down event must be handled by
936 // deleteIdlePeer(): 1) the peer is in Selected state;
937 // 2) the peer has not received any messages for IDLED time;
938 // 3) there are peers in Squelched state in the slot.
939 // 4) peer is in Slot's peers_ (if not then it is deleted
940 // by Slots::deleteIdlePeers())
941 bool mustHandle = false;
942 if (event.state == State::On && BEAST_EXPECT(event.key))
943 {
944 event.isSelected = network_.overlay().isSelected(*event.key, event.peer);
945 auto peers = network_.overlay().getPeers(*event.key);
946 auto d = reduce_relay::epoch<milliseconds>(now).count() -
947 std::get<3>(peers[event.peer]);
948 mustHandle = event.isSelected &&
950 network_.overlay().inState(*event.key, reduce_relay::PeerState::Squelched) >
951 0 &&
952 peers.contains(event.peer);
953 }
954 network_.overlay().deleteIdlePeers([&](PublicKey const& v, PeerWPtr const& ptr) {
955 event.handled = true;
956 if (mustHandle && v == event.key)
957 {
958 event.state = State::WaitReset;
959 sendSquelch(validator, ptr, {});
960 }
961 });
962 bool const handled = (event.handled && event.state == State::WaitReset) ||
963 (!event.handled && !mustHandle);
964 BEAST_EXPECT(handled);
965 }
966 if (event.state == State::WaitReset ||
967 (event.state == State::On &&
968 (now - event.time > (reduce_relay::kIdled + seconds(2)))))
969 {
970 bool const handled = event.state == State::WaitReset || !event.handled;
971 BEAST_EXPECT(handled);
972 event.state = State::Off;
973 event.isSelected = false;
974 event.handledCnt += handled;
975 event.handled = false;
976 network_.enableLink(event.validator, event.peer, true);
977 }
978 });
979
980 auto& down = events[EventType::LinkDown];
981 auto& disconnected = events[EventType::PeerDisconnected];
982 // It's possible the last Down Link event is not handled
983 BEAST_EXPECT(down.handledCnt >= down.cnt - 1);
984 // All Peer Disconnect events must be handled
985 BEAST_EXPECT(disconnected.cnt == disconnected.handledCnt);
986 if (log)
987 {
988 std::cout << "link down count: " << down.cnt << "/" << down.handledCnt
989 << " peer disconnect count: " << disconnected.cnt << "/"
990 << disconnected.handledCnt;
991 }
992 }
993
994 bool
995 checkCounting(PublicKey const& validator, bool isCountingState)
996 {
997 auto countingState = network_.overlay().isCountingState(validator);
998 BEAST_EXPECT(countingState == isCountingState);
999 return countingState == isCountingState;
1000 }
1001
1002 void
1003 doTest(std::string const& msg, bool log, std::function<void(bool)> f)
1004 {
1005 testcase(msg);
1006 f(log);
1007 }
1008
1015 void
1017 {
1018 doTest("Initial Round", log, [this](bool log) { BEAST_EXPECT(propagateAndSquelch(log)); });
1019 }
1020
1025 void
1027 {
1028 doTest("Peer Unsquelched Too Soon", log, [this](bool log) {
1029 BEAST_EXPECT(propagateNoSquelch(log, 1, false, false, false));
1030 });
1031 }
1032
1037 void
1039 {
1041 doTest("Peer Unsquelched", log, [this](bool log) {
1042 BEAST_EXPECT(propagateNoSquelch(log, 2, true, true, false));
1043 });
1044 }
1045
1049 bool
1050 propagateAndSquelch(bool log, bool purge = true, bool resetClock = true)
1051 {
1052 int n = 0;
1053 network_.propagate(
1054 [&](Link& link, MessageSPtr message) {
1055 std::uint16_t squelched = 0;
1056 link.send(
1057 message,
1058 [&](PublicKey const& key, PeerWPtr const& peerPtr, std::uint32_t duration) {
1059 squelched++;
1060 sendSquelch(key, peerPtr, duration);
1061 });
1062 if (squelched)
1063 {
1064 BEAST_EXPECT(
1065 squelched ==
1066 kMaxPeers - env_.app().config().vpReduceRelaySquelchMaxSelectedPeers);
1067 n++;
1068 }
1069 },
1070 1,
1072 purge,
1073 resetClock);
1074 auto selected = network_.overlay().getSelected(network_.validator(0));
1075 BEAST_EXPECT(selected.size() == env_.app().config().vpReduceRelaySquelchMaxSelectedPeers);
1076 BEAST_EXPECT(n == 1); // only one selection round
1077 auto res = checkCounting(network_.validator(0), false);
1078 BEAST_EXPECT(res);
1079 return n == 1 && res;
1080 }
1081
1085 bool
1087 bool log,
1088 std::uint16_t nMessages,
1089 bool countingState,
1090 bool purge = true,
1091 bool resetClock = true)
1092 {
1093 bool squelched = false;
1094 network_.propagate(
1095 [&](Link& link, MessageSPtr message) {
1096 link.send(
1097 message,
1098 [&](PublicKey const& key, PeerWPtr const& peerPtr, std::uint32_t duration) {
1099 squelched = true;
1100 BEAST_EXPECT(false);
1101 });
1102 },
1103 1,
1104 nMessages,
1105 purge,
1106 resetClock);
1107 auto res = checkCounting(network_.validator(0), countingState);
1108 return !squelched && res;
1109 }
1110
1115 void
1117 {
1118 doTest("New Peer", log, [this](bool log) {
1119 BEAST_EXPECT(propagateAndSquelch(log, true, false));
1120 network_.addPeer();
1121 BEAST_EXPECT(propagateNoSquelch(log, 1, true, false, false));
1122 });
1123 }
1124
1129 void
1131 {
1132 doTest("Selected Peer Disconnects", log, [this](bool log) {
1134 BEAST_EXPECT(propagateAndSquelch(log, true, false));
1135 auto id = network_.overlay().getSelectedPeer(network_.validator(0));
1136 std::uint16_t unsquelched = 0;
1137 network_.overlay().deletePeer(
1138 id, [&](PublicKey const& key, PeerWPtr const& peer) { unsquelched++; });
1139 BEAST_EXPECT(
1140 unsquelched ==
1141 kMaxPeers - env_.app().config().vpReduceRelaySquelchMaxSelectedPeers);
1142 BEAST_EXPECT(checkCounting(network_.validator(0), true));
1143 });
1144 }
1145
1150 void
1152 {
1153 doTest("Selected Peer Stops Relaying", log, [this](bool log) {
1155 BEAST_EXPECT(propagateAndSquelch(log, true, false));
1157 std::uint16_t unsquelched = 0;
1158 network_.overlay().deleteIdlePeers(
1159 [&](PublicKey const& key, PeerWPtr const& peer) { unsquelched++; });
1160 auto peers = network_.overlay().getPeers(network_.validator(0));
1161 BEAST_EXPECT(
1162 unsquelched ==
1163 kMaxPeers - env_.app().config().vpReduceRelaySquelchMaxSelectedPeers);
1164 BEAST_EXPECT(checkCounting(network_.validator(0), true));
1165 });
1166 }
1167
1171 void
1173 {
1174 doTest("Squelched Peer Disconnects", log, [this](bool log) {
1176 BEAST_EXPECT(propagateAndSquelch(log, true, false));
1177 auto peers = network_.overlay().getPeers(network_.validator(0));
1178 auto it = std::ranges::find_if(peers, [&](auto it) {
1179 return std::get<reduce_relay::PeerState>(it.second) ==
1181 });
1182 assert(it != peers.end());
1183 std::uint16_t unsquelched = 0;
1184 network_.overlay().deletePeer(
1185 it->first, [&](PublicKey const& key, PeerWPtr const& peer) { unsquelched++; });
1186 BEAST_EXPECT(unsquelched == 0);
1187 BEAST_EXPECT(checkCounting(network_.validator(0), false));
1188 });
1189 }
1190
1191 void
1193 {
1194 doTest("Test Config - squelch enabled (legacy)", log, [&](bool log) {
1195 Config c;
1196
1197 std::string const toLoad(R"xrpldConfig(
1198[reduce_relay]
1199vp_enable=1
1200)xrpldConfig");
1201
1202 c.loadFromString(toLoad);
1203 BEAST_EXPECT(c.vpReduceRelayBaseSquelchEnable == true);
1204 });
1205
1206 doTest("Test Config - squelch disabled (legacy)", log, [&](bool log) {
1207 Config c;
1208
1209 std::string toLoad(R"xrpldConfig(
1210[reduce_relay]
1211vp_enable=0
1212)xrpldConfig");
1213
1214 c.loadFromString(toLoad);
1215 BEAST_EXPECT(c.vpReduceRelayBaseSquelchEnable == false);
1216
1217 Config c1;
1218
1219 toLoad = R"xrpldConfig(
1220[reduce_relay]
1221)xrpldConfig";
1222
1223 c1.loadFromString(toLoad);
1224 BEAST_EXPECT(c1.vpReduceRelayBaseSquelchEnable == false);
1225 });
1226
1227 doTest("Test Config - squelch enabled", log, [&](bool log) {
1228 Config c;
1229
1230 std::string const toLoad(R"xrpldConfig(
1231[reduce_relay]
1232vp_base_squelch_enable=1
1233)xrpldConfig");
1234
1235 c.loadFromString(toLoad);
1236 BEAST_EXPECT(c.vpReduceRelayBaseSquelchEnable == true);
1237 });
1238
1239 doTest("Test Config - squelch disabled", log, [&](bool log) {
1240 Config c;
1241
1242 std::string const toLoad(R"xrpldConfig(
1243[reduce_relay]
1244vp_base_squelch_enable=0
1245)xrpldConfig");
1246
1247 c.loadFromString(toLoad);
1248 BEAST_EXPECT(c.vpReduceRelayBaseSquelchEnable == false);
1249 });
1250
1251 doTest("Test Config - legacy and new", log, [&](bool log) {
1252 Config c;
1253
1254 std::string const toLoad(R"xrpldConfig(
1255[reduce_relay]
1256vp_base_squelch_enable=0
1257vp_enable=0
1258)xrpldConfig");
1259
1260 std::string error;
1261 auto const expectedError =
1262 "Invalid reduce_relay"
1263 " cannot specify both vp_base_squelch_enable and vp_enable "
1264 "options. "
1265 "vp_enable was deprecated and replaced by "
1266 "vp_base_squelch_enable";
1267
1268 try
1269 {
1270 c.loadFromString(toLoad);
1271 }
1272 catch (std::runtime_error const& e)
1273 {
1274 error = e.what();
1275 }
1276
1277 BEAST_EXPECT(error == expectedError);
1278 });
1279
1280 doTest("Test Config - max selected peers", log, [&](bool log) {
1281 Config c;
1282
1283 std::string toLoad(R"xrpldConfig(
1284[reduce_relay]
1285)xrpldConfig");
1286
1287 c.loadFromString(toLoad);
1288 BEAST_EXPECT(c.vpReduceRelaySquelchMaxSelectedPeers == 5);
1289
1290 Config c1;
1291
1292 toLoad = R"xrpldConfig(
1293[reduce_relay]
1294vp_base_squelch_max_selected_peers=6
1295)xrpldConfig";
1296
1297 c1.loadFromString(toLoad);
1298 BEAST_EXPECT(c1.vpReduceRelaySquelchMaxSelectedPeers == 6);
1299
1301
1302 toLoad = R"xrpldConfig(
1303[reduce_relay]
1304vp_base_squelch_max_selected_peers=2
1305)xrpldConfig";
1306
1307 std::string error;
1308 auto const expectedError =
1309 "Invalid reduce_relay"
1310 " vp_base_squelch_max_selected_peers must be "
1311 "greater than or equal to 3";
1312 try
1313 {
1314 c2.loadFromString(toLoad);
1315 }
1316 catch (std::runtime_error const& e)
1317 {
1318 error = e.what();
1319 }
1320
1321 BEAST_EXPECT(error == expectedError);
1322 });
1323 }
1324
1325 void
1326 testBaseSquelchReady(bool log)
1327 {
1328 doTest("BaseSquelchReady", log, [&](bool log) {
1330 auto createSlots = [&](bool baseSquelchEnabled) -> reduce_relay::Slots<ManualClock> {
1331 env_.app().config().vpReduceRelayBaseSquelchEnable = baseSquelchEnabled;
1333 env_.app(), network_.overlay(), env_.app().config());
1334 };
1335 // base squelching must not be ready if squelching is disabled
1336 BEAST_EXPECT(!createSlots(false).baseSquelchReady());
1337
1338 // base squelch must not be ready as not enough time passed from
1339 // bootup
1340 BEAST_EXPECT(!createSlots(true).baseSquelchReady());
1341
1343
1344 // base squelch enabled and bootup time passed
1345 BEAST_EXPECT(createSlots(true).baseSquelchReady());
1346
1347 // even if time passed, base squelching must not be ready if turned
1348 // off in the config
1349 BEAST_EXPECT(!createSlots(false).baseSquelchReady());
1350 });
1351 }
1352
1353 void
1354 testInternalHashRouter(bool log)
1355 {
1356 doTest("Duplicate Message", log, [&](bool log) {
1357 network_.reset();
1358 // update message count for the same peer/validator
1359 std::int16_t const nMessages = 5;
1360 for (int i = 0; i < nMessages; i++)
1361 {
1362 UInt256 const key(i);
1363 network_.overlay().updateSlotAndSquelch(
1364 key, network_.validator(0), 0, [&](PublicKey const&, PeerWPtr, std::uint32_t) {
1365 });
1367 auto peers = network_.overlay().getPeers(network_.validator(0));
1368 // first message changes Slot state to Counting and is not counted,
1369 // hence '-1'.
1370 BEAST_EXPECT(std::get<1>(peers[0]) == (nMessages - 1));
1371 // add duplicate
1372 UInt256 const key(nMessages - 1);
1373 network_.overlay().updateSlotAndSquelch(
1374 key, network_.validator(0), 0, [&](PublicKey const&, PeerWPtr, std::uint32_t) {});
1375 // confirm the same number of messages
1376 peers = network_.overlay().getPeers(network_.validator(0));
1377 BEAST_EXPECT(std::get<1>(peers[0]) == (nMessages - 1));
1378 // advance the clock
1380 network_.overlay().updateSlotAndSquelch(
1381 key, network_.validator(0), 0, [&](PublicKey const&, PeerWPtr, std::uint32_t) {});
1382 peers = network_.overlay().getPeers(network_.validator(0));
1383 // confirm message number increased
1384 BEAST_EXPECT(std::get<1>(peers[0]) == nMessages);
1385 });
1386 }
1387
1388 struct Handler : public reduce_relay::SquelchHandler
1389 {
1390 Handler() = default;
1391 void
1392 squelch(PublicKey const&, Peer::ID, std::uint32_t duration) const override
1393 {
1394 maxDuration = std::max<uint32_t>(duration, maxDuration);
1395 }
1396 void
1397 unsquelch(PublicKey const&, Peer::ID) const override
1398 {
1399 }
1400 mutable int maxDuration{0};
1401 };
1402
1403 void
1404 testRandomSquelch(bool l)
1405 {
1406 doTest("Random Squelch", l, [&](bool l) {
1407 PublicKey validator = std::get<0>(randomKeyPair(KeyType::Ed25519));
1408 Handler handler;
1409
1410 auto run = [&](int npeers) {
1411 handler.maxDuration = 0;
1412 reduce_relay::Slots<ManualClock> slots(env_.app(), handler, env_.app().config());
1413 // 1st message from a new peer switches the slot
1414 // to counting state and resets the counts of all peers +
1415 // MAX_MESSAGE_THRESHOLD + 1 messages to reach the threshold
1416 // and switch the slot's state to peer selection.
1417 for (int m = 1; m <= reduce_relay::kMaxMessageThreshold + 2; m++)
1418 {
1419 for (int peer = 0; peer < npeers; peer++)
1420 {
1421 // make unique message hash to make the
1422 // slot's internal hash router accept the message
1423 std::uint64_t const mid = (m * 1000) + peer;
1424 UInt256 const message{mid};
1425 slots.updateSlotAndSquelch(
1426 message, validator, peer, protocol::MessageType::mtVALIDATION);
1427 }
1428 }
1429 // make Slot's internal hash router expire all messages
1431 };
1432
1433 using namespace reduce_relay;
1434 // expect max duration less than kMaxUnsquelchExpireDefault with
1435 // less than or equal to 60 peers
1436 run(20);
1437 BEAST_EXPECT(
1438 handler.maxDuration >= kMinUnsquelchExpire.count() &&
1439 handler.maxDuration <= kMaxUnsquelchExpireDefault.count());
1440 run(60);
1441 BEAST_EXPECT(
1442 handler.maxDuration >= kMinUnsquelchExpire.count() &&
1443 handler.maxDuration <= kMaxUnsquelchExpireDefault.count());
1444 // expect max duration greater than kMinUnsquelchExpire and less
1445 // than kMaxUnsquelchExpirePeers with peers greater than 60
1446 // and less than 360
1447 run(350);
1448 // can't make this condition stronger. squelch
1449 // duration is probabilistic and max condition may still fail.
1450 // log when the value is low
1451 BEAST_EXPECT(
1452 handler.maxDuration >= kMinUnsquelchExpire.count() &&
1453 handler.maxDuration <= kMaxUnsquelchExpirePeers.count());
1454 using namespace beast::unit_test::detail;
1455 if (handler.maxDuration <= kMaxUnsquelchExpireDefault.count())
1456 {
1457 log << makeReason("warning: squelch duration is low", __FILE__, __LINE__)
1458 << std::endl
1459 << std::flush;
1460 }
1461 // more than 400 is still less than kMaxUnsquelchExpirePeers
1462 run(400);
1463 BEAST_EXPECT(
1464 handler.maxDuration >= kMinUnsquelchExpire.count() &&
1465 handler.maxDuration <= kMaxUnsquelchExpirePeers.count());
1466 if (handler.maxDuration <= kMaxUnsquelchExpireDefault.count())
1467 {
1468 log << makeReason("warning: squelch duration is low", __FILE__, __LINE__)
1469 << std::endl
1470 << std::flush;
1471 }
1472 });
1473 }
1474
1475 void
1476 testHandshake(bool log)
1477 {
1478 doTest("Handshake", log, [&](bool log) {
1479 auto setEnv = [&](bool enable) {
1480 Config c;
1481 std::stringstream str;
1482 str << "[reduce_relay]\n"
1483 << "vp_enable=" << enable << "\n"
1484 << "[compression]\n"
1485 << "1\n";
1486 c.loadFromString(str.str());
1487 env_.app().config().vpReduceRelayBaseSquelchEnable =
1488 c.vpReduceRelayBaseSquelchEnable;
1489
1490 env_.app().config().compression = c.compression;
1491 };
1492 auto handshake = [&](int outboundEnable, int inboundEnable) {
1493 beast::ip::Address const addr = boost::asio::ip::make_address("172.1.1.100");
1494
1495 setEnv(outboundEnable);
1496 auto request = xrpl::makeRequest(
1497 true,
1498 env_.app().config().compression,
1499 false,
1500 env_.app().config().txReduceRelayEnable,
1501 env_.app().config().vpReduceRelayBaseSquelchEnable);
1502 HttpRequestType httpRequest;
1503 httpRequest.version(request.version());
1504 httpRequest.base() = request.base();
1505 // feature enabled on the peer's connection only if both sides
1506 // are enabled
1507 auto const peerEnabled = inboundEnable && outboundEnable;
1508 // inbound is enabled if the request's header has the feature
1509 // enabled and the peer's configuration is enabled
1510 auto const inboundEnabled =
1511 peerFeatureEnabled(httpRequest, kFeatureVprr, inboundEnable);
1512 BEAST_EXPECT(!(peerEnabled ^ inboundEnabled));
1513
1514 setEnv(inboundEnable);
1515 auto httpResp = xrpl::makeResponse(
1516 true, httpRequest, addr, addr, UInt256{1}, 1, {1, 0}, env_.app());
1517 // outbound is enabled if the response's header has the feature
1518 // enabled and the peer's configuration is enabled
1519 auto const outboundEnabled =
1520 peerFeatureEnabled(httpResp, kFeatureVprr, outboundEnable);
1521 BEAST_EXPECT(!(peerEnabled ^ outboundEnabled));
1522 };
1523 handshake(1, 1);
1524 handshake(1, 0);
1525 handshake(0, 1);
1526 handshake(0, 0);
1527 });
1528 }
1529
1530 jtx::Env env_;
1531 Network network_;
1532
1533public:
1534 reduce_relay_test()
1535 : env_(*this, jtx::envconfig([](std::unique_ptr<Config> cfg) {
1536 cfg->vpReduceRelayBaseSquelchEnable = true;
1537 cfg->vpReduceRelaySquelchMaxSelectedPeers = 6;
1538 return cfg;
1539 }))
1540 , network_(env_.app())
1541 {
1542 }
1543
1544 void
1545 run() override
1546 {
1547 bool const log = false;
1548 testConfig(log);
1549 testInitialRound(log);
1550 testPeerUnsquelchedTooSoon(log);
1551 testPeerUnsquelched(log);
1552 testNewPeer(log);
1553 testSquelchedPeerDisconnects(log);
1554 testSelectedPeerDisconnects(log);
1555 testSelectedPeerStopsRelaying(log);
1556 testInternalHashRouter(log);
1557 testRandomSquelch(log);
1558 testHandshake(log);
1559 testBaseSquelchReady(log);
1560 }
1561};
1562
1564{
1565 void
1567 {
1568 doTest("Random Test", log, [&](bool log) { random(log); });
1569 }
1570
1571 void
1572 run() override
1573 {
1574 bool const log = false;
1575 testRandom(log);
1576 }
1577};
1578
1580BEAST_DEFINE_TESTSUITE_MANUAL(reduce_relay_simulate, overlay, xrpl);
1581
1582} // namespace xrpl::test
T any_of(T... args)
T back_inserter(T... args)
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
A testsuite class.
Definition suite.h:52
LogOs< char > log
Logging output stream.
Definition suite.h:150
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
std::size_t vpReduceRelaySquelchMaxSelectedPeers
////////////////// !
void loadFromString(std::string const &fileContents)
Load the config from the contents of the string.
std::uint32_t ID
Uniquely identifies a peer.
A public key.
Definition PublicKey.h:53
std::uint8_t const * data() const noexcept
Definition PublicKey.h:79
static std::size_t size() noexcept
Definition PublicKey.h:85
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.
Slots is a container for validator's Slot and handles Slot update when a message is received from a v...
Maintains squelching of relaying messages from validators.
Definition Squelch.h:19
Manually advanced clock.
static void randAdvance(milliseconds min, milliseconds max)
static duration randDuration(milliseconds min, milliseconds max)
static void advance(duration d) noexcept
std::chrono::time_point< ManualClock > time_point
static void reset() noexcept
static time_point now() noexcept
std::chrono::duration< std::uint32_t, period > duration
void onDisconnectPeer(Peer::ID peer)
static void forRand(std::uint32_t min, std::uint32_t max, std::function< void(std::uint32_t)> f)
bool isSelected(Peer::ID id)
Is peer in Selected state in any of the slots.
void propagate(LinkIterCB link, std::uint16_t nValidators=kMaxValidators, std::uint32_t nMessages=kMaxMessages, bool purge=true, bool resetClock=true)
std::vector< Validator > validators_
Network(Application &app)
bool allCounting(Peer::ID peer)
Check if there are peers to unsquelch - peer is in Selected state in any of the slots and there are p...
void enableLink(std::uint16_t validatorId, Peer::ID peer, bool enable)
Validator & validator(std::uint16_t v)
std::set< ID > getSelected(PublicKey const &validator)
std::uint16_t getNumPeers() const
void updateSlotAndSquelch(UInt256 const &key, PublicKey const &validator, Peer::ID id, SquelchCB f, protocol::MessageType type=protocol::mtVALIDATION) override
PeerSPtr addPeer(bool useCache=true)
bool isSelected(PublicKey const &validator, Peer::ID peer)
ID getSelectedPeer(PublicKey const &validator)
std::unordered_map< ID, std::tuple< reduce_relay::PeerState, std::uint16_t, std::uint32_t, std::uint32_t > > getPeers(PublicKey const &validator)
std::optional< Peer::ID > deleteLastPeer()
std::unordered_map< Peer::ID, PeerSPtr > Peers
void deleteIdlePeers(UnsquelchCB f) override
std::uint16_t inState(PublicKey const &validator, reduce_relay::PeerState state)
bool isCountingState(PublicKey const &validator)
~OverlaySim() override=default
void deletePeer(ID id, UnsquelchCB f) override
reduce_relay::Slots< ManualClock > slots_
void deletePeer(Peer::ID id, bool useCache=true)
void unsquelch(PublicKey const &validator, Peer::ID id) const override
Unsquelch handler.
void squelch(PublicKey const &validator, Peer::ID id, std::uint32_t squelchDuration) const override
Squelch handler.
Simulate server's OverlayImpl.
virtual void deleteIdlePeers(UnsquelchCB)=0
virtual void updateSlotAndSquelch(UInt256 const &key, PublicKey const &validator, Peer::ID id, SquelchCB f, protocol::MessageType type=protocol::mtVALIDATION)=0
virtual ~Overlay()=default
virtual void deletePeer(Peer::ID, UnsquelchCB)=0
Simulate two entities - peer directly connected to the server (via squelch in PeerSim) and PeerImp (v...
virtual void onMessage(protocol::TMSquelch const &squelch)=0
virtual void onMessage(MessageSPtr const &m, SquelchCB f)=0
void send(protocol::TMSquelch const &squelch)
~PeerSim() override=default
void onMessage(protocol::TMSquelch const &squelch) override
Remote Peer (Directly connected Peer).
reduce_relay::Squelch< ManualClock > squelch_
void onMessage(MessageSPtr const &m, SquelchCB f) override
Local Peer (PeerImp).
PeerSim(Overlay &overlay, beast::Journal journal)
ID id() const override
Definition PeerStub.h:75
void send(std::shared_ptr< Message > const &) override
Definition PeerStub.h:44
void addPeer(PeerSPtr peer)
Validator & operator=(Validator &&)=default
void send(SquelchCB f)
Send to all peers.
void forLinks(LinkIterCB f, bool simulateSlow=false)
Validator(Validator const &)=default
Validator & operator=(Validator const &)=default
void send(std::vector< Peer::ID > peers, SquelchCB f)
Send to specific peers.
void deletePeer(Peer::ID id)
std::uint16_t id() const
std::unordered_map< Peer::ID, LinkSPtr > Links
Validator(Validator &&)=default
static std::uint16_t sid
PublicKey const & key()
void forLinks(std::vector< Peer::ID > peers, LinkIterCB f)
void printPeers(std::string const &msg, std::uint16_t validator=0)
void doTest(std::string const &msg, bool log, std::function< void(bool)> f)
bool propagateNoSquelch(bool log, std::uint16_t nMessages, bool countingState, bool purge=true, bool resetClock=true)
Send fewer message so that squelch event is not generated.
bool propagateAndSquelch(bool log, bool purge=true, bool resetClock=true)
Propagate enough messages to generate one squelch event.
static Peer::ID sendSquelch(PublicKey const &validator, PeerWPtr const &peerPtr, std::optional< std::uint32_t > duration)
Send squelch (if duration is set) or unsquelch (if duration not set).
void random(bool log)
Randomly brings the link between a validator and a peer down.
void testSquelchedPeerDisconnects(bool log)
Squelched peer disconnects.
void testPeerUnsquelchedTooSoon(bool log)
Receiving message from squelched peer too soon should not change the slot's state to Counting.
reduce_relay::Slot< ManualClock > Slot
void testInitialRound(bool log)
Initial counting round: three peers receive message "faster" then others.
void testSelectedPeerStopsRelaying(bool log)
Selected peer stops relaying.
void testSelectedPeerDisconnects(bool log)
Selected peer disconnects.
bool checkCounting(PublicKey const &validator, bool isCountingState)
void testPeerUnsquelched(bool log)
Receiving message from squelched peer should change the slot's state to Counting.
void testNewPeer(bool log)
Receiving a message from new peer should change the slot's state to Counting.
T end(T... args)
T endl(T... args)
T find_if(T... args)
T flush(T... args)
T iota(T... args)
T lock(T... args)
T log(T... args)
T make_pair(T... args)
T make_shared(T... args)
T max(T... args)
T min(T... args)
boost::asio::ip::address Address
Definition IPAddress.h:20
static std::string makeReason(String const &reason, char const *file, int line)
Definition suite.h:24
STL namespace.
static constexpr uint16_t kMaxMessageThreshold
static constexpr auto kWaitOnBootup
static constexpr auto kIdled
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:38
std::unique_ptr< Config > validator(std::unique_ptr< Config >, std::string const &)
adjust configuration with params needed to be a validator
static constexpr std::uint32_t kMaxMessages
std::function< void(Link &, MessageSPtr)> LinkIterCB
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
std::shared_ptr< Peer > PeerSPtr
static constexpr std::uint32_t kMaxPeers
BEAST_DEFINE_TESTSUITE_MANUAL(AMMCalc, app, xrpl)
std::shared_ptr< Message > MessageSPtr
static constexpr std::uint32_t kMaxValidators
std::shared_ptr< Link > LinkSPtr
std::weak_ptr< Peer > PeerWPtr
std::function< void(PublicKey const &, PeerWPtr const &)> UnsquelchCB
std::function< void(PublicKey const &, PeerWPtr const &, std::uint32_t)> SquelchCB
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
KeyType
Definition KeyType.h:8
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
int run(int argc, char **argv)
Definition Main.cpp:354
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
HttpResponseType makeResponse(bool crawlPublic, HttpRequestType const &req, beast::ip::Address publicIp, beast::ip::Address remoteIp, UInt256 const &sharedValue, std::optional< std::uint32_t > networkID, ProtocolVersion protocol, Application &app)
Make http response.
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> RequestType
Make outbound http request.
BaseUInt< 256 > UInt256
Definition base_uint.h:580
boost::beast::http::request< boost::beast::http::dynamic_body > HttpRequestType
Definition Handoff.h:12
bool peerFeatureEnabled(Headers const &request, std::string const &feature, std::string value, bool config)
Check if a feature should be enabled for a peer.
Definition Handshake.h:182
constexpr Number squelch(Number const &x, Number const &limit) noexcept
Definition Number.h:907
static constexpr char kFeatureVprr[]
Definition Handshake.h:127
T dynamic_pointer_cast(T... args)
T shuffle(T... args)
T size(T... args)
T str(T... args)
T transform(T... args)
T what(T... args)