rippled
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/Logic.h>
6
7#include <xrpl/basics/chrono.h>
8#include <xrpl/beast/unit_test/suite.h>
9#include <xrpl/protocol/PublicKey.h>
10#include <xrpl/protocol/SecretKey.h>
11
12namespace xrpl {
13namespace PeerFinder {
14
16{
18
19public:
20 PeerFinder_test() : journal_("PeerFinder_test", *this)
21 {
22 }
23
25 {
27 load(load_callback const& cb) override
28 {
29 return 0;
30 }
31
32 void
33 save(std::vector<Entry> const&) override
34 {
35 }
36 };
37
39 {
40 void
42 {
43 }
44
45 void
47 {
48 }
49
50 template <class Handler>
51 void
52 async_connect(beast::IP::Endpoint const& ep, Handler&& handler)
53 {
54 boost::system::error_code ec;
55 handler(ep, ep, ec);
56 }
57 };
58
59 void
61 {
62 auto const seconds = 10000;
63 testcase("backoff 1");
64 TestStore store;
65 TestChecker checker;
66 TestStopwatch clock;
67 Logic<TestChecker> logic(clock, store, checker, journal_);
68 logic.addFixedPeer("test", beast::IP::Endpoint::from_string("65.0.0.1:5"));
69 {
70 Config c;
71 c.autoConnect = false;
72 c.listeningPort = 1024;
73 logic.config(c);
74 }
75 std::size_t n = 0;
76 for (std::size_t i = 0; i < seconds; ++i)
77 {
78 auto const list = logic.autoconnect();
79 if (!list.empty())
80 {
81 BEAST_EXPECT(list.size() == 1);
82 auto const [slot, _] = logic.new_outbound_slot(list.front());
83 BEAST_EXPECT(
84 logic.onConnected(slot, beast::IP::Endpoint::from_string("65.0.0.2:5")));
85 logic.on_closed(slot);
86 ++n;
87 }
88 clock.advance(std::chrono::seconds(1));
89 logic.once_per_second();
90 }
91 // Less than 20 attempts
92 BEAST_EXPECT(n < 20);
93 }
94
95 // with activate
96 void
98 {
99 auto const seconds = 10000;
100 testcase("backoff 2");
101 TestStore store;
102 TestChecker checker;
103 TestStopwatch clock;
104 Logic<TestChecker> logic(clock, store, checker, journal_);
105 logic.addFixedPeer("test", beast::IP::Endpoint::from_string("65.0.0.1:5"));
106 {
107 Config c;
108 c.autoConnect = false;
109 c.listeningPort = 1024;
110 logic.config(c);
111 }
112
114 std::size_t n = 0;
115
116 for (std::size_t i = 0; i < seconds; ++i)
117 {
118 auto const list = logic.autoconnect();
119 if (!list.empty())
120 {
121 BEAST_EXPECT(list.size() == 1);
122 auto const [slot, _] = logic.new_outbound_slot(list.front());
123 if (!BEAST_EXPECT(
124 logic.onConnected(slot, beast::IP::Endpoint::from_string("65.0.0.2:5"))))
125 return;
126 if (!BEAST_EXPECT(logic.activate(slot, pk, false) == PeerFinder::Result::success))
127 return;
128 logic.on_closed(slot);
129 ++n;
130 }
131 clock.advance(std::chrono::seconds(1));
132 logic.once_per_second();
133 }
134 // No more often than once per minute
135 BEAST_EXPECT(n <= (seconds + 59) / 60);
136 }
137
138 // test accepting an incoming slot for an already existing outgoing slot
139 void
141 {
142 testcase("duplicate out/in");
143 TestStore store;
144 TestChecker checker;
145 TestStopwatch clock;
146 Logic<TestChecker> logic(clock, store, checker, journal_);
147 {
148 Config c;
149 c.autoConnect = false;
150 c.listeningPort = 1024;
151 c.ipLimit = 2;
152 logic.config(c);
153 }
154
155 auto const remote = beast::IP::Endpoint::from_string("65.0.0.1:5");
156 auto const [slot1, r] = logic.new_outbound_slot(remote);
157 BEAST_EXPECT(slot1 != nullptr);
158 BEAST_EXPECT(r == Result::success);
159 BEAST_EXPECT(logic.connectedAddresses_.count(remote.address()) == 1);
160
161 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
162 auto const [slot2, r2] = logic.new_inbound_slot(local, remote);
163 BEAST_EXPECT(logic.connectedAddresses_.count(remote.address()) == 1);
164 BEAST_EXPECT(r2 == Result::duplicatePeer);
165
166 if (!BEAST_EXPECT(slot2 == nullptr))
167 logic.on_closed(slot2);
168
169 logic.on_closed(slot1);
170 }
171
172 // test establishing outgoing slot for an already existing incoming slot
173 void
175 {
176 testcase("duplicate in/out");
177 TestStore store;
178 TestChecker checker;
179 TestStopwatch clock;
180 Logic<TestChecker> logic(clock, store, checker, journal_);
181 {
182 Config c;
183 c.autoConnect = false;
184 c.listeningPort = 1024;
185 c.ipLimit = 2;
186 logic.config(c);
187 }
188
189 auto const remote = beast::IP::Endpoint::from_string("65.0.0.1:5");
190 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
191
192 auto const [slot1, r] = logic.new_inbound_slot(local, remote);
193 BEAST_EXPECT(slot1 != nullptr);
194 BEAST_EXPECT(r == Result::success);
195 BEAST_EXPECT(logic.connectedAddresses_.count(remote.address()) == 1);
196
197 auto const [slot2, r2] = logic.new_outbound_slot(remote);
198 BEAST_EXPECT(r2 == Result::duplicatePeer);
199 BEAST_EXPECT(logic.connectedAddresses_.count(remote.address()) == 1);
200 if (!BEAST_EXPECT(slot2 == nullptr))
201 logic.on_closed(slot2);
202 logic.on_closed(slot1);
203 }
204
205 void
207 {
208 testcase("peer limit exceeded");
209 TestStore store;
210 TestChecker checker;
211 TestStopwatch clock;
212 Logic<TestChecker> logic(clock, store, checker, journal_);
213 {
214 Config c;
215 c.autoConnect = false;
216 c.listeningPort = 1024;
217 c.ipLimit = 2;
218 logic.config(c);
219 }
220
221 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
222 auto const [slot, r] =
223 logic.new_inbound_slot(local, beast::IP::Endpoint::from_string("55.104.0.2:1025"));
224 BEAST_EXPECT(slot != nullptr);
225 BEAST_EXPECT(r == Result::success);
226
227 auto const [slot1, r1] =
228 logic.new_inbound_slot(local, beast::IP::Endpoint::from_string("55.104.0.2:1026"));
229 BEAST_EXPECT(slot1 != nullptr);
230 BEAST_EXPECT(r1 == Result::success);
231
232 auto const [slot2, r2] =
233 logic.new_inbound_slot(local, beast::IP::Endpoint::from_string("55.104.0.2:1027"));
234 BEAST_EXPECT(r2 == Result::ipLimitExceeded);
235
236 if (!BEAST_EXPECT(slot2 == nullptr))
237 logic.on_closed(slot2);
238 logic.on_closed(slot1);
239 logic.on_closed(slot);
240 }
241
242 void
244 {
245 testcase("test activate duplicate peer");
246 TestStore store;
247 TestChecker checker;
248 TestStopwatch clock;
249 Logic<TestChecker> logic(clock, store, checker, journal_);
250 {
251 Config c;
252 c.autoConnect = false;
253 c.listeningPort = 1024;
254 c.ipLimit = 2;
255 logic.config(c);
256 }
257
258 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
259
261
262 auto const [slot, rSlot] =
263 logic.new_outbound_slot(beast::IP::Endpoint::from_string("55.104.0.2:1025"));
264 BEAST_EXPECT(slot != nullptr);
265 BEAST_EXPECT(rSlot == Result::success);
266
267 auto const [slot2, r2Slot] =
268 logic.new_outbound_slot(beast::IP::Endpoint::from_string("55.104.0.2:1026"));
269 BEAST_EXPECT(slot2 != nullptr);
270 BEAST_EXPECT(r2Slot == Result::success);
271
272 BEAST_EXPECT(logic.onConnected(slot, local));
273 BEAST_EXPECT(logic.onConnected(slot2, local));
274
275 BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::success);
276
277 // activating a different slot with the same node ID (pk) must fail
278 BEAST_EXPECT(logic.activate(slot2, pk1, false) == Result::duplicatePeer);
279
280 logic.on_closed(slot);
281
282 // accept the same key for a new slot after removing the old slot
283 BEAST_EXPECT(logic.activate(slot2, pk1, false) == Result::success);
284 logic.on_closed(slot2);
285 }
286
287 void
289 {
290 testcase("test activate inbound disabled");
291 TestStore store;
292 TestChecker checker;
293 TestStopwatch clock;
294 Logic<TestChecker> logic(clock, store, checker, journal_);
295 {
296 Config c;
297 c.autoConnect = false;
298 c.listeningPort = 1024;
299 c.ipLimit = 2;
300 logic.config(c);
301 }
302
304 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
305
306 auto const [slot, rSlot] =
307 logic.new_inbound_slot(local, beast::IP::Endpoint::from_string("55.104.0.2:1025"));
308 BEAST_EXPECT(slot != nullptr);
309 BEAST_EXPECT(rSlot == Result::success);
310
311 BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::inboundDisabled);
312
313 {
314 Config c;
315 c.autoConnect = false;
316 c.listeningPort = 1024;
317 c.ipLimit = 2;
318 c.inPeers = 1;
319 logic.config(c);
320 }
321 // new inbound slot must succeed when inbound connections are enabled
322 BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::success);
323
324 // creating a new inbound slot must succeed as IP Limit is not exceeded
325 auto const [slot2, r2Slot] =
326 logic.new_inbound_slot(local, beast::IP::Endpoint::from_string("55.104.0.2:1026"));
327 BEAST_EXPECT(slot2 != nullptr);
328 BEAST_EXPECT(r2Slot == Result::success);
329
331
332 // an inbound slot exceeding inPeers limit must fail
333 BEAST_EXPECT(logic.activate(slot2, pk2, false) == Result::full);
334
335 logic.on_closed(slot2);
336 logic.on_closed(slot);
337 }
338
339 void
341 {
342 testcase("test addFixedPeer no port");
343 TestStore store;
344 TestChecker checker;
345 TestStopwatch clock;
346 Logic<TestChecker> logic(clock, store, checker, journal_);
347 try
348 {
349 logic.addFixedPeer("test", beast::IP::Endpoint::from_string("65.0.0.2"));
350 fail("invalid endpoint successfully added");
351 }
352 catch (std::runtime_error const& e)
353 {
354 pass();
355 }
356 }
357
358 void
360 {
361 testcase("test onConnected self connection");
362 TestStore store;
363 TestChecker checker;
364 TestStopwatch clock;
365 Logic<TestChecker> logic(clock, store, checker, journal_);
366
367 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1234");
368 auto const [slot, r] = logic.new_outbound_slot(local);
369 BEAST_EXPECT(slot != nullptr);
370 BEAST_EXPECT(r == Result::success);
371
372 // Must fail when a slot is to our own IP address
373 BEAST_EXPECT(!logic.onConnected(slot, local));
374 logic.on_closed(slot);
375 }
376
377 void
379 {
380 // if peers_max is configured then peers_in_max and peers_out_max
381 // are ignored
382 auto run = [&](std::string const& test,
386 std::uint16_t port,
387 std::uint16_t expectOut,
388 std::uint16_t expectIn,
389 std::uint16_t expectIpLimit) {
390 xrpl::Config c;
391
392 testcase(test);
393
394 std::string toLoad;
395 int max = 0;
396 if (maxPeers)
397 {
398 max = maxPeers.value();
399 toLoad += "[peers_max]\n" + std::to_string(max) + "\n" + "[peers_in_max]\n" +
400 std::to_string(maxIn.value_or(0)) + "\n" + "[peers_out_max]\n" +
401 std::to_string(maxOut.value_or(0)) + "\n";
402 }
403 else if (maxIn && maxOut)
404 {
405 toLoad += "[peers_in_max]\n" + std::to_string(*maxIn) + "\n" + "[peers_out_max]\n" +
406 std::to_string(*maxOut) + "\n";
407 }
408
409 c.loadFromString(toLoad);
410 BEAST_EXPECT(
411 (c.PEERS_MAX == max && c.PEERS_IN_MAX == 0 && c.PEERS_OUT_MAX == 0) ||
412 (c.PEERS_IN_MAX == *maxIn && c.PEERS_OUT_MAX == *maxOut));
413
414 Config const config = Config::makeConfig(c, port, false, 0);
415
416 Counts counts;
417 counts.onConfig(config);
418 BEAST_EXPECT(
419 counts.out_max() == expectOut && counts.in_max() == expectIn &&
420 config.ipLimit == expectIpLimit);
421
422 TestStore store;
423 TestChecker checker;
424 TestStopwatch clock;
425 Logic<TestChecker> logic(clock, store, checker, journal_);
426 logic.config(config);
427
428 BEAST_EXPECT(logic.config() == config);
429 };
430
431 // if max_peers == 0 => maxPeers = 21,
432 // else if max_peers < 10 => maxPeers = 10 else maxPeers =
433 // max_peers
434 // expectOut => if legacy => max(0.15 * maxPeers, 10),
435 // if legacy && !wantIncoming => maxPeers else max_out_peers
436 // expectIn => if legacy && wantIncoming => maxPeers - outPeers
437 // else if !wantIncoming => 0 else max_in_peers
438 // ipLimit => if expectIn <= 21 => 2 else 2 + min(5, expectIn/21)
439 // ipLimit = max(1, min(ipLimit, expectIn/2))
440
441 // legacy test with max_peers
442 run("legacy no config", {}, {}, {}, 4000, 10, 11, 2);
443 run("legacy max_peers 0", 0, 100, 10, 4000, 10, 11, 2);
444 run("legacy max_peers 5", 5, 100, 10, 4000, 10, 0, 1);
445 run("legacy max_peers 20", 20, 100, 10, 4000, 10, 10, 2);
446 run("legacy max_peers 100", 100, 100, 10, 4000, 15, 85, 6);
447 run("legacy max_peers 20, private", 20, 100, 10, 0, 20, 0, 1);
448
449 // test with max_in_peers and max_out_peers
450 run("new in 100/out 10", {}, 100, 10, 4000, 10, 100, 6);
451 run("new in 0/out 10", {}, 0, 10, 4000, 10, 0, 1);
452 run("new in 100/out 10, private", {}, 100, 10, 0, 10, 0, 6);
453 }
454
455 void
457 {
458 testcase("invalid config");
459
460 auto run = [&](std::string const& toLoad) {
461 xrpl::Config c;
462 try
463 {
464 c.loadFromString(toLoad);
465 fail();
466 }
467 catch (...)
468 {
469 pass();
470 }
471 };
472 run(R"rippleConfig(
473[peers_in_max]
474100
475)rippleConfig");
476 run(R"rippleConfig(
477[peers_out_max]
478100
479)rippleConfig");
480 run(R"rippleConfig(
481[peers_in_max]
482100
483[peers_out_max]
4845
485)rippleConfig");
486 run(R"rippleConfig(
487[peers_in_max]
4881001
489[peers_out_max]
49010
491)rippleConfig");
492 run(R"rippleConfig(
493[peers_in_max]
49410
495[peers_out_max]
4961001
497)rippleConfig");
498 }
499
500 void
515};
516
517BEAST_DEFINE_TESTSUITE(PeerFinder, peerfinder, xrpl);
518
519} // namespace PeerFinder
520} // namespace xrpl
A version-independent IP address and port combination.
Definition IPEndpoint.h:18
static Endpoint from_string(std::string const &s)
A testsuite class.
Definition suite.h:51
void pass()
Record a successful test condition.
Definition suite.h:497
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:519
std::size_t PEERS_IN_MAX
Definition Config.h:167
std::size_t PEERS_OUT_MAX
Definition Config.h:166
void loadFromString(std::string const &fileContents)
Load the config from the contents of the string.
Definition Config.cpp:452
std::size_t PEERS_MAX
Definition Config.h:165
Manages the count of available connections for the various slots.
Definition Counts.h:14
int out_max() const
Returns the total number of outbound slots.
Definition Counts.h:66
int in_max() const
Returns the total number of inbound slots.
Definition Counts.h:128
void onConfig(Config const &config)
Called when the config is set or changed.
Definition Counts.h:98
The Logic for maintaining the list of Slot addresses.
void on_closed(SlotImp::ptr const &slot)
void addFixedPeer(std::string const &name, beast::IP::Endpoint const &ep)
std::pair< SlotImp::ptr, Result > new_inbound_slot(beast::IP::Endpoint const &local_endpoint, beast::IP::Endpoint const &remote_endpoint)
Result activate(SlotImp::ptr const &slot, PublicKey const &key, bool reserved)
bool onConnected(SlotImp::ptr const &slot, beast::IP::Endpoint const &local_endpoint)
std::vector< beast::IP::Endpoint > autoconnect()
Create new outbound connection attempts as needed.
std::multiset< beast::IP::Address > connectedAddresses_
std::pair< SlotImp::ptr, Result > new_outbound_slot(beast::IP::Endpoint const &remote_endpoint)
void run() override
Runs the suite.
Abstract persistence for PeerFinder data.
Definition Store.h:8
A public key.
Definition PublicKey.h:42
T count(T... args)
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.
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.
static Config makeConfig(xrpl::Config const &config, std::uint16_t port, bool validationPublicKey, int ipLimit)
Make PeerFinder::Config from configuration parameters.
std::uint16_t listeningPort
The listening port number.
void async_connect(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)