test: Modularize Peerfinder component and migrate Peerfinder tests from Beast to GTest and GMock (#7054)

Co-authored-by: Alex Kremer <akremer@ripple.com>
This commit is contained in:
Marek Foss
2026-07-23 22:00:06 +01:00
committed by GitHub
parent b89d75a2d5
commit 4acccfeda8
48 changed files with 2308 additions and 1839 deletions

View File

@@ -1,9 +1,6 @@
Loop: xrpld.app xrpld.overlay
xrpld.app > xrpld.overlay
Loop: xrpld.app xrpld.peerfinder
xrpld.peerfinder ~= xrpld.app
Loop: xrpld.app xrpld.rpc
xrpld.rpc > xrpld.app

View File

@@ -25,6 +25,9 @@ libxrpl.nodestore > xrpl.config
libxrpl.nodestore > xrpl.json
libxrpl.nodestore > xrpl.nodestore
libxrpl.nodestore > xrpl.protocol
libxrpl.peerfinder > xrpl.basics
libxrpl.peerfinder > xrpl.peerfinder
libxrpl.peerfinder > xrpl.protocol
libxrpl.protocol > xrpl.basics
libxrpl.protocol > xrpl.json
libxrpl.protocol > xrpl.protocol
@@ -146,19 +149,13 @@ test.overlay > xrpl.config
test.overlay > xrpld.app
test.overlay > xrpld.core
test.overlay > xrpld.overlay
test.overlay > xrpld.peerfinder
test.overlay > xrpl.json
test.overlay > xrpl.nodestore
test.overlay > xrpl.peerfinder
test.overlay > xrpl.protocol
test.overlay > xrpl.resource
test.overlay > xrpl.server
test.overlay > xrpl.shamap
test.peerfinder > test.beast
test.peerfinder > test.unit_test
test.peerfinder > xrpl.basics
test.peerfinder > xrpld.core
test.peerfinder > xrpld.peerfinder
test.peerfinder > xrpl.protocol
test.protocol > test.jtx
test.protocol > test.unit_test
test.protocol > xrpl.basics
@@ -197,6 +194,7 @@ tests.libxrpl > xrpl.json
tests.libxrpl > xrpl.ledger
tests.libxrpl > xrpl.net
tests.libxrpl > xrpl.nodestore
tests.libxrpl > xrpl.peerfinder
tests.libxrpl > xrpl.protocol
tests.libxrpl > xrpl.protocol_autogen
tests.libxrpl > xrpl.resource
@@ -220,6 +218,8 @@ xrpl.nodestore > xrpl.basics
xrpl.nodestore > xrpl.config
xrpl.nodestore > xrpl.json
xrpl.nodestore > xrpl.protocol
xrpl.peerfinder > xrpl.basics
xrpl.peerfinder > xrpl.protocol
xrpl.protocol > xrpl.basics
xrpl.protocol > xrpl.json
xrpl.protocol_autogen > xrpl.json
@@ -253,6 +253,7 @@ xrpld.app > xrpl.json
xrpld.app > xrpl.ledger
xrpld.app > xrpl.net
xrpld.app > xrpl.nodestore
xrpld.app > xrpl.peerfinder
xrpld.app > xrpl.protocol
xrpld.app > xrpl.rdb
xrpld.app > xrpl.resource
@@ -277,15 +278,16 @@ xrpld.overlay > xrpld.core
xrpld.overlay > xrpld.peerfinder
xrpld.overlay > xrpl.json
xrpld.overlay > xrpl.ledger
xrpld.overlay > xrpl.peerfinder
xrpld.overlay > xrpl.protocol
xrpld.overlay > xrpl.resource
xrpld.overlay > xrpl.server
xrpld.overlay > xrpl.shamap
xrpld.overlay > xrpl.tx
xrpld.peerfinder > xrpl.basics
xrpld.peerfinder > xrpl.config
xrpld.peerfinder > xrpld.app
xrpld.peerfinder > xrpld.core
xrpld.peerfinder > xrpl.protocol
xrpld.peerfinder > xrpl.peerfinder
xrpld.peerfinder > xrpl.rdb
xrpld.perflog > xrpl.basics
xrpld.perflog > xrpl.config

View File

@@ -133,6 +133,12 @@ target_link_libraries(
add_module(xrpl resource)
target_link_libraries(xrpl.libxrpl.resource PUBLIC xrpl.libxrpl.protocol)
add_module(xrpl peerfinder)
target_link_libraries(
xrpl.libxrpl.peerfinder
PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.protocol
)
# Level 08
add_module(xrpl net)
target_link_libraries(
@@ -227,6 +233,7 @@ target_link_modules(
ledger
net
nodestore
peerfinder
protocol
protocol_autogen
rdb

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/container/detail/aged_associative_container.h>
#include <xrpl/beast/container/detail/aged_container_iterator.h>
#include <xrpl/beast/container/detail/empty_base_optimization.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp>

View File

@@ -0,0 +1,163 @@
#pragma once
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
namespace xrpl::PeerFinder {
struct PeerLimitConfig
{
std::optional<std::size_t> maxPeers;
std::optional<std::size_t> inPeers;
std::optional<std::size_t> outPeers;
};
/**
* PeerFinder configuration settings.
*/
struct Config
{
/**
* The largest number of public peer slots to allow.
* This includes both inbound and outbound, but does not include
* fixed peers.
*/
std::size_t maxPeers{Tuning::kDefaultMaxPeers};
/**
* The number of automatic outbound connections to maintain.
* Outbound connections are only maintained if autoConnect
* is `true`.
*/
std::size_t outPeers = calcOutPeers(); // Note: relies on `maxPeers` being initialized
/**
* The number of automatic inbound connections to maintain.
* Inbound connections are only maintained if wantIncoming
* is `true`.
*/
std::size_t inPeers{0};
/**
* `true` if we want our IP address kept private.
*/
bool peerPrivate = true;
/**
* `true` if we want to accept incoming connections.
*/
bool wantIncoming{true};
/**
* `true` if we want to establish connections automatically
*/
bool autoConnect{true};
/**
* The listening port number.
*/
std::uint16_t listeningPort{0};
/**
* The set of features we advertise.
*/
std::string features;
/**
* Limit how many incoming connections we allow per IP
*/
int ipLimit{0};
/**
* `true` if we want to verify endpoints in TMEndpoints messages
*/
bool verifyEndpoints = true;
//--------------------------------------------------------------------------
/**
* Returns a suitable value for outPeers according to the rules.
*/
[[nodiscard]] std::size_t
calcOutPeers() const;
/**
* Adjusts the values so they follow the business rules.
*/
void
applyTuning();
/**
* Write the configuration into a property stream
*/
void
onWrite(beast::PropertyStream::Map& map) const;
/**
* Make PeerFinder::Config from peer limit and server mode parameters.
*/
static Config
makeConfig(
bool peerPrivate,
bool standalone,
PeerLimitConfig const& limits,
std::uint16_t port,
bool validationPublicKey,
int ipLimit,
bool verifyEndpoints);
/**
* Compares two configurations for equality field by field.
*/
friend bool
operator==(Config const& lhs, Config const& rhs) = default;
};
//------------------------------------------------------------------------------
/**
* Possible results from activating a slot.
*/
enum class Result { InboundDisabled, DuplicatePeer, IpLimitExceeded, Full, Success };
/**
* @brief Converts a `Result` enum value to its string representation.
*
* This function provides a human-readable string for a given `Result` enum,
* which is useful for logging, debugging, or displaying status messages.
*
* @param result The `Result` enum value to convert.
* @return A `std::string_view` representing the enum value. Returns "unknown"
* if the enum value is not explicitly handled.
*
* @note This function returns a `std::string_view` for performance.
* A `std::string` would need to allocate memory on the heap and copy the
* string literal into it every time the function is called.
*/
inline std::string_view
to_string(Result result) noexcept
{
switch (result)
{
case Result::InboundDisabled:
return "inbound disabled";
case Result::DuplicatePeer:
return "peer already connected";
case Result::IpLimitExceeded:
return "ip limit exceeded";
case Result::Full:
return "slots full";
case Result::Success:
return "success";
}
return "unknown";
}
} // namespace xrpl::PeerFinder

View File

@@ -0,0 +1,179 @@
#pragma once
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/peerfinder/Config.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/protocol/PublicKey.h>
#include <boost/asio/ip/tcp.hpp>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace xrpl::PeerFinder {
/**
* Maintains a set of IP addresses used for getting into the network.
*/
class Manager : public beast::PropertyStream::Source
{
protected:
Manager() noexcept;
public:
/**
* Destroy the object.
* Any pending source fetch operations are aborted.
* There may be some listener calls made before the
* destructor returns.
*/
~Manager() override = default;
/**
* Set the configuration for the manager.
* The new settings will be applied asynchronously.
* Thread safety:
* Can be called from any threads at any time.
*/
virtual void
setConfig(Config const& config) = 0;
/**
* Transition to the started state, synchronously.
*/
virtual void
start() = 0;
/**
* Transition to the stopped state, synchronously.
*/
virtual void
stop() = 0;
/**
* Returns the configuration for the manager.
*/
virtual Config
config() = 0;
/**
* Add a peer that should always be connected.
* This is useful for maintaining a private cluster of peers.
* The string is the name as specified in the configuration
* file, along with the set of corresponding IP addresses.
*/
virtual void
addFixedPeer(std::string_view name, std::vector<beast::IP::Endpoint> const& addresses) = 0;
/**
* Add a set of strings as fallback IP::Endpoint sources.
* @param name A label used for diagnostics.
*/
virtual void
addFallbackStrings(std::string const& name, std::vector<std::string> const& strings) = 0;
/**
* Add a URL as a fallback location to obtain IP::Endpoint sources.
* @param name A label used for diagnostics.
*/
/* VFALCO NOTE Unimplemented
virtual void addFallbackURL (std::string const& name,
std::string const& url) = 0;
*/
//--------------------------------------------------------------------------
/**
* Create a new inbound slot with the specified remote endpoint.
* If nullptr is returned, then the slot could not be assigned.
* Usually this is because of a detected self-connection.
*/
virtual std::pair<std::shared_ptr<Slot>, Result>
newInboundSlot(
beast::IP::Endpoint const& localEndpoint,
beast::IP::Endpoint const& remoteEndpoint) = 0;
/**
* Create a new outbound slot with the specified remote endpoint.
* If nullptr is returned, then the slot could not be assigned.
* Usually this is because of a duplicate connection.
*/
virtual std::pair<std::shared_ptr<Slot>, Result>
newOutboundSlot(beast::IP::Endpoint const& remoteEndpoint) = 0;
/**
* Called when mtENDPOINTS is received.
*/
virtual void
onEndpoints(std::shared_ptr<Slot> const& slot, Endpoints const& endpoints) = 0;
/**
* Called when the slot is closed.
* This always happens when the socket is closed, unless the socket
* was canceled.
*/
virtual void
onClosed(std::shared_ptr<Slot> const& slot) = 0;
/**
* Called when an outbound connection is deemed to have failed
*/
virtual void
onFailure(std::shared_ptr<Slot> const& slot) = 0;
/**
* Called when we received redirect IPs from a busy peer.
*/
virtual void
onRedirects(
boost::asio::ip::tcp::endpoint const& remoteAddress,
std::vector<boost::asio::ip::tcp::endpoint> const& eps) = 0;
//--------------------------------------------------------------------------
/**
* Called when an outbound connection attempt succeeds.
* The local endpoint must be valid. If the caller receives an error
* when retrieving the local endpoint from the socket, it should
* proceed as if the connection attempt failed by calling on_closed
* instead of on_connected.
* @return `true` if the connection should be kept
*/
virtual bool
onConnected(std::shared_ptr<Slot> const& slot, beast::IP::Endpoint const& localEndpoint) = 0;
/**
* Request an active slot type.
*/
virtual Result
activate(std::shared_ptr<Slot> const& slot, PublicKey const& key, bool reserved) = 0;
/**
* Returns a set of endpoints suitable for redirection.
*/
virtual std::vector<Endpoint>
redirect(std::shared_ptr<Slot> const& slot) = 0;
/**
* Return a set of addresses we should connect to.
*/
virtual std::vector<beast::IP::Endpoint>
autoconnect() = 0;
virtual std::vector<std::pair<std::shared_ptr<Slot>, std::vector<Endpoint>>>
buildEndpointsForPeers() = 0;
/**
* Perform periodic activity.
* This should be called once per second.
*/
virtual void
oncePerSecond() = 0;
};
} // namespace xrpl::PeerFinder

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/beast/clock/abstract_clock.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <chrono>
#include <cstdint>
#include <vector>
namespace xrpl::PeerFinder {
using clock_type = beast::AbstractClock<std::chrono::steady_clock>;
/**
* Represents a set of addresses.
*/
using IPAddresses = std::vector<beast::IP::Endpoint>;
//------------------------------------------------------------------------------
/**
* Describes a connectable peer address along with some metadata.
*/
struct Endpoint
{
Endpoint() = default;
Endpoint(beast::IP::Endpoint ep, std::uint32_t hops);
std::uint32_t hops = 0;
beast::IP::Endpoint address;
};
inline bool
operator<(Endpoint const& lhs, Endpoint const& rhs)
{
return lhs.address < rhs.address;
}
/**
* A set of Endpoint used for connecting.
*/
using Endpoints = std::vector<Endpoint>;
} // namespace xrpl::PeerFinder

View File

@@ -1,11 +1,11 @@
#pragma once
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/Store.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/Store.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <boost/bimap.hpp>
#include <boost/bimap/multiset_of.hpp>

View File

@@ -1,11 +1,10 @@
#pragma once
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/peerfinder/Config.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <cstddef>
#include <sstream>

View File

@@ -1,7 +1,7 @@
#pragma once
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <algorithm>
#include <chrono>

View File

@@ -1,12 +1,11 @@
#pragma once
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/SlotImp.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpl/beast/container/aged_set.h>
#include <xrpl/beast/net/IPAddress.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/SlotImp.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <algorithm>
#include <cstddef>

View File

@@ -1,9 +1,5 @@
#pragma once
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpld/peerfinder/detail/iosformat.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/container/aged_map.h>
@@ -12,6 +8,8 @@
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/beast/utility/maybe_const.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <boost/intrusive/list.hpp>
#include <boost/iterator/transform_iterator.hpp>
@@ -22,6 +20,8 @@
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iomanip>
#include <ios>
#include <iterator>
#include <memory>
#include <sstream>
@@ -411,7 +411,7 @@ Livecache<Allocator>::expire()
}
if (n > 0)
{
JLOG(journal_.debug()) << beast::Leftw(18) << "Livecache expired " << n
JLOG(journal_.debug()) << std::left << std::setw(18) << "Livecache expired " << n
<< ((n > 1) ? " entries" : " entry");
}
}
@@ -434,7 +434,7 @@ Livecache<Allocator>::insert(Endpoint const& ep)
if (result.second)
{
hops.insert(e);
JLOG(journal_.debug()) << beast::Leftw(18) << "Livecache insert " << ep.address
JLOG(journal_.debug()) << std::left << std::setw(18) << "Livecache insert " << ep.address
<< " at hops " << ep.hops;
return;
}
@@ -442,7 +442,7 @@ Livecache<Allocator>::insert(Endpoint const& ep)
{
// Drop duplicates at higher hops
std::size_t const excess(ep.hops - e.endpoint.hops);
JLOG(journal_.trace()) << beast::Leftw(18) << "Livecache drop " << ep.address
JLOG(journal_.trace()) << std::left << std::setw(18) << "Livecache drop " << ep.address
<< " at hops +" << excess;
return;
}
@@ -453,12 +453,12 @@ Livecache<Allocator>::insert(Endpoint const& ep)
if (ep.hops < e.endpoint.hops)
{
hops.reinsert(e, ep.hops);
JLOG(journal_.debug()) << beast::Leftw(18) << "Livecache update " << ep.address
JLOG(journal_.debug()) << std::left << std::setw(18) << "Livecache update " << ep.address
<< " at hops " << ep.hops;
}
else
{
JLOG(journal_.trace()) << beast::Leftw(18) << "Livecache refresh " << ep.address
JLOG(journal_.trace()) << std::left << std::setw(18) << "Livecache refresh " << ep.address
<< " at hops " << ep.hops;
}
}

