mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-27 15:28:03 +00:00
test: Share peer test doubles across the overlay and app suites
Five peer doubles existed across seven suites. Two of them, reduce_relay_test's PeerPartial and LedgerReplay_test's TestPeer, independently stubbed the same 22 of Peer's 24 pure virtuals in two different directories, so every method added to Peer broke both files identically. The other three wrapped PeerImp to capture what it would have sent: src/test/overlay/PeerTest, a same-named copy nested in tx_reduce_relay_test, and TMGetObjectByHash_test's own overlay wiring. Both duplications collapse into two headers. CapturePeer, in src/test/overlay, is a real PeerImp that captures the messages it would have sent, built by CapturePeerBuilder. PeerStub, in src/test/jtx, is a null Peer whose methods all return defaults; PeerPartial and TestPeer derive from it and override only what they exercise. CapturePeer replaces PeerTest rather than the reverse on four counts: it hands out a distinct remote address per peer, so the peer finder's per-address limit is never reached; it holds the connection id counter per builder instead of in a static that every suite has to reset; it takes the handshake request as a parameter, so a suite negotiates a feature instead of overriding the negotiated result; and it declares its two sinks by value, which is what lets a suite write `using CapturePeer::CapturePeer;` and reach a protected PeerImp member. It also asks for the newest supported protocol version instead of the unsupported 1.7 that makePeerTest wrote down, which had silently turned off every feature PeerImp gates on the version, so TMGetLedger_test now reaches the LedgerNodeDepth reply shape rather than only the legacy one. That version cannot be derived from outside ProtocolVersion.cpp, whose list is file-local, so this adds a newestSupportedProtocolVersion() accessor beside the existing supportedProtocolVersions() and isProtocolSupported(). PeerStub goes in src/test/jtx because both the overlay and the app suites need it, and test.jtx already sits below every suite; levelization records the resulting "test.jtx > xrpld.overlay" edge, and there is no cycle.
This commit is contained in:
@@ -112,6 +112,7 @@ test.jtx > xrpl.config
|
||||
test.jtx > xrpl.core
|
||||
test.jtx > xrpld.app
|
||||
test.jtx > xrpld.core
|
||||
test.jtx > xrpld.overlay
|
||||
test.jtx > xrpld.rpc
|
||||
test.jtx > xrpl.json
|
||||
test.jtx > xrpl.ledger
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/PeerStub.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/batch.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
@@ -21,7 +22,6 @@
|
||||
#include <xrpld/app/ledger/detail/LedgerReplayMsgHandler.h>
|
||||
#include <xrpld/app/ledger/detail/SkipListAcquire.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/PeerSet.h>
|
||||
#include <xrpld/overlay/detail/Handshake.h>
|
||||
@@ -29,19 +29,14 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/net/IPAddress.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/RippleLedgerHash.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
#include <xrpl/shamap/SHAMapItem.h>
|
||||
|
||||
@@ -268,136 +263,34 @@ enum class PeerFeature {
|
||||
* Simulate a network peer.
|
||||
* Depending on the configured PeerFeature,
|
||||
* it either supports the ProtocolFeature::LedgerReplay or not
|
||||
*
|
||||
* `PeerStub` supplies the rest of the `Peer` interface as no-ops.
|
||||
*/
|
||||
class TestPeer : public Peer
|
||||
class TestPeer : public PeerStub
|
||||
{
|
||||
public:
|
||||
TestPeer(bool enableLedgerReplay)
|
||||
: ledgerReplayEnabled_(enableLedgerReplay)
|
||||
, nodePublicKey_(derivePublicKey(KeyType::Ed25519, randomSecretKey()))
|
||||
// The id is arbitrary but fixed: the replay code only ever compares ids,
|
||||
// and every task here is served by a single peer.
|
||||
explicit TestPeer(bool enableLedgerReplay)
|
||||
: PeerStub(1234), ledgerReplayEnabled_(enableLedgerReplay)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
send(std::shared_ptr<Message> const& m) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] beast::ip::Endpoint
|
||||
getRemoteAddress() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
void
|
||||
charge(resource::Charge const& fee, std::string const& context = {}) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] id_t
|
||||
id() const override
|
||||
{
|
||||
return 1234;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
cluster() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
isHighLatency() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] int
|
||||
getScore(bool) const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
[[nodiscard]] PublicKey const&
|
||||
getNodePublic() const override
|
||||
{
|
||||
return nodePublicKey_;
|
||||
}
|
||||
json::Value
|
||||
json() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
supportsFeature(ProtocolFeature f) const override
|
||||
{
|
||||
return f == ProtocolFeature::LedgerReplay && ledgerReplayEnabled_;
|
||||
}
|
||||
[[nodiscard]] std::optional<std::size_t>
|
||||
publisherListSequence(PublicKey const&) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
void
|
||||
setPublisherListSequence(PublicKey const&, std::size_t const) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] uint256
|
||||
getClosedLedgerHash() const override
|
||||
{
|
||||
static uint256 const kHash{};
|
||||
return kHash;
|
||||
}
|
||||
|
||||
// The replay code only asks peers that already have the ledger.
|
||||
[[nodiscard]] bool
|
||||
hasLedger(uint256 const& hash, std::uint32_t seq) const override
|
||||
hasLedger(uint256 const&, std::uint32_t) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void
|
||||
ledgerRange(std::uint32_t& minSeq, std::uint32_t& maxSeq) const override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
hasTxSet(uint256 const& hash) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
cycleStatus() override
|
||||
{
|
||||
}
|
||||
bool
|
||||
hasRange(std::uint32_t uMin, std::uint32_t uMax) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
compressionEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
sendTxQueue() override
|
||||
{
|
||||
}
|
||||
void
|
||||
addTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
void
|
||||
removeTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
txReduceRelayEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string const&
|
||||
fingerprint() const override
|
||||
{
|
||||
return fingerprint_;
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
std::string fingerprint_;
|
||||
private:
|
||||
bool ledgerReplayEnabled_;
|
||||
PublicKey nodePublicKey_;
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
};
|
||||
|
||||
enum class PeerSetBehavior {
|
||||
|
||||
187
src/test/jtx/PeerStub.h
Normal file
187
src/test/jtx/PeerStub.h
Normal file
@@ -0,0 +1,187 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
/**
|
||||
* A `Peer` whose every method is a no-op returning a default.
|
||||
*
|
||||
* `Peer` is a two dozen method pure interface, and a test double normally
|
||||
* cares about one or two of them. Derive from this and override only those.
|
||||
* Adding a method to `Peer` then costs one stub here rather than one per
|
||||
* double.
|
||||
*
|
||||
* Identity is the exception to "returns a default": the id and the node public
|
||||
* key are real, because the code under test routes and deduplicates on both.
|
||||
*/
|
||||
class PeerStub : public Peer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param id The connection id reported by `id()`.
|
||||
*/
|
||||
explicit PeerStub(id_t id = 0)
|
||||
: id_(id), nodePublicKey_(derivePublicKey(KeyType::Ed25519, randomSecretKey()))
|
||||
{
|
||||
}
|
||||
|
||||
~PeerStub() override = default;
|
||||
|
||||
void
|
||||
send(std::shared_ptr<Message> const&) override
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] beast::ip::Endpoint
|
||||
getRemoteAddress() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
sendTxQueue() override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
addTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
removeTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
charge(resource::Charge const&, std::string const&) override
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] id_t
|
||||
id() const override
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
cluster() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isHighLatency() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] int
|
||||
getScore(bool) const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] PublicKey const&
|
||||
getNodePublic() const override
|
||||
{
|
||||
return nodePublicKey_;
|
||||
}
|
||||
|
||||
json::Value
|
||||
json() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
supportsFeature(ProtocolFeature) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::size_t>
|
||||
publisherListSequence(PublicKey const&) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
setPublisherListSequence(PublicKey const&, std::size_t const) override
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string const&
|
||||
fingerprint() const override
|
||||
{
|
||||
return fingerprint_;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint256
|
||||
getClosedLedgerHash() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasLedger(uint256 const&, std::uint32_t) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
ledgerRange(std::uint32_t&, std::uint32_t&) const override
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasTxSet(uint256 const&) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
cycleStatus() override
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
hasRange(std::uint32_t, std::uint32_t) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
compressionEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
txReduceRelayEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
id_t const id_;
|
||||
PublicKey const nodePublicKey_;
|
||||
std::string const fingerprint_;
|
||||
};
|
||||
|
||||
} // namespace xrpl::test
|
||||
249
src/test/overlay/CapturePeer.h
Normal file
249
src/test/overlay/CapturePeer.h
Normal file
@@ -0,0 +1,249 @@
|
||||
#pragma once
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
/**
|
||||
* A real `PeerImp` that captures the messages it would have sent.
|
||||
*
|
||||
* Only `send` and `run` are overridden, so everything a test drives through
|
||||
* `onMessage` runs production code. Derive from this to reach a `protected`
|
||||
* `PeerImp` member; `CapturePeerBuilder::build` takes the derived type.
|
||||
*/
|
||||
class CapturePeer : public PeerImp
|
||||
{
|
||||
public:
|
||||
using MiddleType = boost::beast::tcp_stream;
|
||||
using StreamType = boost::beast::ssl_stream<MiddleType>;
|
||||
using SocketType = boost::asio::ip::tcp::socket;
|
||||
|
||||
/**
|
||||
* Forwards to `PeerImp`, restating its two sinks by value.
|
||||
*
|
||||
* `PeerImp` declares `request` and `streamPtr` as rvalue references. Taking
|
||||
* them by value instead is what lets a derived double write a plain
|
||||
* `using CapturePeer::CapturePeer;`. An inherited constructor has no body,
|
||||
* so an inherited rvalue-reference parameter is always reported as never
|
||||
* moved from.
|
||||
*
|
||||
* @param app The application owning the peer.
|
||||
* @param id The connection id, unique among the overlay's peers.
|
||||
* @param slot The peer finder slot; must be seated.
|
||||
* @param request The handshake request.
|
||||
* @param publicKey The peer's node public key.
|
||||
* @param protocol The negotiated protocol version.
|
||||
* @param consumer The resource manager endpoint for the peer.
|
||||
* @param streamPtr The connection's ssl stream.
|
||||
* @param overlay The overlay to register with.
|
||||
*/
|
||||
CapturePeer(
|
||||
Application& app,
|
||||
Peer::id_t id,
|
||||
std::shared_ptr<peer_finder::Slot> const& slot,
|
||||
http_request_type request,
|
||||
PublicKey const& publicKey,
|
||||
ProtocolVersion protocol,
|
||||
resource::Consumer consumer,
|
||||
std::unique_ptr<StreamType> streamPtr,
|
||||
OverlayImpl& overlay)
|
||||
: PeerImp(
|
||||
app,
|
||||
id,
|
||||
slot,
|
||||
std::move(request),
|
||||
publicKey,
|
||||
protocol,
|
||||
// `resource::Consumer` is copy-only, so `std::move` here would
|
||||
// be a copy anyway.
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay)
|
||||
{
|
||||
}
|
||||
|
||||
~CapturePeer() override = default;
|
||||
|
||||
/**
|
||||
* Deliberately does nothing, which is what keeps the peer alive.
|
||||
*
|
||||
* `OverlayImpl::addActive` calls `run()`, and for an inbound peer that
|
||||
* reaches `PeerImp::doAccept`, which reads the ssl handshake off a socket
|
||||
* a test never connected. That fails, and the peer closes and detaches
|
||||
* itself again. Doing nothing leaves it registered and inert.
|
||||
*/
|
||||
void
|
||||
run() override
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures rather than writes, which is what makes replies observable.
|
||||
*/
|
||||
void
|
||||
send(std::shared_ptr<Message> const& m) override
|
||||
{
|
||||
sent_.push_back(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Every message sent to this peer, in order.
|
||||
*/
|
||||
std::vector<std::shared_ptr<Message>> const&
|
||||
sent() const
|
||||
{
|
||||
return sent_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The most recent message sent, or null if there was none.
|
||||
*/
|
||||
std::shared_ptr<Message>
|
||||
lastSent() const
|
||||
{
|
||||
return sent_.empty() ? nullptr : sent_.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the charge `PeerImp` has accumulated but not yet applied.
|
||||
*
|
||||
* `PeerImp::currentFeeCharge` is `protected` for exactly this reason: a
|
||||
* test can check which fee a message earned without draining it through
|
||||
* `charge()`. Reading it needs no production accessor, only a derived
|
||||
* class, and every suite that reads it wants the same one.
|
||||
*
|
||||
* @return The charge accumulated on the peer so far.
|
||||
*/
|
||||
resource::Charge
|
||||
feeCharge() const
|
||||
{
|
||||
return currentFeeCharge();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Message>> sent_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds active `CapturePeer` peers against an environment's overlay.
|
||||
*
|
||||
* Holds the SSL context and hands out connection ids and remote addresses, so
|
||||
* no two peers built by one builder collide. Ids start at 1 and only ever
|
||||
* increase, as in production.
|
||||
*/
|
||||
class CapturePeerBuilder
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Build an active peer and register it with the overlay.
|
||||
*
|
||||
* @tparam PeerType The peer class to build; must derive from `CapturePeer`
|
||||
* and inherit its constructor.
|
||||
* @param env The environment owning the overlay.
|
||||
* @param key The peer's node public key, or unseated for a fresh
|
||||
* random one.
|
||||
* @param request The handshake request. Pass one carrying an
|
||||
* `X-Protocol-Ctl` header to negotiate features;
|
||||
* `PeerImp` reads it in its constructor.
|
||||
* @return The peer, already registered with the overlay. Throws rather
|
||||
* than returning if the peer finder refused a slot.
|
||||
*/
|
||||
template <class PeerType = CapturePeer>
|
||||
std::shared_ptr<PeerType>
|
||||
build(
|
||||
jtx::Env& env,
|
||||
std::optional<PublicKey> key = std::nullopt,
|
||||
http_request_type request = {})
|
||||
{
|
||||
auto& overlay = dynamic_cast<OverlayImpl&>(env.app().getOverlay());
|
||||
auto streamPtr = std::make_unique<CapturePeer::StreamType>(
|
||||
CapturePeer::SocketType(env.app().getIOContext()), *context_);
|
||||
|
||||
// Every peer needs its own remote address, not merely its own port. The
|
||||
// peer finder caps inbound connections per address at `ipLimit`, which
|
||||
// is at most 2 unless configured, and it refuses the slot once that is
|
||||
// reached.
|
||||
beast::ip::Endpoint const local(boost::asio::ip::make_address("172.1.1.1"), kPort);
|
||||
beast::ip::Endpoint const remote(boost::asio::ip::address_v4(nextRemote_++), kPort);
|
||||
|
||||
auto consumer = overlay.resourceManager().newInboundEndpoint(remote);
|
||||
auto [slot, _] = overlay.peerFinder().newInboundSlot(local, remote);
|
||||
|
||||
// The slot is unseated when the endpoint is already connected or its
|
||||
// address is at the peer finder's per-address limit, and `PeerImp`
|
||||
// dereferences the slot in its constructor. Fail here, where the cause
|
||||
// is visible, rather than there with a segfault.
|
||||
if (!slot)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"CapturePeerBuilder::build: no slot for " + to_string(remote));
|
||||
}
|
||||
|
||||
if (!key)
|
||||
key = PublicKey(std::get<0>(randomKeyPair(KeyType::Ed25519)));
|
||||
|
||||
auto peer = std::make_shared<PeerType>(
|
||||
env.app(),
|
||||
nextId_++,
|
||||
slot,
|
||||
std::move(request),
|
||||
*key,
|
||||
// A peer claiming an unsupported version would silently fail every
|
||||
// test `PeerImp::supportsFeature` makes against the version, and so
|
||||
// would only ever reach the legacy branch of a version-gated reply.
|
||||
newestSupportedProtocolVersion(),
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay);
|
||||
|
||||
overlay.addActive(peer);
|
||||
return peer;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::uint16_t kPort = 51235;
|
||||
// Remote addresses are handed out from 172.2.0.1 upward, so ~900k fit
|
||||
// before the counter reaches 172.16/12 and the peer finder starts treating
|
||||
// them as private. Keeping them public is what makes a test peer look like
|
||||
// a real inbound connection.
|
||||
static constexpr std::uint32_t kFirstRemote = 0xAC020001;
|
||||
|
||||
std::shared_ptr<boost::asio::ssl::context> context_{makeSslContext("")};
|
||||
Peer::id_t nextId_{1};
|
||||
std::uint32_t nextRemote_{kFirstRemote};
|
||||
};
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1,166 +0,0 @@
|
||||
#include <test/overlay/PeerTest.h>
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
PeerTest::PeerTest(
|
||||
Application& app,
|
||||
std::shared_ptr<peer_finder::Slot> const& slot,
|
||||
http_request_type&& request,
|
||||
PublicKey const& publicKey,
|
||||
ProtocolVersion protocol,
|
||||
resource::Consumer consumer,
|
||||
std::unique_ptr<StreamType>&& streamPtr,
|
||||
OverlayImpl& overlay)
|
||||
: PeerImp{
|
||||
app,
|
||||
id++,
|
||||
slot,
|
||||
std::move(request),
|
||||
publicKey,
|
||||
protocol,
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::run()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::send(std::shared_ptr<Message> const& message)
|
||||
{
|
||||
lastSentMessage_ = message;
|
||||
}
|
||||
|
||||
std::shared_ptr<Message>
|
||||
PeerTest::getLastSentMessage() const
|
||||
{
|
||||
return lastSentMessage_;
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::runProcessGetObjectByHash(std::shared_ptr<protocol::TMGetObjectByHash> const& message)
|
||||
{
|
||||
PeerImp::processGetObjectByHash(message);
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::runProcessLedgerRequest(
|
||||
std::shared_ptr<protocol::TMGetLedger> const& message,
|
||||
std::vector<SHAMapNodeID> nodeIDs)
|
||||
{
|
||||
PeerImp::processLedgerRequest(message, std::move(nodeIDs));
|
||||
}
|
||||
|
||||
resource::Charge
|
||||
PeerTest::getCurrentFeeCharge() const
|
||||
{
|
||||
return PeerImp::currentFeeCharge();
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::resetId()
|
||||
{
|
||||
id = 0;
|
||||
}
|
||||
|
||||
bool
|
||||
PeerTest::compressionEnabled() const
|
||||
{
|
||||
if (compressionEnabled_.has_value())
|
||||
{
|
||||
return *compressionEnabled_;
|
||||
}
|
||||
return PeerImp::compressionEnabled();
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::compressionEnabled(std::optional<bool> enabled)
|
||||
{
|
||||
compressionEnabled_ = enabled;
|
||||
}
|
||||
|
||||
bool
|
||||
PeerTest::txReduceRelayEnabled() const
|
||||
{
|
||||
if (reduceRelayEnabled_.has_value())
|
||||
{
|
||||
return *reduceRelayEnabled_;
|
||||
}
|
||||
return PeerImp::txReduceRelayEnabled();
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::txReduceRelayEnabled(std::optional<bool> enabled)
|
||||
{
|
||||
reduceRelayEnabled_ = enabled;
|
||||
}
|
||||
|
||||
std::shared_ptr<PeerTest>
|
||||
makePeerTest(jtx::Env& env, PeerTest::SharedContext const& context, ProtocolVersion protocolVersion)
|
||||
{
|
||||
using SocketType = boost::asio::ip::tcp::socket;
|
||||
|
||||
auto& overlay = dynamic_cast<OverlayImpl&>(env.app().getOverlay());
|
||||
boost::beast::http::request<boost::beast::http::dynamic_body> request;
|
||||
auto streamPtr =
|
||||
std::make_unique<PeerTest::StreamType>(SocketType(env.app().getIOContext()), *context);
|
||||
|
||||
beast::ip::Endpoint const local(boost::asio::ip::make_address("172.1.1.1"), 51235);
|
||||
beast::ip::Endpoint const remote(boost::asio::ip::make_address("172.1.1.2"), 51235);
|
||||
|
||||
PublicKey const key{std::get<0>(randomKeyPair(KeyType::Ed25519))};
|
||||
auto consumer = overlay.resourceManager().newInboundEndpoint(remote);
|
||||
auto [slot, _] = overlay.peerFinder().newInboundSlot(local, remote);
|
||||
|
||||
auto peer = std::make_shared<PeerTest>(
|
||||
env.app(),
|
||||
slot,
|
||||
std::move(request),
|
||||
key,
|
||||
protocolVersion,
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay);
|
||||
|
||||
overlay.addActive(peer);
|
||||
return peer;
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1,108 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
/**
|
||||
* Test peer that captures sent messages for verification.
|
||||
*/
|
||||
class PeerTest : public PeerImp
|
||||
{
|
||||
inline static Peer::id_t id{};
|
||||
std::shared_ptr<Message> lastSentMessage_;
|
||||
std::optional<bool> compressionEnabled_;
|
||||
std::optional<bool> reduceRelayEnabled_;
|
||||
|
||||
public:
|
||||
using MiddleType = boost::beast::tcp_stream;
|
||||
using SharedContext = std::shared_ptr<boost::asio::ssl::context>;
|
||||
using StreamType = boost::beast::ssl_stream<MiddleType>;
|
||||
|
||||
PeerTest(
|
||||
Application& app,
|
||||
std::shared_ptr<peer_finder::Slot> const& slot,
|
||||
http_request_type&& request,
|
||||
PublicKey const& publicKey,
|
||||
ProtocolVersion protocol,
|
||||
resource::Consumer consumer,
|
||||
std::unique_ptr<StreamType>&& streamPtr,
|
||||
OverlayImpl& overlay);
|
||||
|
||||
~PeerTest() override = default;
|
||||
|
||||
void
|
||||
run() override;
|
||||
|
||||
void
|
||||
send(std::shared_ptr<Message> const& m) override;
|
||||
|
||||
std::shared_ptr<Message>
|
||||
getLastSentMessage() const;
|
||||
|
||||
// Synchronous test access to the JobQueue-dispatched processor.
|
||||
// The production path runs this on JtLedgerReq; tests need a
|
||||
// synchronous entry point to inspect the reply via send().
|
||||
// PeerImp::processGetObjectByHash is `protected` so the derived
|
||||
// test subclass can call it directly.
|
||||
void
|
||||
runProcessGetObjectByHash(std::shared_ptr<protocol::TMGetObjectByHash> const& m);
|
||||
|
||||
void
|
||||
runProcessLedgerRequest(
|
||||
std::shared_ptr<protocol::TMGetLedger> const& m,
|
||||
std::vector<SHAMapNodeID> nodeIDs);
|
||||
|
||||
resource::Charge
|
||||
getCurrentFeeCharge() const;
|
||||
|
||||
static void
|
||||
resetId();
|
||||
|
||||
bool
|
||||
compressionEnabled() const override;
|
||||
|
||||
void
|
||||
compressionEnabled(std::optional<bool> enabled);
|
||||
|
||||
bool
|
||||
txReduceRelayEnabled() const override;
|
||||
|
||||
void
|
||||
txReduceRelayEnabled(std::optional<bool> enabled);
|
||||
};
|
||||
|
||||
std::shared_ptr<PeerTest>
|
||||
makePeerTest(
|
||||
jtx::Env& env,
|
||||
PeerTest::SharedContext const& context,
|
||||
ProtocolVersion protocolVersion);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1,30 +1,19 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/overlay/PeerTest.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
|
||||
#include <xrpld/overlay/Compression.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -33,8 +22,26 @@ using namespace jtx;
|
||||
|
||||
class TMGetLedger_test : public beast::unit_test::Suite
|
||||
{
|
||||
PeerTest::SharedContext context_{makeSslContext("")};
|
||||
ProtocolVersion protocolVersion_{1, 7};
|
||||
/**
|
||||
* Adds a synchronous entry point to the JobQueue-dispatched processor.
|
||||
*
|
||||
* The production path runs this on JtLedgerReq; tests need to call it
|
||||
* directly so the reply can be inspected through `lastSent()`.
|
||||
* `PeerImp::processLedgerRequest` is `protected` for that purpose.
|
||||
*/
|
||||
class GetLedgerPeer : public CapturePeer
|
||||
{
|
||||
public:
|
||||
using CapturePeer::CapturePeer;
|
||||
|
||||
void
|
||||
runProcessLedgerRequest(
|
||||
std::shared_ptr<protocol::TMGetLedger> const& m,
|
||||
std::vector<SHAMapNodeID> nodeIDs)
|
||||
{
|
||||
processLedgerRequest(m, std::move(nodeIDs));
|
||||
}
|
||||
};
|
||||
|
||||
// Build a well-formed TMGetLedger node request carrying `numNodeIds` node
|
||||
// IDs.
|
||||
@@ -64,17 +71,17 @@ class TMGetLedger_test : public beast::unit_test::Suite
|
||||
testcase("Node ID Count Accepted");
|
||||
|
||||
Env env{*this};
|
||||
PeerTest::resetId();
|
||||
CapturePeerBuilder builder;
|
||||
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
auto peer = builder.build(env);
|
||||
peer->onMessage(createRequest(numNodeIds));
|
||||
|
||||
// A request outside the accepted node-ID count is charged kFeeInvalidData; one inside
|
||||
// it is not. The JobQueue handler may run concurrently and update the fee in the
|
||||
// accepted case.
|
||||
BEAST_EXPECT(
|
||||
expectRejected ? (peer->getCurrentFeeCharge() == resource::kFeeInvalidData)
|
||||
: !(peer->getCurrentFeeCharge() == resource::kFeeInvalidData));
|
||||
expectRejected ? (peer->feeCharge() == resource::kFeeInvalidData)
|
||||
: !(peer->feeCharge() == resource::kFeeInvalidData));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -84,9 +91,9 @@ class TMGetLedger_test : public beast::unit_test::Suite
|
||||
|
||||
Env env{*this};
|
||||
env.close();
|
||||
PeerTest::resetId();
|
||||
CapturePeerBuilder builder;
|
||||
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
auto peer = builder.build<GetLedgerPeer>(env);
|
||||
|
||||
// Ask for the account-state root node of the closed ledger.
|
||||
auto request = createRequest(numNodeIds);
|
||||
@@ -96,7 +103,7 @@ class TMGetLedger_test : public beast::unit_test::Suite
|
||||
|
||||
peer->runProcessLedgerRequest(request, std::vector<SHAMapNodeID>(numNodeIds));
|
||||
|
||||
auto sentMessage = peer->getLastSentMessage();
|
||||
auto sentMessage = peer->lastSent();
|
||||
BEAST_EXPECT(sentMessage != nullptr);
|
||||
if (!sentMessage)
|
||||
{
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/overlay/PeerTest.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/Compression.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <cstddef>
|
||||
@@ -41,8 +32,24 @@ using namespace jtx;
|
||||
*/
|
||||
class TMGetObjectByHash_test : public beast::unit_test::Suite
|
||||
{
|
||||
PeerTest::SharedContext context_{makeSslContext("")};
|
||||
ProtocolVersion protocolVersion_{1, 7};
|
||||
/**
|
||||
* Adds a synchronous entry point to the JobQueue-dispatched processor.
|
||||
*
|
||||
* The production path runs this on JtLedgerReq; tests need to call it
|
||||
* directly so the reply can be inspected through `sent()`.
|
||||
* `PeerImp::processGetObjectByHash` is `protected` for that purpose.
|
||||
*/
|
||||
class GetObjectPeer : public CapturePeer
|
||||
{
|
||||
public:
|
||||
using CapturePeer::CapturePeer;
|
||||
|
||||
void
|
||||
runProcessGetObjectByHash(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
|
||||
{
|
||||
processGetObjectByHash(m);
|
||||
}
|
||||
};
|
||||
|
||||
static std::shared_ptr<protocol::TMGetObjectByHash>
|
||||
createRequest(size_t const numObjects, Env& env)
|
||||
@@ -90,15 +97,14 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite
|
||||
testcase("Reply Object Count");
|
||||
|
||||
Env env(*this);
|
||||
PeerTest::resetId();
|
||||
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
CapturePeerBuilder builder;
|
||||
auto peer = builder.build<GetObjectPeer>(env);
|
||||
|
||||
auto request = createRequest(numObjects, env);
|
||||
peer->runProcessGetObjectByHash(request);
|
||||
|
||||
// Verify that a reply was sent
|
||||
auto sentMessage = peer->getLastSentMessage();
|
||||
auto sentMessage = peer->lastSent();
|
||||
BEAST_EXPECT(sentMessage != nullptr);
|
||||
|
||||
// Parse the reply message
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/overlay/PeerTest.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <memory>
|
||||
@@ -26,18 +15,15 @@ using namespace jtx;
|
||||
|
||||
class TMTransaction_test : public beast::unit_test::Suite
|
||||
{
|
||||
PeerTest::SharedContext context_{makeSslContext("")};
|
||||
ProtocolVersion protocolVersion_{1, 7};
|
||||
|
||||
void
|
||||
testFailureDeserializingTransactionIsCharged()
|
||||
{
|
||||
testcase("Undeserializable Transaction Is Charged");
|
||||
|
||||
Env env{*this, envconfig()};
|
||||
PeerTest::resetId();
|
||||
CapturePeerBuilder builder;
|
||||
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
auto peer = builder.build(env);
|
||||
auto tx = std::make_shared<protocol::TMTransaction>();
|
||||
tx->set_status(protocol::tsNEW);
|
||||
|
||||
@@ -45,7 +31,7 @@ class TMTransaction_test : public beast::unit_test::Suite
|
||||
tx->set_rawtransaction("\x01\x02\x03", 3);
|
||||
|
||||
peer->onMessage(tx);
|
||||
BEAST_EXPECT(peer->getCurrentFeeCharge() == resource::kFeeInvalidData);
|
||||
BEAST_EXPECT(peer->feeCharge() == resource::kFeeInvalidData);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
#include <test/jtx/CheckMessageLogs.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/overlay/PeerTest.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
|
||||
#include <xrpld/overlay/ReduceRelayCommon.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
#include <xrpld/overlay/detail/Handshake.h>
|
||||
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
@@ -30,9 +23,6 @@ using namespace jtx;
|
||||
|
||||
class TMTransactions_test : public beast::unit_test::Suite
|
||||
{
|
||||
PeerTest::SharedContext context_{makeSslContext("")};
|
||||
ProtocolVersion protocolVersion_{1, 7};
|
||||
|
||||
static std::shared_ptr<protocol::TMTransactions>
|
||||
createRequest(std::size_t const numTransactions)
|
||||
{
|
||||
@@ -55,13 +45,19 @@ class TMTransactions_test : public beast::unit_test::Suite
|
||||
*this,
|
||||
envconfig(),
|
||||
std::make_unique<CheckMessageLogs>(kLimitExceededMessage, &foundExpectedLog)};
|
||||
PeerTest::resetId();
|
||||
CapturePeerBuilder builder;
|
||||
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
peer->txReduceRelayEnabled(true);
|
||||
// Set before building the peer: `PeerImp` decides
|
||||
// `txReduceRelayEnabled()` in its constructor, from the config and the
|
||||
// handshake header together.
|
||||
env.app().config().txReduceRelayEnable = true;
|
||||
http_request_type request;
|
||||
request.insert("X-Protocol-Ctl", makeFeaturesRequestHeader(false, false, true, false));
|
||||
|
||||
auto peer = builder.build(env, std::nullopt, std::move(request));
|
||||
peer->onMessage(createRequest(numTransactions));
|
||||
|
||||
auto fee = peer->getCurrentFeeCharge();
|
||||
auto fee = peer->feeCharge();
|
||||
if (expectRejected)
|
||||
{
|
||||
BEAST_EXPECT(fee == resource::kFeeMalformedRequest);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/PeerStub.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
@@ -12,10 +13,8 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/net/IPAddress.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
@@ -27,7 +26,6 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
@@ -67,16 +65,16 @@ static constexpr std::uint32_t kMaxMessages = 200000;
|
||||
/**
|
||||
* Simulate two entities - peer directly connected to the server
|
||||
* (via squelch in PeerSim) and PeerImp (via Overlay)
|
||||
*
|
||||
* `PeerStub` supplies the rest of the `Peer` interface as no-ops.
|
||||
*/
|
||||
class PeerPartial : public Peer
|
||||
class PeerPartial : public PeerStub
|
||||
{
|
||||
public:
|
||||
PeerPartial() : nodePublicKey(derivePublicKey(KeyType::Ed25519, randomSecretKey()))
|
||||
{
|
||||
}
|
||||
using PeerStub::PeerStub;
|
||||
// Keep the base overload visible; the one below would otherwise hide it.
|
||||
using PeerStub::send;
|
||||
|
||||
PublicKey nodePublicKey;
|
||||
~PeerPartial() override = default;
|
||||
virtual void
|
||||
onMessage(MessageSPtr const& m, SquelchCB f) = 0;
|
||||
virtual void
|
||||
@@ -86,111 +84,6 @@ public:
|
||||
{
|
||||
onMessage(squelch);
|
||||
}
|
||||
|
||||
// dummy implementation
|
||||
void
|
||||
send(std::shared_ptr<Message> const& m) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] beast::ip::Endpoint
|
||||
getRemoteAddress() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
void
|
||||
charge(resource::Charge const& fee, std::string const& context = {}) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
cluster() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
isHighLatency() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] int
|
||||
getScore(bool) const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
[[nodiscard]] PublicKey const&
|
||||
getNodePublic() const override
|
||||
{
|
||||
return nodePublicKey;
|
||||
}
|
||||
json::Value
|
||||
json() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
supportsFeature(ProtocolFeature f) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] std::optional<std::size_t>
|
||||
publisherListSequence(PublicKey const&) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
void
|
||||
setPublisherListSequence(PublicKey const&, std::size_t const) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] uint256
|
||||
getClosedLedgerHash() const override
|
||||
{
|
||||
static uint256 const kHash{};
|
||||
return kHash;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
hasLedger(uint256 const& hash, std::uint32_t seq) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
ledgerRange(std::uint32_t& minSeq, std::uint32_t& maxSeq) const override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
hasTxSet(uint256 const& hash) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
cycleStatus() override
|
||||
{
|
||||
}
|
||||
bool
|
||||
hasRange(std::uint32_t uMin, std::uint32_t uMax) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
compressionEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
txReduceRelayEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
sendTxQueue() override
|
||||
{
|
||||
}
|
||||
void
|
||||
addTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
void
|
||||
removeTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -466,24 +359,13 @@ class PeerSim : public PeerPartial, public std::enable_shared_from_this<PeerSim>
|
||||
{
|
||||
public:
|
||||
using id_t = Peer::id_t;
|
||||
PeerSim(Overlay& overlay, beast::Journal journal) : overlay_(overlay), squelch_(journal)
|
||||
PeerSim(Overlay& overlay, beast::Journal journal)
|
||||
: PeerPartial(sid++), overlay_(overlay), squelch_(journal)
|
||||
{
|
||||
}
|
||||
|
||||
~PeerSim() override = default;
|
||||
|
||||
id_t
|
||||
id() const override
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
fingerprint() const override
|
||||
{
|
||||
return fingerprint_;
|
||||
}
|
||||
|
||||
static void
|
||||
resetId()
|
||||
{
|
||||
@@ -525,8 +407,6 @@ public:
|
||||
|
||||
private:
|
||||
inline static id_t sid = 0;
|
||||
std::string fingerprint_;
|
||||
id_t id_{sid++};
|
||||
Overlay& overlay_;
|
||||
reduce_relay::Squelch<ManualClock> squelch_;
|
||||
};
|
||||
|
||||
@@ -1,38 +1,24 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/noop.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/Handshake.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.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>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/multi_buffer.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
@@ -47,13 +33,6 @@ namespace xrpl::test {
|
||||
|
||||
class tx_reduce_relay_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
using middle_type = boost::beast::tcp_stream;
|
||||
using stream_type = boost::beast::ssl_stream<middle_type>;
|
||||
using shared_context = std::shared_ptr<boost::asio::ssl::context>;
|
||||
|
||||
private:
|
||||
void
|
||||
doTest(std::string const& msg, bool log, std::function<void(bool)> f)
|
||||
{
|
||||
@@ -116,107 +95,87 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
class PeerTest : public PeerImp
|
||||
/**
|
||||
* Counts the transaction hashes queued for this peer.
|
||||
*
|
||||
* `send` is inherited, so relayed messages are counted through `sent()`.
|
||||
*/
|
||||
class TxReducePeer : public CapturePeer
|
||||
{
|
||||
public:
|
||||
PeerTest(
|
||||
Application& app,
|
||||
std::shared_ptr<peer_finder::Slot> const& slot,
|
||||
http_request_type&& request,
|
||||
PublicKey const& publicKey,
|
||||
ProtocolVersion protocol,
|
||||
resource::Consumer consumer,
|
||||
std::unique_ptr<tx_reduce_relay_test::stream_type>&& streamPtr,
|
||||
OverlayImpl& overlay)
|
||||
: PeerImp(
|
||||
app,
|
||||
sid,
|
||||
slot,
|
||||
std::move(request),
|
||||
publicKey,
|
||||
protocol,
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay)
|
||||
{
|
||||
sid++;
|
||||
}
|
||||
~PeerTest() override = default;
|
||||
using CapturePeer::CapturePeer;
|
||||
|
||||
void
|
||||
run() override
|
||||
addTxQueue(uint256 const&) override
|
||||
{
|
||||
++queued_;
|
||||
}
|
||||
void
|
||||
send(std::shared_ptr<Message> const&) override
|
||||
|
||||
/**
|
||||
* @return The number of transaction hashes queued for this peer.
|
||||
*/
|
||||
std::size_t
|
||||
queued() const
|
||||
{
|
||||
sendTx++;
|
||||
return queued_;
|
||||
}
|
||||
void
|
||||
addTxQueue(uint256 const& hash) override
|
||||
{
|
||||
queueTx++;
|
||||
}
|
||||
static void
|
||||
init()
|
||||
{
|
||||
queueTx = 0;
|
||||
sendTx = 0;
|
||||
sid = 0;
|
||||
}
|
||||
inline static std::size_t sid = 0;
|
||||
inline static std::uint16_t queueTx = 0;
|
||||
inline static std::uint16_t sendTx = 0;
|
||||
|
||||
private:
|
||||
std::size_t queued_{0};
|
||||
};
|
||||
|
||||
std::uint16_t lid_{0};
|
||||
std::uint16_t rid_{1};
|
||||
shared_context context_;
|
||||
ProtocolVersion protocolVersion_;
|
||||
boost::beast::multi_buffer readBuf_;
|
||||
|
||||
public:
|
||||
tx_reduce_relay_test() : context_(makeSslContext("")), protocolVersion_{1, 7}
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Build one peer and register it with the overlay.
|
||||
*
|
||||
* The first `nDisabled` peers are built without an `X-Protocol-Ctl`
|
||||
* header, which is what leaves tx reduce-relay disabled on them. Because
|
||||
* they are built first, they occupy the lowest connection ids, which is
|
||||
* what makes them overlap the skipped peers in `testRelay`.
|
||||
*
|
||||
* @param env The environment owning the overlay.
|
||||
* @param builder Supplies the connection id and remote address.
|
||||
* @param peers Receives the peer; the overlay only holds a weak
|
||||
* pointer, so the caller has to keep it alive.
|
||||
* @param nDisabled How many more peers to leave reduce-relay disabled;
|
||||
* decremented for each one built.
|
||||
*/
|
||||
void
|
||||
addPeer(jtx::Env& env, std::vector<std::shared_ptr<PeerTest>>& peers, std::uint16_t& nDisabled)
|
||||
addPeer(
|
||||
jtx::Env& env,
|
||||
CapturePeerBuilder& builder,
|
||||
std::vector<std::shared_ptr<TxReducePeer>>& peers,
|
||||
std::uint16_t& nDisabled)
|
||||
{
|
||||
auto& overlay = dynamic_cast<OverlayImpl&>(env.app().getOverlay());
|
||||
boost::beast::http::request<boost::beast::http::dynamic_body> request;
|
||||
(nDisabled == 0)
|
||||
? request.insert("X-Protocol-Ctl", makeFeaturesRequestHeader(false, false, true, false))
|
||||
: (void)nDisabled--;
|
||||
auto streamPtr = std::make_unique<stream_type>(
|
||||
socket_type(std::forward<boost::asio::io_context&>(env.app().getIOContext())),
|
||||
*context_);
|
||||
beast::ip::Endpoint const local(
|
||||
boost::asio::ip::make_address("172.1.1." + std::to_string(lid_)));
|
||||
beast::ip::Endpoint const remote(
|
||||
boost::asio::ip::make_address("172.1.1." + std::to_string(rid_)));
|
||||
PublicKey const key(std::get<0>(randomKeyPair(KeyType::Ed25519)));
|
||||
auto consumer = overlay.resourceManager().newInboundEndpoint(remote);
|
||||
auto [slot, _] = overlay.peerFinder().newInboundSlot(local, remote);
|
||||
auto const peer = std::make_shared<PeerTest>(
|
||||
env.app(),
|
||||
slot,
|
||||
std::move(request),
|
||||
key,
|
||||
protocolVersion_,
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay);
|
||||
|
||||
bool const disabled = nDisabled > 0;
|
||||
if (disabled)
|
||||
--nDisabled;
|
||||
|
||||
http_request_type request;
|
||||
if (!disabled)
|
||||
request.insert("X-Protocol-Ctl", makeFeaturesRequestHeader(false, false, true, false));
|
||||
|
||||
BEAST_EXPECT(overlay.findPeerByPublicKey(key) == std::shared_ptr<PeerImp>{});
|
||||
overlay.addActive(peer);
|
||||
auto const peer = builder.build<TxReducePeer>(env, key, std::move(request));
|
||||
BEAST_EXPECT(overlay.findPeerByPublicKey(key) == peer);
|
||||
peers.emplace_back(peer); // overlay stores week ptr to PeerImp
|
||||
lid_ += 2;
|
||||
rid_ += 2;
|
||||
assert(lid_ <= 254);
|
||||
peers.emplace_back(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relay one transaction to `nPeers` peers and check the split.
|
||||
*
|
||||
* @param test The testcase name.
|
||||
* @param txRREnabled The `tx_enable` config value.
|
||||
* @param nPeers How many peers to attach to the overlay.
|
||||
* @param nDisabled How many of those peers have reduce-relay disabled.
|
||||
* @param minPeers The `tx_min_peers` config value.
|
||||
* @param relayPercentage The `tx_relay_percentage` config value.
|
||||
* @param expectRelay The expected number of peers relayed to.
|
||||
* @param expectQueue The expected number of peers queued for.
|
||||
* @param nSkip How many of the first-built peers to skip.
|
||||
*/
|
||||
void
|
||||
testRelay(
|
||||
std::string const& test,
|
||||
@@ -227,19 +186,31 @@ private:
|
||||
std::uint16_t relayPercentage,
|
||||
std::uint16_t expectRelay,
|
||||
std::uint16_t expectQueue,
|
||||
std::set<Peer::id_t> const& toSkip = {})
|
||||
std::size_t nSkip = 0)
|
||||
{
|
||||
testcase(test);
|
||||
jtx::Env env(*this);
|
||||
std::vector<std::shared_ptr<PeerTest>> peers;
|
||||
CapturePeerBuilder builder;
|
||||
std::vector<std::shared_ptr<TxReducePeer>> peers;
|
||||
// Set before building any peer: `PeerImp` decides
|
||||
// `txReduceRelayEnabled()` in its constructor, from the config and the
|
||||
// handshake header together.
|
||||
env.app().config().txReduceRelayEnable = txRREnabled;
|
||||
env.app().config().txReduceRelayMinPeers = minPeers;
|
||||
env.app().config().txRelayPercentage = relayPercentage;
|
||||
PeerTest::init();
|
||||
lid_ = 0;
|
||||
rid_ = 0;
|
||||
for (int i = 0; i < nPeers; i++)
|
||||
addPeer(env, peers, nDisabled);
|
||||
addPeer(env, builder, peers, nDisabled);
|
||||
|
||||
// Bail out rather than fall through: an under-filled skip set would
|
||||
// fail the relay counts below too, for a reason that looks unrelated.
|
||||
if (!BEAST_EXPECT(nSkip <= peers.size()))
|
||||
return;
|
||||
|
||||
// Skip the peers built first, so the skipped set overlaps the
|
||||
// reduce-relay-disabled peers the way the expected counts assume.
|
||||
std::set<Peer::id_t> toSkip;
|
||||
for (std::size_t i = 0; i < nSkip; ++i)
|
||||
toSkip.insert(peers[i]->id());
|
||||
|
||||
auto const jtx = env.jt(noop(env.master));
|
||||
if (BEAST_EXPECT(jtx.stx))
|
||||
@@ -251,7 +222,15 @@ private:
|
||||
m.set_deferred(false);
|
||||
m.set_status(protocol::TransactionStatus::tsNEW);
|
||||
env.app().getOverlay().relay(uint256{0}, m, toSkip);
|
||||
BEAST_EXPECT(PeerTest::sendTx == expectRelay && PeerTest::queueTx == expectQueue);
|
||||
|
||||
std::size_t sendTx = 0;
|
||||
std::size_t queueTx = 0;
|
||||
for (auto const& peer : peers)
|
||||
{
|
||||
sendTx += peer->sent().size();
|
||||
queueTx += peer->queued();
|
||||
}
|
||||
BEAST_EXPECT(sendTx == expectRelay && queueTx == expectQueue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,12 +238,11 @@ private:
|
||||
run() override
|
||||
{
|
||||
bool const log = false;
|
||||
std::set<Peer::id_t> skip = {0, 1, 2, 3, 4};
|
||||
testConfig(log);
|
||||
// relay to all peers, no hash queue
|
||||
testRelay("feature disabled", false, 10, 0, 10, 25, 10, 0);
|
||||
// relay to nPeers - skip (10-5=5)
|
||||
testRelay("feature disabled & skip", false, 10, 0, 10, 25, 5, 0, skip);
|
||||
testRelay("feature disabled & skip", false, 10, 0, 10, 25, 5, 0, 5);
|
||||
// relay to all peers because min is greater than nPeers
|
||||
testRelay("relay all 1", true, 10, 0, 20, 25, 10, 0);
|
||||
// relay to all peers because min + disabled is greater thant nPeers
|
||||
@@ -275,24 +253,22 @@ private:
|
||||
// relay to minPeers + 25% of (nPeers - nPeers) - skip
|
||||
// (20+0.25*(60-20)-5=25), queue the rest, skip counts towards relayed
|
||||
// (60-25-5=30)
|
||||
testRelay("skip", true, 60, 0, 20, 25, 25, 30, skip);
|
||||
testRelay("skip", true, 60, 0, 20, 25, 25, 30, 5);
|
||||
// relay to minPeers + disabled + 25% of (nPeers - minPeers - disabled)
|
||||
// (20+10+0.25*(70-20-10)=40), queue the rest (30)
|
||||
testRelay("disabled", true, 70, 10, 20, 25, 40, 30);
|
||||
// relay to minPeers + disabled-not-in-skip + 25% of (nPeers - minPeers
|
||||
// - disabled) (20+5+0.25*(70-20-10)=35), queue the rest, skip counts
|
||||
// towards relayed (70-35-5=30))
|
||||
testRelay("disabled & skip", true, 70, 10, 20, 25, 35, 30, skip);
|
||||
testRelay("disabled & skip", true, 70, 10, 20, 25, 35, 30, 5);
|
||||
// relay to minPeers + disabled + 25% of (nPeers - minPeers - disabled)
|
||||
// - skip (10+5+0.25*(15-10-5)-10=5), queue the rest, skip counts
|
||||
// towards relayed (15-5-10=0)
|
||||
skip = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
testRelay("disabled & skip, no queue", true, 15, 5, 10, 25, 5, 0, skip);
|
||||
testRelay("disabled & skip, no queue", true, 15, 5, 10, 25, 5, 0, 10);
|
||||
// relay to minPeers + disabled + 25% of (nPeers - minPeers - disabled)
|
||||
// - skip (10+2+0.25*(20-10-2)-14=0), queue the rest, skip counts
|
||||
// towards relayed (20-14=6)
|
||||
skip = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
|
||||
testRelay("disabled & skip, no relay", true, 20, 2, 10, 25, 0, 6, skip);
|
||||
testRelay("disabled & skip, no relay", true, 20, 2, 10, 25, 0, 6, 14);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -151,4 +151,13 @@ isProtocolSupported(ProtocolVersion const& v)
|
||||
return std::end(kSupportedProtocolList) != std::ranges::find(kSupportedProtocolList, v);
|
||||
}
|
||||
|
||||
ProtocolVersion
|
||||
newestSupportedProtocolVersion()
|
||||
{
|
||||
// The list above is sorted, so this could read its last entry instead. It
|
||||
// scans for the maximum so that it stays correct on its own, rather than on
|
||||
// an invariant a separate static_assert keeps. The list holds two entries.
|
||||
return *std::ranges::max_element(kSupportedProtocolList);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -68,4 +68,16 @@ supportedProtocolVersions();
|
||||
bool
|
||||
isProtocolSupported(ProtocolVersion const& v);
|
||||
|
||||
/**
|
||||
* The newest protocol version we support.
|
||||
*
|
||||
* This is the version we negotiate with any peer that speaks everything we
|
||||
* speak, so it is also the version a caller wants when it needs one that
|
||||
* enables every version-gated feature.
|
||||
*
|
||||
* @return The largest version in the list of supported protocol versions.
|
||||
*/
|
||||
ProtocolVersion
|
||||
newestSupportedProtocolVersion();
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
Reference in New Issue
Block a user