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