xrpld
Loading...
Searching...
No Matches
PeerFinder_test.cpp
1#include <test/unit_test/SuiteJournal.h>
2
3#include <xrpld/core/Config.h>
4#include <xrpld/peerfinder/PeerfinderManager.h>
5#include <xrpld/peerfinder/detail/Counts.h>
6#include <xrpld/peerfinder/detail/Logic.h>
7#include <xrpld/peerfinder/detail/Store.h>
8
9#include <xrpl/basics/chrono.h>
10#include <xrpl/beast/net/IPEndpoint.h>
11#include <xrpl/beast/unit_test/suite.h>
12#include <xrpl/protocol/KeyType.h>
13#include <xrpl/protocol/PublicKey.h>
14#include <xrpl/protocol/SecretKey.h>
15
16#include <boost/system/detail/error_code.hpp>
17
18#include <chrono>
19#include <cstddef>
20#include <cstdint>
21#include <optional>
22#include <stdexcept>
23#include <string>
24#include <vector>
25
26namespace xrpl::PeerFinder {
27
29{
31
32public:
33 PeerFinder_test() : journal_("PeerFinder_test", *this)
34 {
35 }
36
38 {
40 load(load_callback const& cb) override
41 {
42 return 0;
43 }
44
45 void
46 save(std::vector<Entry> const&) override
47 {
48 }
49 };
50
52 {
53 void
55 {
56 }
57
58 void
60 {
61 }
62
63 template <class Handler>
64 void
65 asyncConnect(beast::IP::Endpoint const& ep, Handler&& handler)
66 {
67 boost::system::error_code ec;
68 handler(ep, ep, ec);
69 }
70 };
71
72 void
74 {
75 auto const seconds = 10000;
76 testcase("backoff 1");
77 TestStore store;
78 TestChecker checker;
79 TestStopwatch clock;
80 Logic<TestChecker> logic(clock, store, checker, journal_);
81 logic.addFixedPeer("test", beast::IP::Endpoint::fromString("65.0.0.1:5"));
82 {
83 Config c;
84 c.autoConnect = false;
85 c.listeningPort = 1024;
86 logic.config(c);
87 }
88 std::size_t n = 0;
89 for (std::size_t i = 0; i < seconds; ++i)
90 {
91 auto const list = logic.autoconnect();
92 if (!list.empty())
93 {
94 BEAST_EXPECT(list.size() == 1);
95 auto const [slot, _] = logic.newOutboundSlot(list.front());
96 BEAST_EXPECT(
97 logic.onConnected(slot, beast::IP::Endpoint::fromString("65.0.0.2:5")));
98 logic.onClosed(slot);
99 ++n;
100 }
101 clock.advance(std::chrono::seconds(1));
102 logic.oncePerSecond();
103 }
104 // Less than 20 attempts
105 BEAST_EXPECT(n < 20);
106 }
107
108 // with activate
109 void
111 {
112 auto const seconds = 10000;
113 testcase("backoff 2");
114 TestStore store;
115 TestChecker checker;
116 TestStopwatch clock;
117 Logic<TestChecker> logic(clock, store, checker, journal_);
118 logic.addFixedPeer("test", beast::IP::Endpoint::fromString("65.0.0.1:5"));
119 {
120 Config c;
121 c.autoConnect = false;
122 c.listeningPort = 1024;
123 logic.config(c);
124 }
125
127 std::size_t n = 0;
128
129 for (std::size_t i = 0; i < seconds; ++i)
130 {
131 auto const list = logic.autoconnect();
132 if (!list.empty())
133 {
134 BEAST_EXPECT(list.size() == 1);
135 auto const [slot, _] = logic.newOutboundSlot(list.front());
136 if (!BEAST_EXPECT(
137 logic.onConnected(slot, beast::IP::Endpoint::fromString("65.0.0.2:5"))))
138 return;
139 if (!BEAST_EXPECT(logic.activate(slot, pk, false) == PeerFinder::Result::Success))
140 return;
141 logic.onClosed(slot);
142 ++n;
143 }
144 clock.advance(std::chrono::seconds(1));
145 logic.oncePerSecond();
146 }
147 // No more often than once per minute
148 BEAST_EXPECT(n <= (seconds + 59) / 60);
149 }
150
151 // test accepting an incoming slot for an already existing outgoing slot
152 void
154 {
155 testcase("duplicate out/in");
156 TestStore store;
157 TestChecker checker;
158 TestStopwatch clock;
159 Logic<TestChecker> logic(clock, store, checker, journal_);
160 {
161 Config c;
162 c.autoConnect = false;
163 c.listeningPort = 1024;
164 c.ipLimit = 2;
165 logic.config(c);
166 }
167
168 auto const remote = beast::IP::Endpoint::fromString("65.0.0.1:5");
169 auto const [slot1, r] = logic.newOutboundSlot(remote);
170 BEAST_EXPECT(slot1 != nullptr);
171 BEAST_EXPECT(r == Result::Success);
172 BEAST_EXPECT(logic.connectedAddresses.count(remote.address()) == 1);
173
174 auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
175 auto const [slot2, r2] = logic.newInboundSlot(local, remote);
176 BEAST_EXPECT(logic.connectedAddresses.count(remote.address()) == 1);
177 BEAST_EXPECT(r2 == Result::DuplicatePeer);
178
179 if (!BEAST_EXPECT(slot2 == nullptr))
180 logic.onClosed(slot2);
181
182 logic.onClosed(slot1);
183 }
184
185 // test establishing outgoing slot for an already existing incoming slot
186 void
188 {
189 testcase("duplicate in/out");
190 TestStore store;
191 TestChecker checker;
192 TestStopwatch clock;
193 Logic<TestChecker> logic(clock, store, checker, journal_);
194 {
195 Config c;
196 c.autoConnect = false;
197 c.listeningPort = 1024;
198 c.ipLimit = 2;
199 logic.config(c);
200 }
201
202 auto const remote = beast::IP::Endpoint::fromString("65.0.0.1:5");
203 auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
204
205 auto const [slot1, r] = logic.newInboundSlot(local, remote);
206 BEAST_EXPECT(slot1 != nullptr);
207 BEAST_EXPECT(r == Result::Success);
208 BEAST_EXPECT(logic.connectedAddresses.count(remote.address()) == 1);
209
210 auto const [slot2, r2] = logic.newOutboundSlot(remote);
211 BEAST_EXPECT(r2 == Result::DuplicatePeer);
212 BEAST_EXPECT(logic.connectedAddresses.count(remote.address()) == 1);
213 if (!BEAST_EXPECT(slot2 == nullptr))
214 logic.onClosed(slot2);
215 logic.onClosed(slot1);
216 }
217
218 void
220 {
221 testcase("peer limit exceeded");
222 TestStore store;
223 TestChecker checker;
224 TestStopwatch clock;
225 Logic<TestChecker> logic(clock, store, checker, journal_);
226 {
227 Config c;
228 c.autoConnect = false;
229 c.listeningPort = 1024;
230 c.ipLimit = 2;
231 logic.config(c);
232 }
233
234 auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
235 auto const [slot, r] =
236 logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1025"));
237 BEAST_EXPECT(slot != nullptr);
238 BEAST_EXPECT(r == Result::Success);
239
240 auto const [slot1, r1] =
241 logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1026"));
242 BEAST_EXPECT(slot1 != nullptr);
243 BEAST_EXPECT(r1 == Result::Success);
244
245 auto const [slot2, r2] =
246 logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1027"));
247 BEAST_EXPECT(r2 == Result::IpLimitExceeded);
248
249 if (!BEAST_EXPECT(slot2 == nullptr))
250 logic.onClosed(slot2);
251 logic.onClosed(slot1);
252 logic.onClosed(slot);
253 }
254
255 void
257 {
258 testcase("test activate duplicate peer");
259 TestStore store;
260 TestChecker checker;
261 TestStopwatch clock;
262 Logic<TestChecker> logic(clock, store, checker, journal_);
263 {
264 Config c;
265 c.autoConnect = false;
266 c.listeningPort = 1024;
267 c.ipLimit = 2;
268 logic.config(c);
269 }
270
271 auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
272
274
275 auto const [slot, rSlot] =
276 logic.newOutboundSlot(beast::IP::Endpoint::fromString("55.104.0.2:1025"));
277 BEAST_EXPECT(slot != nullptr);
278 BEAST_EXPECT(rSlot == Result::Success);
279
280 auto const [slot2, r2Slot] =
281 logic.newOutboundSlot(beast::IP::Endpoint::fromString("55.104.0.2:1026"));
282 BEAST_EXPECT(slot2 != nullptr);
283 BEAST_EXPECT(r2Slot == Result::Success);
284
285 BEAST_EXPECT(logic.onConnected(slot, local));
286 BEAST_EXPECT(logic.onConnected(slot2, local));
287
288 BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::Success);
289
290 // activating a different slot with the same node ID (pk) must fail
291 BEAST_EXPECT(logic.activate(slot2, pk1, false) == Result::DuplicatePeer);
292
293 logic.onClosed(slot);
294
295 // accept the same key for a new slot after removing the old slot
296 BEAST_EXPECT(logic.activate(slot2, pk1, false) == Result::Success);
297 logic.onClosed(slot2);
298 }
299
300 void
302 {
303 testcase("test activate inbound disabled");
304 TestStore store;
305 TestChecker checker;
306 TestStopwatch clock;
307 Logic<TestChecker> logic(clock, store, checker, journal_);
308 {
309 Config c;
310 c.autoConnect = false;
311 c.listeningPort = 1024;
312 c.ipLimit = 2;
313 logic.config(c);
314 }
315
317 auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
318
319 auto const [slot, rSlot] =
320 logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1025"));
321 BEAST_EXPECT(slot != nullptr);
322 BEAST_EXPECT(rSlot == Result::Success);
323
324 BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::InboundDisabled);
325
326 {
327 Config c;
328 c.autoConnect = false;
329 c.listeningPort = 1024;
330 c.ipLimit = 2;
331 c.inPeers = 1;
332 logic.config(c);
333 }
334 // new inbound slot must succeed when inbound connections are enabled
335 BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::Success);
336
337 // creating a new inbound slot must succeed as IP Limit is not exceeded
338 auto const [slot2, r2Slot] =
339 logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1026"));
340 BEAST_EXPECT(slot2 != nullptr);
341 BEAST_EXPECT(r2Slot == Result::Success);
342
344
345 // an inbound slot exceeding inPeers limit must fail
346 BEAST_EXPECT(logic.activate(slot2, pk2, false) == Result::Full);
347
348 logic.onClosed(slot2);
349 logic.onClosed(slot);
350 }
351
352 void
354 {
355 testcase("test addFixedPeer no port");
356 TestStore store;
357 TestChecker checker;
358 TestStopwatch clock;
359 Logic<TestChecker> logic(clock, store, checker, journal_);
360 try
361 {
362 logic.addFixedPeer("test", beast::IP::Endpoint::fromString("65.0.0.2"));
363 fail("invalid endpoint successfully added");
364 }
365 catch (std::runtime_error const& e)
366 {
367 pass();
368 }
369 }
370
371 void
373 {
374 testcase("test onConnected self connection");
375 TestStore store;
376 TestChecker checker;
377 TestStopwatch clock;
378 Logic<TestChecker> logic(clock, store, checker, journal_);
379
380 auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1234");
381 auto const [slot, r] = logic.newOutboundSlot(local);
382 BEAST_EXPECT(slot != nullptr);
383 BEAST_EXPECT(r == Result::Success);
384
385 // Must fail when a slot is to our own IP address
386 BEAST_EXPECT(!logic.onConnected(slot, local));
387 logic.onClosed(slot);
388 }
389
390 void
392 {
393 // if peers_max is configured then peers_in_max and peers_out_max
394 // are ignored
395 auto run = [&](std::string const& test,
399 std::uint16_t port,
400 std::uint16_t expectOut,
401 std::uint16_t expectIn,
402 std::uint16_t expectIpLimit) {
403 xrpl::Config c;
404
405 testcase(test);
406
407 std::string toLoad;
408 int max = 0;
409 if (maxPeers)
410 {
411 max = maxPeers.value();
412 toLoad += "[peers_max]\n" + std::to_string(max) + "\n" + "[peers_in_max]\n" +
413 std::to_string(maxIn.value_or(0)) + "\n" + "[peers_out_max]\n" +
414 std::to_string(maxOut.value_or(0)) + "\n";
415 }
416 else if (maxIn && maxOut)
417 {
418 toLoad += "[peers_in_max]\n" + std::to_string(*maxIn) + "\n" + "[peers_out_max]\n" +
419 std::to_string(*maxOut) + "\n";
420 }
421
422 c.loadFromString(toLoad);
423 BEAST_EXPECT(
424 (c.peersMax == max && c.peersInMax == 0 && c.peersOutMax == 0) ||
425 (c.peersInMax == *maxIn && c.peersOutMax == *maxOut));
426
427 Config const config = Config::makeConfig(c, port, false, 0, true);
428
429 Counts counts;
430 counts.onConfig(config);
431 BEAST_EXPECT(
432 counts.outMax() == expectOut && counts.inMax() == expectIn &&
433 config.ipLimit == expectIpLimit);
434
435 TestStore store;
436 TestChecker checker;
437 TestStopwatch clock;
438 Logic<TestChecker> logic(clock, store, checker, journal_);
439 logic.config(config);
440
441 BEAST_EXPECT(logic.config() == config);
442 };
443
444 // if max_peers == 0 => maxPeers = 21,
445 // else if max_peers < 10 => maxPeers = 10 else maxPeers =
446 // max_peers
447 // expectOut => if legacy => max(0.15 * maxPeers, 10),
448 // if legacy && !wantIncoming => maxPeers else max_out_peers
449 // expectIn => if legacy && wantIncoming => maxPeers - outPeers
450 // else if !wantIncoming => 0 else max_in_peers
451 // ipLimit => if expectIn <= 21 => 2 else 2 + min(5, expectIn/21)
452 // ipLimit = max(1, min(ipLimit, expectIn/2))
453
454 // legacy test with max_peers
455 run("legacy no config", {}, {}, {}, 4000, 10, 11, 2);
456 run("legacy max_peers 0", 0, 100, 10, 4000, 10, 11, 2);
457 run("legacy max_peers 5", 5, 100, 10, 4000, 10, 0, 1);
458 run("legacy max_peers 20", 20, 100, 10, 4000, 10, 10, 2);
459 run("legacy max_peers 100", 100, 100, 10, 4000, 15, 85, 6);
460 run("legacy max_peers 20, private", 20, 100, 10, 0, 20, 0, 1);
461
462 // test with max_in_peers and max_out_peers
463 run("new in 100/out 10", {}, 100, 10, 4000, 10, 100, 6);
464 run("new in 0/out 10", {}, 0, 10, 4000, 10, 0, 1);
465 run("new in 100/out 10, private", {}, 100, 10, 0, 10, 0, 6);
466 }
467
468 void
470 {
471 testcase("invalid config");
472
473 auto run = [&](std::string const& toLoad) {
474 xrpl::Config c;
475 try
476 {
477 c.loadFromString(toLoad);
478 fail();
479 }
480 catch (...)
481 {
482 pass();
483 }
484 };
485 run(R"xrpldConfig(
486[peers_in_max]
487100
488)xrpldConfig");
489 run(R"xrpldConfig(
490[peers_out_max]
491100
492)xrpldConfig");
493 run(R"xrpldConfig(
494[peers_in_max]
495100
496[peers_out_max]
4975
498)xrpldConfig");
499 run(R"xrpldConfig(
500[peers_in_max]
5011001
502[peers_out_max]
50310
504)xrpldConfig");
505 run(R"xrpldConfig(
506[peers_in_max]
50710
508[peers_out_max]
5091001
510)xrpldConfig");
511 }
512
513 void
514 run() override
515 {
516 testBackoff1();
517 testBackoff2();
520 testConfig();
527 }
528};
529
531
532} // namespace xrpl::PeerFinder
A version-independent IP address and port combination.
Definition IPEndpoint.h:17
static Endpoint fromString(std::string const &s)
A testsuite class.
Definition suite.h:50
void pass()
Record a successful test condition.
Definition suite.h:500
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:522
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
std::size_t peersMax
Definition Config.h:165
std::size_t peersOutMax
Definition Config.h:166
void loadFromString(std::string const &fileContents)
Load the config from the contents of the string.
Definition Config.cpp:473
std::size_t peersInMax
Definition Config.h:167
Manages the count of available connections for the various slots.
Definition Counts.h:16
int outMax() const
Returns the total number of outbound slots.
Definition Counts.h:68
int inMax() const
Returns the total number of inbound slots.
Definition Counts.h:130
void onConfig(Config const &config)
Called when the config is set or changed.
Definition Counts.h:100
The Logic for maintaining the list of Slot addresses.
void addFixedPeer(std::string_view name, beast::IP::Endpoint const &ep)
std::pair< SlotImp::ptr, Result > newOutboundSlot(beast::IP::Endpoint const &remoteEndpoint)
std::pair< SlotImp::ptr, Result > newInboundSlot(beast::IP::Endpoint const &localEndpoint, beast::IP::Endpoint const &remoteEndpoint)
Result activate(SlotImp::ptr const &slot, PublicKey const &key, bool reserved)
std::multiset< beast::IP::Address > connectedAddresses
std::vector< beast::IP::Endpoint > autoconnect()
Create new outbound connection attempts as needed.
void onClosed(SlotImp::ptr const &slot)
bool onConnected(SlotImp::ptr const &slot, beast::IP::Endpoint const &localEndpoint)
void run() override
Runs the suite.
Abstract persistence for PeerFinder data.
Definition Store.h:7
std::function< void(beast::IP::Endpoint, int)> load_callback
Definition Store.h:12
A public key.
Definition PublicKey.h:42
T count(T... args)
BEAST_DEFINE_TESTSUITE(Livecache, peerfinder, xrpl)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
beast::ManualClock< std::chrono::steady_clock > TestStopwatch
A manual Stopwatch for unit tests.
Definition chrono.h:90
PeerFinder configuration settings.
int ipLimit
Limit how many incoming connections we allow per IP.
bool autoConnect
true if we want to establish connections automatically
std::size_t inPeers
The number of automatic inbound connections to maintain.
std::uint16_t listeningPort
The listening port number.
static Config makeConfig(xrpl::Config const &config, std::uint16_t port, bool validationPublicKey, int ipLimit, bool verifyEndpoints)
Make PeerFinder::Config from configuration parameters.
void asyncConnect(beast::IP::Endpoint const &ep, Handler &&handler)
void save(std::vector< Entry > const &) override
std::size_t load(load_callback const &cb) override
T to_string(T... args)