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 ripple {
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(
69 "test", beast::IP::Endpoint::from_string("65.0.0.1:5"));
70 {
71 Config c;
72 c.autoConnect = false;
73 c.listeningPort = 1024;
74 logic.config(c);
75 }
76 std::size_t n = 0;
77 for (std::size_t i = 0; i < seconds; ++i)
78 {
79 auto const list = logic.autoconnect();
80 if (!list.empty())
81 {
82 BEAST_EXPECT(list.size() == 1);
83 auto const [slot, _] = logic.new_outbound_slot(list.front());
84 BEAST_EXPECT(logic.onConnected(
85 slot, beast::IP::Endpoint::from_string("65.0.0.2:5")));
86 logic.on_closed(slot);
87 ++n;
88 }
89 clock.advance(std::chrono::seconds(1));
90 logic.once_per_second();
91 }
92 // Less than 20 attempts
93 BEAST_EXPECT(n < 20);
94 }
95
96 // with activate
97 void
99 {
100 auto const seconds = 10000;
101 testcase("backoff 2");
102 TestStore store;
103 TestChecker checker;
104 TestStopwatch clock;
105 Logic<TestChecker> logic(clock, store, checker, journal_);
106 logic.addFixedPeer(
107 "test", beast::IP::Endpoint::from_string("65.0.0.1:5"));
108 {
109 Config c;
110 c.autoConnect = false;
111 c.listeningPort = 1024;
112 logic.config(c);
113 }
114
116 std::size_t n = 0;
117
118 for (std::size_t i = 0; i < seconds; ++i)
119 {
120 auto const list = logic.autoconnect();
121 if (!list.empty())
122 {
123 BEAST_EXPECT(list.size() == 1);
124 auto const [slot, _] = logic.new_outbound_slot(list.front());
125 if (!BEAST_EXPECT(logic.onConnected(
126 slot, beast::IP::Endpoint::from_string("65.0.0.2:5"))))
127 return;
128 std::string s = ".";
129 if (!BEAST_EXPECT(
130 logic.activate(slot, pk, false) ==
131 PeerFinder::Result::success))
132 return;
133 logic.on_closed(slot);
134 ++n;
135 }
136 clock.advance(std::chrono::seconds(1));
137 logic.once_per_second();
138 }
139 // No more often than once per minute
140 BEAST_EXPECT(n <= (seconds + 59) / 60);
141 }
142
143 // test accepting an incoming slot for an already existing outgoing slot
144 void
146 {
147 testcase("duplicate out/in");
148 TestStore store;
149 TestChecker checker;
150 TestStopwatch clock;
151 Logic<TestChecker> logic(clock, store, checker, journal_);
152 {
153 Config c;
154 c.autoConnect = false;
155 c.listeningPort = 1024;
156 c.ipLimit = 2;
157 logic.config(c);
158 }
159
160 auto const remote = beast::IP::Endpoint::from_string("65.0.0.1:5");
161 auto const [slot1, r] = logic.new_outbound_slot(remote);
162 BEAST_EXPECT(slot1 != nullptr);
163 BEAST_EXPECT(r == Result::success);
164 BEAST_EXPECT(logic.connectedAddresses_.count(remote.address()) == 1);
165
166 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
167 auto const [slot2, r2] = logic.new_inbound_slot(local, remote);
168 BEAST_EXPECT(logic.connectedAddresses_.count(remote.address()) == 1);
169 BEAST_EXPECT(r2 == Result::duplicatePeer);
170
171 if (!BEAST_EXPECT(slot2 == nullptr))
172 logic.on_closed(slot2);
173
174 logic.on_closed(slot1);
175 }
176
177 // test establishing outgoing slot for an already existing incoming slot
178 void
180 {
181 testcase("duplicate in/out");
182 TestStore store;
183 TestChecker checker;
184 TestStopwatch clock;
185 Logic<TestChecker> logic(clock, store, checker, journal_);
186 {
187 Config c;
188 c.autoConnect = false;
189 c.listeningPort = 1024;
190 c.ipLimit = 2;
191 logic.config(c);
192 }
193
194 auto const remote = beast::IP::Endpoint::from_string("65.0.0.1:5");
195 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
196
197 auto const [slot1, r] = logic.new_inbound_slot(local, remote);
198 BEAST_EXPECT(slot1 != nullptr);
199 BEAST_EXPECT(r == Result::success);
200 BEAST_EXPECT(logic.connectedAddresses_.count(remote.address()) == 1);
201
202 auto const [slot2, r2] = logic.new_outbound_slot(remote);
203 BEAST_EXPECT(r2 == Result::duplicatePeer);
204 BEAST_EXPECT(logic.connectedAddresses_.count(remote.address()) == 1);
205 if (!BEAST_EXPECT(slot2 == nullptr))
206 logic.on_closed(slot2);
207 logic.on_closed(slot1);
208 }
209
210 void
212 {
213 testcase("peer limit exceeded");
214 TestStore store;
215 TestChecker checker;
216 TestStopwatch clock;
217 Logic<TestChecker> logic(clock, store, checker, journal_);
218 {
219 Config c;
220 c.autoConnect = false;
221 c.listeningPort = 1024;
222 c.ipLimit = 2;
223 logic.config(c);
224 }
225
226 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
227 auto const [slot, r] = logic.new_inbound_slot(
228 local, beast::IP::Endpoint::from_string("55.104.0.2:1025"));
229 BEAST_EXPECT(slot != nullptr);
230 BEAST_EXPECT(r == Result::success);
231
232 auto const [slot1, r1] = logic.new_inbound_slot(
233 local, beast::IP::Endpoint::from_string("55.104.0.2:1026"));
234 BEAST_EXPECT(slot1 != nullptr);
235 BEAST_EXPECT(r1 == Result::success);
236
237 auto const [slot2, r2] = logic.new_inbound_slot(
238 local, beast::IP::Endpoint::from_string("55.104.0.2:1027"));
239 BEAST_EXPECT(r2 == Result::ipLimitExceeded);
240
241 if (!BEAST_EXPECT(slot2 == nullptr))
242 logic.on_closed(slot2);
243 logic.on_closed(slot1);
244 logic.on_closed(slot);
245 }
246
247 void
249 {
250 testcase("test activate duplicate peer");
251 TestStore store;
252 TestChecker checker;
253 TestStopwatch clock;
254 Logic<TestChecker> logic(clock, store, checker, journal_);
255 {
256 Config c;
257 c.autoConnect = false;
258 c.listeningPort = 1024;
259 c.ipLimit = 2;
260 logic.config(c);
261 }
262
263 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
264
266
267 auto const [slot, rSlot] = logic.new_outbound_slot(
268 beast::IP::Endpoint::from_string("55.104.0.2:1025"));
269 BEAST_EXPECT(slot != nullptr);
270 BEAST_EXPECT(rSlot == Result::success);
271
272 auto const [slot2, r2Slot] = logic.new_outbound_slot(
273 beast::IP::Endpoint::from_string("55.104.0.2:1026"));
274 BEAST_EXPECT(slot2 != nullptr);
275 BEAST_EXPECT(r2Slot == Result::success);
276
277 BEAST_EXPECT(logic.onConnected(slot, local));
278 BEAST_EXPECT(logic.onConnected(slot2, local));
279
280 BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::success);
281
282 // activating a different slot with the same node ID (pk) must fail
283 BEAST_EXPECT(
284 logic.activate(slot2, pk1, false) == Result::duplicatePeer);
285
286 logic.on_closed(slot);
287
288 // accept the same key for a new slot after removing the old slot
289 BEAST_EXPECT(logic.activate(slot2, pk1, false) == Result::success);
290 logic.on_closed(slot2);
291 }
292
293 void
295 {
296 testcase("test activate inbound disabled");
297 TestStore store;
298 TestChecker checker;
299 TestStopwatch clock;
300 Logic<TestChecker> logic(clock, store, checker, journal_);
301 {
302 Config c;
303 c.autoConnect = false;
304 c.listeningPort = 1024;
305 c.ipLimit = 2;
306 logic.config(c);
307 }
308
310 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1024");
311
312 auto const [slot, rSlot] = logic.new_inbound_slot(
313 local, beast::IP::Endpoint::from_string("55.104.0.2:1025"));
314 BEAST_EXPECT(slot != nullptr);
315 BEAST_EXPECT(rSlot == Result::success);
316
317 BEAST_EXPECT(
318 logic.activate(slot, pk1, false) == Result::inboundDisabled);
319
320 {
321 Config c;
322 c.autoConnect = false;
323 c.listeningPort = 1024;
324 c.ipLimit = 2;
325 c.inPeers = 1;
326 logic.config(c);
327 }
328 // new inbound slot must succeed when inbound connections are enabled
329 BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::success);
330
331 // creating a new inbound slot must succeed as IP Limit is not exceeded
332 auto const [slot2, r2Slot] = logic.new_inbound_slot(
333 local, beast::IP::Endpoint::from_string("55.104.0.2:1026"));
334 BEAST_EXPECT(slot2 != nullptr);
335 BEAST_EXPECT(r2Slot == Result::success);
336
338
339 // an inbound slot exceeding inPeers limit must fail
340 BEAST_EXPECT(logic.activate(slot2, pk2, false) == Result::full);
341
342 logic.on_closed(slot2);
343 logic.on_closed(slot);
344 }
345
346 void
348 {
349 testcase("test addFixedPeer no port");
350 TestStore store;
351 TestChecker checker;
352 TestStopwatch clock;
353 Logic<TestChecker> logic(clock, store, checker, journal_);
354 try
355 {
356 logic.addFixedPeer(
357 "test", beast::IP::Endpoint::from_string("65.0.0.2"));
358 fail("invalid endpoint successfully added");
359 }
360 catch (std::runtime_error const& e)
361 {
362 pass();
363 }
364 }
365
366 void
368 {
369 testcase("test onConnected self connection");
370 TestStore store;
371 TestChecker checker;
372 TestStopwatch clock;
373 Logic<TestChecker> logic(clock, store, checker, journal_);
374
375 auto const local = beast::IP::Endpoint::from_string("65.0.0.2:1234");
376 auto const [slot, r] = logic.new_outbound_slot(local);
377 BEAST_EXPECT(slot != nullptr);
378 BEAST_EXPECT(r == Result::success);
379
380 // Must fail when a slot is to our own IP address
381 BEAST_EXPECT(!logic.onConnected(slot, local));
382 logic.on_closed(slot);
383 }
384
385 void
387 {
388 // if peers_max is configured then peers_in_max and peers_out_max
389 // are ignored
390 auto run = [&](std::string const& test,
394 std::uint16_t port,
395 std::uint16_t expectOut,
396 std::uint16_t expectIn,
397 std::uint16_t expectIpLimit) {
399
400 testcase(test);
401
402 std::string toLoad = "";
403 int max = 0;
404 if (maxPeers)
405 {
406 max = maxPeers.value();
407 toLoad += "[peers_max]\n" + std::to_string(max) + "\n" +
408 "[peers_in_max]\n" + std::to_string(maxIn.value_or(0)) +
409 "\n" + "[peers_out_max]\n" +
410 std::to_string(maxOut.value_or(0)) + "\n";
411 }
412 else if (maxIn && maxOut)
413 {
414 toLoad += "[peers_in_max]\n" + std::to_string(*maxIn) + "\n" +
415 "[peers_out_max]\n" + std::to_string(*maxOut) + "\n";
416 }
417
418 c.loadFromString(toLoad);
419 BEAST_EXPECT(
420 (c.PEERS_MAX == max && c.PEERS_IN_MAX == 0 &&
421 c.PEERS_OUT_MAX == 0) ||
422 (c.PEERS_IN_MAX == *maxIn && c.PEERS_OUT_MAX == *maxOut));
423
424 Config config = Config::makeConfig(c, port, false, 0);
425
426 Counts counts;
427 counts.onConfig(config);
428 BEAST_EXPECT(
429 counts.out_max() == expectOut && counts.in_max() == expectIn &&
430 config.ipLimit == expectIpLimit);
431
432 TestStore store;
433 TestChecker checker;
434 TestStopwatch clock;
435 Logic<TestChecker> logic(clock, store, checker, journal_);
436 logic.config(config);
437
438 BEAST_EXPECT(logic.config() == config);
439 };
440
441 // if max_peers == 0 => maxPeers = 21,
442 // else if max_peers < 10 => maxPeers = 10 else maxPeers =
443 // max_peers
444 // expectOut => if legacy => max(0.15 * maxPeers, 10),
445 // if legacy && !wantIncoming => maxPeers else max_out_peers
446 // expectIn => if legacy && wantIncoming => maxPeers - outPeers
447 // else if !wantIncoming => 0 else max_in_peers
448 // ipLimit => if expectIn <= 21 => 2 else 2 + min(5, expectIn/21)
449 // ipLimit = max(1, min(ipLimit, expectIn/2))
450
451 // legacy test with max_peers
452 run("legacy no config", {}, {}, {}, 4000, 10, 11, 2);
453 run("legacy max_peers 0", 0, 100, 10, 4000, 10, 11, 2);
454 run("legacy max_peers 5", 5, 100, 10, 4000, 10, 0, 1);
455 run("legacy max_peers 20", 20, 100, 10, 4000, 10, 10, 2);
456 run("legacy max_peers 100", 100, 100, 10, 4000, 15, 85, 6);
457 run("legacy max_peers 20, private", 20, 100, 10, 0, 20, 0, 1);
458
459 // test with max_in_peers and max_out_peers
460 run("new in 100/out 10", {}, 100, 10, 4000, 10, 100, 6);
461 run("new in 0/out 10", {}, 0, 10, 4000, 10, 0, 1);
462 run("new in 100/out 10, private", {}, 100, 10, 0, 10, 0, 6);
463 }
464
465 void
467 {
468 testcase("invalid config");
469
470 auto run = [&](std::string const& toLoad) {
472 try
473 {
474 c.loadFromString(toLoad);
475 fail();
476 }
477 catch (...)
478 {
479 pass();
480 }
481 };
482 run(R"rippleConfig(
483[peers_in_max]
484100
485)rippleConfig");
486 run(R"rippleConfig(
487[peers_out_max]
488100
489)rippleConfig");
490 run(R"rippleConfig(
491[peers_in_max]
492100
493[peers_out_max]
4945
495)rippleConfig");
496 run(R"rippleConfig(
497[peers_in_max]
4981001
499[peers_out_max]
50010
501)rippleConfig");
502 run(R"rippleConfig(
503[peers_in_max]
50410
505[peers_out_max]
5061001
507)rippleConfig");
508 }
509
510 void
525};
526
527BEAST_DEFINE_TESTSUITE(PeerFinder, peerfinder, ripple);
528
529} // namespace PeerFinder
530} // namespace ripple
A version-independent IP address and port combination.
Definition IPEndpoint.h:19
static Endpoint from_string(std::string const &s)
A testsuite class.
Definition suite.h:52
void pass()
Record a successful test condition.
Definition suite.h:508
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
std::size_t PEERS_IN_MAX
Definition Config.h:162
void loadFromString(std::string const &fileContents)
Load the config from the contents of the string.
Definition Config.cpp:460
std::size_t PEERS_OUT_MAX
Definition Config.h:161
std::size_t PEERS_MAX
Definition Config.h:160
Manages the count of available connections for the various slots.
Definition Counts.h:15
int out_max() const
Returns the total number of outbound slots.
Definition Counts.h:85
int in_max() const
Returns the total number of inbound slots.
Definition Counts.h:147
void onConfig(Config const &config)
Called when the config is set or changed.
Definition Counts.h:117
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:9
A public key.
Definition PublicKey.h:43
T count(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
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)