View File

@@ -1,17 +1,5 @@
#pragma once
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpld/peerfinder/detail/Bootcache.h>
#include <xrpld/peerfinder/detail/Counts.h>
#include <xrpld/peerfinder/detail/Fixed.h>
#include <xrpld/peerfinder/detail/Handouts.h>
#include <xrpld/peerfinder/detail/Livecache.h>
#include <xrpld/peerfinder/detail/SlotImp.h>
#include <xrpld/peerfinder/detail/Source.h>
#include <xrpld/peerfinder/detail/Store.h>
#include <xrpld/peerfinder/detail/iosformat.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/random.h>
@@ -22,18 +10,34 @@
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/peerfinder/Config.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/Bootcache.h>
#include <xrpl/peerfinder/detail/Counts.h>
#include <xrpl/peerfinder/detail/Fixed.h>
#include <xrpl/peerfinder/detail/Handouts.h>
#include <xrpl/peerfinder/detail/Livecache.h>
#include <xrpl/peerfinder/detail/SlotImp.h>
#include <xrpl/peerfinder/detail/Source.h>
#include <xrpl/peerfinder/detail/Store.h>
#include <xrpl/protocol/PublicKey.h>
#include <boost/asio/error.hpp>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iomanip>
#include <ios>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
@@ -197,8 +201,8 @@ public:
if (result.second)
{
JLOG(journal.debug())
<< beast::Leftw(18) << "Logic add fixed '" << name << "' at " << remoteAddress;
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic add fixed '" << name
<< "' at " << remoteAddress;
return;
}
}
@@ -221,7 +225,7 @@ public:
if (iter == slots.end())
{
// The slot disconnected before we finished the check
JLOG(journal.debug()) << beast::Leftw(18) << "Logic tested " << checkedAddress
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic tested " << checkedAddress
<< " but the connection was closed";
return;
}
@@ -255,7 +259,7 @@ public:
beast::IP::Endpoint const& localEndpoint,
beast::IP::Endpoint const& remoteEndpoint)
{
JLOG(journal.debug()) << beast::Leftw(18) << "Logic accept" << remoteEndpoint
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic accept" << remoteEndpoint
<< " on local " << localEndpoint;
std::scoped_lock const _(lock);
@@ -266,7 +270,7 @@ public:
auto const count = connectedAddresses.count(remoteEndpoint.address());
if (count + 1 > config_.ipLimit)
{
JLOG(journal.debug()) << beast::Leftw(18) << "Logic dropping inbound "
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic dropping inbound "
<< remoteEndpoint << " because of ip limits.";
return {SlotImp::ptr(), Result::IpLimitExceeded};
}
@@ -275,8 +279,8 @@ public:
// Check for duplicate connection
if (slots.contains(remoteEndpoint))
{
JLOG(journal.debug()) << beast::Leftw(18) << "Logic dropping " << remoteEndpoint
<< " as duplicate incoming";
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic dropping "
<< remoteEndpoint << " as duplicate incoming";
return {SlotImp::ptr(), Result::DuplicatePeer};
}
@@ -304,15 +308,15 @@ public:
std::pair<SlotImp::ptr, Result>
newOutboundSlot(beast::IP::Endpoint const& remoteEndpoint)
{
JLOG(journal.debug()) << beast::Leftw(18) << "Logic connect " << remoteEndpoint;
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic connect " << remoteEndpoint;
std::scoped_lock const _(lock);
// Check for duplicate connection
if (slots.contains(remoteEndpoint))
{
JLOG(journal.debug()) << beast::Leftw(18) << "Logic dropping " << remoteEndpoint
<< " as duplicate connect";
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic dropping "
<< remoteEndpoint << " as duplicate connect";
return {SlotImp::ptr(), Result::DuplicatePeer};
}
@@ -506,15 +510,15 @@ public:
if (!h.list().empty())
{
JLOG(journal.debug())
<< beast::Leftw(18) << "Logic connect " << h.list().size() << " fixed";
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic connect "
<< h.list().size() << " fixed";
return h.list();
}
if (counts_.attempts() > 0)
{
JLOG(journal.debug())
<< beast::Leftw(18) << "Logic waiting on " << counts_.attempts() << " attempts";
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic waiting on "
<< counts_.attempts() << " attempts";
return none;
}
}
@@ -535,14 +539,14 @@ public:
if (!h.list().empty())
{
JLOG(journal.debug())
<< beast::Leftw(18) << "Logic connect " << h.list().size() << " live "
<< std::left << std::setw(18) << "Logic connect " << h.list().size() << " live "
<< ((h.list().size() > 1) ? "endpoints" : "endpoint");
return h.list();
}
if (counts_.attempts() > 0)
{
JLOG(journal.debug())
<< beast::Leftw(18) << "Logic waiting on " << counts_.attempts() << " attempts";
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic waiting on "
<< counts_.attempts() << " attempts";
return none;
}
}
@@ -568,8 +572,9 @@ public:
if (!h.list().empty())
{
JLOG(journal.debug()) << beast::Leftw(18) << "Logic connect " << h.list().size()
<< " boot " << ((h.list().size() > 1) ? "addresses" : "address");
JLOG(journal.debug()) << std::left << std::setw(18) << "Logic connect "
<< h.list().size() << " boot "
<< ((h.list().size() > 1) ? "addresses" : "address");
return h.list();
}
@@ -689,8 +694,8 @@ public:
// Enforce hop limit
if (ep.hops > Tuning::kMaxHops)
{
JLOG(journal.debug()) << beast::Leftw(18) << "Endpoints drop " << ep.address
<< " for excess hops " << ep.hops;
JLOG(journal.debug()) << std::left << std::setw(18) << "Endpoints drop "
<< ep.address << " for excess hops " << ep.hops;
iter = list.erase(iter);
continue;
}
@@ -706,18 +711,18 @@ public:
}
else
{
JLOG(journal.debug())
<< beast::Leftw(18) << "Endpoints drop " << ep.address << " for extra self";
JLOG(journal.debug()) << std::left << std::setw(18) << "Endpoints drop "
<< ep.address << " for extra self";
iter = list.erase(iter);
continue;
}
}
// Discard invalid addresses
if (config_.verifyEndpoints && !isValidAddress(ep.address))
if (!isValidAddress(ep.address))
{
JLOG(journal.debug())
<< beast::Leftw(18) << "Endpoints drop " << ep.address << " as invalid";
JLOG(journal.debug()) << std::left << std::setw(18) << "Endpoints drop "
<< ep.address << " as invalid";
iter = list.erase(iter);
continue;
}
@@ -727,8 +732,8 @@ public:
return ep.address == other.address;
}))
{
JLOG(journal.debug())
<< beast::Leftw(18) << "Endpoints drop " << ep.address << " as duplicate";
JLOG(journal.debug()) << std::left << std::setw(18) << "Endpoints drop "
<< ep.address << " as duplicate";
iter = list.erase(iter);
continue;
}
@@ -1074,13 +1079,13 @@ public:
if (!results.error)
{
int const count(addBootcacheAddresses(results.addresses));
JLOG(journal.info()) << beast::Leftw(18) << "Logic added " << count << " new "
JLOG(journal.info()) << std::left << std::setw(18) << "Logic added " << count << " new "
<< ((count == 1) ? "address" : "addresses") << " from "
<< source->name();
}
else
{
JLOG(journal.error()) << beast::Leftw(18) << "Logic failed "
JLOG(journal.error()) << std::left << std::setw(18) << "Logic failed "
<< "'" << source->name() << "' fetch, "
<< results.error.message();
}
@@ -1098,8 +1103,6 @@ public:
{
if (isUnspecified(address))
return false;
if (isLoopback(address))
return false;
if (!isPublic(address))
return false;
if (address.port() == 0)
@@ -1221,8 +1224,8 @@ Logic<Checker>::onRedirects(
bootcache.insert(beast::IPAddressConversion::fromAsio(*first));
if (n > 0)
{
JLOG(journal.trace()) << beast::Leftw(18) << "Logic add " << n << " redirect IPs from "
<< remoteAddress;
JLOG(journal.trace()) << std::left << std::setw(18) << "Logic add " << n
<< " redirect IPs from " << remoteAddress;
}
}

View File

@@ -1,10 +1,9 @@
#pragma once
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpl/beast/container/aged_unordered_map.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/protocol/PublicKey.h>
#include <atomic>
@@ -172,7 +171,7 @@ private:
std::optional<beast::IP::Endpoint> localEndpoint_;
std::optional<PublicKey> publicKey_;
static constexpr std::int32_t kUnknownPort = -1;
static std::int32_t constexpr kUnknownPort = -1;
std::atomic<std::int32_t> listeningPort_;
public:

View File

@@ -1,8 +1,7 @@
#pragma once
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/peerfinder/Types.h>
#include <boost/system/error_code.hpp>

View File

@@ -1,6 +1,6 @@
#pragma once
#include <xrpld/peerfinder/detail/Source.h>
#include <xrpl/peerfinder/detail/Source.h>
#include <memory>
#include <string>

View File

@@ -0,0 +1,36 @@
#pragma once
#include <xrpl/beast/insight/Collector.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/peerfinder/PeerfinderManager.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/Store.h>
#include <boost/asio/io_context.hpp>
#include <memory>
namespace xrpl::PeerFinder {
/**
* @brief Create a new Manager.
*
* @param ioContext The io_context used to schedule asynchronous work.
* @param clock The clock used for timekeeping.
* @param journal The journal used for logging.
* @param store The persistence backend for the bootstrap cache. The caller
* retains ownership and must keep it alive (and opened) for the lifetime of
* the returned Manager. This lets consumers supply their own Store
* implementation (e.g. the SQLite-backed StoreSqdb in xrpld).
* @param collector The collector used to report metrics.
* @return The newly created Manager.
*/
std::unique_ptr<Manager>
makeManager(
boost::asio::io_context& ioContext,
clock_type& clock,
beast::Journal journal,
Store& store,
beast::insight::Collector::ptr const& collector);
} // namespace xrpl::PeerFinder

View File

@@ -1,19 +1,19 @@
#include <xrpld/peerfinder/detail/Bootcache.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/Store.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpld/peerfinder/detail/iosformat.h>
#include <xrpl/peerfinder/detail/Bootcache.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/Store.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <ios>
#include <vector>
namespace xrpl::PeerFinder {
@@ -82,13 +82,14 @@ Bootcache::load()
auto const result(this->map_.insert(value_type(endpoint, valence)));
if (!result.second)
{
JLOG(this->journal_.error()) << beast::Leftw(18) << "Bootcache discard " << endpoint;
JLOG(this->journal_.error())
<< std::left << std::setw(18) << "Bootcache discard " << endpoint;
}
}));
if (n > 0)
{
JLOG(journal_.info()) << beast::Leftw(18) << "Bootcache loaded " << n
JLOG(journal_.info()) << std::left << std::setw(18) << "Bootcache loaded " << n
<< ((n > 1) ? " addresses" : " address");
prune();
}
@@ -100,7 +101,7 @@ Bootcache::insert(beast::IP::Endpoint const& endpoint)
auto const result(map_.insert(value_type(endpoint, 0)));
if (result.second)
{
JLOG(journal_.trace()) << beast::Leftw(18) << "Bootcache insert " << endpoint;
JLOG(journal_.trace()) << std::left << std::setw(18) << "Bootcache insert " << endpoint;
prune();
flagForUpdate();
}
@@ -121,7 +122,7 @@ Bootcache::insertStatic(beast::IP::Endpoint const& endpoint)
if (result.second)
{
JLOG(journal_.trace()) << beast::Leftw(18) << "Bootcache insert " << endpoint;
JLOG(journal_.trace()) << std::left << std::setw(18) << "Bootcache insert " << endpoint;
prune();
flagForUpdate();
}
@@ -146,8 +147,9 @@ Bootcache::onSuccess(beast::IP::Endpoint const& endpoint)
XRPL_ASSERT(result.second, "xrpl::PeerFinder::Bootcache::onSuccess : endpoint inserted");
}
Entry const& entry(result.first->right);
JLOG(journal_.info()) << beast::Leftw(18) << "Bootcache connect " << endpoint << " with "
<< entry.valence() << ((entry.valence() > 1) ? " successes" : " success");
JLOG(journal_.info()) << std::left << std::setw(18) << "Bootcache connect " << endpoint
<< " with " << entry.valence()
<< ((entry.valence() > 1) ? " successes" : " success");
flagForUpdate();
}
@@ -170,8 +172,8 @@ Bootcache::onFailure(beast::IP::Endpoint const& endpoint)
}
Entry const& entry(result.first->right);
auto const n(std::abs(entry.valence()));
JLOG(journal_.debug()) << beast::Leftw(18) << "Bootcache failed " << endpoint << " with " << n
<< ((n > 1) ? " attempts" : " attempt");
JLOG(journal_.debug()) << std::left << std::setw(18) << "Bootcache failed " << endpoint
<< " with " << n << ((n > 1) ? " attempts" : " attempt");
flagForUpdate();
}
@@ -209,17 +211,19 @@ Bootcache::prune()
// Work backwards because bimap doesn't handle
// erasing using a reverse iterator very well.
//
for (auto iter(map_.right.end()); count-- > 0 && iter != map_.right.begin(); ++pruned)
for (auto iter(map_.right.end()); count > 0 && iter != map_.right.begin(); ++pruned)
{
--count;
--iter;
beast::IP::Endpoint const& endpoint(iter->get_left());
Entry const& entry(iter->get_right());
JLOG(journal_.trace()) << beast::Leftw(18) << "Bootcache pruned" << endpoint
JLOG(journal_.trace()) << std::left << std::setw(18) << "Bootcache pruned" << endpoint
<< " at valence " << entry.valence();
iter = map_.right.erase(iter);
}
JLOG(journal_.debug()) << beast::Leftw(18) << "Bootcache pruned " << pruned << " entries total";
JLOG(journal_.debug()) << std::left << std::setw(18) << "Bootcache pruned " << pruned
<< " entries total";
}
// Updates the Store with the current set of entries if needed.

