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