xrpld
Loading...
Searching...
No Matches
tests/libxrpl/peerfinder/PeerFinder.cpp
1#include <xrpl/basics/chrono.h>
2#include <xrpl/beast/net/IPEndpoint.h>
3#include <xrpl/beast/utility/Journal.h>
4#include <xrpl/beast/utility/PropertyStream.h>
5#include <xrpl/json/JsonPropertyStream.h>
6#include <xrpl/peerfinder/Config.h>
7#include <xrpl/peerfinder/Slot.h>
8#include <xrpl/peerfinder/Types.h>
9#include <xrpl/peerfinder/detail/Bootcache.h>
10#include <xrpl/peerfinder/detail/Counts.h>
11#include <xrpl/peerfinder/detail/Handouts.h>
12#include <xrpl/peerfinder/detail/Logic.h>
13#include <xrpl/peerfinder/detail/SlotImp.h>
14#include <xrpl/peerfinder/detail/Source.h>
15#include <xrpl/peerfinder/detail/Store.h>
16#include <xrpl/peerfinder/detail/Tuning.h>
17#include <xrpl/protocol/KeyType.h>
18#include <xrpl/protocol/PublicKey.h>
19#include <xrpl/protocol/SecretKey.h>
20
21#include <boost/asio/error.hpp>
22#include <boost/asio/ip/address.hpp>
23#include <boost/asio/ip/tcp.hpp>
24
25#include <gmock/gmock.h>
26#include <gtest/gtest.h>
27#include <helpers/TestSink.h>
28
29#include <algorithm>
30#include <chrono>
31#include <cstddef>
32#include <cstdint>
33#include <exception>
34#include <memory>
35#include <optional>
36#include <stdexcept>
37#include <string>
38#include <utility>
39#include <vector>
40
41namespace xrpl::peer_finder {
42namespace {
43
44using ::testing::_;
45using ::testing::NiceMock;
46using ::testing::Return;
47
48beast::Journal
49journal()
50{
51 return beast::Journal{TestSink::instance()};
52}
53
54beast::ip::Endpoint
55endpoint(std::string const& value)
56{
58}
59
60class MockStore : public Store
61{
62public:
63 MOCK_METHOD(std::size_t, load, (Store::load_callback const& cb), (override));
64 MOCK_METHOD(void, save, (std::vector<Store::Entry> const& entries), (override));
65};
66
67class CapturingStore : public Store
68{
69public:
70 std::vector<Store::Entry> entriesToLoad;
71 std::vector<std::vector<Store::Entry>> saves;
72
73 std::size_t
74 load(Store::load_callback const& cb) override
75 {
76 for (auto const& entry : entriesToLoad)
77 cb(entry.endpoint, entry.valence);
78 return entriesToLoad.size();
79 }
80
81 void
82 save(std::vector<Store::Entry> const& entries) override
83 {
84 saves.push_back(entries);
85 }
86};
87
89storeEntry(beast::ip::Endpoint const& endpoint, int valence)
90{
92 entry.endpoint = endpoint;
93 entry.valence = valence;
94 return entry;
95}
96
97void
98allowEmptyStore(MockStore& store)
99{
100 ON_CALL(store, load(_)).WillByDefault(Return(0));
101 ON_CALL(store, save(_)).WillByDefault([](std::vector<Store::Entry> const&) {});
102}
103
104class MockChecker
105{
106public:
107 MOCK_METHOD(void, stop, ());
108 MOCK_METHOD(void, wait, ());
109 MOCK_METHOD(void, recordAsyncConnect, (beast::ip::Endpoint const& ep));
110
111 boost::system::error_code nextError;
112 bool completeAsync = true;
113 std::vector<beast::ip::Endpoint> asyncConnects;
114
115 template <class Handler>
116 void
117 asyncConnect(beast::ip::Endpoint const& ep, Handler&& handler)
118 {
119 asyncConnects.push_back(ep);
120 recordAsyncConnect(ep);
121 if (completeAsync)
122 std::forward<Handler>(handler)(nextError);
123 }
124};
125
126class TestSource : public Source
127{
128public:
129 explicit TestSource(std::string name) : name_(std::move(name))
130 {
131 }
132
133 std::string const&
134 name() override
135 {
136 return name_;
137 }
138
139 void
140 cancel() override
141 {
142 ++cancelCount;
143 }
144
145 void
146 fetch(Results& results, beast::Journal) override
147 {
148 ++fetchCount;
149 results = resultsToFetch;
150 }
151
152 Results resultsToFetch;
153 int fetchCount = 0;
154 int cancelCount = 0;
155
156private:
157 std::string name_;
158};
159
160class DefaultCancelSource : public Source
161{
162public:
163 std::string const&
164 name() override
165 {
166 return name_;
167 }
168
169 void
170 fetch(Results& results, beast::Journal) override
171 {
172 results = resultsToFetch;
173 }
174
175 Results resultsToFetch;
176
177private:
178 std::string name_{"default"};
179};
180
181class PeerFinderTest : public ::testing::Test
182{
183public:
184 PeerFinderTest()
185 {
186 allowEmptyStore(store_);
187 }
188
189protected:
190 void
191 configure(std::size_t ipLimit = 2)
192 {
193 Config config;
194 config.autoConnect = false;
195 config.listeningPort = 1024;
196 config.ipLimit = static_cast<int>(ipLimit);
197 logic_.config(config);
198 }
199
200 NiceMock<MockStore> store_;
201 NiceMock<MockChecker> checker_;
202 TestStopwatch clock_;
203 Logic<NiceMock<MockChecker>> logic_{clock_, store_, checker_, journal()};
204};
205
206int
207savedValence(std::vector<Store::Entry> const& entries, beast::ip::Endpoint const& endpoint)
208{
209 for (auto const& entry : entries)
210 {
211 if (entry.endpoint == endpoint)
212 return entry.valence;
213 }
214
215 ADD_FAILURE() << "missing saved endpoint " << endpoint.toString();
216 return 0;
217}
218
219TEST_F(PeerFinderTest, backoff_limits_repeated_connection_attempts)
220{
221 auto constexpr kSECONDS = 10000;
222
223 logic_.addFixedPeer("test", endpoint("65.0.0.1:5"));
224 configure();
225
226 std::size_t attempts = 0;
227 for (std::size_t i = 0; i < kSECONDS; ++i)
228 {
229 auto const list = logic_.autoconnect();
230 if (!list.empty())
231 {
232 ASSERT_EQ(list.size(), 1u);
233 auto const [slot, result] = logic_.newOutboundSlot(list.front());
234 ASSERT_NE(slot, nullptr);
235 ASSERT_EQ(result, Result::Success);
236 EXPECT_TRUE(logic_.onConnected(slot, endpoint("65.0.0.2:5")));
237 logic_.onClosed(slot);
238 ++attempts;
239 }
240 clock_.advance(std::chrono::seconds(1));
241 logic_.oncePerSecond();
242 }
243
244 EXPECT_LT(attempts, 20u);
245}
246
247TEST_F(PeerFinderTest, activated_peer_backoff_allows_at_most_one_attempt_per_minute)
248{
249 auto constexpr kSECONDS = 10000;
250
251 logic_.addFixedPeer("test", endpoint("65.0.0.1:5"));
252 configure();
253
254 PublicKey const publicKey(randomKeyPair(KeyType::Secp256k1).first);
255
256 std::size_t attempts = 0;
257 for (std::size_t i = 0; i < kSECONDS; ++i)
258 {
259 auto const list = logic_.autoconnect();
260 if (!list.empty())
261 {
262 ASSERT_EQ(list.size(), 1u);
263 auto const [slot, result] = logic_.newOutboundSlot(list.front());
264 ASSERT_NE(slot, nullptr);
265 ASSERT_EQ(result, Result::Success);
266 ASSERT_TRUE(logic_.onConnected(slot, endpoint("65.0.0.2:5")));
267 ASSERT_EQ(logic_.activate(slot, publicKey, false), Result::Success);
268 logic_.onClosed(slot);
269 ++attempts;
270 }
271 clock_.advance(std::chrono::seconds(1));
272 logic_.oncePerSecond();
273 }
274
275 EXPECT_LE(attempts, (kSECONDS + 59u) / 60u);
276}
277
278TEST_F(PeerFinderTest, duplicate_inbound_slot_is_rejected_for_existing_outbound_slot)
279{
280 configure();
281
282 auto const remote = endpoint("65.0.0.1:5");
283 auto const [slot1, result1] = logic_.newOutboundSlot(remote);
284 ASSERT_NE(slot1, nullptr);
285 EXPECT_EQ(result1, Result::Success);
286 EXPECT_EQ(logic_.connectedAddresses.count(remote.address()), 1u);
287
288 auto const local = endpoint("65.0.0.2:1024");
289 auto const [slot2, result2] = logic_.newInboundSlot(local, remote);
290 EXPECT_EQ(logic_.connectedAddresses.count(remote.address()), 1u);
291 EXPECT_EQ(result2, Result::DuplicatePeer);
292 EXPECT_EQ(slot2, nullptr);
293
294 if (slot2)
295 logic_.onClosed(slot2);
296 logic_.onClosed(slot1);
297}
298
299TEST_F(PeerFinderTest, duplicate_outbound_slot_is_rejected_for_existing_inbound_slot)
300{
301 configure();
302
303 auto const remote = endpoint("65.0.0.1:5");
304 auto const local = endpoint("65.0.0.2:1024");
305
306 auto const [slot1, result1] = logic_.newInboundSlot(local, remote);
307 ASSERT_NE(slot1, nullptr);
308 EXPECT_EQ(result1, Result::Success);
309 EXPECT_EQ(logic_.connectedAddresses.count(remote.address()), 1u);
310
311 auto const [slot2, result2] = logic_.newOutboundSlot(remote);
312 EXPECT_EQ(result2, Result::DuplicatePeer);
313 EXPECT_EQ(logic_.connectedAddresses.count(remote.address()), 1u);
314 EXPECT_EQ(slot2, nullptr);
315
316 if (slot2)
317 logic_.onClosed(slot2);
318 logic_.onClosed(slot1);
319}
320
321TEST_F(PeerFinderTest, peer_limit_exceeded_rejects_additional_inbound_slot)
322{
323 configure();
324
325 auto const local = endpoint("65.0.0.2:1024");
326 auto const [slot, result] = logic_.newInboundSlot(local, endpoint("55.104.0.2:1025"));
327 ASSERT_NE(slot, nullptr);
328 EXPECT_EQ(result, Result::Success);
329
330 auto const [slot1, result1] = logic_.newInboundSlot(local, endpoint("55.104.0.2:1026"));
331 ASSERT_NE(slot1, nullptr);
332 EXPECT_EQ(result1, Result::Success);
333
334 auto const [slot2, result2] = logic_.newInboundSlot(local, endpoint("55.104.0.2:1027"));
335 EXPECT_EQ(result2, Result::IpLimitExceeded);
336 EXPECT_EQ(slot2, nullptr);
337
338 if (slot2)
339 logic_.onClosed(slot2);
340 logic_.onClosed(slot1);
341 logic_.onClosed(slot);
342}
343
344TEST_F(PeerFinderTest, activate_rejects_duplicate_public_key)
345{
346 configure();
347
348 auto const local = endpoint("65.0.0.2:1024");
349 PublicKey const publicKey(randomKeyPair(KeyType::Secp256k1).first);
350
351 auto const [slot, result] = logic_.newOutboundSlot(endpoint("55.104.0.2:1025"));
352 ASSERT_NE(slot, nullptr);
353 EXPECT_EQ(result, Result::Success);
354
355 auto const [slot2, result2] = logic_.newOutboundSlot(endpoint("55.104.0.2:1026"));
356 ASSERT_NE(slot2, nullptr);
357 EXPECT_EQ(result2, Result::Success);
358
359 EXPECT_TRUE(logic_.onConnected(slot, local));
360 EXPECT_TRUE(logic_.onConnected(slot2, local));
361
362 EXPECT_EQ(logic_.activate(slot, publicKey, false), Result::Success);
363 EXPECT_EQ(logic_.activate(slot2, publicKey, false), Result::DuplicatePeer);
364
365 logic_.onClosed(slot);
366
367 EXPECT_EQ(logic_.activate(slot2, publicKey, false), Result::Success);
368 logic_.onClosed(slot2);
369}
370
371TEST_F(PeerFinderTest, activate_rejects_inbound_when_inbound_connections_are_disabled)
372{
373 configure();
374
375 PublicKey const publicKey(randomKeyPair(KeyType::Secp256k1).first);
376 auto const local = endpoint("65.0.0.2:1024");
377
378 auto const [slot, result] = logic_.newInboundSlot(local, endpoint("55.104.0.2:1025"));
379 ASSERT_NE(slot, nullptr);
380 EXPECT_EQ(result, Result::Success);
381
382 EXPECT_EQ(logic_.activate(slot, publicKey, false), Result::InboundDisabled);
383
384 {
385 Config config;
386 config.autoConnect = false;
387 config.listeningPort = 1024;
388 config.ipLimit = 2;
389 config.inPeers = 1;
390 logic_.config(config);
391 }
392
393 EXPECT_EQ(logic_.activate(slot, publicKey, false), Result::Success);
394
395 auto const [slot2, result2] = logic_.newInboundSlot(local, endpoint("55.104.0.2:1026"));
396 ASSERT_NE(slot2, nullptr);
397 EXPECT_EQ(result2, Result::Success);
398
399 PublicKey const publicKey2(randomKeyPair(KeyType::Secp256k1).first);
400 EXPECT_EQ(logic_.activate(slot2, publicKey2, false), Result::Full);
401
402 logic_.onClosed(slot2);
403 logic_.onClosed(slot);
404}
405
406TEST_F(PeerFinderTest, add_fixed_peer_rejects_endpoint_without_port)
407{
408 EXPECT_THROW(logic_.addFixedPeer("test", endpoint("65.0.0.2")), std::runtime_error);
409}
410
411TEST_F(PeerFinderTest, on_connected_rejects_self_connection)
412{
413 auto const local = endpoint("65.0.0.2:1234");
414 auto const [slot, result] = logic_.newOutboundSlot(local);
415 ASSERT_NE(slot, nullptr);
416 EXPECT_EQ(result, Result::Success);
417
418 EXPECT_FALSE(logic_.onConnected(slot, local));
419 logic_.onClosed(slot);
420}
421
422TEST(PeerFinderResult, converts_all_result_values_to_strings)
423{
424 EXPECT_EQ(to_string(Result::InboundDisabled), "inbound disabled");
425 EXPECT_EQ(to_string(Result::DuplicatePeer), "peer already connected");
426 EXPECT_EQ(to_string(Result::IpLimitExceeded), "ip limit exceeded");
427 EXPECT_EQ(to_string(Result::Full), "slots full");
428 EXPECT_EQ(to_string(Result::Success), "success");
429 EXPECT_EQ(to_string(static_cast<Result>(-1)), "unknown");
430}
431
432TEST(PeerFinderEndpoint, orders_by_address)
433{
434 Endpoint const high{endpoint("65.0.0.2:10002"), 1};
435 Endpoint const low{endpoint("65.0.0.1:10001"), 2};
436 std::vector<Endpoint> endpoints{high, low};
437
439 endpoints, [](Endpoint const& lhs, Endpoint const& rhs) { return lhs < rhs; });
440
441 EXPECT_EQ(endpoints.front().address, low.address);
442 EXPECT_EQ(endpoints.back().address, high.address);
443}
444
445TEST(PeerFinderCounts, tracks_slot_states_and_capacity)
446{
448 Counts counts;
449 Config config;
450 config.outPeers = 1;
451 config.inPeers = 1;
452 config.wantIncoming = true;
453 counts.onConfig(config);
454
455 EXPECT_EQ(counts.outMax(), 1);
456 EXPECT_EQ(counts.inMax(), 1);
457 EXPECT_EQ(counts.inboundSlotsFree(), 1);
458 EXPECT_EQ(counts.outboundSlotsFree(), 1);
459 EXPECT_EQ(counts.totalActive(), 0);
460 EXPECT_FALSE(counts.isConnectedToNetwork());
461 EXPECT_EQ(counts.attemptsNeeded(), tuning::kMaxConnectAttempts);
462 EXPECT_EQ(counts.stateString(), "0/1 out, 0/1 in, 0 connecting, 0 closing");
463
464 SlotImp inbound(endpoint("65.0.0.1:10001"), endpoint("65.0.0.2:10002"), false, clock);
465 counts.add(inbound);
466 EXPECT_EQ(counts.acceptCount(), 1);
467 EXPECT_TRUE(counts.canActivate(inbound));
468 counts.remove(inbound);
469 EXPECT_EQ(counts.acceptCount(), 0);
470
471 inbound.activate(clock.now());
472 counts.add(inbound);
473 EXPECT_EQ(counts.inboundActive(), 1);
474 EXPECT_EQ(counts.totalActive(), 1);
475 EXPECT_EQ(counts.inboundSlotsFree(), 0);
476
477 SlotImp const extraInbound(
478 endpoint("65.0.0.3:10003"), endpoint("65.0.0.4:10004"), false, clock);
479 EXPECT_FALSE(counts.canActivate(extraInbound));
480 counts.remove(inbound);
481
482 SlotImp outbound(endpoint("65.0.0.5:10005"), false, clock);
483 counts.add(outbound);
484 EXPECT_EQ(counts.attempts(), 1);
485 EXPECT_EQ(counts.connectCount(), 1);
486 EXPECT_EQ(counts.attemptsNeeded(), tuning::kMaxConnectAttempts - 1);
487 counts.remove(outbound);
488
489 outbound.state(Slot::State::Connected);
490 EXPECT_TRUE(counts.canActivate(outbound));
491 outbound.activate(clock.now());
492 counts.add(outbound);
493 EXPECT_EQ(counts.outActive(), 1);
494 EXPECT_EQ(counts.outboundSlotsFree(), 0);
495
496 SlotImp extraOutbound(endpoint("65.0.0.6:10006"), false, clock);
497 extraOutbound.state(Slot::State::Connected);
498 EXPECT_FALSE(counts.canActivate(extraOutbound));
499
500 SlotImp fixedOutbound(endpoint("65.0.0.7:10007"), true, clock);
501 fixedOutbound.state(Slot::State::Connected);
502 EXPECT_TRUE(counts.canActivate(fixedOutbound));
503 fixedOutbound.activate(clock.now());
504 counts.add(fixedOutbound);
505 EXPECT_EQ(counts.fixed(), 1u);
506 EXPECT_EQ(counts.fixedActive(), 1u);
507 counts.remove(fixedOutbound);
508
509 SlotImp reservedOutbound(endpoint("65.0.0.8:10008"), false, clock);
510 reservedOutbound.reserved(true);
511 reservedOutbound.state(Slot::State::Connected);
512 EXPECT_TRUE(counts.canActivate(reservedOutbound));
513 reservedOutbound.activate(clock.now());
514 counts.add(reservedOutbound);
515
516 JsonPropertyStream stream;
517 {
518 beast::PropertyStream::Map map(stream);
519 counts.onWrite(map);
520 }
521 EXPECT_TRUE(stream.top().isMember("accept"));
522 EXPECT_TRUE(stream.top().isMember("connect"));
523 EXPECT_TRUE(stream.top().isMember("close"));
524 EXPECT_TRUE(stream.top().isMember("reserved"));
525 EXPECT_TRUE(stream.top().isMember("total"));
526 counts.remove(reservedOutbound);
527 counts.remove(outbound);
528
529 SlotImp closing(endpoint("65.0.0.9:10009"), endpoint("65.0.0.10:10010"), false, clock);
530 closing.state(Slot::State::Closing);
531 counts.add(closing);
532 EXPECT_EQ(counts.closingCount(), 1);
533 counts.remove(closing);
534
535 Counts saturatedAttempts;
536 saturatedAttempts.onConfig(config);
537 std::vector<std::unique_ptr<SlotImp>> attempts;
538 for (int i = 0; i < tuning::kMaxConnectAttempts; ++i)
539 {
540 attempts.push_back(
542 endpoint("65.1.0." + std::to_string(i + 1) + ":" + std::to_string(11000 + i)),
543 false,
544 clock));
545 saturatedAttempts.add(*attempts.back());
546 }
547 EXPECT_EQ(saturatedAttempts.attempts(), tuning::kMaxConnectAttempts);
548 EXPECT_EQ(saturatedAttempts.attemptsNeeded(), 0u);
549
550 Config disconnected;
551 disconnected.outPeers = 0;
552 counts.onConfig(disconnected);
553 EXPECT_TRUE(counts.isConnectedToNetwork());
554}
555
556TEST(PeerFinderHandouts, filters_redirect_slot_and_connect_targets)
557{
559 auto const remote = endpoint("65.0.0.2:10002");
560 auto const slot = std::make_shared<SlotImp>(endpoint("65.0.0.1:10001"), remote, false, clock);
561
562 RedirectHandouts redirects(slot);
563 EXPECT_EQ(redirects.slot(), slot);
564 EXPECT_TRUE(redirects.list().empty());
565 EXPECT_FALSE(redirects.full());
566 EXPECT_FALSE(redirects.tryInsert(Endpoint{endpoint("65.0.0.3:10003"), tuning::kMaxHops + 1}));
567 EXPECT_FALSE(redirects.tryInsert(Endpoint{endpoint("65.0.0.3:10003"), 0}));
568 EXPECT_FALSE(redirects.tryInsert(Endpoint{remote.atPort(12000), 1}));
569 EXPECT_TRUE(redirects.tryInsert(Endpoint{endpoint("65.0.0.3:10003"), 1}));
570 EXPECT_FALSE(redirects.tryInsert(Endpoint{endpoint("65.0.0.3:12000"), 1}));
571 EXPECT_EQ(redirects.list().size(), 1u);
572
573 SlotHandouts slotHandouts(slot);
574 EXPECT_EQ(slotHandouts.slot(), slot);
575 EXPECT_FALSE(slotHandouts.full());
576 EXPECT_FALSE(
577 slotHandouts.tryInsert(Endpoint{endpoint("65.0.0.4:10004"), tuning::kMaxHops + 1}));
578 EXPECT_FALSE(slotHandouts.tryInsert(Endpoint{remote.atPort(12001), 1}));
579
580 auto const recent = endpoint("65.0.0.5:10005");
581 slot->recent.insert(recent, 2);
582 EXPECT_FALSE(slotHandouts.tryInsert(Endpoint{recent, 2}));
583 EXPECT_TRUE(slotHandouts.tryInsert(Endpoint{endpoint("65.0.0.6:10006"), 2}));
584 EXPECT_FALSE(slotHandouts.tryInsert(Endpoint{endpoint("65.0.0.6:12000"), 2}));
585 slotHandouts.insert(Endpoint{endpoint("65.0.0.7:10007"), 1});
586 EXPECT_EQ(slotHandouts.list().size(), 2u);
587
588 ConnectHandouts::Squelches squelches(clock);
589 ConnectHandouts connects(2, squelches);
590 EXPECT_TRUE(connects.empty());
591 EXPECT_TRUE(connects.tryInsert(endpoint("65.0.0.8:10008")));
592 EXPECT_FALSE(connects.empty());
593 EXPECT_FALSE(connects.tryInsert(endpoint("65.0.0.8:12000")));
594 EXPECT_TRUE(connects.tryInsert(Endpoint{endpoint("65.0.0.9:10009"), 1}));
595 EXPECT_TRUE(connects.full());
596 EXPECT_FALSE(connects.tryInsert(endpoint("65.0.0.10:10010")));
597 EXPECT_EQ(connects.list().size(), 2u);
598
599 ConnectHandouts squelched(1, squelches);
600 EXPECT_FALSE(squelched.tryInsert(endpoint("65.0.0.9:12000")));
601}
602
603TEST(PeerFinderHandouts, distributes_livecache_entries)
604{
606 Livecache<> cache(clock, journal());
607 cache.insert(Endpoint{endpoint("65.0.0.10:10010"), 1});
608 cache.insert(Endpoint{endpoint("65.0.0.11:10011"), 2});
609
610 auto const slot1 = std::make_shared<SlotImp>(
611 endpoint("65.0.0.1:10001"), endpoint("65.0.0.2:10002"), false, clock);
612 auto const slot2 = std::make_shared<SlotImp>(
613 endpoint("65.0.0.3:10003"), endpoint("65.0.0.4:10004"), false, clock);
614 std::vector<SlotHandouts> targets;
615 targets.emplace_back(slot1);
616 targets.emplace_back(slot2);
617
618 handout(targets.begin(), targets.end(), cache.hops.begin(), cache.hops.end());
619
620 EXPECT_FALSE(targets.front().list().empty());
621 EXPECT_FALSE(targets.back().list().empty());
622
623 for (std::uint32_t i = 0; i < tuning::kNumberOfEndpoints; ++i)
624 targets.front().insert(Endpoint{endpoint("65.1.0." + std::to_string(i + 1) + ":12000"), 1});
625
626 handout(targets.begin(), targets.begin() + 1, cache.hops.begin(), cache.hops.end());
627 EXPECT_TRUE(targets.front().full());
628}
629
630TEST_F(PeerFinderTest, preprocess_filters_invalid_duplicate_and_extra_self_endpoints)
631{
632 auto const local = endpoint("65.0.0.1:10001");
633 auto const remote = endpoint("65.0.0.2:10002");
634 auto const slot = std::make_shared<SlotImp>(local, remote, false, clock_);
635 Endpoints endpoints{
636 Endpoint{endpoint("65.0.0.3:10003"), tuning::kMaxHops + 1},
637 Endpoint{endpoint("0.0.0.0:2459"), 0},
638 Endpoint{endpoint("0.0.0.0:2460"), 0},
639 Endpoint{endpoint("10.0.0.1:10004"), 1},
640 Endpoint{endpoint("65.0.0.5"), 1},
641 Endpoint{endpoint("65.0.0.6:10006"), 1},
642 Endpoint{endpoint("65.0.0.6:10006"), 2}};
643
644 logic_.preprocess(slot, endpoints);
645
646 ASSERT_EQ(endpoints.size(), 2u);
647 EXPECT_EQ(endpoints.front().address, remote.atPort(2459));
648 EXPECT_EQ(endpoints.front().hops, 1u);
649 EXPECT_EQ(endpoints.back().address, endpoint("65.0.0.6:10006"));
650 EXPECT_EQ(endpoints.back().hops, 2u);
651}
652
653TEST_F(PeerFinderTest, on_endpoints_checks_neighbor_before_caching_it)
654{
655 Config config;
656 config.autoConnect = false;
657 config.listeningPort = 1024;
658 config.ipLimit = 2;
659 config.inPeers = 1;
660 logic_.config(config);
661
662 auto const local = endpoint("65.0.0.1:10001");
663 auto const remote = endpoint("55.104.0.2:1025");
664 auto const [slot, result] = logic_.newInboundSlot(local, remote);
665 ASSERT_NE(slot, nullptr);
666 EXPECT_EQ(result, Result::Success);
667 PublicKey const publicKey(randomKeyPair(KeyType::Secp256k1).first);
668 ASSERT_EQ(logic_.activate(slot, publicKey, false), Result::Success);
669
670 Endpoints const advertised{Endpoint{endpoint("0.0.0.0:2459"), 0}};
671 logic_.onEndpoints(slot, advertised);
672
673 ASSERT_EQ(checker_.asyncConnects.size(), 1u);
674 EXPECT_EQ(checker_.asyncConnects.front(), remote.atPort(2459));
675 EXPECT_EQ(slot->listeningPort(), std::optional<std::uint16_t>{2459});
676 EXPECT_TRUE(slot->checked);
677 EXPECT_TRUE(slot->canAccept);
678 EXPECT_TRUE(logic_.livecache.empty());
679
680 clock_.advance(tuning::kSecondsPerMessage);
681 logic_.onEndpoints(slot, advertised);
682 EXPECT_EQ(logic_.livecache.size(), 1u);
683 EXPECT_EQ(logic_.bootcache.size(), 1u);
684
685 logic_.onEndpoints(slot, Endpoints{Endpoint{endpoint("65.0.0.9:10009"), 1}});
686 EXPECT_EQ(logic_.livecache.size(), 1u);
687
688 logic_.onClosed(slot);
689}
690
691TEST_F(PeerFinderTest, on_endpoints_skips_failed_neighbor_connectivity_checks)
692{
693 Config config;
694 config.autoConnect = false;
695 config.listeningPort = 1024;
696 config.ipLimit = 2;
697 config.inPeers = 1;
698 logic_.config(config);
699
700 checker_.nextError = boost::asio::error::host_unreachable;
701 auto const local = endpoint("65.0.0.1:10001");
702 auto const remote = endpoint("55.104.0.3:1025");
703 auto const [slot, result] = logic_.newInboundSlot(local, remote);
704 ASSERT_NE(slot, nullptr);
705 EXPECT_EQ(result, Result::Success);
706 PublicKey const publicKey(randomKeyPair(KeyType::Secp256k1).first);
707 ASSERT_EQ(logic_.activate(slot, publicKey, false), Result::Success);
708
709 Endpoints const advertised{Endpoint{endpoint("0.0.0.0:2459"), 0}};
710 logic_.onEndpoints(slot, advertised);
711 EXPECT_TRUE(slot->checked);
712 EXPECT_FALSE(slot->canAccept);
713
714 clock_.advance(tuning::kSecondsPerMessage);
715 logic_.onEndpoints(slot, advertised);
716 EXPECT_TRUE(logic_.livecache.empty());
717
718 logic_.onClosed(slot);
719}
720
721TEST_F(PeerFinderTest, on_endpoints_waits_for_pending_connectivity_check)
722{
723 Config config;
724 config.autoConnect = false;
725 config.listeningPort = 1024;
726 config.ipLimit = 2;
727 config.inPeers = 1;
728 logic_.config(config);
729
730 checker_.completeAsync = false;
731 auto const local = endpoint("65.0.0.1:10001");
732 auto const remote = endpoint("55.104.0.4:1025");
733 auto const [slot, result] = logic_.newInboundSlot(local, remote);
734 ASSERT_NE(slot, nullptr);
735 EXPECT_EQ(result, Result::Success);
736 PublicKey const publicKey(randomKeyPair(KeyType::Secp256k1).first);
737 ASSERT_EQ(logic_.activate(slot, publicKey, false), Result::Success);
738
739 Endpoints const advertised{Endpoint{endpoint("0.0.0.0:2459"), 0}};
740 logic_.onEndpoints(slot, advertised);
741 EXPECT_TRUE(slot->connectivityCheckInProgress);
742
743 clock_.advance(tuning::kSecondsPerMessage);
744 logic_.onEndpoints(slot, advertised);
745 EXPECT_EQ(checker_.asyncConnects.size(), 1u);
746 EXPECT_TRUE(logic_.livecache.empty());
747
748 checker_.completeAsync = true;
749 logic_.checkComplete(remote, remote.atPort(2459), boost::asio::error::operation_aborted);
750 slot->connectivityCheckInProgress = false;
751 logic_.onClosed(slot);
752}
753
754TEST_F(PeerFinderTest, builds_endpoint_messages_and_redirects_from_livecache)
755{
756 Config config;
757 config.autoConnect = false;
758 config.wantIncoming = true;
759 config.listeningPort = 2459;
760 config.inPeers = 2;
761 config.outPeers = 2;
762 config.ipLimit = 2;
763 logic_.config(config);
764
765 auto const remote = endpoint("55.104.0.5:1025");
766 auto const live = endpoint("65.0.0.10:10010");
767 logic_.livecache.insert(Endpoint{live, 1});
768
769 auto const [slot, result] = logic_.newOutboundSlot(remote);
770 ASSERT_NE(slot, nullptr);
771 EXPECT_EQ(result, Result::Success);
772 ASSERT_TRUE(logic_.onConnected(slot, endpoint("65.0.0.1:10001")));
773 PublicKey const publicKey(randomKeyPair(KeyType::Secp256k1).first);
774 ASSERT_EQ(logic_.activate(slot, publicKey, false), Result::Success);
775
776 auto const messages = logic_.buildEndpointsForPeers();
777 ASSERT_EQ(messages.size(), 1u);
778 auto const& sent = messages.front().second;
779 EXPECT_TRUE(std::ranges::any_of(sent, [](Endpoint const& ep) { return ep.hops == 0; }));
780 EXPECT_TRUE(
781 std::ranges::any_of(sent, [&live](Endpoint const& ep) { return ep.address == live; }));
782 EXPECT_TRUE(logic_.buildEndpointsForPeers().empty());
783
784 auto const redirects = logic_.redirect(slot);
785 EXPECT_FALSE(redirects.empty());
786
787 logic_.onClosed(slot);
788}
789
790TEST_F(PeerFinderTest, autoconnect_uses_livecache_then_bootcache)
791{
792 Config config;
793 config.autoConnect = true;
794 config.wantIncoming = false;
795 config.outPeers = 1;
796 config.inPeers = 0;
797 config.ipLimit = 1;
798 logic_.config(config);
799
800 auto const live = endpoint("65.0.0.11:10011");
801 logic_.livecache.insert(Endpoint{live, 1});
802 auto const liveAddresses = logic_.autoconnect();
803 ASSERT_EQ(liveAddresses.size(), 1u);
804 EXPECT_EQ(liveAddresses.front(), live);
805
806 auto const boot = endpoint("65.0.0.12:10012");
807 EXPECT_TRUE(logic_.bootcache.insertStatic(boot));
808 auto const bootAddresses = logic_.autoconnect();
809 ASSERT_EQ(bootAddresses.size(), 1u);
810 EXPECT_EQ(bootAddresses.front(), boot);
811}
812
813TEST_F(PeerFinderTest, sources_redirects_status_and_validation_paths_are_exercised)
814{
815 auto const source = std::make_shared<TestSource>("static");
816 source->resultsToFetch.addresses = {endpoint("65.0.0.13:10013")};
817 logic_.addStaticSource(source);
818 EXPECT_EQ(source->fetchCount, 1);
819 EXPECT_EQ(logic_.bootcache.size(), 1u);
820
821 auto const failing = std::make_shared<TestSource>("failing");
822 failing->resultsToFetch.error = boost::asio::error::host_unreachable;
823 logic_.fetch(failing);
824 EXPECT_EQ(failing->fetchCount, 1);
825
826 auto const dynamic = std::make_shared<TestSource>("dynamic");
827 logic_.addSource(dynamic);
828 ASSERT_EQ(logic_.sources.size(), 1u);
829 EXPECT_EQ(logic_.sources.front(), dynamic);
830
831 std::vector<boost::asio::ip::tcp::endpoint> redirects{
832 {boost::asio::ip::make_address("65.0.0.14"), 10014},
833 {boost::asio::ip::make_address("65.0.0.15"), 10015}};
834 logic_.onRedirects(redirects.begin(), redirects.end(), redirects.front());
835 EXPECT_EQ(logic_.bootcache.size(), 3u);
836
837 EXPECT_FALSE(logic_.isValidAddress(endpoint("0.0.0.0:10016")));
838 EXPECT_FALSE(logic_.isValidAddress(endpoint("10.0.0.1:10017")));
839 EXPECT_FALSE(logic_.isValidAddress(endpoint("65.0.0.16")));
840 EXPECT_TRUE(logic_.isValidAddress(endpoint("65.0.0.16:10016")));
841
842 JsonPropertyStream stream;
843 {
844 beast::PropertyStream::Map map(stream);
845 logic_.onWrite(map);
846 }
847 EXPECT_TRUE(stream.top().isMember("peers"));
848 EXPECT_TRUE(stream.top().isMember("counts"));
849 EXPECT_TRUE(stream.top().isMember("config"));
850 EXPECT_TRUE(stream.top().isMember("livecache"));
851 EXPECT_TRUE(stream.top().isMember("bootcache"));
852
853 DefaultCancelSource defaultCancel;
854 Source::Results results;
855 EXPECT_TRUE(results.addresses.empty());
856 defaultCancel.cancel();
857 defaultCancel.fetch(results, journal());
858
859 logic_.fetchSource = dynamic;
860 logic_.stop();
861 EXPECT_TRUE(logic_.stopping);
862 EXPECT_EQ(dynamic->cancelCount, 1);
863
864 auto const ignored = std::make_shared<TestSource>("ignored");
865 logic_.fetch(ignored);
866 EXPECT_EQ(ignored->fetchCount, 0);
867
868 logic_.checkComplete(
869 endpoint("65.0.0.18:10018"), endpoint("65.0.0.19:10019"), boost::system::error_code{});
870}
871
872TEST(PeerFinderBootcache, loads_unique_entries_and_clears_cache)
873{
874 CapturingStore store;
876 auto const ep1 = endpoint("65.0.0.1:10001");
877 auto const ep2 = endpoint("65.0.0.2:10002");
878 store.entriesToLoad = {storeEntry(ep1, 3), storeEntry(ep2, -2), storeEntry(ep1, 4)};
879
880 Bootcache cache(store, clock, journal());
881 cache.load();
882
883 EXPECT_FALSE(cache.empty());
884 EXPECT_EQ(cache.size(), 2u);
885 EXPECT_EQ(*cache.begin(), ep1);
886 EXPECT_EQ(*cache.cbegin(), ep1);
887 EXPECT_NE(cache.begin(), cache.end());
888 EXPECT_NE(cache.cbegin(), cache.cend());
889
890 cache.clear();
891 EXPECT_TRUE(cache.empty());
892 EXPECT_EQ(cache.begin(), cache.end());
893}
894
895TEST(PeerFinderBootcache, records_connection_outcomes_and_persists_pending_updates)
896{
897 CapturingStore store;
899 auto const ep1 = endpoint("65.0.0.1:10001");
900 auto const ep2 = endpoint("65.0.0.2:10002");
901 auto const ep3 = endpoint("65.0.0.3:10003");
902 auto const ep4 = endpoint("65.0.0.4:10004");
903
904 {
905 Bootcache cache(store, clock, journal());
906
907 EXPECT_TRUE(cache.insert(ep1));
908 EXPECT_FALSE(cache.insert(ep1));
909
910 cache.onSuccess(ep1);
911 EXPECT_TRUE(cache.insertStatic(ep1));
912 EXPECT_FALSE(cache.insertStatic(ep1));
913
914 EXPECT_TRUE(cache.insertStatic(ep2));
915 cache.onSuccess(ep3);
916 cache.onFailure(ep3);
917 cache.onFailure(ep4);
918
919 EXPECT_EQ(cache.size(), 4u);
920
921 JsonPropertyStream stream;
922 {
923 beast::PropertyStream::Map map(stream);
924 cache.onWrite(map);
925 }
926 EXPECT_TRUE(stream.top().isMember("entries"));
927 EXPECT_EQ(stream.top()["entries"].size(), 4u);
928 }
929
930 ASSERT_EQ(store.saves.size(), 1u);
931 auto const& saved = store.saves.front();
932 ASSERT_EQ(saved.size(), 4u);
933 EXPECT_EQ(savedValence(saved, ep1), Bootcache::kStaticValence);
934 EXPECT_EQ(savedValence(saved, ep2), Bootcache::kStaticValence);
935 EXPECT_EQ(savedValence(saved, ep3), -1);
936 EXPECT_EQ(savedValence(saved, ep4), -1);
937}
938
939TEST(PeerFinderBootcache, periodic_activity_saves_after_cooldown)
940{
941 using namespace std::chrono_literals;
942
943 CapturingStore store;
945
946 {
947 Bootcache cache(store, clock, journal());
948 EXPECT_TRUE(cache.insert(endpoint("65.0.0.1:10001")));
949
950 cache.periodicActivity();
951 EXPECT_TRUE(store.saves.empty());
952
954 cache.periodicActivity();
955 ASSERT_EQ(store.saves.size(), 1u);
956
957 cache.periodicActivity();
958 EXPECT_EQ(store.saves.size(), 1u);
959 }
960
961 EXPECT_EQ(store.saves.size(), 1u);
962}
963
964TEST(PeerFinderBootcache, prunes_when_cache_exceeds_limit)
965{
966 CapturingStore store;
968 Bootcache cache(store, clock, journal());
969
970 for (std::uint16_t i = 0; i <= tuning::kBootcacheSize; ++i)
971 {
972 EXPECT_TRUE(cache.insert(endpoint(
973 "65.0." + std::to_string((i / 256) % 256) + "." + std::to_string(i % 256) + ":" +
974 std::to_string(10000 + i))));
975 }
976
977 EXPECT_LE(cache.size(), tuning::kBootcacheSize);
978}
979
980TEST(PeerFinderEndpoint, clamps_hops_to_overflow_bucket)
981{
982 auto const address = endpoint("65.0.0.1:10001");
983 Endpoint const ep(address, tuning::kMaxHops + 10);
984
985 EXPECT_EQ(ep.address, address);
986 EXPECT_EQ(ep.hops, tuning::kMaxHops + 1);
987}
988
989TEST(PeerFinderSlotImp, tracks_state_and_recent_endpoints)
990{
991 using State = Slot::State;
992 using namespace std::chrono_literals;
993
995 auto const local = endpoint("65.0.0.1:10000");
996 auto const remote = endpoint("65.0.0.2:10001");
997 SlotImp inbound(local, remote, true, clock);
998
999 EXPECT_TRUE(inbound.inbound());
1000 EXPECT_TRUE(inbound.fixed());
1001 EXPECT_FALSE(inbound.reserved());
1002 EXPECT_EQ(inbound.state(), State::Accept);
1003 EXPECT_EQ(inbound.remoteEndpoint(), remote);
1004 EXPECT_EQ(inbound.localEndpoint(), std::optional<beast::ip::Endpoint>{local});
1005 EXPECT_FALSE(inbound.publicKey());
1006 EXPECT_FALSE(inbound.listeningPort());
1007 EXPECT_FALSE(inbound.checked);
1008 EXPECT_FALSE(inbound.canAccept);
1009 EXPECT_FALSE(inbound.connectivityCheckInProgress);
1010
1011 auto const newLocal = endpoint("65.0.0.3:10002");
1012 auto const newRemote = endpoint("65.0.0.4:10003");
1013 PublicKey const publicKey(randomKeyPair(KeyType::Secp256k1).first);
1014
1015 inbound.localEndpoint(newLocal);
1016 inbound.remoteEndpoint(newRemote);
1017 inbound.publicKey(publicKey);
1018 inbound.reserved(true);
1019 inbound.setListeningPort(2459);
1020
1021 EXPECT_EQ(inbound.localEndpoint(), std::optional<beast::ip::Endpoint>{newLocal});
1022 EXPECT_EQ(inbound.remoteEndpoint(), newRemote);
1023 EXPECT_EQ(inbound.publicKey(), std::optional<PublicKey>{publicKey});
1024 EXPECT_TRUE(inbound.reserved());
1025 EXPECT_EQ(inbound.listeningPort(), std::optional<std::uint16_t>{2459});
1026 EXPECT_FALSE(inbound.prefix().empty());
1027
1028 inbound.state(State::Closing);
1029 EXPECT_EQ(inbound.state(), State::Closing);
1030
1031 SlotImp outbound(remote, false, clock);
1032 EXPECT_FALSE(outbound.inbound());
1033 EXPECT_FALSE(outbound.fixed());
1034 EXPECT_EQ(outbound.state(), State::Connect);
1035 EXPECT_TRUE(outbound.checked);
1036 EXPECT_TRUE(outbound.canAccept);
1037
1038 outbound.state(State::Connected);
1039 outbound.activate(clock.now());
1040 EXPECT_EQ(outbound.state(), State::Active);
1041 EXPECT_EQ(outbound.whenAcceptEndpoints, clock.now());
1042
1043 auto const recent = endpoint("65.0.0.5:10004");
1044 EXPECT_FALSE(outbound.recent.filter(recent, 2));
1045
1046 outbound.recent.insert(recent, 2);
1047 EXPECT_TRUE(outbound.recent.filter(recent, 2));
1048 EXPECT_TRUE(outbound.recent.filter(recent, 3));
1049 EXPECT_FALSE(outbound.recent.filter(recent, 1));
1050
1051 outbound.recent.insert(recent, 4);
1052 EXPECT_FALSE(outbound.recent.filter(recent, 1));
1053
1054 outbound.recent.insert(recent, 1);
1055 EXPECT_TRUE(outbound.recent.filter(recent, 1));
1056 EXPECT_FALSE(outbound.recent.filter(recent, 0));
1057
1059 outbound.expire();
1060 EXPECT_FALSE(outbound.recent.filter(recent, 1));
1061}
1062
1063TEST(PeerFinderConfig, writes_property_stream_and_compares_verify_endpoints)
1064{
1065 Config config;
1066 config.maxPeers = 42;
1067 config.outPeers = 12;
1068 config.inPeers = 30;
1069 config.peerPrivate = false;
1070 config.wantIncoming = true;
1071 config.autoConnect = false;
1072 config.listeningPort = 2459;
1073 config.features = "feature";
1074 config.ipLimit = 4;
1075 config.verifyEndpoints = false;
1076
1077 JsonPropertyStream stream;
1078 {
1079 beast::PropertyStream::Map map(stream);
1080 config.onWrite(map);
1081 }
1082
1083 auto const& json = stream.top();
1084 EXPECT_EQ(json["max_peers"].asUInt(), config.maxPeers);
1085 EXPECT_EQ(json["out_peers"].asUInt(), config.outPeers);
1086 EXPECT_TRUE(json.isMember("want_incoming"));
1087 EXPECT_TRUE(json.isMember("auto_connect"));
1088 EXPECT_EQ(json["port"].asUInt(), config.listeningPort);
1089 EXPECT_EQ(json["features"].asString(), config.features);
1090 EXPECT_EQ(json["ip_limit"].asInt(), config.ipLimit);
1091 EXPECT_TRUE(json.isMember("verify_endpoints"));
1092
1093 Config same = config;
1094 EXPECT_EQ(config, same);
1095 same.verifyEndpoints = true;
1096 EXPECT_NE(config, same);
1097}
1098
1099TEST(PeerFinderConfig, validator_and_standalone_settings_disable_auto_connect)
1100{
1101 PeerLimitConfig const limits{.maxPeers = 50, .inPeers = {}, .outPeers = {}};
1102
1103 Config const config = Config::makeConfig(false, true, limits, 2459, true, 7, false);
1104
1105 EXPECT_TRUE(config.peerPrivate);
1106 EXPECT_FALSE(config.autoConnect);
1107 EXPECT_FALSE(config.verifyEndpoints);
1108 EXPECT_EQ(config.ipLimit, 7);
1109}
1110
1111TEST(PeerFinderConfig, calculates_outbound_peers_and_clamps_ip_limits)
1112{
1113 Config config;
1114 config.maxPeers = 1;
1115 EXPECT_EQ(config.calcOutPeers(), tuning::kMinOutCount);
1116
1117 config.maxPeers = 100;
1118 EXPECT_EQ(config.calcOutPeers(), 15u);
1119
1120 config.inPeers = 1;
1121 config.ipLimit = 0;
1122 config.applyTuning();
1123 EXPECT_EQ(config.ipLimit, 1);
1124
1125 Config explicitLimit;
1126 explicitLimit.inPeers = 8;
1127 explicitLimit.ipLimit = 99;
1128 explicitLimit.applyTuning();
1129 EXPECT_EQ(explicitLimit.ipLimit, 4);
1130
1131 Config largeInbound;
1132 largeInbound.inPeers = 200;
1133 largeInbound.ipLimit = 0;
1134 largeInbound.applyTuning();
1135 EXPECT_EQ(largeInbound.ipLimit, 7);
1136}
1137
1138TEST(PeerFinderConfig, applies_legacy_and_explicit_peer_limits)
1139{
1140 struct ConfigCase
1141 {
1142 std::string name;
1143 std::optional<std::uint16_t> maxPeers;
1144 std::optional<std::uint16_t> maxIn;
1145 std::optional<std::uint16_t> maxOut;
1146 std::uint16_t port;
1147 std::uint16_t expectedOut;
1148 std::uint16_t expectedIn;
1149 std::uint16_t expectedIpLimit;
1150 };
1151
1152 std::vector<ConfigCase> const cases{
1153 {.name = "legacy no config",
1154 .maxPeers = {},
1155 .maxIn = {},
1156 .maxOut = {},
1157 .port = 4000,
1158 .expectedOut = 10,
1159 .expectedIn = 11,
1160 .expectedIpLimit = 2},
1161 {.name = "legacy max_peers 0",
1162 .maxPeers = 0,
1163 .maxIn = 100,
1164 .maxOut = 10,
1165 .port = 4000,
1166 .expectedOut = 10,
1167 .expectedIn = 11,
1168 .expectedIpLimit = 2},
1169 {.name = "legacy max_peers 5",
1170 .maxPeers = 5,
1171 .maxIn = 100,
1172 .maxOut = 10,
1173 .port = 4000,
1174 .expectedOut = 10,
1175 .expectedIn = 0,
1176 .expectedIpLimit = 1},
1177 {.name = "legacy max_peers 20",
1178 .maxPeers = 20,
1179 .maxIn = 100,
1180 .maxOut = 10,
1181 .port = 4000,
1182 .expectedOut = 10,
1183 .expectedIn = 10,
1184 .expectedIpLimit = 2},
1185 {.name = "legacy max_peers 100",
1186 .maxPeers = 100,
1187 .maxIn = 100,
1188 .maxOut = 10,
1189 .port = 4000,
1190 .expectedOut = 15,
1191 .expectedIn = 85,
1192 .expectedIpLimit = 6},
1193 {.name = "legacy max_peers 20, private",
1194 .maxPeers = 20,
1195 .maxIn = 100,
1196 .maxOut = 10,
1197 .port = 0,
1198 .expectedOut = 20,
1199 .expectedIn = 0,
1200 .expectedIpLimit = 1},
1201 {.name = "new in 100/out 10",
1202 .maxPeers = {},
1203 .maxIn = 100,
1204 .maxOut = 10,
1205 .port = 4000,
1206 .expectedOut = 10,
1207 .expectedIn = 100,
1208 .expectedIpLimit = 6},
1209 {.name = "new in 0/out 10",
1210 .maxPeers = {},
1211 .maxIn = 0,
1212 .maxOut = 10,
1213 .port = 4000,
1214 .expectedOut = 10,
1215 .expectedIn = 0,
1216 .expectedIpLimit = 1},
1217 {.name = "new in 100/out 10, private",
1218 .maxPeers = {},
1219 .maxIn = 100,
1220 .maxOut = 10,
1221 .port = 0,
1222 .expectedOut = 10,
1223 .expectedIn = 0,
1224 .expectedIpLimit = 6}};
1225
1226 for (auto const& testCase : cases)
1227 {
1228 SCOPED_TRACE(testCase.name);
1229
1230 PeerLimitConfig const limits{
1231 .maxPeers = testCase.maxPeers, .inPeers = testCase.maxIn, .outPeers = testCase.maxOut};
1232
1233 Config const config =
1234 Config::makeConfig(false, false, limits, testCase.port, false, 0, true);
1235
1236 Counts counts;
1237 counts.onConfig(config);
1238 EXPECT_EQ(counts.outMax(), testCase.expectedOut);
1239 EXPECT_EQ(counts.inMax(), testCase.expectedIn);
1240 EXPECT_EQ(config.ipLimit, testCase.expectedIpLimit);
1241
1242 NiceMock<MockStore> store;
1243 allowEmptyStore(store);
1244 NiceMock<MockChecker> checker;
1246 Logic<NiceMock<MockChecker>> logic(clock, store, checker, journal());
1247 logic.config(config);
1248
1249 EXPECT_EQ(logic.config(), config);
1250 }
1251}
1252
1253TEST(PeerFinderConfig, rejects_incomplete_or_out_of_range_peer_limits)
1254{
1255 std::vector<PeerLimitConfig> const configs{
1256 {.maxPeers = {}, .inPeers = 100, .outPeers = {}},
1257 {.maxPeers = {}, .inPeers = {}, .outPeers = 100},
1258 {.maxPeers = {}, .inPeers = 100, .outPeers = 5},
1259 {.maxPeers = {}, .inPeers = 1001, .outPeers = 10},
1260 {.maxPeers = {}, .inPeers = 10, .outPeers = 1001}};
1261
1262 for (auto const& limits : configs)
1263 {
1264 EXPECT_THROW(
1265 Config::makeConfig(false, false, limits, 4000, false, 0, true), std::exception);
1266 }
1267}
1268
1269} // namespace
1270} // namespace xrpl::peer_finder
T any_of(T... args)
T back(T... args)
T begin(T... args)
std::string toString() const
Returns a string representing the endpoint.
static Endpoint fromString(std::string const &s)
static TestSink & instance()
Definition TestSink.h:12
Stores IP addresses useful for gaining initial connections.
Definition Bootcache.h:36
static constexpr int kStaticValence
Definition Bootcache.h:101
Receives handouts for making automatic connections.
Definition Handouts.h:258
beast::aged_set< beast::ip::Address > Squelches
Definition Handouts.h:262
Manages the count of available connections for the various slots.
Definition Counts.h:24
void onConfig(Config const &config)
Called when the config is set or changed.
Definition Counts.h:127
The Livecache holds the short-lived relayed Endpoint messages.
Definition Livecache.h:188
The Logic for maintaining the list of Slot addresses.
Receives handouts for redirecting a connection.
Definition Handouts.h:86
Receives endpoints for a slot during periodic handouts.
Definition Handouts.h:172
A static or dynamic source of peer addresses.
Definition Source.h:22
Abstract persistence for PeerFinder data.
Definition Store.h:15
std::function< void(beast::ip::Endpoint, int)> load_callback
Definition Store.h:20
T clock(T... args)
T emplace_back(T... args)
T end(T... args)
T forward(T... args)
T front(T... args)
T make_shared(T... args)
T make_unique(T... args)
T move(T... args)
void stream(json::Value const &jv, Write const &write)
Stream compact JSON to the specified function.
static constexpr auto kMaxConnectAttempts
Maximum number of simultaneous connection attempts.
constexpr std::chrono::seconds kSecondsPerMessage(151)
static constexpr auto kMinOutCount
A hard minimum on the number of outgoing connections.
static std::chrono::seconds const kBootcacheCooldownTime(60)
constexpr std::chrono::seconds kLiveCacheSecondsToLive(30)
TEST_F(LivecacheTest, basic_insert)
Definition Livecache.cpp:91
Result
Possible results from activating a slot.
std::vector< Endpoint > Endpoints
A set of Endpoint used for connecting.
void handout(TargetFwdIter first, TargetFwdIter last, SeqFwdIter seqFirst, SeqFwdIter seqLast)
Distributes objects to targets according to business rules.
Definition Handouts.h:53
std::string_view to_string(Result result) noexcept
Converts a Result enum value to its string representation.
json::Value cancel(jtx::Account const &dest, uint256 const &checkId)
Cancel a check.
Definition check.cpp:39
json::Value entry(jtx::Env &env, jtx::Account const &account, jtx::Account const &authorize)
Definition delegate.cpp:41
std::uint32_t asUInt(AnyValue const &v)
Definition Oracle.cpp:399
bool same(STPathSet const &st1, Args const &... args)
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
TEST(FileUtilitiesTest, get_file_contents)
beast::ManualClock< std::chrono::steady_clock > TestStopwatch
A manual Stopwatch for unit tests.
Definition chrono.h:95
T push_back(T... args)
T sort(T... args)
PeerFinder configuration settings.
std::size_t outPeers
The number of automatic outbound connections to maintain.
bool autoConnect
true if we want to establish connections automatically
static Config makeConfig(bool peerPrivate, bool standalone, PeerLimitConfig const &limits, std::uint16_t port, bool validationPublicKey, int ipLimit, bool verifyEndpoints)
Make peer_finder::Config from peer limit and server mode parameters.
std::size_t inPeers
The number of automatic inbound connections to maintain.
std::size_t maxPeers
The largest number of public peer slots to allow.
Describes a connectable peer address along with some metadata.
The results of a fetch.
Definition Source.h:28
T to_string(T... args)