View File

@@ -0,0 +1,135 @@
#include <xrpl/peerfinder/Config.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
namespace xrpl::PeerFinder {
std::size_t
Config::calcOutPeers() const
{
return std::max(
((maxPeers * Tuning::kOutPercent) + 50) / 100, std::size_t(Tuning::kMinOutCount));
}
void
Config::applyTuning()
{
if (ipLimit == 0)
{
// Unless a limit is explicitly set, we allow between
// 2 and 5 connections from non RFC-1918 "private"
// IP addresses.
ipLimit = 2;
if (inPeers > Tuning::kDefaultMaxPeers)
ipLimit += std::min(5, static_cast<int>(inPeers / Tuning::kDefaultMaxPeers));
}
// We don't allow a single IP to consume all incoming slots,
// unless we only have one incoming slot available.
ipLimit = std::max(1, std::min(ipLimit, static_cast<int>(inPeers / 2)));
}
void
Config::onWrite(beast::PropertyStream::Map& map) const
{
map["max_peers"] = maxPeers;
map["out_peers"] = outPeers;
map["want_incoming"] = wantIncoming;
map["auto_connect"] = autoConnect;
map["port"] = listeningPort;
map["features"] = features;
map["ip_limit"] = ipLimit;
map["verify_endpoints"] = verifyEndpoints;
}
Config
Config::makeConfig(
bool peerPrivate,
bool standalone,
PeerLimitConfig const& limits,
std::uint16_t port,
bool validationPublicKey,
int ipLimit,
bool verifyEndpoints)
{
PeerFinder::Config config;
if (!limits.maxPeers)
{
if (limits.inPeers && !limits.outPeers)
throw std::runtime_error("Both inbound and outbound peer limits must be configured");
if (limits.outPeers && !limits.inPeers)
throw std::runtime_error("Both inbound and outbound peer limits must be configured");
if (limits.inPeers && *limits.inPeers > 1000)
throw std::runtime_error("Inbound peer limit must be less than or equal to 1000");
if (limits.outPeers && (*limits.outPeers < 10 || *limits.outPeers > 1000))
throw std::runtime_error("Outbound peer limit must be in the range 10-1000");
}
config.peerPrivate = peerPrivate;
// Servers with peer privacy don't want to allow incoming connections
config.wantIncoming = (!config.peerPrivate) && (port != 0);
if (limits.maxPeers || (!limits.inPeers && !limits.outPeers))
{
if (limits.maxPeers && *limits.maxPeers != 0)
config.maxPeers = *limits.maxPeers;
config.maxPeers = std::max<std::size_t>(config.maxPeers, Tuning::kMinOutCount);
config.outPeers = config.calcOutPeers();
// Calculate the number of outbound peers we want. If we dont want
// or can't accept incoming, this will simply be equal to maxPeers.
if (!config.wantIncoming)
config.outPeers = config.maxPeers;
// Calculate the largest number of inbound connections we could
// take.
if (config.maxPeers >= config.outPeers)
{
config.inPeers = config.maxPeers - config.outPeers;
}
else
{
config.inPeers = 0;
}
}
else
{
config.outPeers = *limits.outPeers;
config.inPeers = *limits.inPeers;
config.maxPeers = 0;
}
// This will cause servers configured as validators to request that
// peers they connect to never report their IP address. We set this
// after we set the 'wantIncoming' because we want a "soft" version
// of peer privacy unless the operator explicitly asks for it.
if (validationPublicKey)
config.peerPrivate = true;
// if it's a private peer or we are running as standalone
// automatic connections would defeat the purpose.
config.autoConnect = !standalone && !peerPrivate;
config.listeningPort = port;
config.features = "";
config.ipLimit = ipLimit;
config.verifyEndpoints = verifyEndpoints;
// Enforce business rules
config.applyTuning();
return config;
}
} // namespace xrpl::PeerFinder

View File

@@ -1,7 +1,5 @@
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/peerfinder/Types.h>
#include <algorithm>
#include <cstdint>

View File

@@ -1,11 +1,4 @@
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpld/peerfinder/detail/Checker.h>
#include <xrpld/peerfinder/detail/Logic.h>
#include <xrpld/peerfinder/detail/SlotImp.h>
#include <xrpld/peerfinder/detail/SourceStrings.h>
#include <xrpld/peerfinder/detail/StoreSqdb.h>
#include <xrpl/peerfinder/PeerfinderManager.h>
#include <xrpl/beast/insight/Collector.h>
#include <xrpl/beast/insight/Gauge.h>
@@ -13,7 +6,15 @@
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/peerfinder/Config.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/Checker.h>
#include <xrpl/peerfinder/detail/Logic.h>
#include <xrpl/peerfinder/detail/SlotImp.h>
#include <xrpl/peerfinder/detail/SourceStrings.h>
#include <xrpl/peerfinder/detail/Store.h>
#include <xrpl/peerfinder/make_Manager.h>
#include <xrpl/protocol/PublicKey.h>
#include <boost/asio/executor_work_guard.hpp>
@@ -38,10 +39,9 @@ public:
std::optional<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_;
clock_type& clock_;
beast::Journal journal_;
StoreSqdb store_;
Store& store_;
Checker<boost::asio::ip::tcp> checker_;
Logic<decltype(checker_)> logic_;
BasicConfig const& config_;
// NOLINTEND(readability-identifier-naming)
//--------------------------------------------------------------------------
@@ -50,16 +50,15 @@ public:
boost::asio::io_context& ioContext,
clock_type& clock,
beast::Journal journal,
BasicConfig const& config,
Store& store,
beast::insight::Collector::ptr const& collector)
: io_context_(ioContext)
, work_(std::in_place, boost::asio::make_work_guard(io_context_))
, clock_(clock)
, journal_(journal)
, store_(journal)
, store_(store)
, checker_(io_context_)
, logic_(clock, store_, checker_, journal)
, config_(config)
, stats_([this] { collectMetrics(); }, collector)
{
}
@@ -206,7 +205,6 @@ public:
void
start() override
{
store_.open(config_);
logic_.load();
}
@@ -261,10 +259,10 @@ makeManager(
boost::asio::io_context& ioContext,
clock_type& clock,
beast::Journal journal,
BasicConfig const& config,
Store& store,
beast::insight::Collector::ptr const& collector)
{
return std::make_unique<ManagerImp>(ioContext, clock, journal, config, collector);
return std::make_unique<ManagerImp>(ioContext, clock, journal, store, collector);
}
} // namespace xrpl::PeerFinder

