Add experimental validation & proposal relay reduction support:

- Add validation/proposal reduce-relay feature negotiation to
  the handshake
- Make squelch duration proportional to a number of peers that
  can be squelched
- Refactor makeRequest()/makeResponse() to facilitate handshake
  unit-testing
- Fix compression enable flag for inbound peer
- Fix compression algorithm parsing in the header parser
- Fix squelch duration in onMessage(TMSquelch)

This commit fixes 3624, fixes 3639 and fixes 3641
This commit is contained in:
Gregory Tsipenyuk
2020-10-21 12:41:04 -04:00
committed by Nik Bougalis
parent 44fe0e1fc4
commit 74d96ff4bd
22 changed files with 1076 additions and 260 deletions

View File

@@ -859,6 +859,7 @@ target_sources (rippled PRIVATE
src/test/overlay/short_read_test.cpp
src/test/overlay/compression_test.cpp
src/test/overlay/reduce_relay_test.cpp
src/test/overlay/handshake_test.cpp
#[===============================[
test sources:
subdir: peerfinder

View File

@@ -168,6 +168,7 @@ else
'ripple.tx.OversizeMeta'
'ripple.consensus.DistributedValidators'
'ripple.app.NoRippleCheckLimits'
'ripple.ripple_data.compression'
'ripple.NodeStore.Timing'
'ripple.consensus.ByzantineFailureSim'
'beast.chrono.abstract_clock'

View File

@@ -37,6 +37,19 @@ namespace rfc2616 {
namespace detail {
struct ci_equal_pred
{
explicit ci_equal_pred() = default;
bool
operator()(char c1, char c2)
{
// VFALCO TODO Use a table lookup here
return std::tolower(static_cast<unsigned char>(c1)) ==
std::tolower(static_cast<unsigned char>(c2));
}
};
/** Returns `true` if `c` is linear white space.
This excludes the CRLF sequence allowed for line continuations.
@@ -195,6 +208,179 @@ split_commas(boost::beast::string_view const& s)
return split_commas(s.begin(), s.end());
}
//------------------------------------------------------------------------------
/** Iterates through a comma separated list.
Meets the requirements of ForwardIterator.
List defined in rfc2616 2.1.
@note Values returned may contain backslash escapes.
*/
class list_iterator
{
using iter_type = boost::string_ref::const_iterator;
iter_type it_;
iter_type end_;
boost::string_ref value_;
public:
using value_type = boost::string_ref;
using pointer = value_type const*;
using reference = value_type const&;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
list_iterator(iter_type begin, iter_type end) : it_(begin), end_(end)
{
if (it_ != end_)
increment();
}
bool
operator==(list_iterator const& other) const
{
return other.it_ == it_ && other.end_ == end_ &&
other.value_.size() == value_.size();
}
bool
operator!=(list_iterator const& other) const
{
return !(*this == other);
}
reference
operator*() const
{
return value_;
}
pointer
operator->() const
{
return &*(*this);
}
list_iterator&
operator++()
{
increment();
return *this;
}
list_iterator
operator++(int)
{
auto temp = *this;
++(*this);
return temp;
}
private:
template <class = void>
void
increment();
};
template <class>
void
list_iterator::increment()
{
using namespace detail;
value_.clear();
while (it_ != end_)
{
if (*it_ == '"')
{
// quoted-string
++it_;
if (it_ == end_)
return;
if (*it_ != '"')
{
auto start = it_;
for (;;)
{
++it_;
if (it_ == end_)
{
value_ = boost::string_ref(
&*start, std::distance(start, it_));
return;
}
if (*it_ == '"')
{
value_ = boost::string_ref(
&*start, std::distance(start, it_));
++it_;
return;
}
}
}
++it_;
}
else if (*it_ == ',')
{
it_++;
continue;
}
else if (is_lws(*it_))
{
++it_;
continue;
}
else
{
auto start = it_;
for (;;)
{
++it_;
if (it_ == end_ || *it_ == ',' || is_lws(*it_))
{
value_ =
boost::string_ref(&*start, std::distance(start, it_));
return;
}
}
}
}
}
/** Returns true if two strings are equal.
A case-insensitive comparison is used.
*/
inline bool
ci_equal(boost::string_ref s1, boost::string_ref s2)
{
return boost::range::equal(s1, s2, detail::ci_equal_pred{});
}
/** Returns a range representing the list. */
inline boost::iterator_range<list_iterator>
make_list(boost::string_ref const& field)
{
return boost::iterator_range<list_iterator>{
list_iterator{field.begin(), field.end()},
list_iterator{field.end(), field.end()}};
}
/** Returns true if the specified token exists in the list.
A case-insensitive comparison is used.
*/
template <class = void>
bool
token_in_list(boost::string_ref const& value, boost::string_ref const& token)
{
for (auto const& item : make_list(value))
if (ci_equal(item, token))
return true;
return false;
}
template <bool isRequest, class Body, class Fields>
bool
is_keep_alive(boost::beast::http::message<isRequest, Body, Fields> const& m)

View File

@@ -191,10 +191,18 @@ public:
std::size_t WORKERS = 0;
// Reduce-relay - these parameters are experimental.
// Enable reduce-relay functionality
bool REDUCE_RELAY_ENABLE = false;
// Send squelch message to peers
bool REDUCE_RELAY_SQUELCH = false;
// Enable reduce-relay features
// Validation/proposal reduce-relay feature
bool VP_REDUCE_RELAY_ENABLE = false;
// Send squelch message to peers. Generally this config should
// have the same value as VP_REDUCE_RELAY_ENABLE. It can be
// used for testing the feature's function without
// affecting the message relaying. To use it for testing,
// set it to false and set VP_REDUCE_RELAY_ENABLE to true.
// Squelch messages will not be sent to the peers in this case.
// Set log level to debug so that the feature function can be
// analyzed.
bool VP_REDUCE_RELAY_SQUELCH = false;
// These override the command line client settings
boost::optional<beast::IP::Endpoint> rpc_ip;

View File

@@ -523,8 +523,8 @@ Config::loadFromString(std::string const& fileContents)
if (exists(SECTION_REDUCE_RELAY))
{
auto sec = section(SECTION_REDUCE_RELAY);
REDUCE_RELAY_ENABLE = sec.value_or("enable", false);
REDUCE_RELAY_SQUELCH = sec.value_or("squelch", false);
VP_REDUCE_RELAY_ENABLE = sec.value_or("vp_enable", false);
VP_REDUCE_RELAY_SQUELCH = sec.value_or("vp_squelch", false);
}
if (getSingleSection(secConfig, SECTION_MAX_TRANSACTIONS, strTemp, j_))

View File

@@ -75,7 +75,7 @@ public:
beast::IP::Address public_ip;
int ipLimit = 0;
std::uint32_t crawlOptions = 0;
boost::optional<std::uint32_t> networkID;
std::optional<std::uint32_t> networkID;
bool vlEnabled = true;
};

View File

@@ -17,22 +17,26 @@
*/
//==============================================================================
#ifndef RIPPLE_OVERLAY_SQUELCHCOMMON_H_INCLUDED
#define RIPPLE_OVERLAY_SQUELCHCOMMON_H_INCLUDED
#ifndef RIPPLE_OVERLAY_REDUCERELAYCOMMON_H_INCLUDED
#define RIPPLE_OVERLAY_REDUCERELAYCOMMON_H_INCLUDED
#include <chrono>
namespace ripple {
namespace squelch {
using namespace std::chrono;
namespace reduce_relay {
// Peer's squelch is limited in time to
// rand{MIN_UNSQUELCH_EXPIRE, MAX_UNSQUELCH_EXPIRE}
static constexpr seconds MIN_UNSQUELCH_EXPIRE = seconds{300};
static constexpr seconds MAX_UNSQUELCH_EXPIRE = seconds{600};
// rand{MIN_UNSQUELCH_EXPIRE, max_squelch},
// where max_squelch is
// min(max(MAX_UNSQUELCH_EXPIRE_DEFAULT, SQUELCH_PER_PEER * number_of_peers),
// MAX_UNSQUELCH_EXPIRE_PEERS)
static constexpr auto MIN_UNSQUELCH_EXPIRE = std::chrono::seconds{300};
static constexpr auto MAX_UNSQUELCH_EXPIRE_DEFAULT = std::chrono::seconds{600};
static constexpr auto SQUELCH_PER_PEER = std::chrono::seconds(10);
static constexpr auto MAX_UNSQUELCH_EXPIRE_PEERS = std::chrono::seconds{3600};
// No message received threshold before identifying a peer as idled
static constexpr seconds IDLED = seconds{8};
static constexpr auto IDLED = std::chrono::seconds{8};
// Message count threshold to start selecting peers as the source
// of messages from the validator. We add peers who reach
// MIN_MESSAGE_THRESHOLD to considered pool once MAX_SELECTED_PEERS
@@ -40,13 +44,13 @@ static constexpr seconds IDLED = seconds{8};
static constexpr uint16_t MIN_MESSAGE_THRESHOLD = 9;
static constexpr uint16_t MAX_MESSAGE_THRESHOLD = 10;
// Max selected peers to choose as the source of messages from validator
static constexpr uint16_t MAX_SELECTED_PEERS = 3;
static constexpr uint16_t MAX_SELECTED_PEERS = 5;
// Wait before reduce-relay feature is enabled on boot up to let
// the server establish peer connections
static constexpr minutes WAIT_ON_BOOTUP = minutes{10};
static constexpr auto WAIT_ON_BOOTUP = std::chrono::minutes{10};
} // namespace squelch
} // namespace reduce_relay
} // namespace ripple
#endif // RIPPLED_SQUELCHCOMMON_H
#endif // RIPPLED_REDUCERELAYCOMMON_H_INCLUDED

View File

@@ -25,8 +25,8 @@
#include <ripple/beast/container/aged_unordered_map.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/overlay/Peer.h>
#include <ripple/overlay/ReduceRelayCommon.h>
#include <ripple/overlay/Squelch.h>
#include <ripple/overlay/SquelchCommon.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple.pb.h>
@@ -40,7 +40,7 @@
namespace ripple {
namespace squelch {
namespace reduce_relay {
template <typename clock_type>
class Slots;
@@ -61,7 +61,7 @@ template <typename Unit, typename TP>
Unit
epoch(TP const& t)
{
return duration_cast<Unit>(t.time_since_epoch());
return std::chrono::duration_cast<Unit>(t.time_since_epoch());
}
/** Abstract class. Declares squelch and unsquelch handlers.
@@ -124,10 +124,10 @@ private:
/** Update peer info. If the message is from a new
* peer or from a previously expired squelched peer then switch
* the peer's and slot's state to Counting. If time of last
* selection round is > 2 * MAX_UNSQUELCH_EXPIRE then switch the slot's
* state to Counting. If the number of messages for the peer
* is > MIN_MESSAGE_THRESHOLD then add peer to considered peers pool.
* If the number of considered peers who reached MAX_MESSAGE_THRESHOLD is
* selection round is > 2 * MAX_UNSQUELCH_EXPIRE_DEFAULT then switch the
* slot's state to Counting. If the number of messages for the peer is >
* MIN_MESSAGE_THRESHOLD then add peer to considered peers pool. If the
* number of considered peers who reached MAX_MESSAGE_THRESHOLD is
* MAX_SELECTED_PEERS then randomly select MAX_SELECTED_PEERS from
* considered peers, and call squelch handler for each peer, which is not
* selected and not already in Squelched state. Set the state for those
@@ -197,6 +197,14 @@ private:
void
deleteIdlePeer(PublicKey const& validator);
/** Get random squelch duration between MIN_UNSQUELCH_EXPIRE and
* min(max(MAX_UNSQUELCH_EXPIRE_DEFAULT, SQUELCH_PER_PEER * npeers),
* MAX_UNSQUELCH_EXPIRE_PEERS)
* @param npeers number of peers that can be squelched in the Slot
*/
std::chrono::seconds
getSquelchDuration(std::size_t npeers);
private:
/** Reset counts of peers in Selected or Counting state */
void
@@ -231,6 +239,7 @@ template <typename clock_type>
void
Slot<clock_type>::deleteIdlePeer(PublicKey const& validator)
{
using namespace std::chrono;
auto now = clock_type::now();
for (auto it = peers_.begin(); it != peers_.end();)
{
@@ -239,7 +248,7 @@ Slot<clock_type>::deleteIdlePeer(PublicKey const& validator)
++it;
if (now - peer.lastMessage > IDLED)
{
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "deleteIdlePeer: " << Slice(validator) << " " << id
<< " idled "
<< duration_cast<seconds>(now - peer.lastMessage).count()
@@ -256,12 +265,13 @@ Slot<clock_type>::update(
id_t id,
protocol::MessageType type)
{
using namespace std::chrono;
auto now = clock_type::now();
auto it = peers_.find(id);
// First message from this peer
if (it == peers_.end())
{
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "update: adding peer " << Slice(validator) << " " << id;
peers_.emplace(
std::make_pair(id, PeerInfo{PeerState::Counting, 0, now, now}));
@@ -271,7 +281,7 @@ Slot<clock_type>::update(
// Message from a peer with expired squelch
if (it->second.state == PeerState::Squelched && now > it->second.expire)
{
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "update: squelch expired " << Slice(validator) << " " << id;
it->second.state = PeerState::Counting;
it->second.lastMessage = now;
@@ -281,7 +291,7 @@ Slot<clock_type>::update(
auto& peer = it->second;
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "update: existing peer " << Slice(validator) << " " << id
<< " slot state " << static_cast<int>(state_) << " peer state "
<< static_cast<int>(peer.state) << " count " << peer.count << " last "
@@ -299,9 +309,9 @@ Slot<clock_type>::update(
if (peer.count == (MAX_MESSAGE_THRESHOLD + 1))
++reachedThreshold_;
if (now - lastSelected_ > 2 * MAX_UNSQUELCH_EXPIRE)
if (now - lastSelected_ > 2 * MAX_UNSQUELCH_EXPIRE_DEFAULT)
{
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "update: resetting due to inactivity " << Slice(validator) << " "
<< id << " " << duration_cast<seconds>(now - lastSelected_).count();
initCounting();
@@ -338,7 +348,7 @@ Slot<clock_type>::update(
if (selected.size() != MAX_SELECTED_PEERS)
{
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "update: selection failed " << Slice(validator) << " " << id;
initCounting();
return;
@@ -347,11 +357,13 @@ Slot<clock_type>::update(
lastSelected_ = now;
auto s = selected.begin();
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "update: " << Slice(validator) << " " << id << " pool size "
<< consideredPoolSize << " selected " << *s << " "
<< *std::next(s, 1) << " " << *std::next(s, 2);
assert(peers_.size() >= MAX_SELECTED_PEERS);
// squelch peers which are not selected and
// not already squelched
std::stringstream str;
@@ -363,18 +375,16 @@ Slot<clock_type>::update(
v.state = PeerState::Selected;
else if (v.state != PeerState::Squelched)
{
if (journal_.debug())
if (journal_.trace())
str << k << " ";
v.state = PeerState::Squelched;
auto duration = Squelch<clock_type>::getSquelchDuration();
std::chrono::seconds duration =
getSquelchDuration(peers_.size() - MAX_SELECTED_PEERS);
v.expire = now + duration;
handler_.squelch(
validator,
k,
duration_cast<milliseconds>(duration).count());
handler_.squelch(validator, k, duration.count());
}
}
JLOG(journal_.debug()) << "update: squelching " << Slice(validator)
JLOG(journal_.trace()) << "update: squelching " << Slice(validator)
<< " " << id << " " << str.str();
considered_.clear();
reachedThreshold_ = 0;
@@ -382,6 +392,22 @@ Slot<clock_type>::update(
}
}
template <typename clock_type>
std::chrono::seconds
Slot<clock_type>::getSquelchDuration(std::size_t npeers)
{
using namespace std::chrono;
auto m = std::max(
MAX_UNSQUELCH_EXPIRE_DEFAULT, seconds{SQUELCH_PER_PEER * npeers});
if (m > MAX_UNSQUELCH_EXPIRE_PEERS)
{
m = MAX_UNSQUELCH_EXPIRE_PEERS;
JLOG(journal_.warn())
<< "getSquelchDuration: unexpected squelch duration " << npeers;
}
return seconds{ripple::rand_int(MIN_UNSQUELCH_EXPIRE / 1s, m / 1s)};
}
template <typename clock_type>
void
Slot<clock_type>::deletePeer(PublicKey const& validator, id_t id, bool erase)
@@ -389,7 +415,7 @@ Slot<clock_type>::deletePeer(PublicKey const& validator, id_t id, bool erase)
auto it = peers_.find(id);
if (it != peers_.end())
{
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "deletePeer: " << Slice(validator) << " " << id << " selected "
<< (it->second.state == PeerState::Selected) << " considered "
<< (considered_.find(id) != considered_.end()) << " erase "
@@ -486,6 +512,7 @@ std::unordered_map<
std::tuple<PeerState, uint16_t, uint32_t, uint32_t>>
Slot<clock_type>::getPeers() const
{
using namespace std::chrono;
auto init = std::unordered_map<
id_t,
std::tuple<PeerState, std::uint16_t, std::uint32_t, std::uint32_t>>();
@@ -531,7 +558,6 @@ public:
* @param key Message's hash
* @param validator Validator's public key
* @param id Peer's id which received the message
* @param id Peer's pointer which received the message
* @param type Received protocol message type
*/
void
@@ -643,7 +669,7 @@ template <typename clock_type>
bool
Slots<clock_type>::addPeerMessage(uint256 const& key, id_t id)
{
beast::expire(peersWithMessage_, squelch::IDLED);
beast::expire(peersWithMessage_, reduce_relay::IDLED);
if (key.isNonZero())
{
@@ -686,7 +712,7 @@ Slots<clock_type>::updateSlotAndSquelch(
auto it = slots_.find(validator);
if (it == slots_.end())
{
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "updateSlotAndSquelch: new slot " << Slice(validator);
auto it = slots_
.emplace(std::make_pair(
@@ -716,9 +742,9 @@ Slots<clock_type>::deleteIdlePeers()
for (auto it = slots_.begin(); it != slots_.end();)
{
it->second.deleteIdlePeer(it->first);
if (now - it->second.getLastSelected() > MAX_UNSQUELCH_EXPIRE)
if (now - it->second.getLastSelected() > MAX_UNSQUELCH_EXPIRE_DEFAULT)
{
JLOG(journal_.debug())
JLOG(journal_.trace())
<< "deleteIdlePeers: deleting idle slot " << Slice(it->first);
it = slots_.erase(it);
}
@@ -727,7 +753,7 @@ Slots<clock_type>::deleteIdlePeers()
}
}
} // namespace squelch
} // namespace reduce_relay
} // namespace ripple

View File

@@ -21,15 +21,17 @@
#define RIPPLE_OVERLAY_SQUELCH_H_INCLUDED
#include <ripple/basics/random.h>
#include <ripple/overlay/SquelchCommon.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/overlay/ReduceRelayCommon.h>
#include <ripple/protocol/PublicKey.h>
#include <algorithm>
#include <chrono>
#include <functional>
namespace ripple {
namespace squelch {
namespace reduce_relay {
/** Maintains squelching of relaying messages from validators */
template <typename clock_type>
@@ -38,84 +40,89 @@ class Squelch
using time_point = typename clock_type::time_point;
public:
Squelch() = default;
explicit Squelch(beast::Journal journal) : journal_(journal)
{
}
virtual ~Squelch() = default;
/** Squelch/Unsquelch relaying for the validator
/** Squelch validation/proposal relaying for the validator
* @param validator The validator's public key
* @param squelch Squelch/unsquelch flag
* @param squelchDuration Squelch duration time if squelch is true
*/
void
squelch(PublicKey const& validator, bool squelch, uint64_t squelchDuration);
/** Are the messages to this validator squelched
* @param validator Validator's public key
* @return true if squelched
* @param squelchDuration Squelch duration in seconds
* @return false if invalid squelch duration
*/
bool
isSquelched(PublicKey const& validator);
addSquelch(
PublicKey const& validator,
std::chrono::seconds const& squelchDuration);
/** Get random squelch duration between MIN_UNSQUELCH_EXPIRE and
* MAX_UNSQUELCH_EXPIRE */
static seconds
getSquelchDuration();
/** Remove the squelch
* @param validator The validator's public key
*/
void
removeSquelch(PublicKey const& validator);
/** Remove expired squelch
* @param validator Validator's public key
* @return true if removed or doesn't exist, false if still active
*/
bool
expireSquelch(PublicKey const& validator);
private:
/** Maintains the list of squelched relaying to downstream peers.
* Expiration time is included in the TMSquelch message. */
hash_map<PublicKey, time_point> squelched_;
beast::Journal const journal_;
};
template <typename clock_type>
void
Squelch<clock_type>::squelch(
PublicKey const& validator,
bool squelch,
uint64_t squelchDuration)
{
if (squelch)
{
squelched_[validator] = [squelchDuration]() {
seconds duration = seconds(squelchDuration);
return clock_type::now() +
((duration >= MIN_UNSQUELCH_EXPIRE &&
duration <= MAX_UNSQUELCH_EXPIRE)
? duration
: getSquelchDuration());
}();
}
else
squelched_.erase(validator);
}
template <typename clock_type>
bool
Squelch<clock_type>::isSquelched(PublicKey const& validator)
Squelch<clock_type>::addSquelch(
PublicKey const& validator,
std::chrono::seconds const& squelchDuration)
{
auto now = clock_type::now();
auto const& it = squelched_.find(validator);
if (it == squelched_.end())
return false;
else if (it->second > now)
if (squelchDuration >= MIN_UNSQUELCH_EXPIRE &&
squelchDuration <= MAX_UNSQUELCH_EXPIRE_PEERS)
{
squelched_[validator] = clock_type::now() + squelchDuration;
return true;
}
squelched_.erase(it);
JLOG(journal_.error()) << "squelch: invalid squelch duration "
<< squelchDuration.count();
// unsquelch if invalid duration
removeSquelch(validator);
return false;
}
template <typename clock_type>
seconds
Squelch<clock_type>::getSquelchDuration()
void
Squelch<clock_type>::removeSquelch(PublicKey const& validator)
{
auto d = seconds(ripple::rand_int(
MIN_UNSQUELCH_EXPIRE.count(), MAX_UNSQUELCH_EXPIRE.count()));
return d;
squelched_.erase(validator);
}
} // namespace squelch
template <typename clock_type>
bool
Squelch<clock_type>::expireSquelch(PublicKey const& validator)
{
auto now = clock_type::now();
auto const& it = squelched_.find(validator);
if (it == squelched_.end())
return true;
else if (it->second > now)
return false;
// squelch expired
squelched_.erase(it);
return true;
}
} // namespace reduce_relay
} // namespace ripple

View File

@@ -202,7 +202,9 @@ ConnectAttempt::onHandshake(error_code ec)
return close(); // makeSharedValue logs
req_ = makeRequest(
!overlay_.peerFinder().config().peerPrivate, app_.config().COMPRESSION);
!overlay_.peerFinder().config().peerPrivate,
app_.config().COMPRESSION,
app_.config().VP_REDUCE_RELAY_ENABLE);
buildHandshake(
req_,
@@ -281,23 +283,6 @@ ConnectAttempt::onShutdown(error_code ec)
//--------------------------------------------------------------------------
auto
ConnectAttempt::makeRequest(bool crawl, bool compressionEnabled) -> request_type
{
request_type m;
m.method(boost::beast::http::verb::get);
m.target("/");
m.version(11);
m.insert("User-Agent", BuildInfo::getFullVersionString());
m.insert("Upgrade", supportedProtocolVersions());
m.insert("Connection", "Upgrade");
m.insert("Connect-As", "Peer");
m.insert("Crawl", crawl ? "public" : "private");
if (compressionEnabled)
m.insert("X-Offer-Compression", "lz4");
return m;
}
void
ConnectAttempt::processResponse()
{

View File

@@ -104,10 +104,6 @@ private:
onRead(error_code ec);
void
onShutdown(error_code ec);
static request_type
makeRequest(bool crawl, bool compressionEnabled);
void
processResponse();

View File

@@ -34,6 +34,67 @@
namespace ripple {
std::optional<std::string>
getFeatureValue(
boost::beast::http::fields const& headers,
std::string const& feature)
{
auto const header = headers.find("X-Protocol-Ctl");
if (header == headers.end())
return {};
boost::smatch match;
boost::regex rx(feature + "=([^;\\s]+)");
auto const value = header->value().to_string();
if (boost::regex_search(value, match, rx))
return {match[1]};
return {};
}
bool
isFeatureValue(
boost::beast::http::fields const& headers,
std::string const& feature,
std::string const& value)
{
if (auto const fvalue = getFeatureValue(headers, feature))
return beast::rfc2616::token_in_list(fvalue.value(), value);
return false;
}
bool
featureEnabled(
boost::beast::http::fields const& headers,
std::string const& feature)
{
return isFeatureValue(headers, feature, "1");
}
std::string
makeFeaturesRequestHeader(bool comprEnabled, bool vpReduceRelayEnabled)
{
std::stringstream str;
if (comprEnabled)
str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
if (vpReduceRelayEnabled)
str << FEATURE_VPRR << "=1";
return str.str();
}
std::string
makeFeaturesResponseHeader(
http_request_type const& headers,
bool comprEnabled,
bool vpReduceRelayEnabled)
{
std::stringstream str;
if (comprEnabled && isFeatureValue(headers, FEATURE_COMPR, "lz4"))
str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
if (vpReduceRelayEnabled && featureEnabled(headers, FEATURE_VPRR))
str << FEATURE_VPRR << "=1";
return str.str();
}
/** Hashes the latest finished message from an SSL stream.
@param ssl the session to get the message from.
@@ -48,7 +109,7 @@ namespace ripple {
this topic, see https://github.com/openssl/openssl/issues/5509 and
https://github.com/ripple/rippled/issues/2413.
*/
static boost::optional<base_uint<512>>
static std::optional<base_uint<512>>
hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t))
{
constexpr std::size_t sslMinimumFinishedLength = 12;
@@ -57,7 +118,7 @@ hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t))
size_t len = get(ssl, buf, sizeof(buf));
if (len < sslMinimumFinishedLength)
return boost::none;
return std::nullopt;
sha512_hasher h;
@@ -66,14 +127,14 @@ hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t))
return cookie;
}
boost::optional<uint256>
std::optional<uint256>
makeSharedValue(stream_type& ssl, beast::Journal journal)
{
auto const cookie1 = hashLastMessage(ssl.native_handle(), SSL_get_finished);
if (!cookie1)
{
JLOG(journal.error()) << "Cookie generation: local setup not complete";
return boost::none;
return std::nullopt;
}
auto const cookie2 =
@@ -81,7 +142,7 @@ makeSharedValue(stream_type& ssl, beast::Journal journal)
if (!cookie2)
{
JLOG(journal.error()) << "Cookie generation: peer setup not complete";
return boost::none;
return std::nullopt;
}
auto const result = (*cookie1 ^ *cookie2);
@@ -92,7 +153,7 @@ makeSharedValue(stream_type& ssl, beast::Journal journal)
{
JLOG(journal.error())
<< "Cookie generation: identical finished messages";
return boost::none;
return std::nullopt;
}
return sha512Half(Slice(result.data(), result.size()));
@@ -102,7 +163,7 @@ void
buildHandshake(
boost::beast::http::fields& h,
ripple::uint256 const& sharedValue,
boost::optional<std::uint32_t> networkID,
std::optional<std::uint32_t> networkID,
beast::IP::Address public_ip,
beast::IP::Address remote_ip,
Application& app)
@@ -155,7 +216,7 @@ PublicKey
verifyHandshake(
boost::beast::http::fields const& headers,
ripple::uint256 const& sharedValue,
boost::optional<std::uint32_t> networkID,
std::optional<std::uint32_t> networkID,
beast::IP::Address public_ip,
beast::IP::Address remote,
Application& app)
@@ -291,4 +352,54 @@ verifyHandshake(
return publicKey;
}
auto
makeRequest(bool crawlPublic, bool comprEnabled, bool vpReduceRelayEnabled)
-> request_type
{
request_type m;
m.method(boost::beast::http::verb::get);
m.target("/");
m.version(11);
m.insert("User-Agent", BuildInfo::getFullVersionString());
m.insert("Upgrade", supportedProtocolVersions());
m.insert("Connection", "Upgrade");
m.insert("Connect-As", "Peer");
m.insert("Crawl", crawlPublic ? "public" : "private");
m.insert(
"X-Protocol-Ctl",
makeFeaturesRequestHeader(comprEnabled, vpReduceRelayEnabled));
return m;
}
http_response_type
makeResponse(
bool crawlPublic,
http_request_type const& req,
beast::IP::Address public_ip,
beast::IP::Address remote_ip,
uint256 const& sharedValue,
std::optional<std::uint32_t> networkID,
ProtocolVersion protocol,
Application& app)
{
http_response_type resp;
resp.result(boost::beast::http::status::switching_protocols);
resp.version(req.version());
resp.insert("Connection", "Upgrade");
resp.insert("Upgrade", to_string(protocol));
resp.insert("Connect-As", "Peer");
resp.insert("Server", BuildInfo::getFullVersionString());
resp.insert("Crawl", crawlPublic ? "public" : "private");
resp.insert(
"X-Protocol-Ctl",
makeFeaturesResponseHeader(
req,
app.config().COMPRESSION,
app.config().VP_REDUCE_RELAY_ENABLE));
buildHandshake(resp, sharedValue, networkID, public_ip, remote_ip, app);
return resp;
}
} // namespace ripple

View File

@@ -22,6 +22,7 @@
#include <ripple/app/main/Application.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/overlay/impl/ProtocolVersion.h>
#include <ripple/protocol/BuildInfo.h>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/context.hpp>
@@ -30,14 +31,22 @@
#include <boost/beast/ssl/ssl_stream.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast/http/dynamic_body.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/fields.hpp>
#include <boost/optional.hpp>
#include <optional>
#include <utility>
namespace ripple {
using socket_type = boost::beast::tcp_stream;
using stream_type = boost::beast::ssl_stream<socket_type>;
using request_type =
boost::beast::http::request<boost::beast::http::empty_body>;
using http_request_type =
boost::beast::http::request<boost::beast::http::dynamic_body>;
using http_response_type =
boost::beast::http::response<boost::beast::http::dynamic_body>;
/** Computes a shared value based on the SSL connection state.
@@ -48,7 +57,7 @@ using stream_type = boost::beast::ssl_stream<socket_type>;
@param ssl the SSL/TLS connection state.
@return A 256-bit value on success; an unseated optional otherwise.
*/
boost::optional<uint256>
std::optional<uint256>
makeSharedValue(stream_type& ssl, beast::Journal journal);
/** Insert fields headers necessary for upgrading the link to the peer protocol.
@@ -57,7 +66,7 @@ void
buildHandshake(
boost::beast::http::fields& h,
uint256 const& sharedValue,
boost::optional<std::uint32_t> networkID,
std::optional<std::uint32_t> networkID,
beast::IP::Address public_ip,
beast::IP::Address remote_ip,
Application& app);
@@ -77,11 +86,144 @@ PublicKey
verifyHandshake(
boost::beast::http::fields const& headers,
uint256 const& sharedValue,
boost::optional<std::uint32_t> networkID,
std::optional<std::uint32_t> networkID,
beast::IP::Address public_ip,
beast::IP::Address remote,
Application& app);
/** Make outbound http request
@param crawlPublic if true then server's IP/Port are included in crawl
@param comprEnabled if true then compression feature is enabled
@param vpReduceRelayEnabled if true then reduce-relay feature is enabled
@return http request with empty body
*/
request_type
makeRequest(bool crawlPublic, bool comprEnabled, bool vpReduceRelayEnabled);
/** Make http response
@param crawlPublic if true then server's IP/Port are included in crawl
@param req incoming http request
@param public_ip server's public IP
@param remote_ip peer's IP
@param sharedValue shared value based on the SSL connection state
@param networkID specifies what network we intend to connect to
@param version supported protocol version
@param app Application's reference to access some common properties
@return http response
*/
http_response_type
makeResponse(
bool crawlPublic,
http_request_type const& req,
beast::IP::Address public_ip,
beast::IP::Address remote_ip,
uint256 const& sharedValue,
std::optional<std::uint32_t> networkID,
ProtocolVersion version,
Application& app);
// Protocol features negotiated via HTTP handshake.
// The format is:
// X-Protocol-Ctl: feature1=value1[,value2]*[\s*;\s*feature2=value1[,value2]*]*
// value: \S+
static constexpr char FEATURE_COMPR[] = "compr"; // compression
static constexpr char FEATURE_VPRR[] =
"vprr"; // validation/proposal reduce-relay
static constexpr char DELIM_FEATURE[] = ";";
static constexpr char DELIM_VALUE[] = ",";
/** Get feature's header value
@param headers request/response header
@param feature name
@return seated optional with feature's value if the feature
is found in the header, unseated optional otherwise
*/
std::optional<std::string>
getFeatureValue(
boost::beast::http::fields const& headers,
std::string const& feature);
/** Check if a feature's value is equal to the specified value
@param headers request/response header
@param feature to check
@param value of the feature to check, must be a single value; i.e. not
value1,value2...
@return true if the feature's value matches the specified value, false if
doesn't match or the feature is not found in the header
*/
bool
isFeatureValue(
boost::beast::http::fields const& headers,
std::string const& feature,
std::string const& value);
/** Check if a feature is enabled
@param headers request/response header
@param feature to check
@return true if enabled
*/
bool
featureEnabled(
boost::beast::http::fields const& headers,
std::string const& feature);
/** Check if a feature should be enabled for a peer. The feature
is enabled if its configured value is true and the http header
has the specified feature value.
@tparam headers request (inbound) or response (outbound) header
@param request http headers
@param feature to check
@param config feature's configuration value
@param value feature's value to check in the headers
@return true if the feature is enabled
*/
template <typename headers>
bool
peerFeatureEnabled(
headers const& request,
std::string const& feature,
std::string value,
bool config)
{
return config && isFeatureValue(request, feature, value);
}
/** Wrapper for enable(1)/disable type(0) of feature */
template <typename headers>
bool
peerFeatureEnabled(
headers const& request,
std::string const& feature,
bool config)
{
return config && peerFeatureEnabled(request, feature, "1", config);
}
/** Make request header X-Protocol-Ctl value with supported features
@param comprEnabled if true then compression feature is enabled
@param vpReduceRelayEnabled if true then reduce-relay feature is enabled
@return X-Protocol-Ctl header value
*/
std::string
makeFeaturesRequestHeader(bool comprEnabled, bool vpReduceRelayEnabled);
/** Make response header X-Protocol-Ctl value with supported features.
If the request has a feature that we support enabled
and the feature's configuration is enabled then enable this feature in
the response header.
@param header request's header
@param comprEnabled if true then compression feature is enabled
@param vpReduceRelayEnabled if true then reduce-relay feature is enabled
@return X-Protocol-Ctl header value
*/
std::string
makeFeaturesResponseHeader(
http_request_type const& headers,
bool comprEnabled,
bool vpReduceRelayEnabled);
} // namespace ripple
#endif

View File

@@ -1388,7 +1388,7 @@ std::shared_ptr<Message>
makeSquelchMessage(
PublicKey const& validator,
bool squelch,
uint64_t squelchDuration)
uint32_t squelchDuration)
{
protocol::TMSquelch m;
m.set_squelch(squelch);
@@ -1402,12 +1402,11 @@ void
OverlayImpl::unsquelch(PublicKey const& validator, Peer::id_t id) const
{
if (auto peer = findPeerByShortID(id);
peer && app_.config().REDUCE_RELAY_SQUELCH)
peer && app_.config().VP_REDUCE_RELAY_SQUELCH)
{
// optimize - multiple message with different
// validator might be sent to the same peer
auto m = makeSquelchMessage(validator, false, 0);
peer->send(m);
peer->send(makeSquelchMessage(validator, false, 0));
}
}
@@ -1418,10 +1417,9 @@ OverlayImpl::squelch(
uint32_t squelchDuration) const
{
if (auto peer = findPeerByShortID(id);
peer && app_.config().REDUCE_RELAY_SQUELCH)
peer && app_.config().VP_REDUCE_RELAY_SQUELCH)
{
auto m = makeSquelchMessage(validator, true, squelchDuration);
peer->send(m);
peer->send(makeSquelchMessage(validator, true, squelchDuration));
}
}

View File

@@ -54,7 +54,7 @@ namespace ripple {
class PeerImp;
class BasicConfig;
class OverlayImpl : public Overlay, public squelch::SquelchHandler
class OverlayImpl : public Overlay, public reduce_relay::SquelchHandler
{
public:
class Child
@@ -126,7 +126,7 @@ private:
boost::optional<std::uint32_t> networkID_;
squelch::Slots<UptimeClock> slots_;
reduce_relay::Slots<UptimeClock> slots_;
// A message with the list of manifests we send to peers
std::shared_ptr<Message> manifestMessage_;

View File

@@ -94,16 +94,30 @@ PeerImp::PeerImp(
, publicKey_(publicKey)
, lastPingTime_(clock_type::now())
, creationTime_(clock_type::now())
, squelch_(app_.journal("Squelch"))
, usage_(consumer)
, fee_(Resource::feeLightPeer)
, slot_(slot)
, request_(std::move(request))
, headers_(request_)
, compressionEnabled_(
headers_["X-Offer-Compression"] == "lz4" && app_.config().COMPRESSION
peerFeatureEnabled(
headers_,
FEATURE_COMPR,
"lz4",
app_.config().COMPRESSION)
? Compressed::On
: Compressed::Off)
, vpReduceRelayEnabled_(peerFeatureEnabled(
headers_,
FEATURE_VPRR,
app_.config().VP_REDUCE_RELAY_ENABLE))
{
JLOG(journal_.debug()) << " compression enabled "
<< (compressionEnabled_ == Compressed::On)
<< " vp reduce-relay enabled "
<< vpReduceRelayEnabled_ << " on " << remote_address_
<< " " << id_;
}
PeerImp::~PeerImp()
@@ -223,7 +237,7 @@ PeerImp::send(std::shared_ptr<Message> const& m)
return;
auto validator = m->getValidatorKey();
if (validator && squelch_.isSquelched(*validator))
if (validator && !squelch_.expireSquelch(*validator))
return;
overlay_.reportTraffic(
@@ -739,36 +753,17 @@ PeerImp::doAccept()
// XXX Set timer: connection idle (idle may vary depending on connection
// type.)
auto write_buffer = [this, sharedValue]() {
auto buf = std::make_shared<boost::beast::multi_buffer>();
auto write_buffer = std::make_shared<boost::beast::multi_buffer>();
http_response_type resp;
resp.result(boost::beast::http::status::switching_protocols);
resp.version(request_.version());
resp.insert("Connection", "Upgrade");
resp.insert("Upgrade", to_string(protocol_));
resp.insert("Connect-As", "Peer");
resp.insert("Server", BuildInfo::getFullVersionString());
resp.insert(
"Crawl",
overlay_.peerFinder().config().peerPrivate ? "private" : "public");
if (request_["X-Offer-Compression"] == "lz4" &&
app_.config().COMPRESSION)
resp.insert("X-Offer-Compression", "lz4");
buildHandshake(
resp,
*sharedValue,
overlay_.setup().networkID,
overlay_.setup().public_ip,
remote_address_.address(),
app_);
boost::beast::ostream(*buf) << resp;
return buf;
}();
boost::beast::ostream(*write_buffer) << makeResponse(
!overlay_.peerFinder().config().peerPrivate,
request_,
overlay_.setup().public_ip,
remote_address_.address(),
*sharedValue,
overlay_.setup().networkID,
protocol_,
app_);
// Write the whole buffer and only start protocol when that's done.
boost::asio::async_write(
@@ -971,13 +966,17 @@ void
PeerImp::onMessageBegin(
std::uint16_t type,
std::shared_ptr<::google::protobuf::Message> const& m,
std::size_t size)
std::size_t size,
std::size_t uncompressed_size,
bool isCompressed)
{
load_event_ =
app_.getJobQueue().makeLoadEvent(jtPEER, protocolMessageName(type));
fee_ = Resource::feeLightPeer;
overlay_.reportTraffic(
TrafficCount::categorize(*m, type, true), true, static_cast<int>(size));
JLOG(journal_.trace()) << "onMessageBegin: " << type << " " << size << " "
<< uncompressed_size << " " << isCompressed;
}
void
@@ -1566,12 +1565,8 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMProposeSet> const& m)
{
// Count unique messages (Slots has it's own 'HashRouter'), which a peer
// receives within IDLED seconds since the message has been relayed.
// Wait WAIT_ON_BOOTUP time to let the server establish connections to
// peers.
if (app_.config().REDUCE_RELAY_ENABLE && relayed &&
(stopwatch().now() - *relayed) < squelch::IDLED &&
squelch::epoch<std::chrono::minutes>(UptimeClock::now()) >
squelch::WAIT_ON_BOOTUP)
if (reduceRelayReady() && relayed &&
(stopwatch().now() - *relayed) < reduce_relay::IDLED)
overlay_.updateSlotAndSquelch(
suppression, publicKey, id_, protocol::mtPROPOSE_LEDGER);
JLOG(p_journal_.trace()) << "Proposal: duplicate";
@@ -2173,10 +2168,8 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMValidation> const& m)
// peer receives within IDLED seconds since the message has been
// relayed. Wait WAIT_ON_BOOTUP time to let the server establish
// connections to peers.
if (app_.config().REDUCE_RELAY_ENABLE && (bool)relayed &&
(stopwatch().now() - *relayed) < squelch::IDLED &&
squelch::epoch<std::chrono::minutes>(UptimeClock::now()) >
squelch::WAIT_ON_BOOTUP)
if (reduceRelayReady() && relayed &&
(stopwatch().now() - *relayed) < reduce_relay::IDLED)
overlay_.updateSlotAndSquelch(
key, val->getSignerPublic(), id_, protocol::mtVALIDATION);
JLOG(p_journal_.trace()) << "Validation: duplicate";
@@ -2358,6 +2351,14 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
void
PeerImp::onMessage(std::shared_ptr<protocol::TMSquelch> const& m)
{
using on_message_fn =
void (PeerImp::*)(std::shared_ptr<protocol::TMSquelch> const&);
if (!strand_.running_in_this_thread())
return post(
strand_,
std::bind(
(on_message_fn)&PeerImp::onMessage, shared_from_this(), m));
if (!m->has_validatorpubkey())
{
charge(Resource::feeBadData);
@@ -2371,9 +2372,6 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMSquelch> const& m)
return;
}
PublicKey key(slice);
auto squelch = m->squelch();
auto duration = m->has_squelchduration() ? m->squelchduration() : 0;
auto sp = shared_from_this();
// Ignore the squelch for validator's own messages.
if (key == app_.getValidationPublicKey())
@@ -2383,15 +2381,15 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMSquelch> const& m)
return;
}
if (!strand_.running_in_this_thread())
return post(strand_, [sp, key, squelch, duration]() {
sp->squelch_.squelch(key, squelch, duration);
});
std::uint32_t duration =
m->has_squelchduration() ? m->squelchduration() : 0;
if (!m->squelch())
squelch_.removeSquelch(key);
else if (!squelch_.addSquelch(key, std::chrono::seconds{duration}))
charge(Resource::feeBadData);
JLOG(p_journal_.debug())
<< "onMessage: TMSquelch " << slice << " " << id() << " " << duration;
squelch_.squelch(key, squelch, duration);
}
//--------------------------------------------------------------------------
@@ -2555,9 +2553,7 @@ PeerImp::checkPropose(
// as part of the squelch logic.
auto haveMessage = app_.overlay().relay(
*packet, peerPos.suppressionID(), peerPos.publicKey());
if (app_.config().REDUCE_RELAY_ENABLE && !haveMessage.empty() &&
squelch::epoch<std::chrono::minutes>(UptimeClock::now()) >
squelch::WAIT_ON_BOOTUP)
if (reduceRelayReady() && !haveMessage.empty())
overlay_.updateSlotAndSquelch(
peerPos.suppressionID(),
peerPos.publicKey(),
@@ -2592,9 +2588,7 @@ PeerImp::checkValidation(
// as part of the squelch logic.
auto haveMessage =
overlay_.relay(*packet, suppression, val->getSignerPublic());
if (app_.config().REDUCE_RELAY_ENABLE && !haveMessage.empty() &&
squelch::epoch<std::chrono::minutes>(UptimeClock::now()) >
squelch::WAIT_ON_BOOTUP)
if (reduceRelayReady() && !haveMessage.empty())
{
overlay_.updateSlotAndSquelch(
suppression,
@@ -3038,6 +3032,16 @@ PeerImp::isHighLatency() const
return latency_ >= peerHighLatency;
}
bool
PeerImp::reduceRelayReady()
{
if (!reduceRelayReady_)
reduceRelayReady_ =
reduce_relay::epoch<std::chrono::minutes>(UptimeClock::now()) >
reduce_relay::WAIT_ON_BOOTUP;
return vpReduceRelayEnabled_ && reduceRelayReady_;
}
void
PeerImp::Metrics::add_message(std::uint64_t bytes)
{

View File

@@ -118,7 +118,8 @@ private:
clock_type::time_point lastPingTime_;
clock_type::time_point const creationTime_;
squelch::Squelch<UptimeClock> squelch_;
reduce_relay::Squelch<UptimeClock> squelch_;
inline static std::atomic_bool reduceRelayReady_{false};
// Notes on thread locking:
//
@@ -169,6 +170,10 @@ private:
Compressed compressionEnabled_ = Compressed::Off;
// true if validation/proposal reduce-relay feature is enabled
// on the peer.
bool vpReduceRelayEnabled_ = false;
friend class OverlayImpl;
class Metrics
@@ -459,6 +464,11 @@ private:
void
onWriteMessage(error_code ec, std::size_t bytes_transferred);
// Check if reduce-relay feature is enabled and
// reduce_relay::WAIT_ON_BOOTUP time passed since the start
bool
reduceRelayReady();
public:
//--------------------------------------------------------------------------
//
@@ -473,7 +483,9 @@ public:
onMessageBegin(
std::uint16_t type,
std::shared_ptr<::google::protobuf::Message> const& m,
std::size_t size);
std::size_t size,
std::size_t uncompressed_size,
bool isCompressed);
void
onMessageEnd(
@@ -594,18 +606,32 @@ PeerImp::PeerImp(
, publicKey_(publicKey)
, lastPingTime_(clock_type::now())
, creationTime_(clock_type::now())
, squelch_(app_.journal("Squelch"))
, usage_(usage)
, fee_(Resource::feeLightPeer)
, slot_(std::move(slot))
, response_(std::move(response))
, headers_(response_)
, compressionEnabled_(
headers_["X-Offer-Compression"] == "lz4" && app_.config().COMPRESSION
peerFeatureEnabled(
headers_,
FEATURE_COMPR,
"lz4",
app_.config().COMPRESSION)
? Compressed::On
: Compressed::Off)
, vpReduceRelayEnabled_(peerFeatureEnabled(
headers_,
FEATURE_VPRR,
app_.config().VP_REDUCE_RELAY_ENABLE))
{
read_buffer_.commit(boost::asio::buffer_copy(
read_buffer_.prepare(boost::asio::buffer_size(buffers)), buffers));
JLOG(journal_.debug()) << "compression enabled "
<< (compressionEnabled_ == Compressed::On)
<< " vp reduce-relay enabled "
<< vpReduceRelayEnabled_ << " on " << remote_address_
<< " " << id_;
}
template <class FwdIt, class>

View File

@@ -31,6 +31,7 @@
#include <cassert>
#include <cstdint>
#include <memory>
#include <optional>
#include <type_traits>
#include <vector>
@@ -124,15 +125,25 @@ buffersBegin(BufferSequence const& bufs)
bufs);
}
template <typename BufferSequence>
auto
buffersEnd(BufferSequence const& bufs)
{
return boost::asio::buffers_iterator<BufferSequence, std::uint8_t>::end(
bufs);
}
/** Parse a message header
* @return a seated optional if the message header was successfully
* parsed. An unseated optional otherwise, in which case
* @param ec contains more information:
* - set to `errc::success` if not enough bytes were present
* - set to `errc::no_message` if a valid header was not present
* @bufs - sequence of input buffers, can't be empty
* @size input data size
*/
template <class BufferSequence>
boost::optional<MessageHeader>
std::optional<MessageHeader>
parseMessageHeader(
boost::system::error_code& ec,
BufferSequence const& bufs,
@@ -142,6 +153,7 @@ parseMessageHeader(
MessageHeader hdr;
auto iter = buffersBegin(bufs);
assert(iter != buffersEnd(bufs));
// Check valid header compressed message:
// - 4 bits are the compression algorithm, 1st bit is always set to 1
@@ -156,13 +168,13 @@ parseMessageHeader(
if (size < hdr.header_size)
{
ec = make_error_code(boost::system::errc::success);
return boost::none;
return std::nullopt;
}
if (*iter & 0x0C)
{
ec = make_error_code(boost::system::errc::protocol_error);
return boost::none;
return std::nullopt;
}
hdr.algorithm = static_cast<compression::Algorithm>(*iter & 0xF0);
@@ -170,7 +182,7 @@ parseMessageHeader(
if (hdr.algorithm != compression::Algorithm::LZ4)
{
ec = make_error_code(boost::system::errc::protocol_error);
return boost::none;
return std::nullopt;
}
for (int i = 0; i != 4; ++i)
@@ -200,7 +212,7 @@ parseMessageHeader(
if (size < hdr.header_size)
{
ec = make_error_code(boost::system::errc::success);
return boost::none;
return std::nullopt;
}
hdr.algorithm = Algorithm::None;
@@ -218,7 +230,7 @@ parseMessageHeader(
}
ec = make_error_code(boost::system::errc::no_message);
return boost::none;
return std::nullopt;
}
template <
@@ -268,7 +280,13 @@ invoke(MessageHeader const& header, Buffers const& buffers, Handler& handler)
if (!m)
return false;
handler.onMessageBegin(header.message_type, m, header.payload_wire_size);
using namespace ripple::compression;
handler.onMessageBegin(
header.message_type,
m,
header.payload_wire_size,
header.uncompressed_size,
header.algorithm != Algorithm::None);
handler.onMessage(m);
handler.onMessageEnd(header.message_type, m);

View File

@@ -362,6 +362,6 @@ message TMSquelch
{
required bool squelch = 1; // squelch if true, otherwise unsquelch
required bytes validatorPubKey = 2; // validator's public key
optional uint32 squelchDuration = 3; // squelch duration in milliseconds
optional uint32 squelchDuration = 3; // squelch duration in seconds
}

View File

@@ -25,6 +25,7 @@
#include <ripple/core/TimeKeeper.h>
#include <ripple/overlay/Compression.h>
#include <ripple/overlay/Message.h>
#include <ripple/overlay/impl/Handshake.h>
#include <ripple/overlay/impl/ProtocolMessage.h>
#include <ripple/overlay/impl/ZeroCopyStream.h>
#include <ripple/protocol/HashPrefix.h>
@@ -112,7 +113,7 @@ public:
BEAST_EXPECT(header);
if (header->algorithm == Algorithm::None)
if (!header || header->algorithm == Algorithm::None)
return;
std::vector<std::uint8_t> decompressed;
@@ -242,7 +243,7 @@ public:
uint256 const hash(ripple::sha512Half(123456789));
getLedger->set_ledgerhash(hash.begin(), hash.size());
getLedger->set_ledgerseq(123456789);
ripple::SHAMapNodeID sha(17, hash);
ripple::SHAMapNodeID sha(64, hash);
getLedger->add_nodeids(sha.getRawString());
getLedger->set_requestcookie(123456789);
getLedger->set_querytype(protocol::qtINDIRECT);
@@ -302,7 +303,7 @@ public:
uint256 hash(ripple::sha512Half(i));
auto object = getObject->add_objects();
object->set_hash(hash.data(), hash.size());
ripple::SHAMapNodeID sha(i % 55, hash);
ripple::SHAMapNodeID sha(64, hash);
object->set_nodeid(sha.getRawString());
object->set_index("");
object->set_data("");
@@ -458,14 +459,80 @@ public:
"TMValidatorListCollection");
}
void
testHandshake()
{
testcase("Handshake");
auto getEnv = [&](bool enable) {
Config c;
std::stringstream str;
str << "[reduce_relay]\n"
<< "vp_enable=1\n"
<< "vp_squelch=1\n"
<< "[compression]\n"
<< enable << "\n";
c.loadFromString(str.str());
auto env = std::make_shared<jtx::Env>(*this);
env->app().config().COMPRESSION = c.COMPRESSION;
env->app().config().VP_REDUCE_RELAY_ENABLE =
c.VP_REDUCE_RELAY_ENABLE;
env->app().config().VP_REDUCE_RELAY_SQUELCH =
c.VP_REDUCE_RELAY_SQUELCH;
return env;
};
auto handshake = [&](int outboundEnable, int inboundEnable) {
beast::IP::Address addr =
boost::asio::ip::address::from_string("172.1.1.100");
auto env = getEnv(outboundEnable);
auto request = ripple::makeRequest(
true,
env->app().config().COMPRESSION,
env->app().config().VP_REDUCE_RELAY_ENABLE);
http_request_type http_request;
http_request.version(request.version());
http_request.base() = request.base();
// feature enabled on the peer's connection only if both sides are
// enabled
auto const peerEnabled = inboundEnable && outboundEnable;
// inbound is enabled if the request's header has the feature
// enabled and the peer's configuration is enabled
auto const inboundEnabled = peerFeatureEnabled(
http_request, FEATURE_COMPR, "lz4", inboundEnable);
BEAST_EXPECT(!(peerEnabled ^ inboundEnabled));
env.reset();
env = getEnv(inboundEnable);
auto http_resp = ripple::makeResponse(
true,
http_request,
addr,
addr,
uint256{1},
1,
{1, 0},
env->app());
// outbound is enabled if the response's header has the feature
// enabled and the peer's configuration is enabled
auto const outboundEnabled = peerFeatureEnabled(
http_resp, FEATURE_COMPR, "lz4", outboundEnable);
BEAST_EXPECT(!(peerEnabled ^ outboundEnabled));
};
handshake(1, 1);
handshake(1, 0);
handshake(0, 1);
handshake(0, 0);
}
void
run() override
{
testProtocol();
testHandshake();
}
};
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(compression, ripple_data, ripple, 20);
BEAST_DEFINE_TESTSUITE_MANUAL(compression, ripple_data, ripple);
} // namespace test
} // namespace ripple

View File

@@ -0,0 +1,64 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/beast/unit_test.h>
#include <ripple/overlay/impl/Handshake.h>
namespace ripple {
namespace test {
class handshake_test : public beast::unit_test::suite
{
public:
handshake_test() = default;
void
testHandshake()
{
testcase("X-Protocol-Ctl");
boost::beast::http::fields headers;
headers.insert(
"X-Protocol-Ctl",
"feature1=v1,v2,v3; feature2=v4; feature3=10; feature4=1; "
"feature5=v6");
BEAST_EXPECT(!featureEnabled(headers, "feature1"));
BEAST_EXPECT(!isFeatureValue(headers, "feature1", "2"));
BEAST_EXPECT(isFeatureValue(headers, "feature1", "v1"));
BEAST_EXPECT(isFeatureValue(headers, "feature1", "v2"));
BEAST_EXPECT(isFeatureValue(headers, "feature1", "v3"));
BEAST_EXPECT(isFeatureValue(headers, "feature2", "v4"));
BEAST_EXPECT(!isFeatureValue(headers, "feature3", "1"));
BEAST_EXPECT(isFeatureValue(headers, "feature3", "10"));
BEAST_EXPECT(!isFeatureValue(headers, "feature4", "10"));
BEAST_EXPECT(isFeatureValue(headers, "feature4", "1"));
BEAST_EXPECT(!featureEnabled(headers, "v6"));
}
void
run() override
{
testHandshake();
}
};
BEAST_DEFINE_TESTSUITE(handshake, ripple_data, ripple);
} // namespace test
} // namespace ripple

View File

@@ -21,6 +21,7 @@
#include <ripple/overlay/Message.h>
#include <ripple/overlay/Peer.h>
#include <ripple/overlay/Slot.h>
#include <ripple/overlay/impl/Handshake.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple.pb.h>
#include <test/jtx/Env.h>
@@ -437,7 +438,8 @@ class PeerSim : public PeerPartial, public std::enable_shared_from_this<PeerSim>
{
public:
using id_t = Peer::id_t;
PeerSim(Overlay& overlay) : overlay_(overlay)
PeerSim(Overlay& overlay, beast::Journal journal)
: overlay_(overlay), squelch_(journal)
{
id_ = sid_++;
}
@@ -462,7 +464,7 @@ public:
{
auto validator = m->getValidatorKey();
assert(validator);
if (squelch_.isSquelched(*validator))
if (!squelch_.expireSquelch(*validator))
return;
overlay_.updateSlotAndSquelch({}, *validator, id(), f);
@@ -474,24 +476,28 @@ public:
{
auto validator = squelch.validatorpubkey();
PublicKey key(Slice(validator.data(), validator.size()));
squelch_.squelch(key, squelch.squelch(), squelch.squelchduration());
if (squelch.squelch())
squelch_.addSquelch(
key, std::chrono::seconds{squelch.squelchduration()});
else
squelch_.removeSquelch(key);
}
private:
inline static id_t sid_ = 0;
id_t id_;
Overlay& overlay_;
squelch::Squelch<ManualClock> squelch_;
reduce_relay::Squelch<ManualClock> squelch_;
};
class OverlaySim : public Overlay, public squelch::SquelchHandler
class OverlaySim : public Overlay, public reduce_relay::SquelchHandler
{
using Peers = std::unordered_map<Peer::id_t, PeerSPtr>;
public:
using id_t = Peer::id_t;
using clock_type = ManualClock;
OverlaySim(Application& app) : slots_(app, *this)
OverlaySim(Application& app) : slots_(app, *this), app_(app)
{
}
@@ -506,7 +512,7 @@ public:
}
std::uint16_t
inState(PublicKey const& validator, squelch::PeerState state)
inState(PublicKey const& validator, reduce_relay::PeerState state)
{
auto res = slots_.inState(validator, state);
return res ? *res : 0;
@@ -545,7 +551,7 @@ public:
Peer::id_t id;
if (peersCache_.empty() || !useCache)
{
peer = std::make_shared<PeerSim>(*this);
peer = std::make_shared<PeerSim>(*this, app_.journal("Squelch"));
id = peer->id();
}
else
@@ -602,7 +608,7 @@ public:
bool
isCountingState(PublicKey const& validator)
{
return slots_.inState(validator, squelch::SlotState::Counting);
return slots_.inState(validator, reduce_relay::SlotState::Counting);
}
std::set<id_t>
@@ -629,7 +635,7 @@ public:
std::unordered_map<
id_t,
std::tuple<
squelch::PeerState,
reduce_relay::PeerState,
std::uint16_t,
std::uint32_t,
std::uint32_t>>
@@ -664,7 +670,8 @@ private:
UnsquelchCB unsquelch_;
Peers peers_;
Peers peersCache_;
squelch::Slots<ManualClock> slots_;
reduce_relay::Slots<ManualClock> slots_;
Application& app_;
};
class Network
@@ -843,8 +850,8 @@ public:
for (auto& [_, v] : peers)
{
(void)_;
if (std::get<squelch::PeerState>(v) ==
squelch::PeerState::Squelched)
if (std::get<reduce_relay::PeerState>(v) ==
reduce_relay::PeerState::Squelched)
return false;
}
}
@@ -858,7 +865,7 @@ private:
class reduce_relay_test : public beast::unit_test::suite
{
using Slot = squelch::Slot<ManualClock>;
using Slot = reduce_relay::Slot<ManualClock>;
using id_t = Peer::id_t;
protected:
@@ -870,7 +877,7 @@ protected:
<< "num peers " << (int)network_.overlay().getNumPeers()
<< std::endl;
for (auto& [k, v] : peers)
std::cout << k << ":" << (int)std::get<squelch::PeerState>(v)
std::cout << k << ":" << (int)std::get<reduce_relay::PeerState>(v)
<< " ";
std::cout << std::endl;
}
@@ -950,7 +957,8 @@ protected:
str << s << " ";
if (log)
std::cout
<< (double)squelch::epoch<milliseconds>(now).count() /
<< (double)reduce_relay::epoch<milliseconds>(now)
.count() /
1000.
<< " random, squelched, validator: " << validator.id()
<< " peers: " << str.str() << std::endl;
@@ -958,7 +966,7 @@ protected:
network_.overlay().isCountingState(validator);
BEAST_EXPECT(
countingState == false &&
selected.size() == squelch::MAX_SELECTED_PEERS);
selected.size() == reduce_relay::MAX_SELECTED_PEERS);
}
// Trigger Link Down or Peer Disconnect event
@@ -1038,12 +1046,13 @@ protected:
event.isSelected_ =
network_.overlay().isSelected(event.key_, event.peer_);
auto peers = network_.overlay().getPeers(event.key_);
auto d = squelch::epoch<milliseconds>(now).count() -
auto d = reduce_relay::epoch<milliseconds>(now).count() -
std::get<3>(peers[event.peer_]);
mustHandle = event.isSelected_ &&
d > milliseconds(squelch::IDLED).count() &&
d > milliseconds(reduce_relay::IDLED).count() &&
network_.overlay().inState(
event.key_, squelch::PeerState::Squelched) > 0 &&
event.key_, reduce_relay::PeerState::Squelched) >
0 &&
peers.find(event.peer_) != peers.end();
}
network_.overlay().deleteIdlePeers(
@@ -1062,7 +1071,7 @@ protected:
}
if (event.state_ == State::WaitReset ||
(event.state_ == State::On &&
(now - event.time_ > (squelch::IDLED + seconds(2)))))
(now - event.time_ > (reduce_relay::IDLED + seconds(2)))))
{
bool handled =
event.state_ == State::WaitReset || !event.handled_;
@@ -1158,16 +1167,17 @@ protected:
if (squelched)
{
BEAST_EXPECT(
squelched == MAX_PEERS - squelch::MAX_SELECTED_PEERS);
squelched ==
MAX_PEERS - reduce_relay::MAX_SELECTED_PEERS);
n++;
}
},
1,
squelch::MAX_MESSAGE_THRESHOLD + 2,
reduce_relay::MAX_MESSAGE_THRESHOLD + 2,
purge,
resetClock);
auto selected = network_.overlay().getSelected(network_.validator(0));
BEAST_EXPECT(selected.size() == squelch::MAX_SELECTED_PEERS);
BEAST_EXPECT(selected.size() == reduce_relay::MAX_SELECTED_PEERS);
BEAST_EXPECT(n == 1); // only one selection round
auto res = checkCounting(network_.validator(0), false);
BEAST_EXPECT(res);
@@ -1231,7 +1241,7 @@ protected:
unsquelched++;
});
BEAST_EXPECT(
unsquelched == MAX_PEERS - squelch::MAX_SELECTED_PEERS);
unsquelched == MAX_PEERS - reduce_relay::MAX_SELECTED_PEERS);
BEAST_EXPECT(checkCounting(network_.validator(0), true));
});
}
@@ -1244,7 +1254,7 @@ protected:
doTest("Selected Peer Stops Relaying", log, [this](bool log) {
ManualClock::advance(seconds(601));
BEAST_EXPECT(propagateAndSquelch(log, true, false));
ManualClock::advance(squelch::IDLED + seconds(1));
ManualClock::advance(reduce_relay::IDLED + seconds(1));
std::uint16_t unsquelched = 0;
network_.overlay().deleteIdlePeers(
[&](PublicKey const& key, PeerWPtr const& peer) {
@@ -1252,7 +1262,7 @@ protected:
});
auto peers = network_.overlay().getPeers(network_.validator(0));
BEAST_EXPECT(
unsquelched == MAX_PEERS - squelch::MAX_SELECTED_PEERS);
unsquelched == MAX_PEERS - reduce_relay::MAX_SELECTED_PEERS);
BEAST_EXPECT(checkCounting(network_.validator(0), true));
});
}
@@ -1267,8 +1277,8 @@ protected:
BEAST_EXPECT(propagateAndSquelch(log, true, false));
auto peers = network_.overlay().getPeers(network_.validator(0));
auto it = std::find_if(peers.begin(), peers.end(), [&](auto it) {
return std::get<squelch::PeerState>(it.second) ==
squelch::PeerState::Squelched;
return std::get<reduce_relay::PeerState>(it.second) ==
reduce_relay::PeerState::Squelched;
});
assert(it != peers.end());
std::uint16_t unsquelched = 0;
@@ -1289,37 +1299,37 @@ protected:
std::string toLoad(R"rippleConfig(
[reduce_relay]
enable=1
squelch=1
vp_enable=1
vp_squelch=1
)rippleConfig");
c.loadFromString(toLoad);
BEAST_EXPECT(c.REDUCE_RELAY_ENABLE == true);
BEAST_EXPECT(c.REDUCE_RELAY_SQUELCH == true);
BEAST_EXPECT(c.VP_REDUCE_RELAY_ENABLE == true);
BEAST_EXPECT(c.VP_REDUCE_RELAY_SQUELCH == true);
Config c1;
toLoad = (R"rippleConfig(
[reduce_relay]
enable=0
squelch=0
vp_enable=0
vp_squelch=0
)rippleConfig");
c1.loadFromString(toLoad);
BEAST_EXPECT(c1.REDUCE_RELAY_ENABLE == false);
BEAST_EXPECT(c1.REDUCE_RELAY_SQUELCH == false);
BEAST_EXPECT(c1.VP_REDUCE_RELAY_ENABLE == false);
BEAST_EXPECT(c1.VP_REDUCE_RELAY_SQUELCH == false);
Config c2;
toLoad = R"rippleConfig(
[reduce_relay]
enabled=1
squelched=1
vp_enabled=1
vp_squelched=1
)rippleConfig";
c2.loadFromString(toLoad);
BEAST_EXPECT(c2.REDUCE_RELAY_ENABLE == false);
BEAST_EXPECT(c2.REDUCE_RELAY_SQUELCH == false);
BEAST_EXPECT(c2.VP_REDUCE_RELAY_ENABLE == false);
BEAST_EXPECT(c2.VP_REDUCE_RELAY_SQUELCH == false);
});
}
@@ -1354,7 +1364,7 @@ squelched=1
peers = network_.overlay().getPeers(network_.validator(0));
BEAST_EXPECT(std::get<1>(peers[0]) == (nMessages - 1));
// advance the clock
ManualClock::advance(squelch::IDLED + seconds(1));
ManualClock::advance(reduce_relay::IDLED + seconds(1));
network_.overlay().updateSlotAndSquelch(
key,
network_.validator(0),
@@ -1366,6 +1376,166 @@ squelched=1
});
}
struct Handler : public reduce_relay::SquelchHandler
{
Handler() : maxDuration_(0)
{
}
void
squelch(PublicKey const&, Peer::id_t, std::uint32_t duration)
const override
{
if (duration > maxDuration_)
maxDuration_ = duration;
}
void
unsquelch(PublicKey const&, Peer::id_t) const override
{
}
mutable int maxDuration_;
};
void
testRandomSquelch(bool l)
{
doTest("Random Squelch", l, [&](bool l) {
PublicKey validator = std::get<0>(randomKeyPair(KeyType::ed25519));
Handler handler;
auto run = [&](int npeers) {
handler.maxDuration_ = 0;
reduce_relay::Slots<ManualClock> slots(env_.app(), handler);
// 1st message from a new peer switches the slot
// to counting state and resets the counts of all peers +
// MAX_MESSAGE_THRESHOLD + 1 messages to reach the threshold
// and switch the slot's state to peer selection.
for (int m = 1; m <= reduce_relay::MAX_MESSAGE_THRESHOLD + 2;
m++)
{
for (int peer = 0; peer < npeers; peer++)
{
// make unique message hash to make the
// slot's internal hash router accept the message
std::uint64_t mid = m * 1000 + peer;
uint256 const message{mid};
slots.updateSlotAndSquelch(
message,
validator,
peer,
protocol::MessageType::mtVALIDATION);
}
}
// make Slot's internal hash router expire all messages
ManualClock::advance(hours(1));
};
using namespace reduce_relay;
// expect max duration less than MAX_UNSQUELCH_EXPIRE_DEFAULT with
// less than or equal to 60 peers
run(20);
BEAST_EXPECT(
handler.maxDuration_ >= MIN_UNSQUELCH_EXPIRE.count() &&
handler.maxDuration_ <= MAX_UNSQUELCH_EXPIRE_DEFAULT.count());
run(60);
BEAST_EXPECT(
handler.maxDuration_ >= MIN_UNSQUELCH_EXPIRE.count() &&
handler.maxDuration_ <= MAX_UNSQUELCH_EXPIRE_DEFAULT.count());
// expect max duration greater than MIN_UNSQUELCH_EXPIRE and less
// than MAX_UNSQUELCH_EXPIRE_PEERS with peers greater than 60
// and less than 360
run(350);
// can't make this condition stronger. squelch
// duration is probabilistic and max condition may still fail.
// log when the value is low
BEAST_EXPECT(
handler.maxDuration_ >= MIN_UNSQUELCH_EXPIRE.count() &&
handler.maxDuration_ <= MAX_UNSQUELCH_EXPIRE_PEERS.count());
using namespace beast::unit_test::detail;
if (handler.maxDuration_ <= MAX_UNSQUELCH_EXPIRE_DEFAULT.count())
log << make_reason(
"warning: squelch duration is low",
__FILE__,
__LINE__)
<< std::endl
<< std::flush;
// more than 400 is still less than MAX_UNSQUELCH_EXPIRE_PEERS
run(400);
BEAST_EXPECT(
handler.maxDuration_ >= MIN_UNSQUELCH_EXPIRE.count() &&
handler.maxDuration_ <= MAX_UNSQUELCH_EXPIRE_PEERS.count());
if (handler.maxDuration_ <= MAX_UNSQUELCH_EXPIRE_DEFAULT.count())
log << make_reason(
"warning: squelch duration is low",
__FILE__,
__LINE__)
<< std::endl
<< std::flush;
});
}
void
testHandshake(bool log)
{
doTest("Handshake", log, [&](bool log) {
auto setEnv = [&](bool enable) {
Config c;
std::stringstream str;
str << "[reduce_relay]\n"
<< "vp_enable=" << enable << "\n"
<< "vp_squelch=" << enable << "\n"
<< "[compression]\n"
<< "1\n";
c.loadFromString(str.str());
env_.app().config().VP_REDUCE_RELAY_ENABLE =
c.VP_REDUCE_RELAY_ENABLE;
env_.app().config().VP_REDUCE_RELAY_SQUELCH =
c.VP_REDUCE_RELAY_SQUELCH;
env_.app().config().COMPRESSION = c.COMPRESSION;
};
auto handshake = [&](int outboundEnable, int inboundEnable) {
beast::IP::Address addr =
boost::asio::ip::address::from_string("172.1.1.100");
setEnv(outboundEnable);
auto request = ripple::makeRequest(
true,
env_.app().config().COMPRESSION,
env_.app().config().VP_REDUCE_RELAY_ENABLE);
http_request_type http_request;
http_request.version(request.version());
http_request.base() = request.base();
// feature enabled on the peer's connection only if both sides
// are enabled
auto const peerEnabled = inboundEnable && outboundEnable;
// inbound is enabled if the request's header has the feature
// enabled and the peer's configuration is enabled
auto const inboundEnabled = peerFeatureEnabled(
http_request, FEATURE_VPRR, inboundEnable);
BEAST_EXPECT(!(peerEnabled ^ inboundEnabled));
setEnv(inboundEnable);
auto http_resp = ripple::makeResponse(
true,
http_request,
addr,
addr,
uint256{1},
1,
{1, 0},
env_.app());
// outbound is enabled if the response's header has the feature
// enabled and the peer's configuration is enabled
auto const outboundEnabled =
peerFeatureEnabled(http_resp, FEATURE_VPRR, outboundEnable);
BEAST_EXPECT(!(peerEnabled ^ outboundEnabled));
};
handshake(1, 1);
handshake(1, 0);
handshake(0, 1);
handshake(0, 0);
});
}
jtx::Env env_;
Network network_;
@@ -1387,6 +1557,8 @@ public:
testSelectedPeerDisconnects(log);
testSelectedPeerStopsRelaying(log);
testInternalHashRouter(log);
testRandomSquelch(log);
testHandshake(log);
}
};