mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 11:35:53 +00:00
Add peers_in_max and peers_out_max configuration.
Replaces peers_max with individual inbound and outbound configuration. If peers_max is configured then outbound and inbound connections are derived as prior to this change. Otherwise the new configuration is used. Config::outPeers is no longer adjusted up or down.
This commit is contained in:
@@ -142,7 +142,16 @@ public:
|
||||
|
||||
// True to ask peers not to relay current IP.
|
||||
bool PEER_PRIVATE = false;
|
||||
// peers_max is a legacy configuration, which is going to be replaced
|
||||
// with individual inbound peers peers_in_max and outbound peers
|
||||
// peers_out_max configuration. for now we support both the legacy and
|
||||
// the new configuration. if peers_max is configured then peers_in_max and
|
||||
// peers_out_max are ignored.
|
||||
std::size_t PEERS_MAX = 0;
|
||||
// is true if peers_max is configured
|
||||
bool legacyPeersMax_ = false;
|
||||
std::size_t PEERS_OUT_MAX = 0;
|
||||
std::size_t PEERS_IN_MAX = 0;
|
||||
|
||||
std::chrono::seconds WEBSOCKET_PING_FREQ = std::chrono::minutes{5};
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ struct ConfigSection
|
||||
#define SECTION_PATH_SEARCH_MAX "path_search_max"
|
||||
#define SECTION_PEER_PRIVATE "peer_private"
|
||||
#define SECTION_PEERS_MAX "peers_max"
|
||||
#define SECTION_PEERS_IN_MAX "peers_in_max"
|
||||
#define SECTION_PEERS_OUT_MAX "peers_out_max"
|
||||
#define SECTION_REDUCE_RELAY "reduce_relay"
|
||||
#define SECTION_RELAY_PROPOSALS "relay_proposals"
|
||||
#define SECTION_RELAY_VALIDATIONS "relay_validations"
|
||||
|
||||
@@ -362,7 +362,50 @@ Config::loadFromString(std::string const& fileContents)
|
||||
PEER_PRIVATE = beast::lexicalCastThrow<bool>(strTemp);
|
||||
|
||||
if (getSingleSection(secConfig, SECTION_PEERS_MAX, strTemp, j_))
|
||||
{
|
||||
PEERS_MAX = beast::lexicalCastThrow<std::size_t>(strTemp);
|
||||
legacyPeersMax_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<int> peers_in_max{};
|
||||
if (getSingleSection(secConfig, SECTION_PEERS_IN_MAX, strTemp, j_))
|
||||
{
|
||||
peers_in_max = beast::lexicalCastThrow<std::size_t>(strTemp);
|
||||
if (*peers_in_max > 1000)
|
||||
Throw<std::runtime_error>(
|
||||
"Invalid value specified in [" SECTION_PEERS_IN_MAX
|
||||
"] section; the value must be less or equal than 1000");
|
||||
}
|
||||
|
||||
std::optional<int> peers_out_max{};
|
||||
if (getSingleSection(secConfig, SECTION_PEERS_OUT_MAX, strTemp, j_))
|
||||
{
|
||||
peers_out_max = beast::lexicalCastThrow<std::size_t>(strTemp);
|
||||
if (*peers_out_max < 10 || *peers_out_max > 1000)
|
||||
Throw<std::runtime_error>(
|
||||
"Invalid value specified in [" SECTION_PEERS_OUT_MAX
|
||||
"] section; the value must be in range 10-1000");
|
||||
}
|
||||
|
||||
// if one section is configured then the other must be configured too
|
||||
if ((peers_in_max && !peers_out_max) ||
|
||||
(peers_out_max && !peers_in_max))
|
||||
Throw<std::runtime_error>("Both sections [" SECTION_PEERS_IN_MAX
|
||||
"]"
|
||||
"and [" SECTION_PEERS_OUT_MAX
|
||||
"] must be configured");
|
||||
// if none is configured then we assume the legacy path
|
||||
if (!peers_in_max && !peers_out_max)
|
||||
{
|
||||
legacyPeersMax_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
PEERS_IN_MAX = *peers_in_max;
|
||||
PEERS_OUT_MAX = *peers_out_max;
|
||||
}
|
||||
}
|
||||
|
||||
if (getSingleSection(secConfig, SECTION_NODE_SIZE, strTemp, j_))
|
||||
{
|
||||
|
||||
@@ -499,37 +499,11 @@ OverlayImpl::checkStopped()
|
||||
void
|
||||
OverlayImpl::onPrepare()
|
||||
{
|
||||
PeerFinder::Config config;
|
||||
|
||||
if (app_.config().PEERS_MAX != 0)
|
||||
config.maxPeers = app_.config().PEERS_MAX;
|
||||
|
||||
config.outPeers = config.calcOutPeers();
|
||||
|
||||
auto const port = serverHandler_.setup().overlay.port;
|
||||
|
||||
config.peerPrivate = app_.config().PEER_PRIVATE;
|
||||
|
||||
// Servers with peer privacy don't want to allow incoming connections
|
||||
config.wantIncoming = (!config.peerPrivate) && (port != 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 (!app_.getValidationPublicKey().empty())
|
||||
config.peerPrivate = true;
|
||||
|
||||
// if it's a private peer or we are running as standalone
|
||||
// automatic connections would defeat the purpose.
|
||||
config.autoConnect =
|
||||
!app_.config().standalone() && !app_.config().PEER_PRIVATE;
|
||||
config.listeningPort = port;
|
||||
config.features = "";
|
||||
config.ipLimit = setup_.ipLimit;
|
||||
|
||||
// Enforce business rules
|
||||
config.applyTuning();
|
||||
PeerFinder::Config config = PeerFinder::Config::makeConfig(
|
||||
app_.config(),
|
||||
serverHandler_.setup().overlay.port,
|
||||
!app_.getValidationPublicKey().empty(),
|
||||
setup_.ipLimit);
|
||||
|
||||
m_peerFinder->setConfig(config);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <ripple/beast/clock/abstract_clock.h>
|
||||
#include <ripple/beast/utility/PropertyStream.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/core/Stoppable.h>
|
||||
#include <ripple/peerfinder/Slot.h>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
@@ -45,17 +46,20 @@ struct Config
|
||||
*/
|
||||
int maxPeers;
|
||||
|
||||
/** Legacy config if peers_max is set. */
|
||||
bool legacyConfig;
|
||||
|
||||
/** The number of automatic outbound connections to maintain.
|
||||
Outbound connections are only maintained if autoConnect
|
||||
is `true`. The value can be fractional; The decision to round up
|
||||
or down will be made using a per-process pseudorandom number and
|
||||
a probability proportional to the fractional part.
|
||||
Example:
|
||||
If outPeers is 9.3, then 30% of nodes will maintain 9 outbound
|
||||
connections, while 70% of nodes will maintain 10 outbound
|
||||
connections.
|
||||
is `true`.
|
||||
*/
|
||||
double outPeers;
|
||||
int outPeers;
|
||||
|
||||
/** The number of automatic inbound connections to maintain.
|
||||
Inbound connections are only maintained if wantIncoming
|
||||
is `true`.
|
||||
*/
|
||||
int inPeers;
|
||||
|
||||
/** `true` if we want our IP address kept private. */
|
||||
bool peerPrivate = true;
|
||||
@@ -81,7 +85,7 @@ struct Config
|
||||
Config();
|
||||
|
||||
/** Returns a suitable value for outPeers according to the rules. */
|
||||
double
|
||||
std::size_t
|
||||
calcOutPeers() const;
|
||||
|
||||
/** Adjusts the values so they follow the business rules. */
|
||||
@@ -91,6 +95,20 @@ struct Config
|
||||
/** Write the configuration into a property stream */
|
||||
void
|
||||
onWrite(beast::PropertyStream::Map& map);
|
||||
|
||||
/** 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
|
||||
* @return PeerFinder::Config
|
||||
*/
|
||||
static Config
|
||||
makeConfig(
|
||||
ripple::Config const& config,
|
||||
std::uint16_t port,
|
||||
bool validationPublicKey,
|
||||
int ipLimit);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -48,8 +48,6 @@ public:
|
||||
, m_acceptCount(0)
|
||||
, m_closingCount(0)
|
||||
{
|
||||
m_roundingThreshold =
|
||||
std::generate_canonical<double, 10>(default_prng());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -136,28 +134,28 @@ public:
|
||||
void
|
||||
onConfig(Config const& config)
|
||||
{
|
||||
// Calculate the number of outbound peers we want. If we dont want or
|
||||
// can't accept incoming, this will simply be equal to maxPeers.
|
||||
// Otherwise we calculate a fractional amount based on percentages and
|
||||
// pseudo-randomly round up or down.
|
||||
//
|
||||
if (config.wantIncoming)
|
||||
if (config.legacyConfig)
|
||||
{
|
||||
// Round outPeers upwards using a Bernoulli distribution
|
||||
m_out_max = std::floor(config.outPeers);
|
||||
if (m_roundingThreshold < (config.outPeers - m_out_max))
|
||||
++m_out_max;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_out_max = config.maxPeers;
|
||||
}
|
||||
// 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)
|
||||
m_out_max = config.outPeers;
|
||||
else
|
||||
m_out_max = config.maxPeers;
|
||||
|
||||
// Calculate the largest number of inbound connections we could take.
|
||||
if (config.maxPeers >= m_out_max)
|
||||
m_in_max = config.maxPeers - m_out_max;
|
||||
// Calculate the largest number of inbound connections we could
|
||||
// take.
|
||||
if (config.maxPeers >= m_out_max)
|
||||
m_in_max = config.maxPeers - m_out_max;
|
||||
else
|
||||
m_in_max = 0;
|
||||
}
|
||||
else
|
||||
m_in_max = 0;
|
||||
{
|
||||
m_out_max = config.outPeers;
|
||||
if (config.wantIncoming)
|
||||
m_in_max = config.inPeers;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the number of accepted connections that haven't handshaked. */
|
||||
@@ -350,13 +348,6 @@ private:
|
||||
|
||||
// Number of connections that are gracefully closing.
|
||||
int m_closingCount;
|
||||
|
||||
/** Fractional threshold below which we round down.
|
||||
This is used to round the value of Config::outPeers up or down in
|
||||
such a way that the network-wide average number of outgoing
|
||||
connections approximates the recommended, fractional value.
|
||||
*/
|
||||
double m_roundingThreshold;
|
||||
};
|
||||
|
||||
} // namespace PeerFinder
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace PeerFinder {
|
||||
|
||||
Config::Config()
|
||||
: maxPeers(Tuning::defaultMaxPeers)
|
||||
, legacyConfig(false)
|
||||
, outPeers(calcOutPeers())
|
||||
, inPeers(0)
|
||||
, wantIncoming(true)
|
||||
, autoConnect(true)
|
||||
, listeningPort(0)
|
||||
@@ -33,21 +35,28 @@ Config::Config()
|
||||
{
|
||||
}
|
||||
|
||||
double
|
||||
std::size_t
|
||||
Config::calcOutPeers() const
|
||||
{
|
||||
return std::max(
|
||||
maxPeers * Tuning::outPercent * 0.01, double(Tuning::minOutCount));
|
||||
return std::round(std::max(
|
||||
maxPeers * Tuning::outPercent * 0.01, double(Tuning::minOutCount)));
|
||||
}
|
||||
|
||||
void
|
||||
Config::applyTuning()
|
||||
{
|
||||
if (maxPeers < Tuning::minOutCount)
|
||||
maxPeers = Tuning::minOutCount;
|
||||
outPeers = calcOutPeers();
|
||||
if (legacyConfig)
|
||||
{
|
||||
if (maxPeers < Tuning::minOutCount)
|
||||
maxPeers = Tuning::minOutCount;
|
||||
outPeers = calcOutPeers();
|
||||
|
||||
auto const inPeers = maxPeers - outPeers;
|
||||
inPeers = maxPeers - outPeers;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxPeers = 0;
|
||||
}
|
||||
|
||||
if (ipLimit == 0)
|
||||
{
|
||||
@@ -78,5 +87,53 @@ Config::onWrite(beast::PropertyStream::Map& map)
|
||||
map["ip_limit"] = ipLimit;
|
||||
}
|
||||
|
||||
Config
|
||||
Config::makeConfig(
|
||||
ripple::Config const& cfg,
|
||||
std::uint16_t port,
|
||||
bool validationPublicKey,
|
||||
int ipLimit)
|
||||
{
|
||||
PeerFinder::Config config;
|
||||
|
||||
config.legacyConfig = cfg.legacyPeersMax_;
|
||||
if (config.legacyConfig)
|
||||
{
|
||||
if (cfg.PEERS_MAX != 0)
|
||||
config.maxPeers = cfg.PEERS_MAX;
|
||||
|
||||
config.outPeers = config.calcOutPeers();
|
||||
}
|
||||
else
|
||||
{
|
||||
config.outPeers = cfg.PEERS_OUT_MAX;
|
||||
config.inPeers = cfg.PEERS_IN_MAX;
|
||||
}
|
||||
|
||||
config.peerPrivate = cfg.PEER_PRIVATE;
|
||||
|
||||
// Servers with peer privacy don't want to allow incoming connections
|
||||
config.wantIncoming = (!config.peerPrivate) && (port != 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 = !cfg.standalone() && !cfg.PEER_PRIVATE;
|
||||
config.listeningPort = port;
|
||||
config.features = "";
|
||||
config.ipLimit = ipLimit;
|
||||
|
||||
// Enforce business rules
|
||||
config.applyTuning();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace PeerFinder
|
||||
} // namespace ripple
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <ripple/basics/Slice.h>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/peerfinder/impl/Logic.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
@@ -156,11 +157,130 @@ public:
|
||||
BEAST_EXPECT(n <= (seconds + 59) / 60);
|
||||
}
|
||||
|
||||
void
|
||||
test_config()
|
||||
{
|
||||
// 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) {
|
||||
ripple::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) + "\n" +
|
||||
"[peers_out_max]\n" + std::to_string(*maxOut) + "\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.legacyPeersMax_ && c.PEERS_MAX == max &&
|
||||
c.PEERS_IN_MAX == 0 && c.PEERS_OUT_MAX == 0) ||
|
||||
(!c.legacyPeersMax_ && c.PEERS_IN_MAX == *maxIn &&
|
||||
c.PEERS_OUT_MAX == *maxOut));
|
||||
|
||||
Config config = Config::makeConfig(c, port, false, 0);
|
||||
|
||||
Counts counts;
|
||||
counts.onConfig(config);
|
||||
BEAST_EXPECT(
|
||||
counts.out_max() == expectOut &&
|
||||
counts.inboundSlots() == expectIn &&
|
||||
config.ipLimit == expectIpLimit);
|
||||
};
|
||||
|
||||
// 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, 2);
|
||||
|
||||
// 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
|
||||
test_invalid_config()
|
||||
{
|
||||
testcase("invalid config");
|
||||
|
||||
auto run = [&](std::string const& toLoad) {
|
||||
ripple::Config c;
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
fail();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
};
|
||||
run(R"rippleConfig(
|
||||
[peers_in_max]
|
||||
100
|
||||
)rippleConfig");
|
||||
run(R"rippleConfig(
|
||||
[peers_out_max]
|
||||
100
|
||||
)rippleConfig");
|
||||
run(R"rippleConfig(
|
||||
[peers_in_max]
|
||||
100
|
||||
[peers_out_max]
|
||||
5
|
||||
)rippleConfig");
|
||||
run(R"rippleConfig(
|
||||
[peers_in_max]
|
||||
1001
|
||||
[peers_out_max]
|
||||
10
|
||||
)rippleConfig");
|
||||
run(R"rippleConfig(
|
||||
[peers_in_max]
|
||||
10
|
||||
[peers_out_max]
|
||||
1001
|
||||
)rippleConfig");
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
test_backoff1();
|
||||
test_backoff2();
|
||||
test_config();
|
||||
test_invalid_config();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user