View File

@@ -1,12 +1,10 @@
#include <xrpld/peerfinder/detail/SlotImp.h>
#include <xrpl/peerfinder/detail/SlotImp.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpl/beast/container/detail/aged_unordered_container.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <cstdint>
#include <utility>

View File

@@ -1,9 +1,8 @@
#include <xrpld/peerfinder/detail/SourceStrings.h>
#include <xrpld/peerfinder/detail/Source.h>
#include <xrpl/peerfinder/detail/SourceStrings.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/peerfinder/detail/Source.h>
#include <memory>
#include <string>

View File

@@ -8,7 +8,6 @@
#include <xrpld/overlay/detail/PeerImp.h>
#include <xrpld/overlay/detail/ProtocolVersion.h>
#include <xrpld/overlay/detail/Tuning.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
@@ -16,6 +15,7 @@
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SecretKey.h>

View File

@@ -9,12 +9,12 @@
#include <xrpld/overlay/detail/OverlayImpl.h>
#include <xrpld/overlay/detail/PeerImp.h>
#include <xrpld/overlay/detail/ProtocolVersion.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/make_SSLContext.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SecretKey.h>

View File

@@ -1,212 +0,0 @@
#include <test/beast/IPEndpointCommon.h>
#include <test/unit_test/SuiteJournal.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/Livecache.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <array>
#include <cstdint>
#include <iterator>
#include <utility>
#include <vector>
namespace xrpl::PeerFinder {
bool
operator==(Endpoint const& a, Endpoint const& b)
{
return (a.hops == b.hops && a.address == b.address);
}
class Livecache_test : public beast::unit_test::Suite
{
TestStopwatch clock_;
test::SuiteJournal journal_;
public:
Livecache_test() : journal_("Livecache_test", *this)
{
}
// Add the address as an endpoint
template <class C>
void
add(beast::IP::Endpoint ep, C& c, std::uint32_t hops = 0)
{
Endpoint const cep{ep, hops};
c.insert(cep);
}
void
testBasicInsert()
{
testcase("Basic Insert");
Livecache<> c(clock_, journal_);
BEAST_EXPECT(c.empty());
for (auto i = 0; i < 10; ++i)
add(beast::IP::randomEP(true), c);
BEAST_EXPECT(!c.empty());
BEAST_EXPECT(c.size() == 10);
for (auto i = 0; i < 10; ++i)
add(beast::IP::randomEP(false), c);
BEAST_EXPECT(!c.empty());
BEAST_EXPECT(c.size() == 20);
}
void
testInsertUpdate()
{
testcase("Insert/Update");
Livecache<> c(clock_, journal_);
auto ep1 = Endpoint{beast::IP::randomEP(), 2};
c.insert(ep1);
BEAST_EXPECT(c.size() == 1);
// third position list will contain the entry
BEAST_EXPECT((c.hops.begin() + 2)->begin()->hops == 2);
auto ep2 = Endpoint{ep1.address, 4};
// this will not change the entry has higher hops
c.insert(ep2);
BEAST_EXPECT(c.size() == 1);
// still in third position list
BEAST_EXPECT((c.hops.begin() + 2)->begin()->hops == 2);
auto ep3 = Endpoint{ep1.address, 2};
// this will not change the entry has the same hops as existing
c.insert(ep3);
BEAST_EXPECT(c.size() == 1);
// still in third position list
BEAST_EXPECT((c.hops.begin() + 2)->begin()->hops == 2);
auto ep4 = Endpoint{ep1.address, 1};
c.insert(ep4);
BEAST_EXPECT(c.size() == 1);
// now at second position list
BEAST_EXPECT((c.hops.begin() + 1)->begin()->hops == 1);
}
void
testExpire()
{
testcase("Expire");
using namespace std::chrono_literals;
Livecache<> c(clock_, journal_);
auto ep1 = Endpoint{beast::IP::randomEP(), 1};
c.insert(ep1);
BEAST_EXPECT(c.size() == 1);
c.expire();
BEAST_EXPECT(c.size() == 1);
// verify that advancing to 1 sec before expiration
// leaves our entry intact
clock_.advance(Tuning::kLiveCacheSecondsToLive - 1s);
c.expire();
BEAST_EXPECT(c.size() == 1);
// now advance to the point of expiration
clock_.advance(1s);
c.expire();
BEAST_EXPECT(c.empty());
}
void
testHistogram()
{
testcase("Histogram");
static constexpr auto kNumEps = 40;
Livecache<> c(clock_, journal_);
for (auto i = 0; i < kNumEps; ++i)
add(beast::IP::randomEP(true), c, xrpl::randInt<std::uint32_t>());
auto h = c.hops.histogram();
if (!BEAST_EXPECT(!h.empty()))
return;
std::vector<std::string> v;
boost::split(v, h, boost::algorithm::is_any_of(","));
auto sum = 0;
for (auto const& n : v)
{
auto val = boost::lexical_cast<int>(boost::trim_copy(n));
sum += val;
BEAST_EXPECT(val >= 0);
}
BEAST_EXPECT(sum == kNumEps);
}
void
testShuffle()
{
testcase("Shuffle");
Livecache<> c(clock_, journal_);
for (auto i = 0; i < 100; ++i)
add(beast::IP::randomEP(true), c, xrpl::randInt(Tuning::kMaxHops + 1));
using at_hop = std::vector<xrpl::PeerFinder::Endpoint>;
using all_hops = std::array<at_hop, 1 + Tuning::kMaxHops + 1>;
auto cmpEp = [](Endpoint const& a, Endpoint const& b) {
return (b.hops < a.hops || (b.hops == a.hops && b.address < a.address));
};
all_hops before;
all_hops beforeSorted;
for (auto i = std::make_pair(0, c.hops.begin()); i.second != c.hops.end();
++i.first, ++i.second)
{
std::ranges::copy(*i.second, std::back_inserter(before[i.first]));
std::ranges::copy(*i.second, std::back_inserter(beforeSorted[i.first]));
std::ranges::sort(beforeSorted[i.first], cmpEp);
}
c.hops.shuffle();
all_hops after;
all_hops afterSorted;
for (auto i = std::make_pair(0, c.hops.begin()); i.second != c.hops.end();
++i.first, ++i.second)
{
std::ranges::copy(*i.second, std::back_inserter(after[i.first]));
std::ranges::copy(*i.second, std::back_inserter(afterSorted[i.first]));
std::ranges::sort(afterSorted[i.first], cmpEp);
}
// each hop bucket should contain the same items
// before and after sort, albeit in different order
bool allMatch = true;
for (auto i = 0; i < before.size(); ++i)
{
BEAST_EXPECT(before[i].size() == after[i].size());
allMatch = allMatch && (before[i] == after[i]);
BEAST_EXPECT(beforeSorted[i] == afterSorted[i]);
}
BEAST_EXPECT(!allMatch);
}
void
run() override
{
testBasicInsert();
testInsertUpdate();
testExpire();
testHistogram();
testShuffle();
}
};
BEAST_DEFINE_TESTSUITE(Livecache, peerfinder, xrpl);
} // namespace xrpl::PeerFinder

View File

