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>
21#include <boost/asio/error.hpp>
22#include <boost/asio/ip/address.hpp>
23#include <boost/asio/ip/tcp.hpp>
25#include <gmock/gmock.h>
26#include <gtest/gtest.h>
27#include <helpers/TestSink.h>
45using ::testing::NiceMock;
46using ::testing::Return;
55endpoint(std::string
const& value)
60class MockStore :
public Store
64 MOCK_METHOD(
void, save, (std::vector<Store::Entry>
const& entries), (
override));
67class CapturingStore :
public Store
70 std::vector<Store::Entry> entriesToLoad;
71 std::vector<std::vector<Store::Entry>> saves;
76 for (
auto const& entry : entriesToLoad)
78 return entriesToLoad.size();
82 save(std::vector<Store::Entry>
const& entries)
override
84 saves.push_back(entries);
89storeEntry(beast::ip::Endpoint
const& endpoint,
int valence)
92 entry.endpoint = endpoint;
93 entry.valence = valence;
98allowEmptyStore(MockStore& store)
100 ON_CALL(store, load(_)).WillByDefault(Return(0));
101 ON_CALL(store, save(_)).WillByDefault([](std::vector<Store::Entry>
const&) {});
107 MOCK_METHOD(
void, stop, ());
108 MOCK_METHOD(
void, wait, ());
109 MOCK_METHOD(
void, recordAsyncConnect, (beast::ip::Endpoint
const& ep));
111 boost::system::error_code nextError;
112 bool completeAsync =
true;
113 std::vector<beast::ip::Endpoint> asyncConnects;
115 template <
class Handler>
117 asyncConnect(beast::ip::Endpoint
const& ep, Handler&& handler)
119 asyncConnects.push_back(ep);
120 recordAsyncConnect(ep);
126class TestSource :
public Source
129 explicit TestSource(std::string name) : name_(std::
move(name))
146 fetch(Results& results, beast::Journal)
override
149 results = resultsToFetch;
152 Results resultsToFetch;
160class DefaultCancelSource :
public Source
170 fetch(Results& results, beast::Journal)
override
172 results = resultsToFetch;
175 Results resultsToFetch;
178 std::string name_{
"default"};
181class PeerFinderTest :
public ::testing::Test
186 allowEmptyStore(store_);
191 configure(std::size_t ipLimit = 2)
194 config.autoConnect =
false;
195 config.listeningPort = 1024;
196 config.ipLimit =
static_cast<int>(ipLimit);
197 logic_.config(config);
200 NiceMock<MockStore> store_;
201 NiceMock<MockChecker> checker_;
203 Logic<NiceMock<MockChecker>> logic_{clock_, store_, checker_, journal()};
207savedValence(std::vector<Store::Entry>
const& entries, beast::ip::Endpoint
const& endpoint)
209 for (
auto const& entry : entries)
211 if (
entry.endpoint == endpoint)
212 return entry.valence;
215 ADD_FAILURE() <<
"missing saved endpoint " << endpoint.
toString();
219TEST_F(PeerFinderTest, backoff_limits_repeated_connection_attempts)
221 auto constexpr kSECONDS = 10000;
223 logic_.addFixedPeer(
"test", endpoint(
"65.0.0.1:5"));
226 std::size_t attempts = 0;
227 for (std::size_t i = 0; i < kSECONDS; ++i)
229 auto const list = logic_.autoconnect();
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);
240 clock_.advance(std::chrono::seconds(1));
241 logic_.oncePerSecond();
244 EXPECT_LT(attempts, 20u);
247TEST_F(PeerFinderTest, activated_peer_backoff_allows_at_most_one_attempt_per_minute)
249 auto constexpr kSECONDS = 10000;
251 logic_.addFixedPeer(
"test", endpoint(
"65.0.0.1:5"));
256 std::size_t attempts = 0;
257 for (std::size_t i = 0; i < kSECONDS; ++i)
259 auto const list = logic_.autoconnect();
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);
271 clock_.advance(std::chrono::seconds(1));
272 logic_.oncePerSecond();
275 EXPECT_LE(attempts, (kSECONDS + 59u) / 60u);
278TEST_F(PeerFinderTest, duplicate_inbound_slot_is_rejected_for_existing_outbound_slot)
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);
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);
295 logic_.onClosed(slot2);
296 logic_.onClosed(slot1);
299TEST_F(PeerFinderTest, duplicate_outbound_slot_is_rejected_for_existing_inbound_slot)
303 auto const remote = endpoint(
"65.0.0.1:5");
304 auto const local = endpoint(
"65.0.0.2:1024");
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);
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);
317 logic_.onClosed(slot2);
318 logic_.onClosed(slot1);
321TEST_F(PeerFinderTest, peer_limit_exceeded_rejects_additional_inbound_slot)
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);
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);
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);
339 logic_.onClosed(slot2);
340 logic_.onClosed(slot1);
341 logic_.onClosed(slot);
344TEST_F(PeerFinderTest, activate_rejects_duplicate_public_key)
348 auto const local = endpoint(
"65.0.0.2:1024");
351 auto const [slot, result] = logic_.newOutboundSlot(endpoint(
"55.104.0.2:1025"));
352 ASSERT_NE(slot,
nullptr);
353 EXPECT_EQ(result, Result::Success);
355 auto const [slot2, result2] = logic_.newOutboundSlot(endpoint(
"55.104.0.2:1026"));
356 ASSERT_NE(slot2,
nullptr);
357 EXPECT_EQ(result2, Result::Success);
359 EXPECT_TRUE(logic_.onConnected(slot, local));
360 EXPECT_TRUE(logic_.onConnected(slot2, local));
362 EXPECT_EQ(logic_.activate(slot, publicKey,
false), Result::Success);
363 EXPECT_EQ(logic_.activate(slot2, publicKey,
false), Result::DuplicatePeer);
365 logic_.onClosed(slot);
367 EXPECT_EQ(logic_.activate(slot2, publicKey,
false), Result::Success);
368 logic_.onClosed(slot2);
371TEST_F(PeerFinderTest, activate_rejects_inbound_when_inbound_connections_are_disabled)
376 auto const local = endpoint(
"65.0.0.2:1024");
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);
382 EXPECT_EQ(logic_.activate(slot, publicKey,
false), Result::InboundDisabled);
387 config.listeningPort = 1024;
390 logic_.config(config);
393 EXPECT_EQ(logic_.activate(slot, publicKey,
false), Result::Success);
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);
400 EXPECT_EQ(logic_.activate(slot2, publicKey2,
false), Result::Full);
402 logic_.onClosed(slot2);
403 logic_.onClosed(slot);
406TEST_F(PeerFinderTest, add_fixed_peer_rejects_endpoint_without_port)
408 EXPECT_THROW(logic_.addFixedPeer(
"test", endpoint(
"65.0.0.2")), std::runtime_error);
411TEST_F(PeerFinderTest, on_connected_rejects_self_connection)
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);
418 EXPECT_FALSE(logic_.onConnected(slot, local));
419 logic_.onClosed(slot);
422TEST(PeerFinderResult, converts_all_result_values_to_strings)
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");
432TEST(PeerFinderEndpoint, orders_by_address)
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};
439 endpoints, [](
Endpoint const& lhs,
Endpoint const& rhs) {
return lhs < rhs; });
441 EXPECT_EQ(endpoints.
front().address, low.address);
442 EXPECT_EQ(endpoints.
back().address, high.address);
445TEST(PeerFinderCounts, tracks_slot_states_and_capacity)
452 config.wantIncoming =
true;
453 counts.onConfig(config);
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());
462 EXPECT_EQ(counts.stateString(),
"0/1 out, 0/1 in, 0 connecting, 0 closing");
464 SlotImp inbound(endpoint(
"65.0.0.1:10001"), endpoint(
"65.0.0.2:10002"),
false, clock);
466 EXPECT_EQ(counts.acceptCount(), 1);
467 EXPECT_TRUE(counts.canActivate(inbound));
468 counts.remove(inbound);
469 EXPECT_EQ(counts.acceptCount(), 0);
471 inbound.activate(
clock.now());
473 EXPECT_EQ(counts.inboundActive(), 1);
474 EXPECT_EQ(counts.totalActive(), 1);
475 EXPECT_EQ(counts.inboundSlotsFree(), 0);
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);
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);
487 counts.remove(outbound);
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);
496 SlotImp extraOutbound(endpoint(
"65.0.0.6:10006"),
false, clock);
498 EXPECT_FALSE(counts.canActivate(extraOutbound));
500 SlotImp fixedOutbound(endpoint(
"65.0.0.7:10007"),
true, clock);
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);
509 SlotImp reservedOutbound(endpoint(
"65.0.0.8:10008"),
false, clock);
510 reservedOutbound.reserved(
true);
512 EXPECT_TRUE(counts.canActivate(reservedOutbound));
513 reservedOutbound.activate(
clock.now());
514 counts.add(reservedOutbound);
516 JsonPropertyStream
stream;
518 beast::PropertyStream::Map map(stream);
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);
529 SlotImp closing(endpoint(
"65.0.0.9:10009"), endpoint(
"65.0.0.10:10010"),
false, clock);
532 EXPECT_EQ(counts.closingCount(), 1);
533 counts.remove(closing);
537 std::vector<std::unique_ptr<SlotImp>> attempts;
545 saturatedAttempts.add(*attempts.
back());
548 EXPECT_EQ(saturatedAttempts.attemptsNeeded(), 0u);
552 counts.onConfig(disconnected);
553 EXPECT_TRUE(counts.isConnectedToNetwork());
556TEST(PeerFinderHandouts, filters_redirect_slot_and_connect_targets)
559 auto const remote = endpoint(
"65.0.0.2:10002");
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);
574 EXPECT_EQ(slotHandouts.slot(), slot);
575 EXPECT_FALSE(slotHandouts.full());
577 slotHandouts.tryInsert(
Endpoint{endpoint(
"65.0.0.4:10004"), tuning::kMaxHops + 1}));
578 EXPECT_FALSE(slotHandouts.tryInsert(
Endpoint{remote.atPort(12001), 1}));
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);
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);
600 EXPECT_FALSE(squelched.tryInsert(endpoint(
"65.0.0.9:12000")));
603TEST(PeerFinderHandouts, distributes_livecache_entries)
607 cache.insert(
Endpoint{endpoint(
"65.0.0.10:10010"), 1});
608 cache.insert(
Endpoint{endpoint(
"65.0.0.11:10011"), 2});
611 endpoint(
"65.0.0.1:10001"), endpoint(
"65.0.0.2:10002"),
false, clock);
613 endpoint(
"65.0.0.3:10003"), endpoint(
"65.0.0.4:10004"),
false, clock);
614 std::vector<SlotHandouts> targets;
618 handout(targets.
begin(), targets.
end(), cache.hops.begin(), cache.hops.end());
620 EXPECT_FALSE(targets.
front().list().empty());
621 EXPECT_FALSE(targets.
back().list().empty());
626 handout(targets.
begin(), targets.
begin() + 1, cache.hops.begin(), cache.hops.end());
627 EXPECT_TRUE(targets.
front().full());
630TEST_F(PeerFinderTest, preprocess_filters_invalid_duplicate_and_extra_self_endpoints)
632 auto const local = endpoint(
"65.0.0.1:10001");
633 auto const remote = endpoint(
"65.0.0.2:10002");
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},
641 Endpoint{endpoint(
"65.0.0.6:10006"), 1},
642 Endpoint{endpoint(
"65.0.0.6:10006"), 2}};
644 logic_.preprocess(slot, endpoints);
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);
653TEST_F(PeerFinderTest, on_endpoints_checks_neighbor_before_caching_it)
657 config.listeningPort = 1024;
660 logic_.config(config);
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);
668 ASSERT_EQ(logic_.activate(slot, publicKey,
false), Result::Success);
671 logic_.onEndpoints(slot, advertised);
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());
681 logic_.onEndpoints(slot, advertised);
682 EXPECT_EQ(logic_.livecache.size(), 1u);
683 EXPECT_EQ(logic_.bootcache.size(), 1u);
686 EXPECT_EQ(logic_.livecache.size(), 1u);
688 logic_.onClosed(slot);
691TEST_F(PeerFinderTest, on_endpoints_skips_failed_neighbor_connectivity_checks)
695 config.listeningPort = 1024;
698 logic_.config(config);
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);
707 ASSERT_EQ(logic_.activate(slot, publicKey,
false), Result::Success);
710 logic_.onEndpoints(slot, advertised);
711 EXPECT_TRUE(slot->checked);
712 EXPECT_FALSE(slot->canAccept);
715 logic_.onEndpoints(slot, advertised);
716 EXPECT_TRUE(logic_.livecache.empty());
718 logic_.onClosed(slot);
721TEST_F(PeerFinderTest, on_endpoints_waits_for_pending_connectivity_check)
725 config.listeningPort = 1024;
728 logic_.config(config);
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);
737 ASSERT_EQ(logic_.activate(slot, publicKey,
false), Result::Success);
740 logic_.onEndpoints(slot, advertised);
741 EXPECT_TRUE(slot->connectivityCheckInProgress);
744 logic_.onEndpoints(slot, advertised);
745 EXPECT_EQ(checker_.asyncConnects.size(), 1u);
746 EXPECT_TRUE(logic_.livecache.empty());
748 checker_.completeAsync =
true;
749 logic_.checkComplete(remote, remote.atPort(2459), boost::asio::error::operation_aborted);
750 slot->connectivityCheckInProgress =
false;
751 logic_.onClosed(slot);
754TEST_F(PeerFinderTest, builds_endpoint_messages_and_redirects_from_livecache)
758 config.wantIncoming =
true;
759 config.listeningPort = 2459;
763 logic_.config(config);
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});
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")));
774 ASSERT_EQ(logic_.activate(slot, publicKey,
false), Result::Success);
776 auto const messages = logic_.buildEndpointsForPeers();
777 ASSERT_EQ(messages.size(), 1u);
778 auto const& sent = messages.front().second;
782 EXPECT_TRUE(logic_.buildEndpointsForPeers().empty());
784 auto const redirects = logic_.redirect(slot);
785 EXPECT_FALSE(redirects.empty());
787 logic_.onClosed(slot);
790TEST_F(PeerFinderTest, autoconnect_uses_livecache_then_bootcache)
794 config.wantIncoming =
false;
798 logic_.config(config);
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);
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);
813TEST_F(PeerFinderTest, sources_redirects_status_and_validation_paths_are_exercised)
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);
822 failing->resultsToFetch.error = boost::asio::error::host_unreachable;
823 logic_.fetch(failing);
824 EXPECT_EQ(failing->fetchCount, 1);
827 logic_.addSource(dynamic);
828 ASSERT_EQ(logic_.sources.size(), 1u);
829 EXPECT_EQ(logic_.sources.front(), dynamic);
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);
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")));
842 JsonPropertyStream
stream;
844 beast::PropertyStream::Map map(stream);
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"));
853 DefaultCancelSource defaultCancel;
855 EXPECT_TRUE(results.addresses.empty());
856 defaultCancel.cancel();
857 defaultCancel.fetch(results, journal());
859 logic_.fetchSource = dynamic;
861 EXPECT_TRUE(logic_.stopping);
862 EXPECT_EQ(dynamic->cancelCount, 1);
865 logic_.fetch(ignored);
866 EXPECT_EQ(ignored->fetchCount, 0);
868 logic_.checkComplete(
869 endpoint(
"65.0.0.18:10018"), endpoint(
"65.0.0.19:10019"), boost::system::error_code{});
872TEST(PeerFinderBootcache, loads_unique_entries_and_clears_cache)
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)};
880 Bootcache cache(store, clock, journal());
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());
891 EXPECT_TRUE(cache.empty());
892 EXPECT_EQ(cache.begin(), cache.end());
895TEST(PeerFinderBootcache, records_connection_outcomes_and_persists_pending_updates)
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");
905 Bootcache cache(store, clock, journal());
907 EXPECT_TRUE(cache.insert(ep1));
908 EXPECT_FALSE(cache.insert(ep1));
910 cache.onSuccess(ep1);
911 EXPECT_TRUE(cache.insertStatic(ep1));
912 EXPECT_FALSE(cache.insertStatic(ep1));
914 EXPECT_TRUE(cache.insertStatic(ep2));
915 cache.onSuccess(ep3);
916 cache.onFailure(ep3);
917 cache.onFailure(ep4);
919 EXPECT_EQ(cache.size(), 4u);
921 JsonPropertyStream
stream;
923 beast::PropertyStream::Map map(stream);
926 EXPECT_TRUE(
stream.top().isMember(
"entries"));
927 EXPECT_EQ(
stream.top()[
"entries"].size(), 4u);
930 ASSERT_EQ(store.saves.size(), 1u);
931 auto const& saved = store.saves.front();
932 ASSERT_EQ(saved.size(), 4u);
935 EXPECT_EQ(savedValence(saved, ep3), -1);
936 EXPECT_EQ(savedValence(saved, ep4), -1);
939TEST(PeerFinderBootcache, periodic_activity_saves_after_cooldown)
941 using namespace std::chrono_literals;
943 CapturingStore store;
947 Bootcache cache(store, clock, journal());
948 EXPECT_TRUE(cache.insert(endpoint(
"65.0.0.1:10001")));
950 cache.periodicActivity();
951 EXPECT_TRUE(store.saves.empty());
954 cache.periodicActivity();
955 ASSERT_EQ(store.saves.size(), 1u);
957 cache.periodicActivity();
958 EXPECT_EQ(store.saves.size(), 1u);
961 EXPECT_EQ(store.saves.size(), 1u);
964TEST(PeerFinderBootcache, prunes_when_cache_exceeds_limit)
966 CapturingStore store;
968 Bootcache cache(store, clock, journal());
972 EXPECT_TRUE(cache.insert(endpoint(
980TEST(PeerFinderEndpoint, clamps_hops_to_overflow_bucket)
982 auto const address = endpoint(
"65.0.0.1:10001");
985 EXPECT_EQ(ep.address, address);
989TEST(PeerFinderSlotImp, tracks_state_and_recent_endpoints)
992 using namespace std::chrono_literals;
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);
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);
1011 auto const newLocal = endpoint(
"65.0.0.3:10002");
1012 auto const newRemote = endpoint(
"65.0.0.4:10003");
1015 inbound.localEndpoint(newLocal);
1016 inbound.remoteEndpoint(newRemote);
1017 inbound.publicKey(publicKey);
1018 inbound.reserved(
true);
1019 inbound.setListeningPort(2459);
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());
1028 inbound.state(State::Closing);
1029 EXPECT_EQ(inbound.state(), State::Closing);
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);
1038 outbound.state(State::Connected);
1039 outbound.activate(
clock.now());
1040 EXPECT_EQ(outbound.state(), State::Active);
1041 EXPECT_EQ(outbound.whenAcceptEndpoints,
clock.now());
1043 auto const recent = endpoint(
"65.0.0.5:10004");
1044 EXPECT_FALSE(outbound.recent.filter(recent, 2));
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));
1051 outbound.recent.insert(recent, 4);
1052 EXPECT_FALSE(outbound.recent.filter(recent, 1));
1054 outbound.recent.insert(recent, 1);
1055 EXPECT_TRUE(outbound.recent.filter(recent, 1));
1056 EXPECT_FALSE(outbound.recent.filter(recent, 0));
1060 EXPECT_FALSE(outbound.recent.filter(recent, 1));
1063TEST(PeerFinderConfig, writes_property_stream_and_compares_verify_endpoints)
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";
1075 config.verifyEndpoints =
false;
1077 JsonPropertyStream
stream;
1079 beast::PropertyStream::Map map(stream);
1080 config.onWrite(map);
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"));
1094 EXPECT_EQ(config, same);
1095 same.verifyEndpoints =
true;
1096 EXPECT_NE(config, same);
1099TEST(PeerFinderConfig, validator_and_standalone_settings_disable_auto_connect)
1101 PeerLimitConfig const limits{.maxPeers = 50, .inPeers = {}, .outPeers = {}};
1105 EXPECT_TRUE(config.peerPrivate);
1106 EXPECT_FALSE(config.autoConnect);
1107 EXPECT_FALSE(config.verifyEndpoints);
1108 EXPECT_EQ(config.ipLimit, 7);
1111TEST(PeerFinderConfig, calculates_outbound_peers_and_clamps_ip_limits)
1117 config.maxPeers = 100;
1118 EXPECT_EQ(config.calcOutPeers(), 15u);
1122 config.applyTuning();
1123 EXPECT_EQ(config.ipLimit, 1);
1127 explicitLimit.ipLimit = 99;
1128 explicitLimit.applyTuning();
1129 EXPECT_EQ(explicitLimit.ipLimit, 4);
1133 largeInbound.ipLimit = 0;
1134 largeInbound.applyTuning();
1135 EXPECT_EQ(largeInbound.ipLimit, 7);
1138TEST(PeerFinderConfig, applies_legacy_and_explicit_peer_limits)
1143 std::optional<std::uint16_t> maxPeers;
1144 std::optional<std::uint16_t> maxIn;
1145 std::optional<std::uint16_t> maxOut;
1147 std::uint16_t expectedOut;
1148 std::uint16_t expectedIn;
1149 std::uint16_t expectedIpLimit;
1152 std::vector<ConfigCase>
const cases{
1153 {.name =
"legacy no config",
1160 .expectedIpLimit = 2},
1161 {.name =
"legacy max_peers 0",
1168 .expectedIpLimit = 2},
1169 {.name =
"legacy max_peers 5",
1176 .expectedIpLimit = 1},
1177 {.name =
"legacy max_peers 20",
1184 .expectedIpLimit = 2},
1185 {.name =
"legacy max_peers 100",
1192 .expectedIpLimit = 6},
1193 {.name =
"legacy max_peers 20, private",
1200 .expectedIpLimit = 1},
1201 {.name =
"new in 100/out 10",
1208 .expectedIpLimit = 6},
1209 {.name =
"new in 0/out 10",
1216 .expectedIpLimit = 1},
1217 {.name =
"new in 100/out 10, private",
1224 .expectedIpLimit = 6}};
1226 for (
auto const& testCase : cases)
1228 SCOPED_TRACE(testCase.name);
1231 .maxPeers = testCase.maxPeers, .inPeers = testCase.maxIn, .outPeers = testCase.maxOut};
1238 EXPECT_EQ(counts.outMax(), testCase.expectedOut);
1239 EXPECT_EQ(counts.inMax(), testCase.expectedIn);
1240 EXPECT_EQ(config.ipLimit, testCase.expectedIpLimit);
1242 NiceMock<MockStore> store;
1243 allowEmptyStore(store);
1244 NiceMock<MockChecker> checker;
1247 logic.config(config);
1249 EXPECT_EQ(logic.config(), config);
1253TEST(PeerFinderConfig, rejects_incomplete_or_out_of_range_peer_limits)
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}};
1262 for (
auto const& limits : configs)
std::string toString() const
Returns a string representing the endpoint.
static Endpoint fromString(std::string const &s)
static TestSink & instance()
Stores IP addresses useful for gaining initial connections.
static constexpr int kStaticValence
Receives handouts for making automatic connections.
beast::aged_set< beast::ip::Address > Squelches
Manages the count of available connections for the various slots.
void onConfig(Config const &config)
Called when the config is set or changed.
The Livecache holds the short-lived relayed Endpoint messages.
The Logic for maintaining the list of Slot addresses.
Receives handouts for redirecting a connection.
Receives endpoints for a slot during periodic handouts.
A static or dynamic source of peer addresses.
Abstract persistence for PeerFinder data.
std::function< void(beast::ip::Endpoint, int)> load_callback
T emplace_back(T... args)
void stream(json::Value const &jv, Write const &write)
Stream compact JSON to the specified function.
constexpr std::uint32_t kNumberOfEndpoints
static constexpr auto kMaxConnectAttempts
Maximum number of simultaneous connection attempts.
constexpr std::uint32_t kMaxHops
static constexpr auto kBootcacheSize
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)
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.
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.
json::Value entry(jtx::Env &env, jtx::Account const &account, jtx::Account const &authorize)
std::uint32_t asUInt(AnyValue const &v)
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.
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.