@@ -1,789 +0,0 @@
#include <test/unit_test/SuiteJournal.h>
#include <xrpld/core/Config.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/Counts.h>
#include <xrpld/peerfinder/detail/Logic.h>
#include <xrpld/peerfinder/detail/Store.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SecretKey.h>
#include <boost/system/detail/error_code.hpp>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace xrpl::PeerFinder {
class PeerFinder_test : public beast::unit_test::Suite
{
test::SuiteJournal journal_;
public:
PeerFinder_test() : journal_("PeerFinder_test", *this)
{
}
struct TestStore : Store
{
std::size_t
load(load_callback const& cb) override
{
return 0;
}
void
save(std::vector<Entry> const&) override
{
}
};
struct TestChecker
{
void
stop()
{
}
void
wait()
{
}
template <class Handler>
void
asyncConnect(beast::IP::Endpoint const& ep, Handler&& handler)
{
// NOLINTNEXTLINE(misc-const-correctness)
boost::system::error_code ec;
handler(ec);
}
};
void
testBackoff1()
{
auto const seconds = 10000;
testcase("backoff 1");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
logic.addFixedPeer("test", beast::IP::Endpoint::fromString("65.0.0.1:5"));
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
logic.config(c);
}
std::size_t n = 0;
for (std::size_t i = 0; i < seconds; ++i)
{
auto const list = logic.autoconnect();
if (!list.empty())
{
BEAST_EXPECT(list.size() == 1);
auto const [slot, _] = logic.newOutboundSlot(list.front());
BEAST_EXPECT(
logic.onConnected(slot, beast::IP::Endpoint::fromString("65.0.0.2:5")));
logic.onClosed(slot);
++n;
}
clock.advance(std::chrono::seconds(1));
logic.oncePerSecond();
}
// Less than 20 attempts
BEAST_EXPECT(n < 20);
}
// with activate
void
testBackoff2()
{
auto const seconds = 10000;
testcase("backoff 2");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
logic.addFixedPeer("test", beast::IP::Endpoint::fromString("65.0.0.1:5"));
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
logic.config(c);
}
PublicKey const pk(randomKeyPair(KeyType::Secp256k1).first);
std::size_t n = 0;
for (std::size_t i = 0; i < seconds; ++i)
{
auto const list = logic.autoconnect();
if (!list.empty())
{
BEAST_EXPECT(list.size() == 1);
auto const [slot, _] = logic.newOutboundSlot(list.front());
if (!BEAST_EXPECT(
logic.onConnected(slot, beast::IP::Endpoint::fromString("65.0.0.2:5"))))
return;
if (!BEAST_EXPECT(logic.activate(slot, pk, false) == PeerFinder::Result::Success))
return;
logic.onClosed(slot);
++n;
}
clock.advance(std::chrono::seconds(1));
logic.oncePerSecond();
}
// No more often than once per minute
BEAST_EXPECT(n <= (seconds + 59) / 60);
}
// test accepting an incoming slot for an already existing outgoing slot
void
testDuplicateOutIn()
{
testcase("duplicate out/in");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
c.ipLimit = 2;
logic.config(c);
}
auto const remote = beast::IP::Endpoint::fromString("65.0.0.1:5");
auto const [slot1, r] = logic.newOutboundSlot(remote);
BEAST_EXPECT(slot1 != nullptr);
BEAST_EXPECT(r == Result::Success);
BEAST_EXPECT(logic.connectedAddresses.count(remote.address()) == 1);
auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
auto const [slot2, r2] = logic.newInboundSlot(local, remote);
BEAST_EXPECT(logic.connectedAddresses.count(remote.address()) == 1);
BEAST_EXPECT(r2 == Result::DuplicatePeer);
if (!BEAST_EXPECT(slot2 == nullptr))
logic.onClosed(slot2);
logic.onClosed(slot1);
}
// test establishing outgoing slot for an already existing incoming slot
void
testDuplicateInOut()
{
testcase("duplicate in/out");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
c.ipLimit = 2;
logic.config(c);
}
auto const remote = beast::IP::Endpoint::fromString("65.0.0.1:5");
auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
auto const [slot1, r] = logic.newInboundSlot(local, remote);
BEAST_EXPECT(slot1 != nullptr);
BEAST_EXPECT(r == Result::Success);
BEAST_EXPECT(logic.connectedAddresses.count(remote.address()) == 1);
auto const [slot2, r2] = logic.newOutboundSlot(remote);
BEAST_EXPECT(r2 == Result::DuplicatePeer);
BEAST_EXPECT(logic.connectedAddresses.count(remote.address()) == 1);
if (!BEAST_EXPECT(slot2 == nullptr))
logic.onClosed(slot2);
logic.onClosed(slot1);
}
void
testPeerLimitExceeded()
{
testcase("peer limit exceeded");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
c.ipLimit = 2;
logic.config(c);
}
auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
auto const [slot, r] =
logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1025"));
BEAST_EXPECT(slot != nullptr);
BEAST_EXPECT(r == Result::Success);
auto const [slot1, r1] =
logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1026"));
BEAST_EXPECT(slot1 != nullptr);
BEAST_EXPECT(r1 == Result::Success);
auto const [slot2, r2] =
logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1027"));
BEAST_EXPECT(r2 == Result::IpLimitExceeded);
if (!BEAST_EXPECT(slot2 == nullptr))
logic.onClosed(slot2);
logic.onClosed(slot1);
logic.onClosed(slot);
}
void
testActivateDuplicatePeer()
{
testcase("test activate duplicate peer");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
c.ipLimit = 2;
logic.config(c);
}
auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
PublicKey const pk1(randomKeyPair(KeyType::Secp256k1).first);
auto const [slot, rSlot] =
logic.newOutboundSlot(beast::IP::Endpoint::fromString("55.104.0.2:1025"));
BEAST_EXPECT(slot != nullptr);
BEAST_EXPECT(rSlot == Result::Success);
auto const [slot2, r2Slot] =
logic.newOutboundSlot(beast::IP::Endpoint::fromString("55.104.0.2:1026"));
BEAST_EXPECT(slot2 != nullptr);
BEAST_EXPECT(r2Slot == Result::Success);
BEAST_EXPECT(logic.onConnected(slot, local));
BEAST_EXPECT(logic.onConnected(slot2, local));
BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::Success);
// activating a different slot with the same node ID (pk) must fail
BEAST_EXPECT(logic.activate(slot2, pk1, false) == Result::DuplicatePeer);
logic.onClosed(slot);
// accept the same key for a new slot after removing the old slot
BEAST_EXPECT(logic.activate(slot2, pk1, false) == Result::Success);
logic.onClosed(slot2);
}
void
testActivateInboundDisabled()
{
testcase("test activate inbound disabled");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
c.ipLimit = 2;
logic.config(c);
}
PublicKey const pk1(randomKeyPair(KeyType::Secp256k1).first);
auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
auto const [slot, rSlot] =
logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1025"));
BEAST_EXPECT(slot != nullptr);
BEAST_EXPECT(rSlot == Result::Success);
BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::InboundDisabled);
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
c.ipLimit = 2;
c.inPeers = 1;
logic.config(c);
}
// new inbound slot must succeed when inbound connections are enabled
BEAST_EXPECT(logic.activate(slot, pk1, false) == Result::Success);
// creating a new inbound slot must succeed as IP Limit is not exceeded
auto const [slot2, r2Slot] =
logic.newInboundSlot(local, beast::IP::Endpoint::fromString("55.104.0.2:1026"));
BEAST_EXPECT(slot2 != nullptr);
BEAST_EXPECT(r2Slot == Result::Success);
PublicKey const pk2(randomKeyPair(KeyType::Secp256k1).first);
// an inbound slot exceeding inPeers limit must fail
BEAST_EXPECT(logic.activate(slot2, pk2, false) == Result::Full);
logic.onClosed(slot2);
logic.onClosed(slot);
}
void
testAddFixedPeerNoPort()
{
testcase("test addFixedPeer no port");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
try
{
logic.addFixedPeer("test", beast::IP::Endpoint::fromString("65.0.0.2"));
fail("invalid endpoint successfully added");
}
catch (std::runtime_error const& e)
{
pass();
}
}
void
testIsValidAddress()
{
testcase("is_valid_address");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
auto const pass = [&](std::string const& s) {
BEAST_EXPECT(logic.isValidAddress(beast::IP::Endpoint::fromString(s)));
};
auto const fail = [&](std::string const& s) {
BEAST_EXPECT(!logic.isValidAddress(beast::IP::Endpoint::fromString(s)));
};
// Invalid: port 0
fail("65.0.0.1:0");
// --- IPv4 ranges ---
// For each range: 1 before (pass), first (fail), last (fail),
// 1 after (pass)
// 0.0.0.0/8 - "This network"
// No "before" - nothing before 0.0.0.0
fail("0.0.0.0:8080");
fail("0.255.255.255:8080");
pass("1.0.0.0:8080");
// 10.0.0.0/8 - Private (RFC 1918)
pass("9.255.255.255:8080");
fail("10.0.0.0:8080");
fail("10.255.255.255:8080");
pass("11.0.0.0:8080");
// 100.64.0.0/10 - Shared Address Space / CGNAT (RFC 6598)
pass("100.63.255.255:8080");
fail("100.64.0.0:8080");
fail("100.127.255.255:8080");
pass("100.128.0.0:8080");
// 127.0.0.0/8 - Loopback
pass("126.255.255.255:8080");
fail("127.0.0.0:8080");
fail("127.255.255.255:8080");
pass("128.0.0.0:8080");
// 169.254.0.0/16 - Link-local
pass("169.253.255.255:8080");
fail("169.254.0.0:8080");
fail("169.254.255.255:8080");
pass("169.255.0.0:8080");
// 172.16.0.0/12 - Private (RFC 1918)
pass("172.15.255.255:8080");
fail("172.16.0.0:8080");
fail("172.31.255.255:8080");
pass("172.32.0.0:8080");
// 192.0.0.0/24 - IETF Protocol Assignments (RFC 6890)
pass("191.255.255.255:8080");
fail("192.0.0.0:8080");
fail("192.0.0.255:8080");
pass("192.0.1.0:8080");
// 192.0.2.0/24 - TEST-NET-1 (RFC 5737)
pass("192.0.1.255:8080");
fail("192.0.2.0:8080");
fail("192.0.2.255:8080");
pass("192.0.3.0:8080");
// 192.88.99.0/24 - 6to4 Relay Anycast (RFC 7526)
pass("192.88.98.255:8080");
fail("192.88.99.0:8080");
fail("192.88.99.255:8080");
pass("192.88.100.0:8080");
// 192.168.0.0/16 - Private (RFC 1918)
pass("192.167.255.255:8080");
fail("192.168.0.0:8080");
fail("192.168.255.255:8080");
pass("192.169.0.0:8080");
// 198.18.0.0/15 - Benchmarking (RFC 2544)
pass("198.17.255.255:8080");
fail("198.18.0.0:8080");
fail("198.19.255.255:8080");
pass("198.20.0.0:8080");
// 198.51.100.0/24 - TEST-NET-2 (RFC 5737)
pass("198.51.99.255:8080");
fail("198.51.100.0:8080");
fail("198.51.100.255:8080");
pass("198.51.101.0:8080");
// 203.0.113.0/24 - TEST-NET-3 (RFC 5737)
pass("203.0.112.255:8080");
fail("203.0.113.0:8080");
fail("203.0.113.255:8080");
pass("203.0.114.0:8080");
// 224.0.0.0/4 - Multicast
pass("223.255.255.255:8080");
fail("224.0.0.0:8080");
fail("239.255.255.255:8080");
// 240.0.0.0 (after multicast) is also blocked (reserved)
// 240.0.0.0/4 - Reserved (RFC 1112)
// 239.255.255.255 (before reserved) is also blocked (multicast)
fail("240.0.0.0:8080");
fail("255.255.255.255:8080");
// --- IPv6 ranges ---
// ::1 - Loopback (single address)
fail("[::1]:8080");
// :: - Unspecified (single address)
fail("[::]:8080");
// fc00::/7 - Unique Local Address (ULA)
pass("[fb00::1]:8080");
fail("[fc00::1]:8080");
fail("[fdff::1]:8080");
pass("[fe00::1]:8080");
// fe80::/10 - Link-local
pass("[fe7f::1]:8080");
fail("[fe80::1]:8080");
fail("[febf::1]:8080");
pass("[fec0::1]:8080");
// ff00::/8 - Multicast
pass("[feff::1]:8080");
fail("[ff00::1]:8080");
fail("[ffff::1]:8080");
// No "after" - ffff:... is the highest IPv6 range
// 100::/64 - Discard prefix (RFC 6666)
pass("[ff::1]:8080");
fail("[100::]:8080");
fail("[100::ffff:ffff:ffff:ffff]:8080");
pass("[100:0:0:1::1]:8080");
// 2001::/32 - IETF Protocol Assignments / Teredo (RFC 4380)
pass("[2000:ffff::1]:8080");
fail("[2001::]:8080");
fail("[2001:0:ffff::1]:8080");
pass("[2001:1::1]:8080");
// 2001:20::/28 - ORCHIDv2 (RFC 7343)
pass("[2001:1f::1]:8080");
fail("[2001:20::1]:8080");
fail("[2001:2f::1]:8080");
pass("[2001:30::1]:8080");
// 2001:db8::/32 - Documentation (RFC 3849)
pass("[2001:db7::1]:8080");
fail("[2001:db8::1]:8080");
fail("[2001:db8:ffff::1]:8080");
pass("[2001:db9::1]:8080");
// 2002::/16 - 6to4 (RFC 3056, deprecated)
pass("[2001:ffff::1]:8080");
fail("[2002::1]:8080");
fail("[2002:ffff::1]:8080");
pass("[2003::1]:8080");
// --- IPv6 v4-mapped (delegates to IPv4 checks) ---
fail("[::ffff:10.0.0.1]:8080");
fail("[::ffff:100.64.0.1]:8080");
fail("[::ffff:169.254.1.1]:8080");
fail("[::ffff:192.0.2.1]:8080");
fail("[::ffff:198.18.0.1]:8080");
fail("[::ffff:224.0.0.1]:8080");
fail("[::ffff:240.0.0.1]:8080");
// --- Valid public addresses ---
pass("8.8.8.8:443");
pass("65.0.0.1:8080");
pass("[2001:4860:4860::8888]:8080");
pass("[2606:4700:4700::1111]:8080");
}
void
testVerifyEndpoints()
{
// Helper that sets up a Logic instance, creates and activates a slot,
// then calls on_endpoints with the given list and returns the
// livecache size afterwards.
auto run = [&](bool verifyEndpoints, Endpoints eps) -> std::size_t {
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
c.ipLimit = 2;
c.verifyEndpoints = verifyEndpoints;
logic.config(c);
}
auto const remote = beast::IP::Endpoint::fromString("65.0.0.1:5");
auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1024");
auto const [slot, r] = logic.newOutboundSlot(remote);
BEAST_EXPECT(slot != nullptr);
BEAST_EXPECT(r == Result::Success);
BEAST_EXPECT(logic.onConnected(slot, local));
PublicKey const pk(randomKeyPair(KeyType::Secp256k1).first);
BEAST_EXPECT(logic.activate(slot, pk, false) == Result::Success);
logic.onEndpoints(slot, std::move(eps));
auto const size = logic.livecache.size();
logic.onClosed(slot);
return size;
};
{
testcase("verify_endpoints enabled");
// Valid public addresses
Endpoints eps;
eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.1:5"), 1);
eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.2:6"), 1);
// Invalid: private address
eps.emplace_back(beast::IP::Endpoint::fromString("10.0.0.1:5"), 1);
// Invalid: port 0
eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.3:0"), 1);
// With verification enabled, only the 2 valid endpoints survive
BEAST_EXPECT(run(true, eps) == 2);
}
{
testcase("verify_endpoints disabled");
Endpoints eps;
eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.1:5"), 1);
eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.2:6"), 1);
// Private address — kept when verification is off
eps.emplace_back(beast::IP::Endpoint::fromString("10.0.0.1:5"), 1);
// Port 0 — kept when verification is off
eps.emplace_back(beast::IP::Endpoint::fromString("44.0.0.3:0"), 1);
// Without verification, all 4 endpoints survive
BEAST_EXPECT(run(false, eps) == 4);
}
}
void
testOnConnectedSelfConnection()
{
testcase("test onConnected self connection");
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
auto const local = beast::IP::Endpoint::fromString("65.0.0.2:1234");
auto const [slot, r] = logic.newOutboundSlot(local);
BEAST_EXPECT(slot != nullptr);
BEAST_EXPECT(r == Result::Success);
// Must fail when a slot is to our own IP address
BEAST_EXPECT(!logic.onConnected(slot, local));
logic.onClosed(slot);
}
void
testConfig()
{
// if peers_max is configured then peers_in_max and peers_out_max
// are ignored
auto run = [&](std::string const& test,
std::optional<std::uint16_t> maxPeers,
std::optional<std::uint16_t> maxIn,
std::optional<std::uint16_t> maxOut,
std::uint16_t port,
std::uint16_t expectOut,
std::uint16_t expectIn,
std::uint16_t expectIpLimit) {
xrpl::Config c;
testcase(test);
std::string toLoad;
int max = 0;
if (maxPeers)
{
max = maxPeers.value();
toLoad += "[peers_max]\n" + std::to_string(max) + "\n" + "[peers_in_max]\n" +
std::to_string(maxIn.value_or(0)) + "\n" + "[peers_out_max]\n" +
std::to_string(maxOut.value_or(0)) + "\n";
}
else if (maxIn && maxOut)
{
toLoad += "[peers_in_max]\n" + std::to_string(*maxIn) + "\n" + "[peers_out_max]\n" +
std::to_string(*maxOut) + "\n";
}
c.loadFromString(toLoad);
BEAST_EXPECT(
(c.peersMax == max && c.peersInMax == 0 && c.peersOutMax == 0) ||
(c.peersInMax == *maxIn && c.peersOutMax == *maxOut));
Config const config = Config::makeConfig(c, port, false, 0, true);
Counts counts;
counts.onConfig(config);
BEAST_EXPECT(
counts.outMax() == expectOut && counts.inMax() == expectIn &&
config.ipLimit == expectIpLimit);
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
logic.config(config);
BEAST_EXPECT(logic.config() == config);
};
// if max_peers == 0 => maxPeers = 21,
// else if max_peers < 10 => maxPeers = 10 else maxPeers =
// max_peers
// expectOut => if legacy => max(0.15 * maxPeers, 10),
// if legacy && !wantIncoming => maxPeers else max_out_peers
// expectIn => if legacy && wantIncoming => maxPeers - outPeers
// else if !wantIncoming => 0 else max_in_peers
// ipLimit => if expectIn <= 21 => 2 else 2 + min(5, expectIn/21)
// ipLimit = max(1, min(ipLimit, expectIn/2))
// legacy test with max_peers
run("legacy no config", {}, {}, {}, 4000, 10, 11, 2);
run("legacy max_peers 0", 0, 100, 10, 4000, 10, 11, 2);
run("legacy max_peers 5", 5, 100, 10, 4000, 10, 0, 1);
run("legacy max_peers 20", 20, 100, 10, 4000, 10, 10, 2);
run("legacy max_peers 100", 100, 100, 10, 4000, 15, 85, 6);
run("legacy max_peers 20, private", 20, 100, 10, 0, 20, 0, 1);
// test with max_in_peers and max_out_peers
run("new in 100/out 10", {}, 100, 10, 4000, 10, 100, 6);
run("new in 0/out 10", {}, 0, 10, 4000, 10, 0, 1);
run("new in 100/out 10, private", {}, 100, 10, 0, 10, 0, 6);
}
void
testInvalidConfig()
{
testcase("invalid config");
auto run = [&](std::string const& toLoad) {
xrpl::Config c;
try
{
c.loadFromString(toLoad);
fail();
}
catch (...)
{
pass();
}
};
run(R"xrpldConfig(
[peers_in_max]
100
)xrpldConfig");
run(R"xrpldConfig(
[peers_out_max]
100
)xrpldConfig");
run(R"xrpldConfig(
[peers_in_max]
100
[peers_out_max]
5
)xrpldConfig");
run(R"xrpldConfig(
[peers_in_max]
1001
[peers_out_max]
10
)xrpldConfig");
run(R"xrpldConfig(
[peers_in_max]
10
[peers_out_max]
1001
)xrpldConfig");
}
void
run() override
{
testBackoff1();
testBackoff2();
testDuplicateOutIn();
testDuplicateInOut();
testConfig();
testInvalidConfig();
testPeerLimitExceeded();
testActivateDuplicatePeer();
testActivateInboundDisabled();
testAddFixedPeerNoPort();
testOnConnectedSelfConnection();
testIsValidAddress();
testVerifyEndpoints();
}
};
BEAST_DEFINE_TESTSUITE(PeerFinder, peerfinder, xrpl);
} // namespace xrpl::PeerFinder

View File

@@ -21,7 +21,7 @@ set_target_properties(
)
# Lets test sources include the shared helpers as <helpers/...>.
target_include_directories(xrpl_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(xrpl_tests PRIVATE GTest::gtest xrpl.libxrpl)
target_link_libraries(xrpl_tests PRIVATE GTest::gtest GTest::gmock xrpl.libxrpl)
# One source subdirectory per module. Network unit tests are currently not
# supported on Windows.
@@ -29,6 +29,7 @@ set(test_modules
basics
crypto
json
peerfinder
resource
shamap
tx

View File

@@ -1,8 +1,9 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
int
main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
::testing::InitGoogleMock(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,294 @@
#include <xrpl/peerfinder/detail/Livecache.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/net/IPAddressV4.h>
#include <xrpl/beast/net/IPAddressV6.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/json/JsonPropertyStream.h>
#include <xrpl/json/json_forwards.h>
#include <xrpl/json/json_value.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/peerfinder/detail/Tuning.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/lexical_cast.hpp>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
namespace xrpl::PeerFinder {
namespace {
class LivecacheTest : public ::testing::Test
{
protected:
static beast::Journal
journal()
{
return beast::Journal{TestSink::instance()};
}
static beast::IP::Endpoint
endpoint(std::uint16_t index, bool v4 = true)
{
auto const port = static_cast<std::uint16_t>(10000 + index);
if (v4)
{
auto bytes = beast::IP::AddressV4::bytes_type{
{54,
static_cast<std::uint8_t>((index / 256) % 256),
static_cast<std::uint8_t>(index % 256),
1}};
return beast::IP::Endpoint{beast::IP::Address{beast::IP::AddressV4{bytes}}, port};
}
auto bytes = beast::IP::AddressV6::bytes_type{
{0x20,
0x01,
0x0d,
0xb8,
0,
0,
0,
0,
0,
0,
0,
0,
0,
static_cast<std::uint8_t>((index / 256) % 256),
static_cast<std::uint8_t>(index % 256),
1}};
return beast::IP::Endpoint{beast::IP::Address{beast::IP::AddressV6{bytes}}, port};
}
void
addEndpoint(beast::IP::Endpoint const& ep, std::uint32_t hops = 0)
{
cache_.insert(Endpoint{ep, hops});
}
TestStopwatch clock_;
Livecache<> cache_{clock_, journal()};
};
} // namespace
TEST_F(LivecacheTest, basic_insert)
{
EXPECT_TRUE(cache_.empty());
for (auto i = 0; i < 10; ++i)
addEndpoint(endpoint(i, true));
EXPECT_FALSE(cache_.empty());
EXPECT_EQ(cache_.size(), 10u);
for (auto i = 10; i < 20; ++i)
addEndpoint(endpoint(i, false));
EXPECT_FALSE(cache_.empty());
EXPECT_EQ(cache_.size(), 20u);
}
TEST_F(LivecacheTest, insert_update_keeps_lowest_hop_count)
{
auto const ep1 = Endpoint{endpoint(1), 2};
cache_.insert(ep1);
ASSERT_EQ(cache_.size(), 1u);
EXPECT_EQ((cache_.hops.begin() + 2)->begin()->hops, 2u);
auto const ep2 = Endpoint{ep1.address, 4};
cache_.insert(ep2);
EXPECT_EQ(cache_.size(), 1u);
EXPECT_EQ((cache_.hops.begin() + 2)->begin()->hops, 2u);
auto const ep3 = Endpoint{ep1.address, 2};
cache_.insert(ep3);
EXPECT_EQ(cache_.size(), 1u);
EXPECT_EQ((cache_.hops.begin() + 2)->begin()->hops, 2u);
auto const ep4 = Endpoint{ep1.address, 1};
cache_.insert(ep4);
EXPECT_EQ(cache_.size(), 1u);
EXPECT_EQ((cache_.hops.begin() + 1)->begin()->hops, 1u);
}
TEST_F(LivecacheTest, hop_iterators_support_const_reverse_and_move_back)
{
auto const ep1 = Endpoint{endpoint(1), 1};
auto const ep2 = Endpoint{endpoint(2), 1};
cache_.insert(ep1);
cache_.insert(ep2);
auto hop = *(cache_.hops.begin() + 1);
ASSERT_NE(hop.begin(), hop.end());
ASSERT_NE(hop.cbegin(), hop.cend());
ASSERT_NE(hop.rbegin(), hop.rend());
ASSERT_NE(hop.crbegin(), hop.crend());
auto const firstAddress = hop.begin()->address;
hop.moveBack(hop.begin());
EXPECT_EQ(hop.rbegin()->address, firstAddress);
auto const& constHops = cache_.hops;
EXPECT_NE(constHops.begin(), constHops.end());
EXPECT_NE(constHops.cbegin(), constHops.cend());
EXPECT_NE(constHops.rbegin(), constHops.rend());
EXPECT_NE(constHops.crbegin(), constHops.crend());
auto const constHop = *(constHops.cbegin() + 1);
EXPECT_EQ(std::distance(constHop.begin(), constHop.end()), 2);
EXPECT_EQ(std::distance(constHop.cbegin(), constHop.cend()), 2);
EXPECT_EQ(std::distance(constHop.rbegin(), constHop.rend()), 2);
EXPECT_EQ(std::distance(constHop.crbegin(), constHop.crend()), 2);
}
TEST_F(LivecacheTest, on_write_reports_entries_and_expiration)
{
cache_.insert(Endpoint{endpoint(1), 1});
cache_.insert(Endpoint{endpoint(2), Tuning::kMaxHops + 1});
JsonPropertyStream stream;
{
beast::PropertyStream::Map map(stream);
cache_.onWrite(map);
}
auto const& top = stream.top();
EXPECT_EQ(top["size"].asUInt(), 2u);
EXPECT_FALSE(top["hist"].asString().empty());
ASSERT_TRUE(top.isMember("entries"));
ASSERT_EQ(top["entries"].size(), 2u);
auto const& entry = top["entries"][json::UInt{0}];
EXPECT_TRUE(entry.isMember("hops"));
EXPECT_TRUE(entry.isMember("address"));
EXPECT_TRUE(entry.isMember("expires"));
}
TEST_F(LivecacheTest, expire_removes_entries_after_ttl)
{
using namespace std::chrono_literals;
cache_.insert(Endpoint{endpoint(1), 1});
ASSERT_EQ(cache_.size(), 1u);
cache_.expire();
EXPECT_EQ(cache_.size(), 1u);
clock_.advance(Tuning::kLiveCacheSecondsToLive - 1s);
cache_.expire();
EXPECT_EQ(cache_.size(), 1u);
clock_.advance(1s);
cache_.expire();
EXPECT_TRUE(cache_.empty());
}
TEST_F(LivecacheTest, expire_removes_multiple_entries_after_ttl)
{
using namespace std::chrono_literals;
cache_.insert(Endpoint{endpoint(1), 1});
cache_.insert(Endpoint{endpoint(2), 2});
clock_.advance(Tuning::kLiveCacheSecondsToLive);
cache_.expire();
EXPECT_TRUE(cache_.empty());
}
TEST_F(LivecacheTest, histogram_counts_all_entries)
{
constexpr auto kNumEndpoints = 40;
for (auto i = 0; i < kNumEndpoints; ++i)
{
addEndpoint(endpoint(static_cast<std::uint16_t>(i)), xrpl::randInt<std::uint32_t>());
}
auto const histogram = cache_.hops.histogram();
ASSERT_FALSE(histogram.empty());
std::vector<std::string> values;
boost::split(values, histogram, boost::algorithm::is_any_of(","));
auto sum = 0;
for (auto const& value : values)
{
auto const count = boost::lexical_cast<int>(boost::trim_copy(value));
sum += count;
EXPECT_GE(count, 0);
}
EXPECT_EQ(sum, kNumEndpoints);
}
TEST_F(LivecacheTest, shuffle_preserves_bucket_contents)
{
for (auto i = 0; i < 100; ++i)
{
addEndpoint(endpoint(static_cast<std::uint16_t>(i)), xrpl::randInt(Tuning::kMaxHops + 1));
}
using AtHop = std::vector<Endpoint>;
using AllHops = std::array<AtHop, 1 + Tuning::kMaxHops + 1>;
auto const compareEndpoint = [](Endpoint const& lhs, Endpoint const& rhs) {
return rhs.hops < lhs.hops || (rhs.hops == lhs.hops && rhs.address < lhs.address);
};
auto const sameEndpoint = [](Endpoint const& lhs, Endpoint const& rhs) {
return lhs.hops == rhs.hops && lhs.address == rhs.address;
};
auto const sameEndpoints =
[&sameEndpoint](std::vector<Endpoint> const& lhs, std::vector<Endpoint> const& rhs) {
return lhs.size() == rhs.size() &&
std::equal(lhs.begin(), lhs.end(), rhs.begin(), sameEndpoint);
};
AllHops before;
AllHops beforeSorted;
for (auto i = std::make_pair(0, cache_.hops.begin()); i.second != cache_.hops.end();
++i.first, ++i.second)
{
std::ranges::copy(*i.second, std::back_inserter(before[i.first]));
std::ranges::copy(*i.second, std::back_inserter(beforeSorted[i.first]));
std::ranges::sort(beforeSorted[i.first], compareEndpoint);
}
cache_.hops.shuffle();
AllHops after;
AllHops afterSorted;
for (auto i = std::make_pair(0, cache_.hops.begin()); i.second != cache_.hops.end();
++i.first, ++i.second)
{
std::ranges::copy(*i.second, std::back_inserter(after[i.first]));
std::ranges::copy(*i.second, std::back_inserter(afterSorted[i.first]));
std::ranges::sort(afterSorted[i.first], compareEndpoint);
}
auto allBucketsKeptOriginalOrder = true;
for (auto i = 0u; i < before.size(); ++i)
{
EXPECT_EQ(before[i].size(), after[i].size());
allBucketsKeptOriginalOrder =
allBucketsKeptOriginalOrder && sameEndpoints(before[i], after[i]);
EXPECT_TRUE(sameEndpoints(beforeSorted[i], afterSorted[i]));
}
EXPECT_FALSE(allBucketsKeptOriginalOrder);
}
} // namespace xrpl::PeerFinder

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,8 @@
#pragma once
#include <xrpld/peerfinder/detail/Store.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/peerfinder/detail/Store.h>
#include <xrpl/rdb/DatabaseCon.h>
#include <functional>

View File

@@ -1,12 +1,11 @@
#include <xrpld/app/rdb/PeerFinder.h>
#include <xrpld/peerfinder/detail/Store.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/peerfinder/detail/Store.h>
#include <xrpl/rdb/SociDB.h>
#include <boost/optional/optional.hpp> // IWYU pragma: keep

View File

@@ -7,8 +7,6 @@
#include <xrpld/overlay/detail/OverlayImpl.h>
#include <xrpld/overlay/detail/PeerImp.h>
#include <xrpld/overlay/detail/ProtocolVersion.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/net/IPAddressConversion.h>
@@ -16,6 +14,8 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_reader.h>
#include <xrpl/json/json_value.h>
#include <xrpl/peerfinder/Config.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/tokens.h>
#include <xrpl/resource/Consumer.h>

View File

@@ -3,12 +3,12 @@
#include <xrpld/app/main/Application.h>
#include <xrpld/overlay/Peer.h>
#include <xrpld/overlay/detail/OverlayImpl.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpl/beast/net/IPAddressConversion.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/resource/Consumer.h>
#include <chrono>

View File

@@ -10,8 +10,6 @@
#include <xrpld/overlay/detail/TrafficCount.h>
#include <xrpld/overlay/detail/Tuning.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpld/peerfinder/make_Manager.h>
#include <xrpld/rpc/ServerHandler.h>
#include <xrpld/rpc/handlers/admin/status/GetCounts.h>
#include <xrpld/rpc/json_body.h>
@@ -39,6 +37,9 @@
#include <xrpl/config/Constants.h>
#include <xrpl/core/HashRouter.h>
#include <xrpl/json/json_value.h>
#include <xrpl/peerfinder/Config.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/peerfinder/make_Manager.h>
#include <xrpl/protocol/BuildInfo.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/Serializer.h>
@@ -179,12 +180,13 @@ OverlayImpl::OverlayImpl(
, journal_(app_.getJournal("Overlay"))
, serverHandler_(serverHandler)
, resourceManager_(resourceManager)
, store_(app_.getJournal("PeerFinder"))
, peerFinder_(
PeerFinder::makeManager(
ioContext,
stopwatch(),
app_.getJournal("PeerFinder"),
config,
store_,
collector))
, resolver_(resolver)
, nextId_(1)
@@ -201,6 +203,7 @@ OverlayImpl::OverlayImpl(
return ret;
}())
{
store_.open(config);
beast::PropertyStream::Source::add(peerFinder_.get());
}
@@ -505,7 +508,7 @@ OverlayImpl::remove(std::shared_ptr<PeerFinder::Slot> const& slot)
void
OverlayImpl::start()
{
PeerFinder::Config const config = PeerFinder::Config::makeConfig(
PeerFinder::Config const config = PeerFinder::makeConfig(
app_.config(),
serverHandler_.setup().overlay.port(),
app_.getValidationPublicKey().has_value(),

View File

@@ -8,8 +8,7 @@
#include <xrpld/overlay/detail/Handshake.h>
#include <xrpld/overlay/detail/TrafficCount.h>
#include <xrpld/overlay/detail/TxMetrics.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpld/peerfinder/detail/StoreSqdb.h>
#include <xrpld/rpc/ServerHandler.h>
#include <xrpl/basics/Resolver.h>
@@ -24,6 +23,8 @@
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
#include <xrpl/peerfinder/PeerfinderManager.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/resource/ResourceManager.h>
#include <xrpl/server/Handoff.h>
#include <xrpl/server/Writer.h>
@@ -109,6 +110,7 @@ private:
beast::Journal const journal_;
ServerHandler& serverHandler_;
Resource::Manager& resourceManager_;
PeerFinder::StoreSqdb store_;
std::unique_ptr<PeerFinder::Manager> peerFinder_;
TrafficCount traffic_;
hash_map<std::shared_ptr<PeerFinder::Slot>, std::weak_ptr<PeerImp>> peers_;

View File

@@ -19,8 +19,6 @@
#include <xrpld/overlay/detail/ProtocolVersion.h>
#include <xrpld/overlay/detail/TrafficCount.h>
#include <xrpld/overlay/detail/Tuning.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Log.h>
@@ -43,6 +41,8 @@
#include <xrpl/json/json_forwards.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/Ledger.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/Protocol.h>

View File

@@ -9,8 +9,6 @@
#include <xrpld/overlay/Squelch.h>
#include <xrpld/overlay/detail/OverlayImpl.h>
#include <xrpld/overlay/detail/ProtocolVersion.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
@@ -24,6 +22,8 @@
#include <xrpl/core/HashRouter.h>
#include <xrpl/core/LoadEvent.h>
#include <xrpl/json/json_value.h>
#include <xrpl/peerfinder/Slot.h>
#include <xrpl/peerfinder/Types.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/STTx.h>

View File

@@ -1,368 +1,19 @@
#pragma once
#include <xrpld/core/Config.h>
#include <xrpld/peerfinder/Slot.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpl/beast/clock/abstract_clock.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/peerfinder/Config.h>
#include <boost/asio/ip/tcp.hpp>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace xrpl::PeerFinder {
using clock_type = beast::AbstractClock<std::chrono::steady_clock>;
/**
* Represents a set of addresses.
*/
using IPAddresses = std::vector<beast::IP::Endpoint>;
//------------------------------------------------------------------------------
/**
* PeerFinder configuration settings.
*/
struct Config
{
/**
* The largest number of public peer slots to allow.
* This includes both inbound and outbound, but does not include
* fixed peers.
*/
std::size_t maxPeers{Tuning::kDefaultMaxPeers};
/**
* The number of automatic outbound connections to maintain.
* Outbound connections are only maintained if autoConnect
* is `true`.
*/
std::size_t outPeers;
/**
* The number of automatic inbound connections to maintain.
* Inbound connections are only maintained if wantIncoming
* is `true`.
*/
std::size_t inPeers{0};
/**
* `true` if we want our IP address kept private.
*/
bool peerPrivate = true;
/**
* `true` if we want to accept incoming connections.
*/
bool wantIncoming{true};
/**
* `true` if we want to establish connections automatically
*/
bool autoConnect{true};
/**
* The listening port number.
*/
std::uint16_t listeningPort{0};
/**
* The set of features we advertise.
*/
std::string features;
/**
* Limit how many incoming connections we allow per IP
*/
int ipLimit{0};
/**
* `true` if we want to verify endpoints in TMEndpoints messages
*/
bool verifyEndpoints = true;
//--------------------------------------------------------------------------
/**
* Create a configuration with default values.
*/
Config();
/**
* Returns a suitable value for outPeers according to the rules.
*/
[[nodiscard]] std::size_t
calcOutPeers() const;
/**
* Adjusts the values so they follow the business rules.
*/
void
applyTuning();
/**
* Write the configuration into a property stream
*/
void
onWrite(beast::PropertyStream::Map& map) const;
/**
* Make PeerFinder::Config from configuration parameters
* @param config server's configuration
* @param port server's listening port
* @param validationPublicKey true if validation public key is not empty
* @param ipLimit limit of incoming connections per IP
* @param verifyEndpoints `true` if we want to verify endpoints in
* TMEndpoints messages
* @return PeerFinder::Config
*/
static Config
makeConfig(
xrpl::Config const& config,
std::uint16_t port,
bool validationPublicKey,
int ipLimit,
bool verifyEndpoints);
friend bool
operator==(Config const& lhs, Config const& rhs) = default;
};
//------------------------------------------------------------------------------
/**
* Describes a connectable peer address along with some metadata.
*/
struct Endpoint
{
Endpoint() = default;
Endpoint(beast::IP::Endpoint ep, std::uint32_t hops);
std::uint32_t hops = 0;
beast::IP::Endpoint address;
};
inline bool
operator<(Endpoint const& lhs, Endpoint const& rhs)
{
return lhs.address < rhs.address;
}
/**
* A set of Endpoint used for connecting.
*/
using Endpoints = std::vector<Endpoint>;
//------------------------------------------------------------------------------
/**
* Possible results from activating a slot.
*/
enum class Result { InboundDisabled, DuplicatePeer, IpLimitExceeded, Full, Success };
/**
* @brief Converts a `Result` enum value to its string representation.
*
* This function provides a human-readable string for a given `Result` enum,
* which is useful for logging, debugging, or displaying status messages.
*
* @param result The `Result` enum value to convert.
* @return A `std::string_view` representing the enum value. Returns "unknown"
* if the enum value is not explicitly handled.
*
* @note This function returns a `std::string_view` for performance.
* A `std::string` would need to allocate memory on the heap and copy the
* string literal into it every time the function is called.
*/
inline std::string_view
to_string(Result result) noexcept
{
switch (result)
{
case Result::InboundDisabled:
return "inbound disabled";
case Result::DuplicatePeer:
return "peer already connected";
case Result::IpLimitExceeded:
return "ip limit exceeded";
case Result::Full:
return "slots full";
case Result::Success:
return "success";
}
return "unknown";
}
/**
* Maintains a set of IP addresses used for getting into the network.
*/
class Manager : public beast::PropertyStream::Source
{
protected:
Manager() noexcept;
public:
/**
* Destroy the object.
* Any pending source fetch operations are aborted.
* There may be some listener calls made before the
* destructor returns.
*/
~Manager() override = default;
/**
* Set the configuration for the manager.
* The new settings will be applied asynchronously.
* Thread safety:
* Can be called from any threads at any time.
*/
virtual void
setConfig(Config const& config) = 0;
/**
* Transition to the started state, synchronously.
*/
virtual void
start() = 0;
/**
* Transition to the stopped state, synchronously.
*/
virtual void
stop() = 0;
/**
* Returns the configuration for the manager.
*/
virtual Config
config() = 0;
/**
* Add a peer that should always be connected.
* This is useful for maintaining a private cluster of peers.
* The string is the name as specified in the configuration
* file, along with the set of corresponding IP addresses.
*/
virtual void
addFixedPeer(std::string_view name, std::vector<beast::IP::Endpoint> const& addresses) = 0;
/**
* Add a set of strings as fallback IP::Endpoint sources.
* @param name A label used for diagnostics.
*/
virtual void
addFallbackStrings(std::string const& name, std::vector<std::string> const& strings) = 0;
/**
* Add a URL as a fallback location to obtain IP::Endpoint sources.
* @param name A label used for diagnostics.
*/
/* VFALCO NOTE Unimplemented
virtual void addFallbackURL (std::string const& name,
std::string const& url) = 0;
*/
//--------------------------------------------------------------------------
/**
* Create a new inbound slot with the specified remote endpoint.
* If nullptr is returned, then the slot could not be assigned.
* Usually this is because of a detected self-connection.
*/
virtual std::pair<std::shared_ptr<Slot>, Result>
newInboundSlot(
beast::IP::Endpoint const& localEndpoint,
beast::IP::Endpoint const& remoteEndpoint) = 0;
/**
* Create a new outbound slot with the specified remote endpoint.
* If nullptr is returned, then the slot could not be assigned.
* Usually this is because of a duplicate connection.
*/
virtual std::pair<std::shared_ptr<Slot>, Result>
newOutboundSlot(beast::IP::Endpoint const& remoteEndpoint) = 0;
/**
* Called when mtENDPOINTS is received.
*/
virtual void
onEndpoints(std::shared_ptr<Slot> const& slot, Endpoints const& endpoints) = 0;
/**
* Called when the slot is closed.
* This always happens when the socket is closed, unless the socket
* was canceled.
*/
virtual void
onClosed(std::shared_ptr<Slot> const& slot) = 0;
/**
* Called when an outbound connection is deemed to have failed
*/
virtual void
onFailure(std::shared_ptr<Slot> const& slot) = 0;
/**
* Called when we received redirect IPs from a busy peer.
*/
virtual void
onRedirects(
boost::asio::ip::tcp::endpoint const& remoteAddress,
std::vector<boost::asio::ip::tcp::endpoint> const& eps) = 0;
//--------------------------------------------------------------------------
/**
* Called when an outbound connection attempt succeeds.
* The local endpoint must be valid. If the caller receives an error
* when retrieving the local endpoint from the socket, it should
* proceed as if the connection attempt failed by calling on_closed
* instead of on_connected.
* @return `true` if the connection should be kept
*/
virtual bool
onConnected(std::shared_ptr<Slot> const& slot, beast::IP::Endpoint const& localEndpoint) = 0;
/**
* Request an active slot type.
*/
virtual Result
activate(std::shared_ptr<Slot> const& slot, PublicKey const& key, bool reserved) = 0;
/**
* Returns a set of endpoints suitable for redirection.
*/
virtual std::vector<Endpoint>
redirect(std::shared_ptr<Slot> const& slot) = 0;
/**
* Return a set of addresses we should connect to.
*/
virtual std::vector<beast::IP::Endpoint>
autoconnect() = 0;
virtual std::vector<std::pair<std::shared_ptr<Slot>, std::vector<Endpoint>>>
buildEndpointsForPeers() = 0;
/**
* Perform periodic activity.
* This should be called once per second.
*/
virtual void
oncePerSecond() = 0;
};
Config
makeConfig(
xrpl::Config const& config,
std::uint16_t port,
bool validationPublicKey,
int ipLimit,
bool verifyEndpoints);
} // namespace xrpl::PeerFinder

View File

@@ -1,124 +1,39 @@
#include <xrpld/core/Config.h>
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpld/peerfinder/detail/Tuning.h>
#include <xrpl/beast/utility/PropertyStream.h>
#include <xrpl/peerfinder/Config.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
namespace xrpl::PeerFinder {
Config::Config() : outPeers(calcOutPeers())
{
}
std::size_t
Config::calcOutPeers() const
{
return std::max(
((maxPeers * Tuning::kOutPercent) + 50) / 100, std::size_t(Tuning::kMinOutCount));
}
void
Config::applyTuning()
{
if (ipLimit == 0)
{
// Unless a limit is explicitly set, we allow between
// 2 and 5 connections from non RFC-1918 "private"
// IP addresses.
ipLimit = 2;
if (inPeers > Tuning::kDefaultMaxPeers)
ipLimit += std::min(5, static_cast<int>(inPeers / Tuning::kDefaultMaxPeers));
}
// We don't allow a single IP to consume all incoming slots,
// unless we only have one incoming slot available.
ipLimit = std::max(1, std::min(ipLimit, static_cast<int>(inPeers / 2)));
}
void
Config::onWrite(beast::PropertyStream::Map& map) const
{
map["max_peers"] = maxPeers;
map["out_peers"] = outPeers;
map["want_incoming"] = wantIncoming;
map["auto_connect"] = autoConnect;
map["port"] = listeningPort;
map["features"] = features;
map["ip_limit"] = ipLimit;
map["verify_endpoints"] = verifyEndpoints;
}
Config
Config::makeConfig(
makeConfig(
xrpl::Config const& cfg,
std::uint16_t port,
bool validationPublicKey,
int ipLimit,
bool verifyEndpoints)
{
PeerFinder::Config config;
config.peerPrivate = cfg.peerPrivate;
// Servers with peer privacy don't want to allow incoming connections
config.wantIncoming = (!config.peerPrivate) && (port != 0);
PeerLimitConfig limits;
if ((cfg.peersOutMax == 0u) && (cfg.peersInMax == 0u))
{
if (cfg.peersMax != 0)
config.maxPeers = cfg.peersMax;
config.maxPeers = std::max<std::size_t>(config.maxPeers, Tuning::kMinOutCount);
config.outPeers = config.calcOutPeers();
// Calculate the number of outbound peers we want. If we dont want
// or can't accept incoming, this will simply be equal to maxPeers.
if (!config.wantIncoming)
config.outPeers = config.maxPeers;
// Calculate the largest number of inbound connections we could
// take.
if (config.maxPeers >= config.outPeers)
{
config.inPeers = config.maxPeers - config.outPeers;
}
else
{
config.inPeers = 0;
}
limits.maxPeers = cfg.peersMax;
}
else
{
config.outPeers = cfg.peersOutMax;
config.inPeers = cfg.peersInMax;
config.maxPeers = 0;
limits.inPeers = cfg.peersInMax;
limits.outPeers = cfg.peersOutMax;
}
// This will cause servers configured as validators to request that
// peers they connect to never report their IP address. We set this
// after we set the 'wantIncoming' because we want a "soft" version
// of peer privacy unless the operator explicitly asks for it.
if (validationPublicKey)
config.peerPrivate = true;
// if it's a private peer or we are running as standalone
// automatic connections would defeat the purpose.
config.autoConnect = !cfg.standalone() && !cfg.peerPrivate;
config.listeningPort = port;
config.features = "";
config.ipLimit = ipLimit;
config.verifyEndpoints = verifyEndpoints;
// Enforce business rules
config.applyTuning();
return config;
return Config::makeConfig(
cfg.peerPrivate,
cfg.standalone(),
limits,
port,
validationPublicKey,
ipLimit,
verifyEndpoints);
}
} // namespace xrpl::PeerFinder

View File

@@ -1,11 +1,11 @@
#pragma once
#include <xrpld/app/rdb/PeerFinder.h>
#include <xrpld/peerfinder/detail/Store.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/net/IPEndpoint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/peerfinder/detail/Store.h>
#include <xrpl/rdb/SociDB.h>
#include <soci/session.h>

View File

@@ -1,201 +0,0 @@
#pragma once
#include <cstddef>
#include <ios>
#include <memory>
#include <ostream>
#include <sstream>
#include <string>
namespace beast {
// A collection of handy stream manipulators and
// functions to produce nice looking log output.
/**
* Left justifies a field at the specified width.
*/
struct Leftw
{
explicit Leftw(int width) : width(width)
{
}
int const width;
template <class CharT, class Traits>
friend std::basic_ios<CharT, Traits>&
operator<<(std::basic_ios<CharT, Traits>& ios, Leftw const& p)
{
ios.setf(std::ios_base::left, std::ios_base::adjustfield);
ios.width(p.width);
return ios;
}
};
/**
* Produce a section heading and fill the rest of the line with dashes.
*/
template <class CharT, class Traits, class Allocator>
std::basic_string<CharT, Traits, Allocator>
heading(std::basic_string<CharT, Traits, Allocator> title, int width = 80, CharT fill = CharT('-'))
{
title.reserve(width);
title.push_back(CharT(' '));
title.resize(width, fill);
return title;
}
/**
* Produce a dashed line separator, with a specified or default size.
*/
struct Divider
{
using CharT = char;
explicit Divider(int width = 80, CharT fill = CharT('-')) : width(width), fill(fill)
{
}
int const width;
CharT const fill;
template <class CharT, class Traits>
friend std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os, Divider const& d)
{
os << std::basic_string<CharT, Traits>(d.width, d.fill);
return os;
}
};
/**
* Creates a padded field with an optional fill character.
*/
struct Fpad
{
explicit Fpad(int width, int pad = 0, char fill = ' ') : width(width + pad), fill(fill)
{
}
int const width;
char const fill;
template <class CharT, class Traits>
friend std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os, Fpad const& f)
{
os << std::basic_string<CharT, Traits>(f.width, f.fill);
return os;
}
};
//------------------------------------------------------------------------------
namespace detail {
template <typename T>
std::string
to_string(T const& t)
{
std::stringstream ss;
ss << t;
return ss.str();
}
} // namespace detail
/**
* Justifies a field at the specified width.
*/
/** @{ */
template <
class CharT,
class Traits = std::char_traits<CharT>,
class Allocator = std::allocator<CharT>>
class FieldT
{
public:
using string_t = std::basic_string<CharT, Traits, Allocator>;
FieldT(string_t const& text, int width, int pad, bool right)
: text(text), width(width), pad(pad), right(right)
{
}
string_t const text;
int const width;
int const pad;
bool const right;
template <class CharT2, class Traits2>
friend std::basic_ostream<CharT2, Traits2>&
operator<<(std::basic_ostream<CharT2, Traits2>& os, FieldT<CharT, Traits, Allocator> const& f)
{
std::size_t const length(f.text.length());
if (f.right)
{
if (length < f.width)
os << std::basic_string<CharT2, Traits2>(f.width - length, CharT2(' '));
os << f.text;
}
else
{
os << f.text;
if (length < f.width)
os << std::basic_string<CharT2, Traits2>(f.width - length, CharT2(' '));
}
if (f.pad != 0)
os << string_t(f.pad, CharT(' '));
return os;
}
};
template <class CharT, class Traits, class Allocator>
FieldT<CharT, Traits, Allocator>
field(
std::basic_string<CharT, Traits, Allocator> const& text,
int width = 8,
int pad = 0,
bool right = false)
{
return FieldT<CharT, Traits, Allocator>(text, width, pad, right);
}
template <class CharT>
FieldT<CharT>
field(CharT const* text, int width = 8, int pad = 0, bool right = false)
{
return FieldT<CharT, std::char_traits<CharT>, std::allocator<CharT>>(
std::basic_string<CharT, std::char_traits<CharT>, std::allocator<CharT>>(text),
width,
pad,
right);
}
template <typename T>
FieldT<char>
field(T const& t, int width = 8, int pad = 0, bool right = false)
{
std::string const text(detail::to_string(t));
return field(text, width, pad, right);
}
template <class CharT, class Traits, class Allocator>
FieldT<CharT, Traits, Allocator>
rField(std::basic_string<CharT, Traits, Allocator> const& text, int width = 8, int pad = 0)
{
return FieldT<CharT, Traits, Allocator>(text, width, pad, true);
}
template <class CharT>
FieldT<CharT>
rField(CharT const* text, int width = 8, int pad = 0)
{
return FieldT<CharT, std::char_traits<CharT>, std::allocator<CharT>>(
std::basic_string<CharT, std::char_traits<CharT>, std::allocator<CharT>>(text),
width,
pad,
true);
}
template <typename T>
FieldT<char>
rField(T const& t, int width = 8, int pad = 0)
{
std::string const text(detail::to_string(t));
return field(text, width, pad, true);
}
/** @} */
} // namespace beast

View File

@@ -1,26 +0,0 @@
#pragma once
#include <xrpld/peerfinder/PeerfinderManager.h>
#include <xrpl/beast/insight/Collector.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/config/BasicConfig.h>
#include <boost/asio/io_context.hpp>
#include <memory>
namespace xrpl::PeerFinder {
/**
* Create a new Manager.
*/
std::unique_ptr<Manager>
makeManager(
boost::asio::io_context& ioContext,
clock_type& clock,
beast::Journal journal,
BasicConfig const& config,
beast::insight::Collector::ptr const& collector);
} // namespace xrpl::PeerFinder