mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-22 14:50:54 +00:00
Merge branch 'ripple/se/wasmi-tests' into ripple/se/fees
Conflict resolution: - features.macro: both sides added an amendment at the top of the list. Kept both, with fixCleanup3_4_0 above SmartEscrow. - NetworkOPs.cpp: develop moved the ledgerClosed stream publisher out of pubLedger and into the new publishLedgerStreams helper, while this branch had added the Smart Escrow fee fields to the old inline version. Took the helper and moved the gas_limit / bytecode_size_limit / gas_price block into it, keeping the featureSmartEscrow guard.
This commit is contained in:
@@ -11,7 +11,7 @@ namespace xrpl {
|
||||
class Resolver
|
||||
{
|
||||
public:
|
||||
using HandlerType = std::function<void(std::string, std::vector<beast::IP::Endpoint>)>;
|
||||
using HandlerType = std::function<void(std::string, std::vector<beast::ip::Endpoint>)>;
|
||||
|
||||
virtual ~Resolver() = 0;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
@@ -251,4 +252,11 @@ makeSlice(std::basic_string<char, Traits, Alloc> const& s)
|
||||
return Slice(s.data(), s.size());
|
||||
}
|
||||
|
||||
template <class Traits>
|
||||
Slice
|
||||
makeSlice(std::basic_string_view<char, Traits> s)
|
||||
{
|
||||
return Slice(s.data(), s.size());
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -41,6 +41,35 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace base64 {
|
||||
|
||||
/**
|
||||
* Returns the maximum number of characters needed to base64-encode @p nBytes bytes.
|
||||
*
|
||||
* @param nBytes Number of input bytes.
|
||||
* @return Size of the encoded string, including padding.
|
||||
*/
|
||||
constexpr std::size_t
|
||||
encodedSize(std::size_t const nBytes)
|
||||
{
|
||||
return 4 * ((nBytes + 2) / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes a base64 string of @p numChars characters
|
||||
* decodes to.
|
||||
*
|
||||
* @param numChars Number of base64 characters.
|
||||
* @return Upper bound on the number of decoded bytes.
|
||||
*/
|
||||
constexpr std::size_t
|
||||
decodedSize(std::size_t const numChars)
|
||||
{
|
||||
return ((numChars / 4) * 3) + 2;
|
||||
}
|
||||
|
||||
} // namespace base64
|
||||
|
||||
std::string
|
||||
base64Encode(std::uint8_t const* data, std::size_t len);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/beast/container/detail/aged_associative_container.h>
|
||||
#include <xrpl/beast/container/detail/aged_container_iterator.h>
|
||||
#include <xrpl/beast/container/detail/empty_base_optimization.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/intrusive/unordered_set.hpp>
|
||||
|
||||
@@ -58,7 +58,7 @@ struct LexicalCast<Out, std::string_view>
|
||||
"beast::LexicalCast can only be used with integral types");
|
||||
|
||||
template <class Integral = Out>
|
||||
bool
|
||||
constexpr bool
|
||||
operator()(Integral& out, std::string_view in) const
|
||||
requires(std::is_integral_v<Integral> && !std::is_same_v<Integral, bool>)
|
||||
{
|
||||
@@ -110,7 +110,7 @@ struct LexicalCast<Out, boost::core::basic_string_view<char>>
|
||||
{
|
||||
explicit LexicalCast() = default;
|
||||
|
||||
bool
|
||||
constexpr bool
|
||||
operator()(Out& out, boost::core::basic_string_view<char> in) const
|
||||
{
|
||||
return LexicalCast<Out, std::string_view>()(out, in);
|
||||
@@ -123,7 +123,7 @@ struct LexicalCast<Out, std::string>
|
||||
{
|
||||
explicit LexicalCast() = default;
|
||||
|
||||
bool
|
||||
constexpr bool
|
||||
operator()(Out& out, std::string in) const
|
||||
{
|
||||
return LexicalCast<Out, std::string_view>()(out, in);
|
||||
@@ -136,7 +136,7 @@ struct LexicalCast<Out, char const*>
|
||||
{
|
||||
explicit LexicalCast() = default;
|
||||
|
||||
bool
|
||||
constexpr bool
|
||||
operator()(Out& out, char const* in) const
|
||||
{
|
||||
XRPL_ASSERT(in, "beast::detail::LexicalCast(char const*) : non-null input");
|
||||
@@ -151,7 +151,7 @@ struct LexicalCast<Out, char*>
|
||||
{
|
||||
explicit LexicalCast() = default;
|
||||
|
||||
bool
|
||||
constexpr bool
|
||||
operator()(Out& out, char* in) const
|
||||
{
|
||||
XRPL_ASSERT(in, "beast::detail::LexicalCast(char*) : non-null input");
|
||||
@@ -177,7 +177,7 @@ struct BadLexicalCast : public std::bad_cast
|
||||
* @return `false` if there was a parsing or range error
|
||||
*/
|
||||
template <class Out, class In>
|
||||
bool
|
||||
constexpr bool
|
||||
lexicalCastChecked(Out& out, In in)
|
||||
{
|
||||
return detail::LexicalCast<Out, In>()(out, in);
|
||||
@@ -191,7 +191,7 @@ lexicalCastChecked(Out& out, In in)
|
||||
* @return The new type.
|
||||
*/
|
||||
template <class Out, class In>
|
||||
Out
|
||||
constexpr Out
|
||||
lexicalCastThrow(In in)
|
||||
{
|
||||
if (Out out; lexicalCastChecked(out, in))
|
||||
@@ -207,7 +207,7 @@ lexicalCastThrow(In in)
|
||||
* @return The new type.
|
||||
*/
|
||||
template <class Out, class In>
|
||||
Out
|
||||
constexpr Out
|
||||
lexicalCast(In in, Out defaultValue = Out())
|
||||
{
|
||||
if (Out out; lexicalCastChecked(out, in))
|
||||
|
||||
@@ -17,14 +17,14 @@ namespace beast {
|
||||
class SemanticVersion
|
||||
{
|
||||
public:
|
||||
using identifier_list = std::vector<std::string>;
|
||||
using IdentifierList = std::vector<std::string>;
|
||||
|
||||
int majorVersion;
|
||||
int minorVersion;
|
||||
int patchVersion;
|
||||
|
||||
identifier_list preReleaseIdentifiers;
|
||||
identifier_list metaData;
|
||||
IdentifierList preReleaseIdentifiers;
|
||||
IdentifierList metaData;
|
||||
|
||||
SemanticVersion();
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
* @param journal Destination for logging output.
|
||||
*/
|
||||
static std::shared_ptr<StatsDCollector>
|
||||
make(IP::Endpoint const& address, std::string const& prefix, Journal journal);
|
||||
make(ip::Endpoint const& address, std::string const& prefix, Journal journal);
|
||||
};
|
||||
|
||||
} // namespace beast::insight
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace beast {
|
||||
namespace IP {
|
||||
namespace ip {
|
||||
|
||||
using Address = boost::asio::ip::address;
|
||||
|
||||
@@ -73,13 +73,13 @@ isPublic(Address const& addr)
|
||||
return (addr.is_v4()) ? isPublic(addr.to_v4()) : isPublic(addr.to_v6());
|
||||
}
|
||||
|
||||
} // namespace IP
|
||||
} // namespace ip
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Hasher>
|
||||
void
|
||||
hash_append(Hasher& h, beast::IP::Address const& addr) noexcept
|
||||
hash_append(Hasher& h, beast::ip::Address const& addr) noexcept
|
||||
{
|
||||
using beast::hash_append;
|
||||
if (addr.is_v4())
|
||||
@@ -101,12 +101,12 @@ hash_append(Hasher& h, beast::IP::Address const& addr) noexcept
|
||||
|
||||
namespace boost {
|
||||
template <>
|
||||
struct hash<::beast::IP::Address>
|
||||
struct hash<::beast::ip::Address>
|
||||
{
|
||||
explicit hash() = default;
|
||||
|
||||
std::size_t
|
||||
operator()(::beast::IP::Address const& addr) const
|
||||
operator()(::beast::ip::Address const& addr) const
|
||||
{
|
||||
return ::beast::Uhash<>{}(addr);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace beast::IP {
|
||||
namespace beast::ip {
|
||||
|
||||
/**
|
||||
* Convert to Endpoint.
|
||||
@@ -32,7 +32,7 @@ toAsioAddress(Endpoint const& endpoint);
|
||||
boost::asio::ip::tcp::endpoint
|
||||
toAsioEndpoint(Endpoint const& endpoint);
|
||||
|
||||
} // namespace beast::IP
|
||||
} // namespace beast::ip
|
||||
|
||||
namespace beast {
|
||||
|
||||
@@ -41,25 +41,25 @@ struct IPAddressConversion
|
||||
{
|
||||
explicit IPAddressConversion() = default;
|
||||
|
||||
static IP::Endpoint
|
||||
static ip::Endpoint
|
||||
fromAsio(boost::asio::ip::address const& address)
|
||||
{
|
||||
return IP::fromAsio(address);
|
||||
return ip::fromAsio(address);
|
||||
}
|
||||
static IP::Endpoint
|
||||
static ip::Endpoint
|
||||
fromAsio(boost::asio::ip::tcp::endpoint const& endpoint)
|
||||
{
|
||||
return IP::fromAsio(endpoint);
|
||||
return ip::fromAsio(endpoint);
|
||||
}
|
||||
static boost::asio::ip::address
|
||||
toAsioAddress(IP::Endpoint const& address)
|
||||
toAsioAddress(ip::Endpoint const& address)
|
||||
{
|
||||
return IP::toAsioAddress(address);
|
||||
return ip::toAsioAddress(address);
|
||||
}
|
||||
static boost::asio::ip::tcp::endpoint
|
||||
toAsioEndpoint(IP::Endpoint const& address)
|
||||
toAsioEndpoint(ip::Endpoint const& address)
|
||||
{
|
||||
return IP::toAsioEndpoint(address);
|
||||
return ip::toAsioEndpoint(address);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
|
||||
namespace beast::IP {
|
||||
namespace beast::ip {
|
||||
|
||||
using AddressV4 = boost::asio::ip::address_v4;
|
||||
|
||||
@@ -25,4 +25,4 @@ isPublic(AddressV4 const& addr);
|
||||
char
|
||||
getClass(AddressV4 const& address);
|
||||
|
||||
} // namespace beast::IP
|
||||
} // namespace beast::ip
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <boost/asio/ip/address_v6.hpp>
|
||||
|
||||
namespace beast::IP {
|
||||
namespace beast::ip {
|
||||
|
||||
using AddressV6 = boost::asio::ip::address_v6;
|
||||
|
||||
@@ -18,4 +18,4 @@ isPrivate(AddressV6 const& addr);
|
||||
bool
|
||||
isPublic(AddressV6 const& addr);
|
||||
|
||||
} // namespace beast::IP
|
||||
} // namespace beast::ip
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace beast::IP {
|
||||
namespace beast::ip {
|
||||
|
||||
using Port = std::uint16_t;
|
||||
|
||||
@@ -223,7 +223,7 @@ operator<<(OutputStream& os, Endpoint const& endpoint)
|
||||
std::istream&
|
||||
operator>>(std::istream& is, Endpoint& endpoint);
|
||||
|
||||
} // namespace beast::IP
|
||||
} // namespace beast::ip
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -232,12 +232,12 @@ namespace std {
|
||||
* std::hash support.
|
||||
*/
|
||||
template <>
|
||||
struct hash<::beast::IP::Endpoint>
|
||||
struct hash<::beast::ip::Endpoint>
|
||||
{
|
||||
hash() = default;
|
||||
|
||||
std::size_t
|
||||
operator()(::beast::IP::Endpoint const& endpoint) const
|
||||
operator()(::beast::ip::Endpoint const& endpoint) const
|
||||
{
|
||||
return ::beast::Uhash<>{}(endpoint);
|
||||
}
|
||||
@@ -249,12 +249,12 @@ namespace boost {
|
||||
* boost::hash support.
|
||||
*/
|
||||
template <>
|
||||
struct hash<::beast::IP::Endpoint>
|
||||
struct hash<::beast::ip::Endpoint>
|
||||
{
|
||||
hash() = default;
|
||||
|
||||
std::size_t
|
||||
operator()(::beast::IP::Endpoint const& endpoint) const
|
||||
operator()(::beast::ip::Endpoint const& endpoint) const
|
||||
{
|
||||
return ::beast::Uhash<>{}(endpoint);
|
||||
}
|
||||
|
||||
@@ -295,6 +295,20 @@ public:
|
||||
return runner_->arg();
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Lets a suite compose other suites (e.g. an aggregator that reruns a
|
||||
* group of related suites under its own name) via `SuiteInfo::run`.
|
||||
*
|
||||
* @return The runner this suite is executing under.
|
||||
*/
|
||||
Runner&
|
||||
runner() const
|
||||
{
|
||||
return *runner_;
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* DEPRECATED
|
||||
* @return `true` if the test condition indicates success(a false value)
|
||||
|
||||
@@ -25,6 +25,7 @@ struct Sections
|
||||
static constexpr auto kLedgerHistory = "ledger_history";
|
||||
static constexpr auto kLedgerReplay = "ledger_replay";
|
||||
static constexpr auto kLedgerTxTables = "ledger_tx_tables";
|
||||
static constexpr auto kMaxSubscriptionsPerConnection = "max_subscriptions_per_connection";
|
||||
static constexpr auto kMaxTransactions = "max_transactions";
|
||||
static constexpr auto kNetworkId = "network_id";
|
||||
static constexpr auto kNetworkQuorum = "network_quorum";
|
||||
@@ -121,7 +122,9 @@ struct Keys
|
||||
static constexpr auto kLogInterval = "log_interval";
|
||||
static constexpr auto kMaxDivergedTime = "max_diverged_time";
|
||||
static constexpr auto kMaxLedgerCountsToStore = "max_ledger_counts_to_store";
|
||||
static constexpr auto kMaxTrustedCount = "max_trusted_count";
|
||||
static constexpr auto kMaxUnknownTime = "max_unknown_time";
|
||||
static constexpr auto kMaxUntrustedCount = "max_untrusted_count";
|
||||
static constexpr auto kMaximumTxnInLedger = "maximum_txn_in_ledger";
|
||||
static constexpr auto kMaximumTxnPerAccount = "maximum_txn_per_account";
|
||||
static constexpr auto kMemoryLevel = "memory_level";
|
||||
|
||||
126
include/xrpl/consensus/CensorshipDetector.h
Normal file
126
include/xrpl/consensus/CensorshipDetector.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/algorithm.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <class TxID, class Sequence>
|
||||
class CensorshipDetector
|
||||
{
|
||||
public:
|
||||
struct TxIDSeq
|
||||
{
|
||||
TxID txid;
|
||||
Sequence seq;
|
||||
|
||||
TxIDSeq(TxID const& txid, Sequence const& seq) : txid(txid), seq(seq)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
friend bool
|
||||
operator<(TxIDSeq const& lhs, TxIDSeq const& rhs)
|
||||
{
|
||||
if (lhs.txid != rhs.txid)
|
||||
return lhs.txid < rhs.txid;
|
||||
return lhs.seq < rhs.seq;
|
||||
}
|
||||
|
||||
friend bool
|
||||
operator<(TxIDSeq const& lhs, TxID const& rhs)
|
||||
{
|
||||
return lhs.txid < rhs;
|
||||
}
|
||||
|
||||
friend bool
|
||||
operator<(TxID const& lhs, TxIDSeq const& rhs)
|
||||
{
|
||||
return lhs < rhs.txid;
|
||||
}
|
||||
|
||||
using TxIDSeqVec = std::vector<TxIDSeq>;
|
||||
|
||||
private:
|
||||
TxIDSeqVec tracker_;
|
||||
|
||||
public:
|
||||
CensorshipDetector() = default;
|
||||
|
||||
/**
|
||||
* Add transactions being proposed for the current consensus round.
|
||||
*
|
||||
* @param proposed The set of transactions that we are initially proposing
|
||||
* for this round.
|
||||
*/
|
||||
void
|
||||
propose(TxIDSeqVec proposed)
|
||||
{
|
||||
// We want to remove any entries that we proposed in a previous round
|
||||
// that did not make it in yet if we are no longer proposing them.
|
||||
// And we also want to preserve the Sequence of entries that we proposed
|
||||
// in the last round and want to propose again.
|
||||
std::sort(proposed.begin(), proposed.end());
|
||||
generalizedSetIntersection(
|
||||
proposed.begin(),
|
||||
proposed.end(),
|
||||
tracker_.cbegin(),
|
||||
tracker_.cend(),
|
||||
[](auto& x, auto const& y) { x.seq = y.seq; },
|
||||
[](auto const& x, auto const& y) { return x.txid < y.txid; });
|
||||
tracker_ = std::move(proposed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which transactions made it and perform censorship detection.
|
||||
*
|
||||
* This function is called when the server is proposing and a consensus
|
||||
* round it participated in completed.
|
||||
*
|
||||
* @param accepted The set of transactions that the network agreed
|
||||
* should be included in the ledger being built.
|
||||
* @param pred A predicate invoked for every transaction we've proposed
|
||||
* but which hasn't yet made it. The predicate must be
|
||||
* callable as:
|
||||
* bool pred(TxID const&, Sequence)
|
||||
* It must return true for entries that should be removed.
|
||||
*/
|
||||
template <class Predicate>
|
||||
void
|
||||
check(std::vector<TxID> accepted, Predicate&& pred)
|
||||
{
|
||||
auto acceptTxid = accepted.begin();
|
||||
auto const ae = accepted.end();
|
||||
std::sort(acceptTxid, ae);
|
||||
|
||||
// We want to remove all tracking entries for transactions that were
|
||||
// accepted as well as those which match the predicate.
|
||||
|
||||
auto i = removeIfIntersectOrMatch(
|
||||
tracker_.begin(),
|
||||
tracker_.end(),
|
||||
accepted.begin(),
|
||||
accepted.end(),
|
||||
[&pred](auto const& x) { return pred(x.txid, x.seq); },
|
||||
std::less<void>{});
|
||||
tracker_.erase(i, tracker_.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the tracker
|
||||
*
|
||||
* Typically, this function might be called after we reconnect to the
|
||||
* network following an outage, or after we start tracking the network.
|
||||
*/
|
||||
void
|
||||
reset()
|
||||
{
|
||||
tracker_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
1888
include/xrpl/consensus/Consensus.h
Normal file
1888
include/xrpl/consensus/Consensus.h
Normal file
File diff suppressed because it is too large
Load Diff
210
include/xrpl/consensus/ConsensusParms.h
Normal file
210
include/xrpl/consensus/ConsensusParms.h
Normal file
@@ -0,0 +1,210 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Consensus algorithm parameters
|
||||
*
|
||||
* Parameters which control the consensus algorithm. This are not
|
||||
* meant to be changed arbitrarily.
|
||||
*/
|
||||
struct ConsensusParms
|
||||
{
|
||||
explicit ConsensusParms() = default;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Validation and proposal durations are relative to NetClock times, so use
|
||||
// second resolution
|
||||
/**
|
||||
* The duration a validation remains current after its ledger's
|
||||
* close time.
|
||||
*
|
||||
* This is a safety to protect against very old validations and the time
|
||||
* it takes to adjust the close time accuracy window.
|
||||
*/
|
||||
std::chrono::seconds const validationValidWall = std::chrono::minutes{5};
|
||||
|
||||
/**
|
||||
* Duration a validation remains current after first observed.
|
||||
*
|
||||
* The duration a validation remains current after the time we
|
||||
* first saw it. This provides faster recovery in very rare cases where the
|
||||
* number of validations produced by the network is lower than normal
|
||||
*/
|
||||
std::chrono::seconds const validationValidLocal = std::chrono::minutes{3};
|
||||
|
||||
/**
|
||||
* Duration pre-close in which validations are acceptable.
|
||||
*
|
||||
* The number of seconds before a close time that we consider a validation
|
||||
* acceptable. This protects against extreme clock errors
|
||||
*/
|
||||
std::chrono::seconds const validationValidEarly = std::chrono::minutes{3};
|
||||
|
||||
/**
|
||||
* How long we consider a proposal fresh
|
||||
*/
|
||||
std::chrono::seconds const proposeFRESHNESS = std::chrono::seconds{20};
|
||||
|
||||
/**
|
||||
* How often we force generating a new proposal to keep ours fresh
|
||||
*/
|
||||
std::chrono::seconds const proposeINTERVAL = std::chrono::seconds{12};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Consensus durations are relative to the internal Consensus clock and use
|
||||
// millisecond resolution.
|
||||
|
||||
/**
|
||||
* The percentage threshold above which we can declare consensus.
|
||||
*/
|
||||
std::size_t const minConsensusPct = 80;
|
||||
|
||||
/**
|
||||
* The duration a ledger may remain idle before closing
|
||||
*/
|
||||
std::chrono::milliseconds const ledgerIdleInterval = std::chrono::seconds{15};
|
||||
|
||||
/**
|
||||
* The number of seconds we wait minimum to ensure participation
|
||||
*/
|
||||
std::chrono::milliseconds const ledgerMinConsensus = std::chrono::milliseconds{1950};
|
||||
|
||||
/**
|
||||
* The maximum amount of time to spend pausing for laggards.
|
||||
*
|
||||
* This should be sufficiently less than validationFRESHNESS so that
|
||||
* validators don't appear to be offline that are merely waiting for
|
||||
* laggards.
|
||||
*/
|
||||
std::chrono::milliseconds const ledgerMaxConsensus = std::chrono::seconds{15};
|
||||
|
||||
/**
|
||||
* Minimum number of seconds to wait to ensure others have computed the LCL
|
||||
*/
|
||||
std::chrono::milliseconds const ledgerMinClose = std::chrono::seconds{2};
|
||||
|
||||
/**
|
||||
* How often we check state or change positions
|
||||
*/
|
||||
std::chrono::milliseconds const ledgerGRANULARITY = std::chrono::seconds{1};
|
||||
|
||||
/**
|
||||
* How long to wait before completely abandoning consensus
|
||||
*/
|
||||
std::size_t const ledgerAbandonConsensusFactor = 10;
|
||||
|
||||
/**
|
||||
* Maximum amount of time to give a consensus round
|
||||
*
|
||||
* Does not include the time to build the LCL, so there is no reason for a
|
||||
* round to go this long, regardless of how big the ledger is.
|
||||
*/
|
||||
std::chrono::milliseconds const ledgerAbandonConsensus = std::chrono::seconds{120};
|
||||
|
||||
/**
|
||||
* The minimum amount of time to consider the previous round
|
||||
* to have taken.
|
||||
*
|
||||
* The minimum amount of time to consider the previous round
|
||||
* to have taken. This ensures that there is an opportunity
|
||||
* for a round at each avalanche threshold even if the
|
||||
* previous consensus was very fast. This should be at least
|
||||
* twice the interval between proposals (0.7s) divided by
|
||||
* the interval between mid and late consensus ([85-50]/100).
|
||||
*/
|
||||
std::chrono::milliseconds const avMinConsensusTime = std::chrono::seconds{5};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Avalanche tuning
|
||||
// As a function of the percent this round's duration is of the prior round,
|
||||
// we increase the threshold for yes votes to add a transaction to our
|
||||
// position.
|
||||
enum class AvalancheState { Init, Mid, Late, Stuck };
|
||||
struct AvalancheCutoff
|
||||
{
|
||||
int const consensusTime;
|
||||
std::size_t const consensusPct;
|
||||
AvalancheState const next;
|
||||
};
|
||||
/**
|
||||
* Map the consensus requirement avalanche state to the amount of time that
|
||||
* must pass before moving to that state, the agreement percentage required
|
||||
* at that state, and the next state. "stuck" loops back on itself because
|
||||
* once we're stuck, we're stuck.
|
||||
* This structure allows for "looping" of states if needed.
|
||||
*/
|
||||
std::map<AvalancheState, AvalancheCutoff> const avalancheCutoffs{
|
||||
// {state, {time, percent, nextState}},
|
||||
// Initial state: 50% of nodes must vote yes
|
||||
{AvalancheState::Init,
|
||||
{.consensusTime = 0, .consensusPct = 50, .next = AvalancheState::Mid}},
|
||||
// mid-consensus starts after 50% of the previous round time, and
|
||||
// requires 65% yes
|
||||
{AvalancheState::Mid,
|
||||
{.consensusTime = 50, .consensusPct = 65, .next = AvalancheState::Late}},
|
||||
// late consensus starts after 85% time, and requires 70% yes
|
||||
{AvalancheState::Late,
|
||||
{.consensusTime = 85, .consensusPct = 70, .next = AvalancheState::Stuck}},
|
||||
// we're stuck after 2x time, requires 95% yes votes
|
||||
{AvalancheState::Stuck,
|
||||
{.consensusTime = 200, .consensusPct = 95, .next = AvalancheState::Stuck}},
|
||||
};
|
||||
|
||||
/**
|
||||
* Percentage of nodes required to reach agreement on ledger close time
|
||||
*/
|
||||
std::size_t const avCtConsensusPct = 75;
|
||||
|
||||
/**
|
||||
* Number of rounds before certain actions can happen.
|
||||
*/
|
||||
// (Moving to the next avalanche level, considering that votes are stalled
|
||||
// without consensus.)
|
||||
std::size_t const avMinRounds = 2;
|
||||
|
||||
/**
|
||||
* Number of rounds before a stuck vote is considered unlikely to change
|
||||
* because voting stalled
|
||||
*/
|
||||
std::size_t const avStalledRounds = 4;
|
||||
};
|
||||
|
||||
inline std::pair<std::size_t, std::optional<ConsensusParms::AvalancheState>>
|
||||
getNeededWeight(
|
||||
ConsensusParms const& p,
|
||||
ConsensusParms::AvalancheState currentState,
|
||||
int percentTime,
|
||||
std::size_t currentRounds,
|
||||
std::size_t minimumRounds)
|
||||
{
|
||||
// at() can throw, but the map is built by hand to ensure all valid
|
||||
// values are available.
|
||||
auto const& currentCutoff = p.avalancheCutoffs.at(currentState);
|
||||
// Should we consider moving to the next state?
|
||||
if (currentCutoff.next != currentState && currentRounds >= minimumRounds)
|
||||
{
|
||||
// at() can throw, but the map is built by hand to ensure all
|
||||
// valid values are available.
|
||||
auto const& nextCutoff = p.avalancheCutoffs.at(currentCutoff.next);
|
||||
// See if enough time has passed to move on to the next.
|
||||
XRPL_ASSERT(
|
||||
nextCutoff.consensusTime >= currentCutoff.consensusTime,
|
||||
"xrpl::getNeededWeight : next state valid");
|
||||
if (percentTime >= nextCutoff.consensusTime)
|
||||
{
|
||||
return {nextCutoff.consensusPct, currentCutoff.next};
|
||||
}
|
||||
}
|
||||
return {currentCutoff.consensusPct, {}};
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
299
include/xrpl/consensus/ConsensusProposal.h
Normal file
299
include/xrpl/consensus/ConsensusProposal.h
Normal file
@@ -0,0 +1,299 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
/**
|
||||
* Represents a proposed position taken during a round of consensus.
|
||||
*
|
||||
* During consensus, peers seek agreement on a set of transactions to
|
||||
* apply to the prior ledger to generate the next ledger. Each peer takes a
|
||||
* position on whether to include or exclude potential transactions.
|
||||
* The position on the set of transactions is proposed to its peers as an
|
||||
* instance of the ConsensusProposal class.
|
||||
*
|
||||
* An instance of ConsensusProposal can be either our own proposal or one of
|
||||
* our peer's.
|
||||
*
|
||||
* As consensus proceeds, peers may change their position on the transaction,
|
||||
* or choose to abstain. Each successive proposal includes a strictly
|
||||
* monotonically increasing number (or, if a peer is choosing to abstain,
|
||||
* the special value `kSeqLeave`).
|
||||
*
|
||||
* Refer to @ref Consensus for requirements of the template arguments.
|
||||
*
|
||||
* @tparam NodeId Type used to uniquely identify nodes/peers
|
||||
* @tparam LedgerId Type used to uniquely identify ledgers
|
||||
* @tparam Position Type used to represent the position taken on transactions
|
||||
* under consideration during this round of consensus
|
||||
*/
|
||||
template <class NodeId, class LedgerId, class Position>
|
||||
class ConsensusProposal
|
||||
{
|
||||
public:
|
||||
using NodeID = NodeId;
|
||||
|
||||
//< Sequence value when a peer initially joins consensus
|
||||
static std::uint32_t const kSeqJoin = 0;
|
||||
|
||||
//< Sequence number when a peer wants to bow out and leave consensus
|
||||
static std::uint32_t const kSeqLeave = 0xffffffff;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param prevLedger The previous ledger this proposal is building on.
|
||||
* @param seq The sequence number of this proposal.
|
||||
* @param position The position taken on transactions in this round.
|
||||
* @param closeTime Position of when this ledger closed.
|
||||
* @param now Time when the proposal was taken.
|
||||
* @param nodeID ID of node/peer taking this position.
|
||||
*/
|
||||
ConsensusProposal(
|
||||
LedgerId const& prevLedger,
|
||||
std::uint32_t seq,
|
||||
Position const& position,
|
||||
NetClock::time_point closeTime,
|
||||
NetClock::time_point now,
|
||||
NodeId const& nodeID)
|
||||
: previousLedger_(prevLedger)
|
||||
, position_(position)
|
||||
, closeTime_(closeTime)
|
||||
, time_(now)
|
||||
, proposeSeq_(seq)
|
||||
, nodeID_(nodeID)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifying which peer took this position.
|
||||
*/
|
||||
NodeId const&
|
||||
nodeID() const
|
||||
{
|
||||
return nodeID_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the proposed position.
|
||||
*/
|
||||
Position const&
|
||||
position() const
|
||||
{
|
||||
return position_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prior accepted ledger this position is based on.
|
||||
*/
|
||||
LedgerId const&
|
||||
prevLedger() const
|
||||
{
|
||||
return previousLedger_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sequence number of this proposal
|
||||
*
|
||||
* Starting with an initial sequence number of `kSeqJoin`, successive
|
||||
* proposals from a peer will increase the sequence number.
|
||||
*
|
||||
* @return the sequence number
|
||||
*/
|
||||
std::uint32_t
|
||||
proposeSeq() const
|
||||
{
|
||||
return proposeSeq_;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current position on the consensus close time.
|
||||
*/
|
||||
NetClock::time_point const&
|
||||
closeTime() const
|
||||
{
|
||||
return closeTime_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when this position was taken.
|
||||
*/
|
||||
NetClock::time_point const&
|
||||
seenTime() const
|
||||
{
|
||||
return time_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is the first position taken during the current
|
||||
* consensus round.
|
||||
*/
|
||||
bool
|
||||
isInitial() const
|
||||
{
|
||||
return proposeSeq_ == kSeqJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this node left the consensus process
|
||||
*/
|
||||
bool
|
||||
isBowOut() const
|
||||
{
|
||||
return proposeSeq_ == kSeqLeave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this position is stale relative to the provided cutoff
|
||||
*/
|
||||
bool
|
||||
isStale(NetClock::time_point cutoff) const
|
||||
{
|
||||
return time_ <= cutoff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position during the consensus process. This will increment
|
||||
* the proposal's sequence number if it has not already bowed out.
|
||||
*
|
||||
* @param newPosition The new position taken.
|
||||
* @param newCloseTime The new close time.
|
||||
* @param now the time The new position was taken
|
||||
*/
|
||||
void
|
||||
changePosition(
|
||||
Position const& newPosition,
|
||||
NetClock::time_point newCloseTime,
|
||||
NetClock::time_point now)
|
||||
{
|
||||
signingHash_.reset();
|
||||
position_ = newPosition;
|
||||
closeTime_ = newCloseTime;
|
||||
time_ = now;
|
||||
if (proposeSeq_ != kSeqLeave)
|
||||
++proposeSeq_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave consensus
|
||||
*
|
||||
* Update position to indicate the node left consensus.
|
||||
*
|
||||
* @param now Time when this node left consensus.
|
||||
*/
|
||||
void
|
||||
bowOut(NetClock::time_point now)
|
||||
{
|
||||
signingHash_.reset();
|
||||
time_ = now;
|
||||
proposeSeq_ = kSeqLeave;
|
||||
}
|
||||
|
||||
std::string
|
||||
render() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "proposal: previous_ledger: " << previousLedger_ << " proposal_seq: " << proposeSeq_
|
||||
<< " position: " << position_ << " close_time: " << to_string(closeTime_)
|
||||
<< " now: " << to_string(time_) << " is_bow_out:" << isBowOut()
|
||||
<< " node_id: " << nodeID_;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSON representation for debugging
|
||||
*/
|
||||
json::Value
|
||||
getJson() const
|
||||
{
|
||||
using std::to_string;
|
||||
|
||||
json::Value ret = json::ValueType::Object;
|
||||
ret[jss::previous_ledger] = to_string(prevLedger());
|
||||
|
||||
if (!isBowOut())
|
||||
{
|
||||
ret[jss::transaction_hash] = to_string(position());
|
||||
ret[jss::propose_seq] = proposeSeq();
|
||||
}
|
||||
|
||||
ret[jss::close_time] = to_string(closeTime().time_since_epoch().count());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* The digest for this proposal, used for signing purposes.
|
||||
*/
|
||||
uint256 const&
|
||||
signingHash() const
|
||||
{
|
||||
if (!signingHash_)
|
||||
{
|
||||
signingHash_ = sha512Half(
|
||||
HashPrefix::Proposal,
|
||||
std::uint32_t(proposeSeq()),
|
||||
closeTime().time_since_epoch().count(),
|
||||
prevLedger(),
|
||||
position());
|
||||
}
|
||||
|
||||
return signingHash_.value();
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Unique identifier of prior ledger this proposal is based on
|
||||
*/
|
||||
LedgerId previousLedger_;
|
||||
|
||||
/**
|
||||
* Unique identifier of the position this proposal is taking
|
||||
*/
|
||||
Position position_;
|
||||
|
||||
/**
|
||||
* The ledger close time this position is taking
|
||||
*/
|
||||
NetClock::time_point closeTime_;
|
||||
|
||||
// !The time this position was last updated
|
||||
NetClock::time_point time_;
|
||||
|
||||
/**
|
||||
* The sequence number of these positions taken by this node
|
||||
*/
|
||||
std::uint32_t proposeSeq_;
|
||||
|
||||
/**
|
||||
* The identifier of the node taking this position
|
||||
*/
|
||||
NodeId nodeID_;
|
||||
|
||||
/**
|
||||
* The signing hash for this proposal
|
||||
*/
|
||||
mutable std::optional<uint256> signingHash_;
|
||||
};
|
||||
|
||||
template <class NodeId, class LedgerId, class Position>
|
||||
bool
|
||||
operator==(
|
||||
ConsensusProposal<NodeId, LedgerId, Position> const& a,
|
||||
ConsensusProposal<NodeId, LedgerId, Position> const& b)
|
||||
{
|
||||
return a.nodeID() == b.nodeID() && a.proposeSeq() == b.proposeSeq() &&
|
||||
a.prevLedger() == b.prevLedger() && a.position() == b.position() &&
|
||||
a.closeTime() == b.closeTime() && a.seenTime() == b.seenTime();
|
||||
}
|
||||
} // namespace xrpl
|
||||
326
include/xrpl/consensus/ConsensusTypes.h
Normal file
326
include/xrpl/consensus/ConsensusTypes.h
Normal file
@@ -0,0 +1,326 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/consensus/ConsensusProposal.h>
|
||||
#include <xrpl/consensus/DisputedTx.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Represents how a node currently participates in Consensus.
|
||||
*
|
||||
* A node participates in consensus in varying modes, depending on how
|
||||
* the node was configured by its operator and how well it stays in sync
|
||||
* with the network during consensus.
|
||||
*
|
||||
* @code
|
||||
* proposing observing
|
||||
* \ /
|
||||
* \---> wrongLedger <---/
|
||||
* ^
|
||||
* |
|
||||
* |
|
||||
* v
|
||||
* switchedLedger
|
||||
* @endcode
|
||||
*
|
||||
* We enter the round proposing or observing. If we detect we are working
|
||||
* on the wrong prior ledger, we go to wrongLedger and attempt to acquire
|
||||
* the right one. Once we acquire the right one, we go to the switchedLedger
|
||||
* mode. It is possible we fall behind again and find there is a new better
|
||||
* ledger, moving back and forth between wrongLedger and switchLedger as
|
||||
* we attempt to catch up.
|
||||
*/
|
||||
enum class ConsensusMode {
|
||||
/**
|
||||
* We are normal participant in consensus and propose our position
|
||||
*/
|
||||
Proposing,
|
||||
/**
|
||||
* We are observing peer positions, but not proposing our position
|
||||
*/
|
||||
Observing,
|
||||
/**
|
||||
* We have the wrong ledger and are attempting to acquire it
|
||||
*/
|
||||
WrongLedger,
|
||||
/**
|
||||
* We switched ledgers since we started this consensus round but are now
|
||||
* running on what we believe is the correct ledger. This mode is as
|
||||
* if we entered the round observing, but is used to indicate we did
|
||||
* have the wrongLedger at some point.
|
||||
*/
|
||||
SwitchedLedger
|
||||
};
|
||||
|
||||
inline std::string
|
||||
to_string(ConsensusMode m)
|
||||
{
|
||||
switch (m)
|
||||
{
|
||||
case ConsensusMode::Proposing:
|
||||
return "proposing";
|
||||
case ConsensusMode::Observing:
|
||||
return "observing";
|
||||
case ConsensusMode::WrongLedger:
|
||||
return "wrongLedger";
|
||||
case ConsensusMode::SwitchedLedger:
|
||||
return "switchedLedger";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Phases of consensus for a single ledger round.
|
||||
*
|
||||
* @code
|
||||
* "close" "accept"
|
||||
* open ------- > establish ---------> accepted
|
||||
* ^ | |
|
||||
* |---------------| |
|
||||
* ^ "startRound" |
|
||||
* |------------------------------------|
|
||||
* @endcode
|
||||
*
|
||||
* The typical transition goes from open to establish to accepted and
|
||||
* then a call to startRound begins the process anew. However, if a wrong prior
|
||||
* ledger is detected and recovered during the establish or accept phase,
|
||||
* consensus will internally go back to open (see Consensus::handleWrongLedger).
|
||||
*/
|
||||
enum class ConsensusPhase {
|
||||
/**
|
||||
* We haven't closed our ledger yet, but others might have
|
||||
*/
|
||||
Open,
|
||||
|
||||
/**
|
||||
* Establishing consensus by exchanging proposals with our peers
|
||||
*/
|
||||
Establish,
|
||||
|
||||
/**
|
||||
* We have accepted a new last closed ledger and are waiting on a call
|
||||
* to startRound to begin the next consensus round. No changes
|
||||
* to consensus phase occur while in this phase.
|
||||
*/
|
||||
Accepted,
|
||||
};
|
||||
|
||||
inline std::string
|
||||
to_string(ConsensusPhase p)
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case ConsensusPhase::Open:
|
||||
return "open";
|
||||
case ConsensusPhase::Establish:
|
||||
return "establish";
|
||||
case ConsensusPhase::Accepted:
|
||||
return "accepted";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures the duration of phases of consensus
|
||||
*/
|
||||
class ConsensusTimer
|
||||
{
|
||||
using time_point = std::chrono::steady_clock::time_point;
|
||||
time_point start_;
|
||||
std::chrono::milliseconds dur_{};
|
||||
|
||||
public:
|
||||
[[nodiscard]] std::chrono::milliseconds
|
||||
read() const
|
||||
{
|
||||
return dur_;
|
||||
}
|
||||
|
||||
void
|
||||
tick(std::chrono::milliseconds fixed)
|
||||
{
|
||||
dur_ += fixed;
|
||||
}
|
||||
|
||||
void
|
||||
reset(time_point tp)
|
||||
{
|
||||
start_ = tp;
|
||||
dur_ = std::chrono::milliseconds{0};
|
||||
}
|
||||
|
||||
void
|
||||
tick(time_point tp)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
dur_ = duration_cast<milliseconds>(tp - start_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores the set of initial close times
|
||||
*
|
||||
* The initial consensus proposal from each peer has that peer's view of
|
||||
* when the ledger closed. This object stores all those close times for
|
||||
* analysis of clock drift between peers.
|
||||
*/
|
||||
struct ConsensusCloseTimes
|
||||
{
|
||||
explicit ConsensusCloseTimes() = default;
|
||||
|
||||
/**
|
||||
* Close time estimates, keep ordered for predictable traverse
|
||||
*/
|
||||
std::map<NetClock::time_point, int> peers;
|
||||
|
||||
/**
|
||||
* Our close time estimate
|
||||
*/
|
||||
NetClock::time_point self;
|
||||
};
|
||||
|
||||
/**
|
||||
* Offset of the network's close time relative to ours, using a weighted median.
|
||||
*
|
||||
* Treats the sample set as `{self x 1}` merged with `{t x w}` for each
|
||||
* `(t, w)` in `times.peers`, in time order, and returns `(median - self)`
|
||||
* in whole seconds. Uses the lower weighted median: the median is the
|
||||
* earliest time at which the running weight reaches half the total, so an
|
||||
* even total whose halfway point falls between two bins resolves to the
|
||||
* earlier bin.
|
||||
*
|
||||
* @param times Our own close time and the weighted close times of peers.
|
||||
* @return Weighted median of all close times minus our own, in whole seconds.
|
||||
*/
|
||||
inline std::chrono::seconds
|
||||
medianCloseOffset(ConsensusCloseTimes const& times)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
using time_point = NetClock::time_point;
|
||||
|
||||
std::int64_t totalWeight = 1;
|
||||
for (auto const& [_, w] : times.peers)
|
||||
totalWeight += w;
|
||||
|
||||
std::int64_t const halfWeight = (totalWeight + 1) / 2;
|
||||
|
||||
std::optional<time_point> median{};
|
||||
std::int64_t tally = 0;
|
||||
bool selfPlaced = false;
|
||||
|
||||
// Accumulate weight in time order; the first bin to reach halfWeight is
|
||||
// the (lower) weighted median. Returns true once that bin is found.
|
||||
auto step = [&](time_point t, std::int64_t w) {
|
||||
XRPL_ASSERT(tally < halfWeight, "xrpl::medianCloseOffset::step : median not yet found");
|
||||
tally += w;
|
||||
if (tally >= halfWeight)
|
||||
{
|
||||
median = t;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (auto const& [t, w] : times.peers)
|
||||
{
|
||||
if (!selfPlaced && times.self <= t)
|
||||
{
|
||||
selfPlaced = true;
|
||||
if (step(times.self, 1))
|
||||
break;
|
||||
}
|
||||
if (step(t, w))
|
||||
break;
|
||||
}
|
||||
if (!selfPlaced && !median)
|
||||
step(times.self, 1);
|
||||
|
||||
if (!median)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::medianCloseOffset : median not found");
|
||||
median = times.self;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return duration_cast<seconds>(
|
||||
duration<std::int64_t>{median->time_since_epoch().count()} -
|
||||
duration<std::int64_t>{times.self.time_since_epoch().count()});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we have or don't have a consensus
|
||||
*/
|
||||
enum class ConsensusState {
|
||||
No, ///< We do not have consensus
|
||||
MovedOn, ///< The network has consensus without us
|
||||
Expired, ///< Consensus time limit has hard-expired
|
||||
Yes ///< We have consensus along with the network
|
||||
};
|
||||
|
||||
/**
|
||||
* Encapsulates the result of consensus.
|
||||
*
|
||||
* Stores all relevant data for the outcome of consensus on a single
|
||||
* ledger.
|
||||
*
|
||||
* @tparam Traits Traits class defining the concrete consensus types used
|
||||
* by the application.
|
||||
*/
|
||||
template <class Traits>
|
||||
struct ConsensusResult
|
||||
{
|
||||
using Ledger_t = Traits::Ledger_t;
|
||||
using TxSet_t = Traits::TxSet_t;
|
||||
using NodeID_t = Traits::NodeID_t;
|
||||
|
||||
using Tx_t = TxSet_t::Tx;
|
||||
using Proposal_t = ConsensusProposal<NodeID_t, typename Ledger_t::ID, typename TxSet_t::ID>;
|
||||
using Dispute_t = DisputedTx<Tx_t, NodeID_t>;
|
||||
|
||||
ConsensusResult(TxSet_t&& s, Proposal_t&& p) : txns{std::move(s)}, position{std::move(p)}
|
||||
{
|
||||
XRPL_ASSERT(txns.id() == position.position(), "xrpl::ConsensusResult : valid inputs");
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of transactions consensus agrees go in the ledger
|
||||
*/
|
||||
TxSet_t txns;
|
||||
|
||||
/**
|
||||
* Our proposed position on transactions/close time
|
||||
*/
|
||||
Proposal_t position;
|
||||
|
||||
/**
|
||||
* Transactions which are under dispute with our peers
|
||||
*/
|
||||
hash_map<typename Tx_t::ID, Dispute_t> disputes;
|
||||
|
||||
// Set of TxSet ids we have already compared/created disputes
|
||||
hash_set<typename TxSet_t::ID> compares;
|
||||
|
||||
// Measures the duration of the establish phase for this consensus round
|
||||
ConsensusTimer roundTime;
|
||||
|
||||
// Indicates state in which consensus ended. Once in the accept phase
|
||||
// will be either Yes or MovedOn or Expired
|
||||
ConsensusState state = ConsensusState::No;
|
||||
|
||||
// The number of peers proposing during the round
|
||||
std::size_t proposers = 0;
|
||||
};
|
||||
} // namespace xrpl
|
||||
367
include/xrpl/consensus/DisputedTx.h
Normal file
367
include/xrpl/consensus/DisputedTx.h
Normal file
@@ -0,0 +1,367 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/consensus/ConsensusParms.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* A transaction discovered to be in dispute during consensus.
|
||||
*
|
||||
* During consensus, a @ref DisputedTx is created when a transaction
|
||||
* is discovered to be disputed. The object persists only as long as
|
||||
* the dispute.
|
||||
*
|
||||
* Undisputed transactions have no corresponding @ref DisputedTx object.
|
||||
*
|
||||
* Refer to @ref Consensus for details on the template type requirements.
|
||||
*
|
||||
* @tparam Tx The type for a transaction
|
||||
* @tparam NodeId The type for a node identifier
|
||||
*/
|
||||
|
||||
template <class Tx, class NodeId>
|
||||
class DisputedTx
|
||||
{
|
||||
using TxID_t = Tx::ID;
|
||||
using Map_t = boost::container::flat_map<NodeId, bool>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param tx The transaction under dispute
|
||||
* @param ourVote Our vote on whether tx should be included
|
||||
* @param numPeers Anticipated number of peer votes
|
||||
* @param j Journal for debugging
|
||||
*/
|
||||
DisputedTx(Tx tx, bool ourVote, std::size_t numPeers, beast::Journal j)
|
||||
: ourVote_(ourVote), tx_(std::move(tx)), j_(j)
|
||||
{
|
||||
votes_.reserve(numPeers);
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique id/hash of the disputed transaction.
|
||||
*/
|
||||
[[nodiscard]] TxID_t const&
|
||||
id() const
|
||||
{
|
||||
return tx_.id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Our vote on whether the transaction should be included.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
getOurVote() const
|
||||
{
|
||||
return ourVote_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we and our peers "stalled" where we probably won't change
|
||||
* our vote?
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
stalled(
|
||||
ConsensusParms const& p,
|
||||
bool proposing,
|
||||
int peersUnchanged,
|
||||
beast::Journal j,
|
||||
std::unique_ptr<std::stringstream> const& clog) const
|
||||
{
|
||||
// at() can throw, but the map is built by hand to ensure all valid
|
||||
// values are available.
|
||||
auto const& currentCutoff = p.avalancheCutoffs.at(avalancheState_);
|
||||
auto const& nextCutoff = p.avalancheCutoffs.at(currentCutoff.next);
|
||||
|
||||
// We're have not reached the final avalanche state, or been there long
|
||||
// enough, so there's room for change. Check the times in case the state
|
||||
// machine is altered to allow states to loop.
|
||||
if (nextCutoff.consensusTime > currentCutoff.consensusTime ||
|
||||
avalancheCounter_ < p.avMinRounds)
|
||||
return false;
|
||||
|
||||
// We've haven't had this vote for minimum rounds yet. Things could
|
||||
// change.
|
||||
if (proposing && currentVoteCounter_ < p.avMinRounds)
|
||||
return false;
|
||||
|
||||
// If we or any peers have changed a vote in several rounds, then
|
||||
// things could still change. But if _either_ has not changed in that
|
||||
// long, we're unlikely to change our vote any time soon. (This prevents
|
||||
// a malicious peer from flip-flopping a vote to prevent consensus.)
|
||||
if (peersUnchanged < p.avStalledRounds &&
|
||||
(proposing && currentVoteCounter_ < p.avStalledRounds))
|
||||
return false;
|
||||
|
||||
// Does this transaction have more than 80% agreement
|
||||
|
||||
// Compute the percentage of nodes voting 'yes' (possibly including us)
|
||||
int const support = (yays_ + (proposing && ourVote_ ? 1 : 0)) * 100;
|
||||
int const total = nays_ + yays_ + (proposing ? 1 : 0);
|
||||
if (total == 0)
|
||||
{
|
||||
// There are no votes, so we know nothing
|
||||
return false;
|
||||
}
|
||||
int const weight = support / total;
|
||||
// Returns true if the tx has more than minCONSENSUS_PCT (80) percent
|
||||
// agreement. Either voting for _or_ voting against the tx.
|
||||
bool const stalled = weight > p.minConsensusPct || weight < (100 - p.minConsensusPct);
|
||||
|
||||
if (stalled)
|
||||
{
|
||||
// stalling is an error condition for even a single
|
||||
// transaction.
|
||||
std::stringstream s;
|
||||
s << "Transaction " << id() << " is stalled. We have been voting "
|
||||
<< (getOurVote() ? "YES" : "NO") << " for " << currentVoteCounter_
|
||||
<< " rounds. Peers have not changed their votes in " << peersUnchanged
|
||||
<< " rounds. The transaction has " << weight << "% support. ";
|
||||
JLOG(j_.error()) << s.str();
|
||||
CLOG(clog) << s.str();
|
||||
}
|
||||
|
||||
return stalled;
|
||||
}
|
||||
|
||||
/**
|
||||
* The disputed transaction.
|
||||
*/
|
||||
[[nodiscard]] Tx const&
|
||||
tx() const
|
||||
{
|
||||
return tx_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change our vote
|
||||
*/
|
||||
void
|
||||
setOurVote(bool o)
|
||||
{
|
||||
ourVote_ = o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a peer's vote
|
||||
*
|
||||
* @param peer Identifier of peer.
|
||||
* @param votesYes Whether peer votes to include the disputed transaction.
|
||||
*
|
||||
* @return bool Whether the peer changed its vote. (A new vote counts as a
|
||||
* change.)
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
setVote(NodeId const& peer, bool votesYes);
|
||||
|
||||
/**
|
||||
* Remove a peer's vote
|
||||
*
|
||||
* @param peer Identifier of peer.
|
||||
*/
|
||||
void
|
||||
unVote(NodeId const& peer);
|
||||
|
||||
/**
|
||||
* Update our vote given progression of consensus.
|
||||
*
|
||||
* Updates our vote on this disputed transaction based on our peers' votes
|
||||
* and how far along consensus has proceeded.
|
||||
*
|
||||
* @param percentTime Percentage progress through consensus, e.g. 50%
|
||||
* through or 90%.
|
||||
* @param proposing Whether we are proposing to our peers in this round.
|
||||
* @param p Consensus parameters controlling thresholds for voting
|
||||
* @return Whether our vote changed
|
||||
*/
|
||||
bool
|
||||
updateVote(int percentTime, bool proposing, ConsensusParms const& p);
|
||||
|
||||
/**
|
||||
* JSON representation of dispute, used for debugging
|
||||
*/
|
||||
[[nodiscard]] json::Value
|
||||
getJson() const;
|
||||
|
||||
private:
|
||||
int yays_{0}; //< Number of yes votes
|
||||
int nays_{0}; //< Number of no votes
|
||||
bool ourVote_; //< Our vote (true is yes)
|
||||
Tx tx_; //< Transaction under dispute
|
||||
Map_t votes_; //< Map from NodeID to vote
|
||||
/**
|
||||
* The number of rounds we've gone without changing our vote
|
||||
*/
|
||||
std::size_t currentVoteCounter_ = 0;
|
||||
/**
|
||||
* Which minimum acceptance percentage phase we are currently in
|
||||
*/
|
||||
ConsensusParms::AvalancheState avalancheState_ = ConsensusParms::AvalancheState::Init;
|
||||
/**
|
||||
* How long we have been in the current acceptance phase
|
||||
*/
|
||||
std::size_t avalancheCounter_ = 0;
|
||||
beast::Journal const j_;
|
||||
};
|
||||
|
||||
// Track a peer's yes/no vote on a particular disputed tx_
|
||||
template <class Tx, class NodeId>
|
||||
bool
|
||||
DisputedTx<Tx, NodeId>::setVote(NodeId const& peer, bool votesYes)
|
||||
{
|
||||
auto const [it, inserted] = votes_.insert(std::make_pair(peer, votesYes));
|
||||
|
||||
// new vote
|
||||
if (inserted)
|
||||
{
|
||||
if (votesYes)
|
||||
{
|
||||
JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
|
||||
++yays_;
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
|
||||
++nays_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// changes vote to yes
|
||||
if (votesYes && !it->second)
|
||||
{
|
||||
JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
|
||||
--nays_;
|
||||
++yays_;
|
||||
it->second = true;
|
||||
return true;
|
||||
}
|
||||
// changes vote to no
|
||||
if (!votesYes && it->second)
|
||||
{
|
||||
JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
|
||||
++nays_;
|
||||
--yays_;
|
||||
it->second = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove a peer's vote on this disputed transaction
|
||||
template <class Tx, class NodeId>
|
||||
void
|
||||
DisputedTx<Tx, NodeId>::unVote(NodeId const& peer)
|
||||
{
|
||||
auto it = votes_.find(peer);
|
||||
|
||||
if (it != votes_.end())
|
||||
{
|
||||
if (it->second)
|
||||
{
|
||||
--yays_;
|
||||
}
|
||||
else
|
||||
{
|
||||
--nays_;
|
||||
}
|
||||
|
||||
votes_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Tx, class NodeId>
|
||||
bool
|
||||
DisputedTx<Tx, NodeId>::updateVote(int percentTime, bool proposing, ConsensusParms const& p)
|
||||
{
|
||||
if (ourVote_ && (nays_ == 0))
|
||||
return false;
|
||||
|
||||
if (!ourVote_ && (yays_ == 0))
|
||||
return false;
|
||||
|
||||
bool newPosition = false;
|
||||
int weight = 0;
|
||||
|
||||
// When proposing, to prevent avalanche stalls, we increase the needed
|
||||
// weight slightly over time. We also need to ensure that the consensus has
|
||||
// made a minimum number of attempts at each "state" before moving
|
||||
// to the next.
|
||||
// Proposing or not, we need to keep track of which state we've reached so
|
||||
// we can determine if the vote has stalled.
|
||||
auto const [requiredPct, newState] =
|
||||
getNeededWeight(p, avalancheState_, percentTime, ++avalancheCounter_, p.avMinRounds);
|
||||
if (newState)
|
||||
{
|
||||
avalancheState_ = *newState;
|
||||
avalancheCounter_ = 0;
|
||||
}
|
||||
|
||||
if (proposing) // give ourselves full weight
|
||||
{
|
||||
// This is basically the percentage of nodes voting 'yes' (including us)
|
||||
weight = ((yays_ * 100) + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
|
||||
|
||||
newPosition = weight > requiredPct;
|
||||
}
|
||||
else
|
||||
{
|
||||
// don't let us outweigh a proposing node, just recognize consensus
|
||||
weight = -1;
|
||||
newPosition = yays_ > nays_;
|
||||
}
|
||||
|
||||
if (newPosition == ourVote_)
|
||||
{
|
||||
++currentVoteCounter_;
|
||||
JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO") << ") on " << tx_.id()
|
||||
<< " : weight " << weight << ", percent " << percentTime
|
||||
<< ", round(s) with this vote: " << currentVoteCounter_;
|
||||
JLOG(j_.debug()) << json::Compact{getJson()};
|
||||
return false;
|
||||
}
|
||||
|
||||
currentVoteCounter_ = 0;
|
||||
ourVote_ = newPosition;
|
||||
JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on " << tx_.id();
|
||||
JLOG(j_.debug()) << json::Compact{getJson()};
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Tx, class NodeId>
|
||||
json::Value
|
||||
DisputedTx<Tx, NodeId>::getJson() const
|
||||
{
|
||||
using std::to_string;
|
||||
|
||||
json::Value ret(json::ValueType::Object);
|
||||
|
||||
ret["yays"] = yays_;
|
||||
ret["nays"] = nays_;
|
||||
ret["our_vote"] = ourVote_;
|
||||
|
||||
if (!votes_.empty())
|
||||
{
|
||||
json::Value votes(json::ValueType::Object);
|
||||
for (auto const& [nodeId, vote] : votes_)
|
||||
votes[to_string(nodeId)] = vote;
|
||||
ret["votes"] = std::move(votes);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
846
include/xrpl/consensus/LedgerTrie.h
Normal file
846
include/xrpl/consensus/LedgerTrie.h
Normal file
@@ -0,0 +1,846 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/ToString.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* The tip of a span of ledger ancestry
|
||||
*/
|
||||
template <class Ledger>
|
||||
class SpanTip
|
||||
{
|
||||
public:
|
||||
using Seq = Ledger::Seq;
|
||||
using ID = Ledger::ID;
|
||||
|
||||
SpanTip(Seq s, ID i, Ledger const lgr) : seq{s}, id{i}, ledger_{std::move(lgr)}
|
||||
{
|
||||
}
|
||||
|
||||
// The sequence number of the tip ledger
|
||||
Seq seq;
|
||||
// The ID of the tip ledger
|
||||
ID id;
|
||||
|
||||
/**
|
||||
* Lookup the ID of an ancestor of the tip ledger
|
||||
*
|
||||
* @param s The sequence number of the ancestor
|
||||
* @return The ID of the ancestor with that sequence number
|
||||
*
|
||||
* @note s must be less than or equal to the sequence number of the
|
||||
* tip ledger
|
||||
*/
|
||||
[[nodiscard]] ID
|
||||
ancestor(Seq const& s) const
|
||||
{
|
||||
XRPL_ASSERT(s <= seq, "xrpl::SpanTip::ancestor : valid input");
|
||||
return ledger_[s];
|
||||
}
|
||||
|
||||
private:
|
||||
Ledger const ledger_;
|
||||
};
|
||||
|
||||
namespace ledger_trie_detail {
|
||||
|
||||
// Represents a span of ancestry of a ledger
|
||||
template <class Ledger>
|
||||
class Span
|
||||
{
|
||||
using Seq = Ledger::Seq;
|
||||
using ID = Ledger::ID;
|
||||
|
||||
// The span is the half-open interval [start,end) of ledger_
|
||||
Seq start_{0};
|
||||
Seq end_{1};
|
||||
Ledger ledger_;
|
||||
|
||||
public:
|
||||
Span() : ledger_{typename Ledger::MakeGenesis{}}
|
||||
{
|
||||
// Require default ledger to be genesis seq
|
||||
XRPL_ASSERT(ledger_.seq() == start_, "xrpl::Span::Span : ledger is genesis");
|
||||
}
|
||||
|
||||
Span(Ledger ledger) : end_{ledger.seq() + Seq{1}}, ledger_{std::move(ledger)}
|
||||
{
|
||||
}
|
||||
|
||||
Span(Span const& s) = default;
|
||||
Span(Span&& s) = default;
|
||||
Span&
|
||||
operator=(Span const&) = default;
|
||||
Span&
|
||||
operator=(Span&&) = default;
|
||||
|
||||
[[nodiscard]] Seq
|
||||
start() const
|
||||
{
|
||||
return start_;
|
||||
}
|
||||
|
||||
[[nodiscard]] Seq
|
||||
end() const
|
||||
{
|
||||
return end_;
|
||||
}
|
||||
|
||||
// Return the Span from [spot,end_) or none if no such valid span
|
||||
[[nodiscard]] std::optional<Span>
|
||||
from(Seq spot) const
|
||||
{
|
||||
return sub(spot, end_);
|
||||
}
|
||||
|
||||
// Return the Span from [start_,spot) or none if no such valid span
|
||||
[[nodiscard]] std::optional<Span>
|
||||
before(Seq spot) const
|
||||
{
|
||||
return sub(start_, spot);
|
||||
}
|
||||
|
||||
// Return the ID of the ledger that starts this span
|
||||
[[nodiscard]] ID
|
||||
startID() const
|
||||
{
|
||||
return ledger_[start_];
|
||||
}
|
||||
|
||||
// Return the ledger sequence number of the first possible difference
|
||||
// between this span and a given ledger.
|
||||
[[nodiscard]] Seq
|
||||
diff(Ledger const& o) const
|
||||
{
|
||||
return clamp(mismatch(ledger_, o));
|
||||
}
|
||||
|
||||
// The tip of this span
|
||||
[[nodiscard]] SpanTip<Ledger>
|
||||
tip() const
|
||||
{
|
||||
Seq const tipSeq{end_ - Seq{1}};
|
||||
return SpanTip<Ledger>{tipSeq, ledger_[tipSeq], ledger_};
|
||||
}
|
||||
|
||||
private:
|
||||
Span(Seq start, Seq end, Ledger l) : start_{start}, end_{end}, ledger_{std::move(l)}
|
||||
{
|
||||
// Spans cannot be empty
|
||||
XRPL_ASSERT(start < end, "xrpl::Span::Span : non-empty span input");
|
||||
}
|
||||
|
||||
[[nodiscard]] Seq
|
||||
clamp(Seq val) const
|
||||
{
|
||||
return std::min(std::max(start_, val), end_);
|
||||
}
|
||||
|
||||
// Return a span of this over the half-open interval [from,to)
|
||||
[[nodiscard]] std::optional<Span>
|
||||
sub(Seq from, Seq to) const
|
||||
{
|
||||
Seq const newFrom = clamp(from);
|
||||
Seq const newTo = clamp(to);
|
||||
if (newFrom < newTo)
|
||||
return Span(newFrom, newTo, ledger_);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
friend std::ostream&
|
||||
operator<<(std::ostream& o, Span const& s)
|
||||
{
|
||||
return o << s.tip().id << "[" << s.start_ << "," << s.end_ << ")";
|
||||
}
|
||||
|
||||
friend Span
|
||||
merge(Span const& a, Span const& b)
|
||||
{
|
||||
// Return combined span, using ledger_ from higher sequence span
|
||||
if (a.end_ < b.end_)
|
||||
return Span(std::min(a.start_, b.start_), b.end_, b.ledger_);
|
||||
|
||||
return Span(std::min(a.start_, b.start_), a.end_, a.ledger_);
|
||||
}
|
||||
};
|
||||
|
||||
// A node in the trie
|
||||
template <class Ledger>
|
||||
struct Node
|
||||
{
|
||||
Node() = default;
|
||||
|
||||
explicit Node(Ledger const& l) : span{l}, tipSupport{1}, branchSupport{1}
|
||||
{
|
||||
}
|
||||
|
||||
explicit Node(Span<Ledger> s) : span{std::move(s)}
|
||||
{
|
||||
}
|
||||
|
||||
Span<Ledger> span;
|
||||
std::uint32_t tipSupport = 0;
|
||||
std::uint32_t branchSupport = 0;
|
||||
|
||||
std::vector<std::unique_ptr<Node>> children;
|
||||
Node* parent = nullptr;
|
||||
|
||||
/**
|
||||
* Remove the given node from this Node's children
|
||||
*
|
||||
* @param child The address of the child node to remove
|
||||
* @note The child must be a member of the vector. The passed pointer
|
||||
* will be dangling as a result of this call
|
||||
*/
|
||||
void
|
||||
erase(Node const* child)
|
||||
{
|
||||
auto it = std::ranges::find_if(
|
||||
children, [child](std::unique_ptr<Node> const& curr) { return curr.get() == child; });
|
||||
XRPL_ASSERT(it != children.end(), "xrpl::Node::erase : valid input");
|
||||
std::swap(*it, children.back());
|
||||
children.pop_back();
|
||||
}
|
||||
|
||||
friend std::ostream&
|
||||
operator<<(std::ostream& o, Node const& s)
|
||||
{
|
||||
return o << s.span << "(T:" << s.tipSupport << ",B:" << s.branchSupport << ")";
|
||||
}
|
||||
|
||||
[[nodiscard]] json::Value
|
||||
getJson() const
|
||||
{
|
||||
json::Value res;
|
||||
std::stringstream sps;
|
||||
sps << span;
|
||||
res["span"] = sps.str();
|
||||
res["startID"] = to_string(span.startID());
|
||||
res["seq"] = static_cast<std::uint32_t>(span.tip().seq);
|
||||
res["tipSupport"] = tipSupport;
|
||||
res["branchSupport"] = branchSupport;
|
||||
if (!children.empty())
|
||||
{
|
||||
json::Value& cs = (res["children"] = json::ValueType::Array);
|
||||
for (auto const& child : children)
|
||||
{
|
||||
cs.append(child->getJson());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
} // namespace ledger_trie_detail
|
||||
|
||||
/**
|
||||
* Ancestry trie of ledgers
|
||||
*
|
||||
* A compressed trie tree that maintains validation support of recent ledgers
|
||||
* based on their ancestry.
|
||||
*
|
||||
* The compressed trie structure comes from recognizing that ledger history
|
||||
* can be viewed as a string over the alphabet of ledger ids. That is,
|
||||
* a given ledger with sequence number `seq` defines a length `seq` string,
|
||||
* with i-th entry equal to the id of the ancestor ledger with sequence
|
||||
* number i. "Sequence" strings with a common prefix share those ancestor
|
||||
* ledgers in common. Tracking this ancestry information and relations across
|
||||
* all validated ledgers is done conveniently in a compressed trie. A node in
|
||||
* the trie is an ancestor of all its children. If a parent node has sequence
|
||||
* number `seq`, each child node has a different ledger starting at `seq+1`.
|
||||
* The compression comes from the invariant that any non-root node with 0 tip
|
||||
* support has either no children or multiple children. In other words, a
|
||||
* non-root 0-tip-support node can be combined with its single child.
|
||||
*
|
||||
* Each node has a tipSupport, which is the number of current validations for
|
||||
* that particular ledger. The node's branch support is the sum of the tip
|
||||
* support and the branch support of that node's children:
|
||||
*
|
||||
* @code
|
||||
* node->branchSupport = node->tipSupport;
|
||||
* for (child : node->children)
|
||||
* node->branchSupport += child->branchSupport;
|
||||
* @endcode
|
||||
*
|
||||
* The templated Ledger type represents a ledger which has a unique history.
|
||||
* It should be lightweight and cheap to copy.
|
||||
*
|
||||
* @code
|
||||
* // Identifier types that should be equality-comparable and copyable
|
||||
* struct ID;
|
||||
* struct Seq;
|
||||
*
|
||||
* struct Ledger
|
||||
* {
|
||||
* struct MakeGenesis{};
|
||||
*
|
||||
* // The genesis ledger represents a ledger that prefixes all other
|
||||
* // ledgers
|
||||
* Ledger(MakeGenesis{});
|
||||
*
|
||||
* Ledger(Ledger const&);
|
||||
* Ledger& operator=(Ledger const&);
|
||||
*
|
||||
* // Return the sequence number of this ledger
|
||||
* Seq seq() const;
|
||||
*
|
||||
* // Return the ID of this ledger's ancestor with given sequence number
|
||||
* // or ID{0} if unknown
|
||||
* ID
|
||||
* operator[](Seq s);
|
||||
*
|
||||
* };
|
||||
*
|
||||
* // Return the sequence number of the first possible mismatching ancestor
|
||||
* // between two ledgers
|
||||
* Seq
|
||||
* mismatch(ledgerA, ledgerB);
|
||||
* @endcode
|
||||
*
|
||||
* The unique history invariant of ledgers requires any ledgers that agree
|
||||
* on the id of a given sequence number agree on ALL ancestors before that
|
||||
* ledger:
|
||||
*
|
||||
* @code
|
||||
* Ledger a,b;
|
||||
* // For all Seq s:
|
||||
* if(a[s] == b[s]);
|
||||
* for(Seq p = 0; p < s; ++p)
|
||||
* assert(a[p] == b[p]);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Ledger A type representing a ledger and its history
|
||||
*/
|
||||
template <class Ledger>
|
||||
class LedgerTrie
|
||||
{
|
||||
using Seq = Ledger::Seq;
|
||||
using ID = Ledger::ID;
|
||||
|
||||
using Node = ledger_trie_detail::Node<Ledger>;
|
||||
using Span = ledger_trie_detail::Span<Ledger>;
|
||||
|
||||
// The root of the trie. The root is allowed to break the no-single child
|
||||
// invariant.
|
||||
std::unique_ptr<Node> root_;
|
||||
|
||||
// Count of the tip support for each sequence number
|
||||
std::map<Seq, std::uint32_t> seqSupport_;
|
||||
|
||||
/**
|
||||
* Find the node in the trie that represents the longest common ancestry
|
||||
* with the given ledger.
|
||||
*
|
||||
* @return Pair of the found node and the sequence number of the first
|
||||
* ledger difference.
|
||||
*/
|
||||
[[nodiscard]] std::pair<Node*, Seq>
|
||||
find(Ledger const& ledger) const
|
||||
{
|
||||
// NOLINTNEXTLINE(misc-const-correctness)
|
||||
Node* curr = root_.get();
|
||||
|
||||
// Root is always defined and is in common with all ledgers
|
||||
XRPL_ASSERT(curr, "xrpl::LedgerTrie::find : non-null root");
|
||||
Seq pos = curr->span.diff(ledger);
|
||||
|
||||
bool done = false;
|
||||
|
||||
// Continue searching for a better span as long as the current position
|
||||
// matches the entire span
|
||||
while (!done && pos == curr->span.end())
|
||||
{
|
||||
done = true;
|
||||
// Find the child with the longest ancestry match
|
||||
for (std::unique_ptr<Node> const& child : curr->children)
|
||||
{
|
||||
auto const childPos = child->span.diff(ledger);
|
||||
if (childPos > pos)
|
||||
{
|
||||
done = false;
|
||||
pos = childPos;
|
||||
curr = child.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::make_pair(curr, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the node in the trie with an exact match to the given ledger ID
|
||||
*
|
||||
* @return the found node or nullptr if an exact match was not found.
|
||||
*
|
||||
* @note O(n) since this searches all nodes until a match is found
|
||||
*/
|
||||
Node*
|
||||
findByLedgerID(Ledger const& ledger, Node* parent = nullptr) const
|
||||
{
|
||||
if (parent == nullptr)
|
||||
parent = root_.get();
|
||||
if (ledger.id() == parent->span.tip().id)
|
||||
return parent;
|
||||
for (auto const& child : parent->children)
|
||||
{
|
||||
auto cl = findByLedgerID(ledger, child.get());
|
||||
if (cl)
|
||||
return cl;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
dumpImpl(std::ostream& o, std::unique_ptr<Node> const& curr, int offset) const
|
||||
{
|
||||
if (curr)
|
||||
{
|
||||
if (offset > 0)
|
||||
o << std::setw(offset) << "|-";
|
||||
|
||||
std::stringstream ss;
|
||||
ss << *curr;
|
||||
o << ss.str() << std::endl;
|
||||
for (std::unique_ptr<Node> const& child : curr->children)
|
||||
dumpImpl(o, child, offset + 1 + ss.str().size() + 2);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
LedgerTrie() : root_{std::make_unique<Node>()}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert and/or increment the support for the given ledger.
|
||||
*
|
||||
* @param ledger A ledger and its ancestry
|
||||
* @param count The count of support for this ledger
|
||||
*/
|
||||
void
|
||||
insert(Ledger const& ledger, std::uint32_t count = 1)
|
||||
{
|
||||
auto const [loc, diffSeq] = find(ledger);
|
||||
|
||||
// There is always a place to insert
|
||||
XRPL_ASSERT(loc, "xrpl::LedgerTrie::insert : valid input ledger");
|
||||
|
||||
// Node from which to start incrementing branchSupport
|
||||
Node* incNode = loc;
|
||||
|
||||
// loc->span has the longest common prefix with Span{ledger} of all
|
||||
// existing nodes in the trie. The optional<Span>'s below represent
|
||||
// the possible common suffixes between loc->span and Span{ledger}.
|
||||
//
|
||||
// loc->span
|
||||
// a b c | d e f
|
||||
// prefix | oldSuffix
|
||||
//
|
||||
// Span{ledger}
|
||||
// a b c | g h i
|
||||
// prefix | newSuffix
|
||||
|
||||
std::optional<Span> prefix = loc->span.before(diffSeq);
|
||||
std::optional<Span> oldSuffix = loc->span.from(diffSeq);
|
||||
std::optional<Span> newSuffix = Span{ledger}.from(diffSeq);
|
||||
|
||||
if (oldSuffix)
|
||||
{
|
||||
// Have
|
||||
// abcdef -> ....
|
||||
// Inserting
|
||||
// abc
|
||||
// Becomes
|
||||
// abc -> def -> ...
|
||||
|
||||
// Create oldSuffix node that takes over loc
|
||||
auto newNode = std::make_unique<Node>(*oldSuffix);
|
||||
newNode->tipSupport = loc->tipSupport;
|
||||
newNode->branchSupport = loc->branchSupport;
|
||||
newNode->children = std::move(loc->children);
|
||||
XRPL_ASSERT(loc->children.empty(), "xrpl::LedgerTrie::insert : moved-from children");
|
||||
for (std::unique_ptr<Node>& child : newNode->children)
|
||||
child->parent = newNode.get();
|
||||
|
||||
// Loc truncates to prefix and newNode is its child
|
||||
XRPL_ASSERT(prefix, "xrpl::LedgerTrie::insert : prefix is set");
|
||||
loc->span = *prefix; // NOLINT(bugprone-unchecked-optional-access) assert above
|
||||
newNode->parent = loc;
|
||||
loc->children.emplace_back(std::move(newNode));
|
||||
loc->tipSupport = 0;
|
||||
}
|
||||
if (newSuffix)
|
||||
{
|
||||
// Have
|
||||
// abc -> ...
|
||||
// Inserting
|
||||
// abcdef-> ...
|
||||
// Becomes
|
||||
// abc -> ...
|
||||
// \-> def
|
||||
|
||||
auto newNode = std::make_unique<Node>(*newSuffix);
|
||||
newNode->parent = loc;
|
||||
// increment support starting from the new node
|
||||
incNode = newNode.get();
|
||||
loc->children.push_back(std::move(newNode));
|
||||
}
|
||||
|
||||
incNode->tipSupport += count;
|
||||
while (incNode)
|
||||
{
|
||||
incNode->branchSupport += count;
|
||||
incNode = incNode->parent;
|
||||
}
|
||||
|
||||
seqSupport_[ledger.seq()] += count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease support for a ledger, removing and compressing if possible.
|
||||
*
|
||||
* @param ledger The ledger history to remove
|
||||
* @param count The amount of tip support to remove
|
||||
*
|
||||
* @return Whether a matching node was decremented and possibly removed.
|
||||
*/
|
||||
bool
|
||||
remove(Ledger const& ledger, std::uint32_t count = 1)
|
||||
{
|
||||
Node* loc = findByLedgerID(ledger);
|
||||
// Must be exact match with tip support
|
||||
if ((loc == nullptr) || loc->tipSupport == 0)
|
||||
return false;
|
||||
|
||||
// found our node, remove it
|
||||
count = std::min(count, loc->tipSupport);
|
||||
loc->tipSupport -= count;
|
||||
|
||||
auto const it = seqSupport_.find(ledger.seq());
|
||||
XRPL_ASSERT(
|
||||
it != seqSupport_.end() && it->second >= count,
|
||||
"xrpl::LedgerTrie::remove : valid input ledger");
|
||||
it->second -= count;
|
||||
if (it->second == 0)
|
||||
seqSupport_.erase(it->first);
|
||||
|
||||
Node* decNode = loc;
|
||||
while (decNode)
|
||||
{
|
||||
decNode->branchSupport -= count;
|
||||
decNode = decNode->parent;
|
||||
}
|
||||
|
||||
while (loc->tipSupport == 0 && loc != root_.get())
|
||||
{
|
||||
Node* parent = loc->parent;
|
||||
if (loc->children.empty())
|
||||
{
|
||||
// this node can be erased
|
||||
parent->erase(loc);
|
||||
}
|
||||
else if (loc->children.size() == 1)
|
||||
{
|
||||
// This node can be combined with its child
|
||||
std::unique_ptr<Node> child = std::move(loc->children.front());
|
||||
child->span = merge(loc->span, child->span);
|
||||
child->parent = parent;
|
||||
parent->children.emplace_back(std::move(child));
|
||||
parent->erase(loc);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
loc = parent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return count of tip support for the specific ledger.
|
||||
*
|
||||
* @param ledger The ledger to lookup
|
||||
* @return The number of entries in the trie for this *exact* ledger
|
||||
*/
|
||||
[[nodiscard]] std::uint32_t
|
||||
tipSupport(Ledger const& ledger) const
|
||||
{
|
||||
if (auto const* loc = findByLedgerID(ledger))
|
||||
return loc->tipSupport;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of branch support for the specific ledger
|
||||
*
|
||||
* @param ledger The ledger to lookup
|
||||
* @return The number of entries in the trie for this ledger or a
|
||||
* descendant
|
||||
*/
|
||||
[[nodiscard]] std::uint32_t
|
||||
branchSupport(Ledger const& ledger) const
|
||||
{
|
||||
Node const* loc = findByLedgerID(ledger);
|
||||
if (loc == nullptr)
|
||||
{
|
||||
Seq diffSeq;
|
||||
std::tie(loc, diffSeq) = find(ledger);
|
||||
// Check that ledger is a proper prefix of loc
|
||||
if (!(diffSeq > ledger.seq() && ledger.seq() < loc->span.end()))
|
||||
loc = nullptr;
|
||||
}
|
||||
return loc ? loc->branchSupport : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the preferred ledger ID
|
||||
*
|
||||
* The preferred ledger is used to determine the working ledger
|
||||
* for consensus amongst competing alternatives.
|
||||
*
|
||||
* Recall that each validator is normally validating a chain of ledgers,
|
||||
* e.g. A->B->C->D. However, if due to network connectivity or other
|
||||
* issues, validators generate different chains
|
||||
*
|
||||
* @code
|
||||
* /->C
|
||||
* A->B
|
||||
* \->D->E
|
||||
* @endcode
|
||||
*
|
||||
* we need a way for validators to converge on the chain with the most
|
||||
* support. We call this the preferred ledger. Intuitively, the idea is to
|
||||
* be conservative and only switch to a different branch when you see
|
||||
* enough peer validations to *know* another branch won't have preferred
|
||||
* support.
|
||||
*
|
||||
* The preferred ledger is found by walking this tree of validated ledgers
|
||||
* starting from the common ancestor ledger.
|
||||
*
|
||||
* At each sequence number, we have
|
||||
*
|
||||
* - The prior sequence preferred ledger, e.g. B.
|
||||
* - The (tip) support of ledgers with this sequence number,e.g. the
|
||||
* number of validators whose last validation was for C or D.
|
||||
* - The (branch) total support of all descendants of the current
|
||||
* sequence number ledgers, e.g. the branch support of D is the
|
||||
* tip support of D plus the tip support of E; the branch support of
|
||||
* C is just the tip support of C.
|
||||
* - The number of validators that have yet to validate a ledger
|
||||
* with this sequence number (uncommitted support). Uncommitted
|
||||
* includes all validators whose last sequence number is smaller than
|
||||
* our last issued sequence number, since due to asynchrony, we may
|
||||
* not have heard from those nodes yet.
|
||||
*
|
||||
* The preferred ledger for this sequence number is then the ledger
|
||||
* with relative majority of support, where uncommitted support
|
||||
* can be given to ANY ledger at that sequence number
|
||||
* (including one not yet known). If no such preferred ledger exists, then
|
||||
* the prior sequence preferred ledger is the overall preferred ledger.
|
||||
*
|
||||
* In this example, for D to be preferred, the number of validators
|
||||
* supporting it or a descendant must exceed the number of validators
|
||||
* supporting C _plus_ the current uncommitted support. This is because if
|
||||
* all uncommitted validators end up validating C, that new support must
|
||||
* be less than that for D to be preferred.
|
||||
*
|
||||
* If a preferred ledger does exist, then we continue with the next
|
||||
* sequence using that ledger as the root.
|
||||
*
|
||||
* @param largestIssued The sequence number of the largest validation
|
||||
* issued by this node.
|
||||
* @return Pair with the sequence number and ID of the preferred ledger or
|
||||
* std::nullopt if no preferred ledger exists
|
||||
*/
|
||||
[[nodiscard]] std::optional<SpanTip<Ledger>>
|
||||
getPreferred(Seq const largestIssued) const
|
||||
{
|
||||
if (empty())
|
||||
return std::nullopt;
|
||||
|
||||
Node* curr = root_.get();
|
||||
|
||||
bool done = false;
|
||||
|
||||
std::uint32_t uncommitted = 0;
|
||||
auto uncommittedIt = seqSupport_.begin();
|
||||
|
||||
while (curr && !done)
|
||||
{
|
||||
// Within a single span, the preferred by branch strategy is simply
|
||||
// to continue along the span as long as the branch support of
|
||||
// the next ledger exceeds the uncommitted support for that ledger.
|
||||
{
|
||||
// Add any initial uncommitted support prior for ledgers
|
||||
// earlier than nextSeq or earlier than largestIssued
|
||||
Seq nextSeq = curr->span.start() + Seq{1};
|
||||
while (uncommittedIt != seqSupport_.end() &&
|
||||
uncommittedIt->first < std::max(nextSeq, largestIssued))
|
||||
{
|
||||
uncommitted += uncommittedIt->second;
|
||||
uncommittedIt++;
|
||||
}
|
||||
|
||||
// Advance nextSeq along the span
|
||||
while (nextSeq < curr->span.end() && curr->branchSupport > uncommitted)
|
||||
{
|
||||
// Jump to the next seqSupport change
|
||||
if (uncommittedIt != seqSupport_.end() &&
|
||||
uncommittedIt->first < curr->span.end())
|
||||
{
|
||||
nextSeq = uncommittedIt->first + Seq{1};
|
||||
uncommitted += uncommittedIt->second;
|
||||
uncommittedIt++;
|
||||
}
|
||||
else
|
||||
{ // otherwise we jump to the end of the span
|
||||
nextSeq = curr->span.end();
|
||||
}
|
||||
}
|
||||
// We did not consume the entire span, so we have found the
|
||||
// preferred ledger
|
||||
if (nextSeq < curr->span.end())
|
||||
{
|
||||
// nextSeq within span guarantees before() is set
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
||||
return curr->span.before(nextSeq)->tip();
|
||||
}
|
||||
}
|
||||
|
||||
// We have reached the end of the current span, so we need to
|
||||
// find the best child
|
||||
Node* best = nullptr;
|
||||
std::uint32_t margin = 0;
|
||||
if (curr->children.size() == 1)
|
||||
{
|
||||
best = curr->children[0].get();
|
||||
margin = best->branchSupport;
|
||||
}
|
||||
else if (!curr->children.empty())
|
||||
{
|
||||
// Sort placing children with largest branch support in the
|
||||
// front, breaking ties with the span's starting ID
|
||||
std::partial_sort(
|
||||
curr->children.begin(),
|
||||
curr->children.begin() + 2,
|
||||
curr->children.end(),
|
||||
[](std::unique_ptr<Node> const& a, std::unique_ptr<Node> const& b) {
|
||||
return std::make_tuple(a->branchSupport, a->span.startID()) >
|
||||
std::make_tuple(b->branchSupport, b->span.startID());
|
||||
});
|
||||
|
||||
best = curr->children[0].get();
|
||||
margin = curr->children[0]->branchSupport - curr->children[1]->branchSupport;
|
||||
|
||||
// If best holds the tie-breaker, gets one larger margin
|
||||
// since the second best needs additional branchSupport
|
||||
// to overcome the tie
|
||||
if (best->span.startID() > curr->children[1]->span.startID())
|
||||
margin++;
|
||||
}
|
||||
|
||||
// If the best child has margin exceeding the uncommitted support,
|
||||
// continue from that child, otherwise we are done
|
||||
if (best && ((margin > uncommitted) || (uncommitted == 0)))
|
||||
{
|
||||
curr = best;
|
||||
}
|
||||
else
|
||||
{ // current is the best
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
return curr->span.tip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the trie is tracking any ledgers
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return !root_ || root_->branchSupport == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump an ascii representation of the trie to the stream
|
||||
*/
|
||||
void
|
||||
dump(std::ostream& o) const
|
||||
{
|
||||
dumpImpl(o, root_, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump JSON representation of trie state
|
||||
*/
|
||||
[[nodiscard]] json::Value
|
||||
getJson() const
|
||||
{
|
||||
json::Value res;
|
||||
res["trie"] = root_->getJson();
|
||||
res["seq_support"] = json::ValueType::Object;
|
||||
for (auto const& [seq, sup] : seqSupport_)
|
||||
res["seq_support"][to_string(seq)] = sup;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the compressed trie and support invariants.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
checkInvariants() const
|
||||
{
|
||||
std::map<Seq, std::uint32_t> expectedSeqSupport;
|
||||
|
||||
std::stack<Node const*> nodes;
|
||||
nodes.push(root_.get());
|
||||
while (!nodes.empty())
|
||||
{
|
||||
Node const* curr = nodes.top();
|
||||
nodes.pop();
|
||||
if (curr == nullptr)
|
||||
continue;
|
||||
|
||||
// Node with 0 tip support must have multiple children
|
||||
// unless it is the root node
|
||||
if (curr != root_.get() && curr->tipSupport == 0 && curr->children.size() < 2)
|
||||
return false;
|
||||
|
||||
// branchSupport = tipSupport + sum(child->branchSupport)
|
||||
std::size_t support = curr->tipSupport;
|
||||
if (curr->tipSupport != 0)
|
||||
expectedSeqSupport[curr->span.end() - Seq{1}] += curr->tipSupport;
|
||||
|
||||
for (auto const& child : curr->children)
|
||||
{
|
||||
if (child->parent != curr)
|
||||
return false;
|
||||
|
||||
support += child->branchSupport;
|
||||
nodes.push(child.get());
|
||||
}
|
||||
if (support != curr->branchSupport)
|
||||
return false;
|
||||
}
|
||||
return expectedSeqSupport == seqSupport_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
8
include/xrpl/consensus/README.md
Normal file
8
include/xrpl/consensus/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Consensus
|
||||
|
||||
This directory contains the implementation of a
|
||||
generic consensus algorithm. The implementation
|
||||
follows a CRTP design, requiring client code to implement
|
||||
specific functions and types to use consensus in their
|
||||
application. The interface is undergoing refactoring and
|
||||
is not yet finalized.
|
||||
1179
include/xrpl/consensus/Validations.h
Normal file
1179
include/xrpl/consensus/Validations.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,12 +15,12 @@
|
||||
namespace xrpl {
|
||||
|
||||
// Forward declarations
|
||||
namespace NodeStore {
|
||||
namespace node_store {
|
||||
class Database;
|
||||
} // namespace NodeStore
|
||||
namespace Resource {
|
||||
} // namespace node_store
|
||||
namespace resource {
|
||||
class Manager;
|
||||
} // namespace Resource
|
||||
} // namespace resource
|
||||
namespace perf {
|
||||
class PerfLog;
|
||||
} // namespace perf
|
||||
@@ -160,11 +160,11 @@ public:
|
||||
virtual PeerReservationTable&
|
||||
getPeerReservations() = 0;
|
||||
|
||||
virtual Resource::Manager&
|
||||
virtual resource::Manager&
|
||||
getResourceManager() = 0;
|
||||
|
||||
// Storage services
|
||||
virtual NodeStore::Database&
|
||||
virtual node_store::Database&
|
||||
getNodeStore() = 0;
|
||||
|
||||
virtual SHAMapStore&
|
||||
|
||||
@@ -623,6 +623,7 @@ class ValueConstIterator : public ValueIteratorBase
|
||||
public:
|
||||
using size_t = unsigned int;
|
||||
using difference_type = int;
|
||||
using value_type = Value const;
|
||||
using reference = Value const&;
|
||||
using pointer = Value const*;
|
||||
using SelfType = ValueConstIterator;
|
||||
@@ -687,6 +688,7 @@ class ValueIterator : public ValueIteratorBase
|
||||
public:
|
||||
using size_t = unsigned int;
|
||||
using difference_type = int;
|
||||
using value_type = Value;
|
||||
using reference = Value&;
|
||||
using pointer = Value*;
|
||||
using SelfType = ValueIterator;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -241,10 +243,25 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
auto finalAmt = amount;
|
||||
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
|
||||
{
|
||||
// compute transfer fee, if any
|
||||
auto const xferFee = amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
|
||||
// compute balance to transfer
|
||||
finalAmt = amount.value() - xferFee;
|
||||
if (ctx.view.rules().enabled(fixCleanup3_4_0))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
lockedRate >= kParityRate,
|
||||
"xrpl::escrowUnlockApplyHelper<MPTIssue> : lockedRate is at least parity");
|
||||
// MPTs are integral, so round the delivered amount down and
|
||||
// charge any fractional transfer fee to the escrowed amount.
|
||||
auto const delivered =
|
||||
mulRatio(amount.mpt(), kParityRate.value, lockedRate.value, false);
|
||||
finalAmt = STAmount(amount.asset(), delivered.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
// compute transfer fee, if any
|
||||
auto const xferFee =
|
||||
amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
|
||||
// compute balance to transfer
|
||||
finalAmt = amount.value() - xferFee;
|
||||
}
|
||||
}
|
||||
return unlockEscrowMPT(
|
||||
ctx.view,
|
||||
|
||||
@@ -286,6 +286,77 @@ computeFullPaymentInterest(
|
||||
std::uint32_t startDate,
|
||||
TenthBips32 closeInterestRate);
|
||||
|
||||
// Deltas applied to Vault.AssetsTotal and LoanBroker.DebtTotal at a single
|
||||
// accounting touch point (origination, payment, impair/unimpair/default).
|
||||
struct AccountingDeltas
|
||||
{
|
||||
Number assetsTotalDelta;
|
||||
Number debtTotalDelta;
|
||||
};
|
||||
|
||||
// Whole-life (pre-LendingProtocolV1_1) recognition model: interest is
|
||||
// recognized into AssetsTotal/DebtTotal up front, at origination.
|
||||
namespace accrual {
|
||||
|
||||
// LoanSet origination: what's added to Vault.AssetsTotal and LoanBroker.DebtTotal
|
||||
AccountingDeltas
|
||||
loanOriginationDeltas(Number const& principalRequested, Number const& interestDue);
|
||||
|
||||
// LoanSet origination: would recognizing this loan's interest push
|
||||
// Vault.AssetsTotal past Vault.AssetsMaximum?
|
||||
bool
|
||||
loanOriginationExceedsVaultMaximum(
|
||||
Number const& vaultMaximum,
|
||||
Number const& vaultTotal,
|
||||
Number const& interestDue);
|
||||
|
||||
// LoanManage impair/unimpair/default: the vault's exposure to this loan
|
||||
Number
|
||||
loanVaultExposure(SLE::const_ref loanSle);
|
||||
|
||||
// LoanPay: what's added to Vault.AssetsTotal and subtracted from LoanBroker.DebtTotal for a payment
|
||||
AccountingDeltas
|
||||
loanPaymentDeltas(LoanPaymentParts const& parts);
|
||||
|
||||
} // namespace accrual
|
||||
|
||||
// Cash-basis (LendingProtocolV1_1) recognition model: AssetsTotal/DebtTotal
|
||||
// are principal-only, interest is recognized only as it's actually paid.
|
||||
namespace cash_basis {
|
||||
|
||||
AccountingDeltas
|
||||
loanOriginationDeltas(Number const& principalRequested);
|
||||
|
||||
Number
|
||||
loanVaultExposure(SLE::const_ref loanSle);
|
||||
|
||||
AccountingDeltas
|
||||
loanPaymentDeltas(LoanPaymentParts const& parts);
|
||||
|
||||
} // namespace cash_basis
|
||||
|
||||
// Public dispatchers: pick cash_basis:: if featureLendingProtocolV1_1 is
|
||||
// enabled AND the Vault's LEVersion (VaultHelpers::getVaultVersion) is
|
||||
// VaultVersion::CashBasis, else accrual::. These are the only entry points
|
||||
// transactors call.
|
||||
AccountingDeltas
|
||||
loanOriginationDeltas(
|
||||
SLE::const_ref vaultSle,
|
||||
Number const& principalRequested,
|
||||
Number const& interestDue);
|
||||
|
||||
bool
|
||||
loanOriginationExceedsVaultMaximum(
|
||||
SLE::const_ref vaultSle,
|
||||
Number const& vaultTotal,
|
||||
Number const& interestDue);
|
||||
|
||||
Number
|
||||
loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle);
|
||||
|
||||
AccountingDeltas
|
||||
loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const& parts);
|
||||
|
||||
namespace detail {
|
||||
// These classes and functions should only be accessed by LendingHelper
|
||||
// functions and unit tests
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
@@ -107,4 +108,19 @@ sharesToAssetsWithdraw(
|
||||
[[nodiscard]] bool
|
||||
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance);
|
||||
|
||||
/**
|
||||
* Resolves a Vault's LEVersion, the single point every accounting touch
|
||||
* point should call to determine which recognition model (accrual vs.
|
||||
* cash-basis) a Vault uses. Vaults created before featureLendingProtocolV1_1
|
||||
* activated never have sfLEVersion set, which resolves here to
|
||||
* VaultVersion::Legacy.
|
||||
*
|
||||
* @param vault The vault SLE.
|
||||
*
|
||||
* @return The Vault's LEVersion, or VaultVersion::Legacy if the field is
|
||||
* absent.
|
||||
*/
|
||||
[[nodiscard]] VaultVersion
|
||||
getVaultVersion(SLE::const_ref vault);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -67,16 +67,16 @@ public:
|
||||
return socket_->next_layer();
|
||||
}
|
||||
|
||||
beast::IP::Endpoint
|
||||
beast::ip::Endpoint
|
||||
localEndpoint()
|
||||
{
|
||||
return beast::IP::fromAsio(lowestLayer().local_endpoint());
|
||||
return beast::ip::fromAsio(lowestLayer().local_endpoint());
|
||||
}
|
||||
|
||||
beast::IP::Endpoint
|
||||
beast::ip::Endpoint
|
||||
remoteEndpoint()
|
||||
{
|
||||
return beast::IP::fromAsio(lowestLayer().remote_endpoint());
|
||||
return beast::ip::fromAsio(lowestLayer().remote_endpoint());
|
||||
}
|
||||
|
||||
lowest_layer_type&
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* A backend used for the NodeStore.
|
||||
@@ -163,4 +163,4 @@ public:
|
||||
fdRequired() const = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace xrpl {
|
||||
class Section;
|
||||
} // namespace xrpl
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* Persistency layer for NodeObject
|
||||
@@ -248,7 +248,7 @@ protected:
|
||||
void
|
||||
storeStats(std::uint64_t count, std::uint64_t sz)
|
||||
{
|
||||
XRPL_ASSERT(count <= sz, "xrpl::NodeStore::Database::storeStats : valid inputs");
|
||||
XRPL_ASSERT(count <= sz, "xrpl::node_store::Database::storeStats : valid inputs");
|
||||
storeCount_ += count;
|
||||
storeSz_ += sz;
|
||||
}
|
||||
@@ -308,4 +308,4 @@ private:
|
||||
threadEntry();
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/* This class has two key-value store Backend objects for persisting SHAMap
|
||||
* records. This facilitates online deletion of data. New backends are
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
*/
|
||||
virtual void
|
||||
rotate(
|
||||
std::unique_ptr<NodeStore::Backend>&& newBackend,
|
||||
std::unique_ptr<node_store::Backend>&& newBackend,
|
||||
std::function<void(std::string const& writableName, std::string const& archiveName)> const&
|
||||
f) = 0;
|
||||
|
||||
@@ -56,4 +56,4 @@ public:
|
||||
setRotationInFlight(bool inFlight) = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpl/nodestore/Scheduler.h>
|
||||
#include <xrpl/nodestore/Task.h>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* Simple NodeStore Scheduler that just performs the tasks synchronously.
|
||||
@@ -21,4 +21,4 @@ public:
|
||||
onBatchWrite(BatchWriteReport const& report) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace xrpl {
|
||||
class Section;
|
||||
} // namespace xrpl
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* Base class for backend factories.
|
||||
@@ -70,4 +70,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* Singleton for managing NodeStore factories and back ends.
|
||||
@@ -98,4 +98,4 @@ public:
|
||||
beast::Journal journal) = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
// VFALCO NOTE Intentionally not in the NodeStore namespace
|
||||
// VFALCO NOTE Intentionally not in the node_store namespace
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
enum class FetchType { Synchronous, Async };
|
||||
|
||||
@@ -71,4 +71,4 @@ public:
|
||||
onBatchWrite(BatchWriteReport const& report) = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* Derived classes perform scheduled tasks.
|
||||
@@ -17,4 +17,4 @@ struct Task
|
||||
performScheduledTask() = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
// This is only used to pre-allocate the array for
|
||||
// batch objects and does not affect the amount written.
|
||||
@@ -36,4 +36,4 @@ enum class Status {
|
||||
*/
|
||||
using Batch = std::vector<std::shared_ptr<NodeObject>>;
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* Batch-writing assist logic.
|
||||
@@ -86,4 +86,4 @@ private:
|
||||
Batch writeSet_;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
class DatabaseNodeImp : public Database
|
||||
{
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
|
||||
XRPL_ASSERT(
|
||||
backend_,
|
||||
"xrpl::NodeStore::DatabaseNodeImp::DatabaseNodeImp : non-null "
|
||||
"xrpl::node_store::DatabaseNodeImp::DatabaseNodeImp : non-null "
|
||||
"backend");
|
||||
}
|
||||
|
||||
@@ -138,4 +138,4 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
class DatabaseRotatingImp : public DatabaseRotating
|
||||
{
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
|
||||
void
|
||||
rotate(
|
||||
std::unique_ptr<NodeStore::Backend>&& newBackend,
|
||||
std::unique_ptr<node_store::Backend>&& newBackend,
|
||||
std::function<void(std::string const& writableName, std::string const& archiveName)> const&
|
||||
f) override;
|
||||
|
||||
@@ -94,4 +94,4 @@ private:
|
||||
forEach(std::function<void(std::shared_ptr<NodeObject>)> f) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* Parsed key/value blob into NodeObject components.
|
||||
@@ -49,4 +49,4 @@ private:
|
||||
int dataBytes_;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* Convert a NodeObject from in-memory to database format.
|
||||
@@ -68,7 +68,7 @@ class EncodedBlob
|
||||
public:
|
||||
explicit EncodedBlob(std::shared_ptr<NodeObject> const& obj)
|
||||
: size_([&obj]() {
|
||||
XRPL_ASSERT(obj, "xrpl::NodeStore::EncodedBlob::EncodedBlob : non-null input");
|
||||
XRPL_ASSERT(obj, "xrpl::node_store::EncodedBlob::EncodedBlob : non-null input");
|
||||
|
||||
if (!obj)
|
||||
throw std::runtime_error("EncodedBlob: unseated std::shared_ptr used.");
|
||||
@@ -88,7 +88,7 @@ public:
|
||||
XRPL_ASSERT(
|
||||
((ptr_ == payload_.data()) && (size_ <= payload_.size())) ||
|
||||
((ptr_ != payload_.data()) && (size_ > payload_.size())),
|
||||
"xrpl::NodeStore::EncodedBlob::~EncodedBlob : valid payload "
|
||||
"xrpl::node_store::EncodedBlob::~EncodedBlob : valid payload "
|
||||
"pointer");
|
||||
|
||||
if (ptr_ != payload_.data())
|
||||
@@ -114,4 +114,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
class ManagerImp : public Manager
|
||||
{
|
||||
@@ -57,4 +57,4 @@ public:
|
||||
beast::Journal journal) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -6,25 +6,25 @@
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
// This is a variant of the base128 varint format from
|
||||
// google protocol buffers:
|
||||
// https://developers.google.com/protocol-buffers/docs/encoding#varints
|
||||
|
||||
// field tag
|
||||
struct varint;
|
||||
struct Varint;
|
||||
|
||||
// Metafuncton to return largest
|
||||
// Metafunction to return largest
|
||||
// possible size of T represented as varint.
|
||||
// T must be unsigned
|
||||
template <class T, bool = std::is_unsigned_v<T>>
|
||||
struct varint_traits;
|
||||
struct VarintTraits;
|
||||
|
||||
template <class T>
|
||||
struct varint_traits<T, true>
|
||||
struct VarintTraits<T, true>
|
||||
{
|
||||
explicit varint_traits() = default;
|
||||
explicit VarintTraits() = default;
|
||||
|
||||
static constexpr std::size_t kMax = ((8 * sizeof(T)) + 6) / 7;
|
||||
};
|
||||
@@ -104,7 +104,7 @@ writeVarint(void* p0, std::size_t v)
|
||||
template <class T>
|
||||
void
|
||||
read(nudb::detail::istream& is, std::size_t& u)
|
||||
requires(std::is_same_v<T, varint>)
|
||||
requires(std::is_same_v<T, Varint>)
|
||||
{
|
||||
auto p0 = is(1);
|
||||
auto p1 = p0;
|
||||
@@ -118,9 +118,9 @@ read(nudb::detail::istream& is, std::size_t& u)
|
||||
template <class T>
|
||||
void
|
||||
write(nudb::detail::ostream& os, std::size_t t)
|
||||
requires(std::is_same_v<T, varint>)
|
||||
requires(std::is_same_v<T, Varint>)
|
||||
{
|
||||
writeVarint(os.data(sizeVarint(t)), t);
|
||||
}
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/nodestore/detail/varint.h>
|
||||
#include <xrpl/nodestore/detail/Varint.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
|
||||
#include <nudb/detail/field.hpp>
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace xrpl::node_store {
|
||||
|
||||
template <class BufferFactory>
|
||||
std::pair<void const*, std::size_t>
|
||||
@@ -59,7 +59,7 @@ lz4Compress(void const* in, std::size_t inSize, BufferFactory&& bf)
|
||||
using std::runtime_error;
|
||||
using namespace nudb::detail;
|
||||
std::pair<void const*, std::size_t> result;
|
||||
std::array<std::uint8_t, varint_traits<std::size_t>::kMax> vi{};
|
||||
std::array<std::uint8_t, VarintTraits<std::size_t>::kMax> vi{};
|
||||
auto const n = writeVarint(vi.data(), inSize);
|
||||
auto const outMax = LZ4_compressBound(inSize);
|
||||
auto* out = reinterpret_cast<std::uint8_t*>(bf(n + outMax));
|
||||
@@ -240,7 +240,7 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf)
|
||||
auto* out = reinterpret_cast<std::uint8_t*>(bf(result.second));
|
||||
result.first = out;
|
||||
ostream os(out, result.second);
|
||||
write<varint>(os, type);
|
||||
write<Varint>(os, type);
|
||||
write<std::uint16_t>(os, mask);
|
||||
write(os, vh.data(), n * 32);
|
||||
return result;
|
||||
@@ -252,13 +252,13 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf)
|
||||
auto* out = reinterpret_cast<std::uint8_t*>(bf(result.second));
|
||||
result.first = out;
|
||||
ostream os(out, result.second);
|
||||
write<varint>(os, type);
|
||||
write<Varint>(os, type);
|
||||
write(os, vh.data(), n * 32);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
std::array<std::uint8_t, varint_traits<std::size_t>::kMax> vi{};
|
||||
std::array<std::uint8_t, VarintTraits<std::size_t>::kMax> vi{};
|
||||
|
||||
static constexpr std::size_t kCodecType = 1;
|
||||
auto const vn = writeVarint(vi.data(), kCodecType);
|
||||
@@ -269,7 +269,7 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf)
|
||||
case 1: // lz4
|
||||
{
|
||||
std::uint8_t* p = nullptr;
|
||||
auto const lzr = NodeStore::lz4Compress(in, inSize, [&p, &vn, &bf](std::size_t n) {
|
||||
auto const lzr = node_store::lz4Compress(in, inSize, [&p, &vn, &bf](std::size_t n) {
|
||||
p = reinterpret_cast<std::uint8_t*>(bf(vn + n));
|
||||
return p + vn;
|
||||
});
|
||||
@@ -316,4 +316,4 @@ filterInner(void* in, std::size_t inSize)
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
163
include/xrpl/peerfinder/Config.h
Normal file
163
include/xrpl/peerfinder/Config.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/PropertyStream.h>
|
||||
#include <xrpl/peerfinder/detail/Tuning.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
struct PeerLimitConfig
|
||||
{
|
||||
std::optional<std::size_t> maxPeers;
|
||||
std::optional<std::size_t> inPeers;
|
||||
std::optional<std::size_t> outPeers;
|
||||
};
|
||||
|
||||
/**
|
||||
* PeerFinder configuration settings.
|
||||
*/
|
||||
struct Config
|
||||
{
|
||||
/**
|
||||
* The largest number of public peer slots to allow.
|
||||
* This includes both inbound and outbound, but does not include
|
||||
* fixed peers.
|
||||
*/
|
||||
std::size_t maxPeers{tuning::kDefaultMaxPeers};
|
||||
|
||||
/**
|
||||
* The number of automatic outbound connections to maintain.
|
||||
* Outbound connections are only maintained if autoConnect
|
||||
* is `true`.
|
||||
*/
|
||||
std::size_t outPeers = calcOutPeers(); // Note: relies on `maxPeers` being initialized
|
||||
|
||||
/**
|
||||
* The number of automatic inbound connections to maintain.
|
||||
* Inbound connections are only maintained if wantIncoming
|
||||
* is `true`.
|
||||
*/
|
||||
std::size_t inPeers{0};
|
||||
|
||||
/**
|
||||
* `true` if we want our IP address kept private.
|
||||
*/
|
||||
bool peerPrivate = true;
|
||||
|
||||
/**
|
||||
* `true` if we want to accept incoming connections.
|
||||
*/
|
||||
bool wantIncoming{true};
|
||||
|
||||
/**
|
||||
* `true` if we want to establish connections automatically
|
||||
*/
|
||||
bool autoConnect{true};
|
||||
|
||||
/**
|
||||
* The listening port number.
|
||||
*/
|
||||
std::uint16_t listeningPort{0};
|
||||
|
||||
/**
|
||||
* The set of features we advertise.
|
||||
*/
|
||||
std::string features;
|
||||
|
||||
/**
|
||||
* Limit how many incoming connections we allow per IP
|
||||
*/
|
||||
int ipLimit{0};
|
||||
|
||||
/**
|
||||
* `true` if we want to verify endpoints in TMEndpoints messages
|
||||
*/
|
||||
bool verifyEndpoints = true;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a suitable value for outPeers according to the rules.
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
calcOutPeers() const;
|
||||
|
||||
/**
|
||||
* Adjusts the values so they follow the business rules.
|
||||
*/
|
||||
void
|
||||
applyTuning();
|
||||
|
||||
/**
|
||||
* Write the configuration into a property stream
|
||||
*/
|
||||
void
|
||||
onWrite(beast::PropertyStream::Map& map) const;
|
||||
|
||||
/**
|
||||
* Make peer_finder::Config from peer limit and server mode parameters.
|
||||
*/
|
||||
static Config
|
||||
makeConfig(
|
||||
bool peerPrivate,
|
||||
bool standalone,
|
||||
PeerLimitConfig const& limits,
|
||||
std::uint16_t port,
|
||||
bool validationPublicKey,
|
||||
int ipLimit,
|
||||
bool verifyEndpoints);
|
||||
|
||||
/**
|
||||
* Compares two configurations for equality field by field.
|
||||
*/
|
||||
friend bool
|
||||
operator==(Config const& lhs, Config const& rhs) = default;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Possible results from activating a slot.
|
||||
*/
|
||||
enum class Result { InboundDisabled, DuplicatePeer, IpLimitExceeded, Full, Success };
|
||||
|
||||
/**
|
||||
* @brief Converts a `Result` enum value to its string representation.
|
||||
*
|
||||
* This function provides a human-readable string for a given `Result` enum,
|
||||
* which is useful for logging, debugging, or displaying status messages.
|
||||
*
|
||||
* @param result The `Result` enum value to convert.
|
||||
* @return A `std::string_view` representing the enum value. Returns "unknown"
|
||||
* if the enum value is not explicitly handled.
|
||||
*
|
||||
* @note This function returns a `std::string_view` for performance.
|
||||
* A `std::string` would need to allocate memory on the heap and copy the
|
||||
* string literal into it every time the function is called.
|
||||
*/
|
||||
inline std::string_view
|
||||
to_string(Result result) noexcept
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case Result::InboundDisabled:
|
||||
return "inbound disabled";
|
||||
case Result::DuplicatePeer:
|
||||
return "peer already connected";
|
||||
case Result::IpLimitExceeded:
|
||||
return "ip limit exceeded";
|
||||
case Result::Full:
|
||||
return "slots full";
|
||||
case Result::Success:
|
||||
return "success";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
179
include/xrpl/peerfinder/PeerfinderManager.h
Normal file
179
include/xrpl/peerfinder/PeerfinderManager.h
Normal file
@@ -0,0 +1,179 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/utility/PropertyStream.h>
|
||||
#include <xrpl/peerfinder/Config.h>
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/peerfinder/Types.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* Maintains a set of IP addresses used for getting into the network.
|
||||
*/
|
||||
class Manager : public beast::PropertyStream::Source
|
||||
{
|
||||
protected:
|
||||
Manager() noexcept;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Destroy the object.
|
||||
* Any pending source fetch operations are aborted.
|
||||
* There may be some listener calls made before the
|
||||
* destructor returns.
|
||||
*/
|
||||
~Manager() override = default;
|
||||
|
||||
/**
|
||||
* Set the configuration for the manager.
|
||||
* The new settings will be applied asynchronously.
|
||||
* Thread safety:
|
||||
* Can be called from any threads at any time.
|
||||
*/
|
||||
virtual void
|
||||
setConfig(Config const& config) = 0;
|
||||
|
||||
/**
|
||||
* Transition to the started state, synchronously.
|
||||
*/
|
||||
virtual void
|
||||
start() = 0;
|
||||
|
||||
/**
|
||||
* Transition to the stopped state, synchronously.
|
||||
*/
|
||||
virtual void
|
||||
stop() = 0;
|
||||
|
||||
/**
|
||||
* Returns the configuration for the manager.
|
||||
*/
|
||||
virtual Config
|
||||
config() = 0;
|
||||
|
||||
/**
|
||||
* Add a peer that should always be connected.
|
||||
* This is useful for maintaining a private cluster of peers.
|
||||
* The string is the name as specified in the configuration
|
||||
* file, along with the set of corresponding IP addresses.
|
||||
*/
|
||||
virtual void
|
||||
addFixedPeer(std::string_view name, std::vector<beast::ip::Endpoint> const& addresses) = 0;
|
||||
|
||||
/**
|
||||
* Add a set of strings as fallback ip::Endpoint sources.
|
||||
* @param name A label used for diagnostics.
|
||||
*/
|
||||
virtual void
|
||||
addFallbackStrings(std::string const& name, std::vector<std::string> const& strings) = 0;
|
||||
|
||||
/**
|
||||
* Add a URL as a fallback location to obtain ip::Endpoint sources.
|
||||
* @param name A label used for diagnostics.
|
||||
*/
|
||||
/* VFALCO NOTE Unimplemented
|
||||
virtual void addFallbackURL (std::string const& name,
|
||||
std::string const& url) = 0;
|
||||
*/
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a new inbound slot with the specified remote endpoint.
|
||||
* If nullptr is returned, then the slot could not be assigned.
|
||||
* Usually this is because of a detected self-connection.
|
||||
*/
|
||||
virtual std::pair<std::shared_ptr<Slot>, Result>
|
||||
newInboundSlot(
|
||||
beast::ip::Endpoint const& localEndpoint,
|
||||
beast::ip::Endpoint const& remoteEndpoint) = 0;
|
||||
|
||||
/**
|
||||
* Create a new outbound slot with the specified remote endpoint.
|
||||
* If nullptr is returned, then the slot could not be assigned.
|
||||
* Usually this is because of a duplicate connection.
|
||||
*/
|
||||
virtual std::pair<std::shared_ptr<Slot>, Result>
|
||||
newOutboundSlot(beast::ip::Endpoint const& remoteEndpoint) = 0;
|
||||
|
||||
/**
|
||||
* Called when mtENDPOINTS is received.
|
||||
*/
|
||||
virtual void
|
||||
onEndpoints(std::shared_ptr<Slot> const& slot, Endpoints const& endpoints) = 0;
|
||||
|
||||
/**
|
||||
* Called when the slot is closed.
|
||||
* This always happens when the socket is closed, unless the socket
|
||||
* was canceled.
|
||||
*/
|
||||
virtual void
|
||||
onClosed(std::shared_ptr<Slot> const& slot) = 0;
|
||||
|
||||
/**
|
||||
* Called when an outbound connection is deemed to have failed
|
||||
*/
|
||||
virtual void
|
||||
onFailure(std::shared_ptr<Slot> const& slot) = 0;
|
||||
|
||||
/**
|
||||
* Called when we received redirect IPs from a busy peer.
|
||||
*/
|
||||
virtual void
|
||||
onRedirects(
|
||||
boost::asio::ip::tcp::endpoint const& remoteAddress,
|
||||
std::vector<boost::asio::ip::tcp::endpoint> const& eps) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Called when an outbound connection attempt succeeds.
|
||||
* The local endpoint must be valid. If the caller receives an error
|
||||
* when retrieving the local endpoint from the socket, it should
|
||||
* proceed as if the connection attempt failed by calling on_closed
|
||||
* instead of on_connected.
|
||||
* @return `true` if the connection should be kept
|
||||
*/
|
||||
virtual bool
|
||||
onConnected(std::shared_ptr<Slot> const& slot, beast::ip::Endpoint const& localEndpoint) = 0;
|
||||
|
||||
/**
|
||||
* Request an active slot type.
|
||||
*/
|
||||
virtual Result
|
||||
activate(std::shared_ptr<Slot> const& slot, PublicKey const& key, bool reserved) = 0;
|
||||
|
||||
/**
|
||||
* Returns a set of endpoints suitable for redirection.
|
||||
*/
|
||||
virtual std::vector<Endpoint>
|
||||
redirect(std::shared_ptr<Slot> const& slot) = 0;
|
||||
|
||||
/**
|
||||
* Return a set of addresses we should connect to.
|
||||
*/
|
||||
virtual std::vector<beast::ip::Endpoint>
|
||||
autoconnect() = 0;
|
||||
|
||||
virtual std::vector<std::pair<std::shared_ptr<Slot>, std::vector<Endpoint>>>
|
||||
buildEndpointsForPeers() = 0;
|
||||
|
||||
/**
|
||||
* Perform periodic activity.
|
||||
* This should be called once per second.
|
||||
*/
|
||||
virtual void
|
||||
oncePerSecond() = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
75
include/xrpl/peerfinder/Slot.h
Normal file
75
include/xrpl/peerfinder/Slot.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* Properties and state associated with a peer to peer overlay connection.
|
||||
*/
|
||||
class Slot
|
||||
{
|
||||
public:
|
||||
using ptr = std::shared_ptr<Slot>;
|
||||
|
||||
enum class State { Accept, Connect, Connected, Active, Closing };
|
||||
|
||||
virtual ~Slot() = 0;
|
||||
|
||||
/**
|
||||
* Returns `true` if this is an inbound connection.
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
inbound() const = 0;
|
||||
|
||||
/**
|
||||
* Returns `true` if this is a fixed connection.
|
||||
* A connection is fixed if its remote endpoint is in the list of
|
||||
* remote endpoints for fixed connections.
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
fixed() const = 0;
|
||||
|
||||
/**
|
||||
* Returns `true` if this is a reserved connection.
|
||||
* It might be a cluster peer, or a peer with a reservation.
|
||||
* This is only known after then handshake completes.
|
||||
*/
|
||||
[[nodiscard]] virtual bool
|
||||
reserved() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the state of the connection.
|
||||
*/
|
||||
[[nodiscard]] virtual State
|
||||
state() const = 0;
|
||||
|
||||
/**
|
||||
* The remote endpoint of socket.
|
||||
*/
|
||||
[[nodiscard]] virtual beast::ip::Endpoint const&
|
||||
remoteEndpoint() const = 0;
|
||||
|
||||
/**
|
||||
* The local endpoint of the socket, when known.
|
||||
*/
|
||||
[[nodiscard]] virtual std::optional<beast::ip::Endpoint> const&
|
||||
localEndpoint() const = 0;
|
||||
|
||||
[[nodiscard]] virtual std::optional<std::uint16_t>
|
||||
listeningPort() const = 0;
|
||||
|
||||
/**
|
||||
* The peer's public key, when known.
|
||||
* The public key is established when the handshake is complete.
|
||||
*/
|
||||
[[nodiscard]] virtual std::optional<PublicKey> const&
|
||||
publicKey() const = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
46
include/xrpl/peerfinder/Types.h
Normal file
46
include/xrpl/peerfinder/Types.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/clock/abstract_clock.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/peerfinder/detail/Tuning.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
using clock_type = beast::AbstractClock<std::chrono::steady_clock>;
|
||||
|
||||
/**
|
||||
* Represents a set of addresses.
|
||||
*/
|
||||
using IPAddresses = std::vector<beast::ip::Endpoint>;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Describes a connectable peer address along with some metadata.
|
||||
*/
|
||||
struct Endpoint
|
||||
{
|
||||
Endpoint() = default;
|
||||
|
||||
Endpoint(beast::ip::Endpoint ep, std::uint32_t hops);
|
||||
|
||||
std::uint32_t hops = 0;
|
||||
beast::ip::Endpoint address;
|
||||
};
|
||||
|
||||
inline bool
|
||||
operator<(Endpoint const& lhs, Endpoint const& rhs)
|
||||
{
|
||||
return lhs.address < rhs.address;
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of Endpoint used for connecting.
|
||||
*/
|
||||
using Endpoints = std::vector<Endpoint>;
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
192
include/xrpl/peerfinder/detail/Bootcache.h
Normal file
192
include/xrpl/peerfinder/detail/Bootcache.h
Normal file
@@ -0,0 +1,192 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/PropertyStream.h>
|
||||
#include <xrpl/peerfinder/Types.h>
|
||||
#include <xrpl/peerfinder/detail/Store.h>
|
||||
#include <xrpl/peerfinder/detail/Tuning.h>
|
||||
|
||||
#include <boost/bimap.hpp>
|
||||
#include <boost/bimap/multiset_of.hpp>
|
||||
#include <boost/bimap/unordered_set_of.hpp>
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* Stores IP addresses useful for gaining initial connections.
|
||||
*
|
||||
* This is one of the caches that is consulted when additional outgoing
|
||||
* connections are needed. Along with the address, each entry has this
|
||||
* additional metadata:
|
||||
*
|
||||
* Valence
|
||||
* A signed integer which represents the number of successful
|
||||
* consecutive connection attempts when positive, and the number of
|
||||
* failed consecutive connection attempts when negative.
|
||||
*
|
||||
* When choosing addresses from the boot cache for the purpose of
|
||||
* establishing outgoing connections, addresses are ranked in decreasing
|
||||
* order of high uptime, with valence as the tie breaker.
|
||||
*/
|
||||
class Bootcache
|
||||
{
|
||||
private:
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
Entry(int valence) : valence_(valence)
|
||||
{
|
||||
}
|
||||
|
||||
int&
|
||||
valence()
|
||||
{
|
||||
return valence_;
|
||||
}
|
||||
|
||||
[[nodiscard]] int
|
||||
valence() const
|
||||
{
|
||||
return valence_;
|
||||
}
|
||||
|
||||
friend bool
|
||||
operator<(Entry const& lhs, Entry const& rhs)
|
||||
{
|
||||
return lhs.valence() > rhs.valence();
|
||||
}
|
||||
|
||||
private:
|
||||
int valence_;
|
||||
};
|
||||
|
||||
using left_t = boost::bimaps::
|
||||
unordered_set_of<beast::ip::Endpoint, boost::hash<beast::ip::Endpoint>, std::equal_to<>>;
|
||||
using right_t = boost::bimaps::multiset_of<Entry, std::less<>>;
|
||||
using map_type = boost::bimap<left_t, right_t>;
|
||||
using value_type = map_type::value_type;
|
||||
|
||||
struct Transform
|
||||
{
|
||||
using first_argument_type = map_type::right_map::const_iterator::value_type const&;
|
||||
using result_type = beast::ip::Endpoint const&;
|
||||
|
||||
explicit Transform() = default;
|
||||
|
||||
beast::ip::Endpoint const&
|
||||
operator()(map_type::right_map::const_iterator::value_type const& v) const
|
||||
{
|
||||
return v.get_left();
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
map_type map_;
|
||||
|
||||
Store& store_;
|
||||
clock_type& clock_;
|
||||
beast::Journal journal_;
|
||||
|
||||
// Time after which we can update the database again
|
||||
clock_type::time_point whenUpdate_;
|
||||
|
||||
// Set to true when a database update is needed
|
||||
bool needsUpdate_{false};
|
||||
|
||||
public:
|
||||
static constexpr int kStaticValence = 32;
|
||||
|
||||
using iterator = boost::transform_iterator<Transform, map_type::right_map::const_iterator>;
|
||||
|
||||
using const_iterator = iterator;
|
||||
|
||||
Bootcache(Store& store, clock_type& clock, beast::Journal journal);
|
||||
|
||||
~Bootcache();
|
||||
|
||||
/**
|
||||
* Returns `true` if the cache is empty.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
empty() const;
|
||||
|
||||
/**
|
||||
* Returns the number of entries in the cache.
|
||||
*/
|
||||
[[nodiscard]] map_type::size_type
|
||||
size() const;
|
||||
|
||||
/**
|
||||
* ip::Endpoint iterators that traverse in decreasing valence.
|
||||
*/
|
||||
/** @{ */
|
||||
[[nodiscard]] const_iterator
|
||||
begin() const;
|
||||
[[nodiscard]] const_iterator
|
||||
cbegin() const;
|
||||
[[nodiscard]] const_iterator
|
||||
end() const;
|
||||
[[nodiscard]] const_iterator
|
||||
cend() const;
|
||||
void
|
||||
clear();
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Load the persisted data from the Store into the container.
|
||||
*/
|
||||
void
|
||||
load();
|
||||
|
||||
/**
|
||||
* Add a newly-learned address to the cache.
|
||||
*/
|
||||
bool
|
||||
insert(beast::ip::Endpoint const& endpoint);
|
||||
|
||||
/**
|
||||
* Add a staticallyconfigured address to the cache.
|
||||
*/
|
||||
bool
|
||||
insertStatic(beast::ip::Endpoint const& endpoint);
|
||||
|
||||
/**
|
||||
* Called when an outbound connection handshake completes.
|
||||
*/
|
||||
void
|
||||
onSuccess(beast::ip::Endpoint const& endpoint);
|
||||
|
||||
/**
|
||||
* Called when an outbound connection attempt fails to handshake.
|
||||
*/
|
||||
void
|
||||
onFailure(beast::ip::Endpoint const& endpoint);
|
||||
|
||||
/**
|
||||
* Stores the cache in the persistent database on a timer.
|
||||
*/
|
||||
void
|
||||
periodicActivity();
|
||||
|
||||
/**
|
||||
* Write the cache state to the property stream.
|
||||
*/
|
||||
void
|
||||
onWrite(beast::PropertyStream::Map& map);
|
||||
|
||||
private:
|
||||
void
|
||||
prune();
|
||||
void
|
||||
update();
|
||||
void
|
||||
checkUpdate();
|
||||
void
|
||||
flagForUpdate();
|
||||
};
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
205
include/xrpl/peerfinder/detail/Checker.h
Normal file
205
include/xrpl/peerfinder/detail/Checker.h
Normal file
@@ -0,0 +1,205 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/net/IPAddressConversion.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* Tests remote listening sockets to make sure they are connectable.
|
||||
*/
|
||||
template <class Protocol = boost::asio::ip::tcp>
|
||||
class Checker
|
||||
{
|
||||
private:
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
struct BasicAsyncOp : boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<boost::intrusive::normal_link>>
|
||||
{
|
||||
virtual ~BasicAsyncOp() = default;
|
||||
|
||||
virtual void
|
||||
stop() = 0;
|
||||
|
||||
virtual void
|
||||
operator()(error_code const& ec) = 0;
|
||||
};
|
||||
|
||||
template <class Handler>
|
||||
struct AsyncOp : BasicAsyncOp
|
||||
{
|
||||
using socket_type = Protocol::socket;
|
||||
using endpoint_type = Protocol::endpoint;
|
||||
|
||||
Checker& checker;
|
||||
socket_type socket;
|
||||
Handler handler;
|
||||
|
||||
AsyncOp(Checker& owner, boost::asio::io_context& ioContext, Handler&& handler);
|
||||
|
||||
~AsyncOp() override
|
||||
{
|
||||
checker.remove(*this);
|
||||
}
|
||||
|
||||
void
|
||||
stop() override;
|
||||
|
||||
void
|
||||
operator()(error_code const& ec) override; // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
using list_type =
|
||||
boost::intrusive::make_list<BasicAsyncOp, boost::intrusive::constant_time_size<true>>::type;
|
||||
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cond_;
|
||||
boost::asio::io_context& ioContext_;
|
||||
list_type list_;
|
||||
bool stop_ = false;
|
||||
|
||||
public:
|
||||
explicit Checker(boost::asio::io_context& ioContext);
|
||||
|
||||
/**
|
||||
* Destroy the service.
|
||||
* Any pending I/O operations will be canceled. This call blocks until
|
||||
* all pending operations complete (either with success or with
|
||||
* operation_aborted) and the associated thread and io_context have
|
||||
* no more work remaining.
|
||||
*/
|
||||
~Checker();
|
||||
|
||||
/**
|
||||
* Stop the service.
|
||||
* Pending I/O operations will be canceled.
|
||||
* This issues cancel orders for all pending I/O operations and then
|
||||
* returns immediately. Handlers will receive operation_aborted errors,
|
||||
* or if they were already queued they will complete normally.
|
||||
*/
|
||||
void
|
||||
stop();
|
||||
|
||||
/**
|
||||
* Block until all pending I/O completes.
|
||||
*/
|
||||
void
|
||||
wait();
|
||||
|
||||
/**
|
||||
* Performs an async connection test on the specified endpoint.
|
||||
* The port must be non-zero. Note that the execution guarantees
|
||||
* offered by asio handlers are NOT enforced.
|
||||
*/
|
||||
template <class Handler>
|
||||
void
|
||||
asyncConnect(beast::ip::Endpoint const& endpoint, Handler&& handler);
|
||||
|
||||
private:
|
||||
void
|
||||
remove(BasicAsyncOp& op);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Protocol>
|
||||
template <class Handler>
|
||||
Checker<Protocol>::AsyncOp<Handler>::AsyncOp(
|
||||
Checker& owner,
|
||||
boost::asio::io_context& ioContext,
|
||||
Handler&&
|
||||
handler) // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) -- forwarded in init
|
||||
: checker(owner), socket(ioContext), handler(std::forward<Handler>(handler))
|
||||
{
|
||||
}
|
||||
|
||||
template <class Protocol>
|
||||
template <class Handler>
|
||||
void
|
||||
Checker<Protocol>::AsyncOp<Handler>::stop()
|
||||
{
|
||||
error_code ec;
|
||||
socket.cancel(ec);
|
||||
}
|
||||
|
||||
template <class Protocol>
|
||||
template <class Handler>
|
||||
void
|
||||
Checker<Protocol>::AsyncOp<Handler>::operator()(error_code const& ec)
|
||||
{
|
||||
handler(ec);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Protocol>
|
||||
Checker<Protocol>::Checker(boost::asio::io_context& ioContext) : ioContext_(ioContext)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Protocol>
|
||||
Checker<Protocol>::~Checker()
|
||||
{
|
||||
wait();
|
||||
}
|
||||
|
||||
template <class Protocol>
|
||||
void
|
||||
Checker<Protocol>::stop()
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
if (!stop_)
|
||||
{
|
||||
stop_ = true;
|
||||
for (auto& c : list_)
|
||||
c.stop();
|
||||
}
|
||||
}
|
||||
|
||||
template <class Protocol>
|
||||
void
|
||||
Checker<Protocol>::wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
while (!list_.empty())
|
||||
cond_.wait(lock);
|
||||
}
|
||||
|
||||
template <class Protocol>
|
||||
template <class Handler>
|
||||
void
|
||||
Checker<Protocol>::asyncConnect(beast::ip::Endpoint const& endpoint, Handler&& handler)
|
||||
{
|
||||
auto const op =
|
||||
std::make_shared<AsyncOp<Handler>>(*this, ioContext_, std::forward<Handler>(handler));
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
list_.push_back(*op);
|
||||
}
|
||||
op->socket.async_connect(
|
||||
beast::IPAddressConversion::toAsioEndpoint(endpoint),
|
||||
[op](error_code const& ec) { (*op)(ec); });
|
||||
}
|
||||
|
||||
template <class Protocol>
|
||||
void
|
||||
Checker<Protocol>::remove(BasicAsyncOp& op)
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
list_.erase(list_.iterator_to(op));
|
||||
if (list_.size() == 0)
|
||||
cond_.notify_all();
|
||||
}
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
394
include/xrpl/peerfinder/detail/Counts.h
Normal file
394
include/xrpl/peerfinder/detail/Counts.h
Normal file
@@ -0,0 +1,394 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/PropertyStream.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/peerfinder/Config.h>
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/peerfinder/detail/Tuning.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* Direction of a slot count adjustment.
|
||||
*/
|
||||
enum class CountAdjustment : int { Decrement = -1, Increment = 1 };
|
||||
|
||||
/**
|
||||
* Manages the count of available connections for the various slots.
|
||||
*/
|
||||
class Counts
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Adds the slot state and properties to the slot counts.
|
||||
*/
|
||||
void
|
||||
add(Slot const& s)
|
||||
{
|
||||
adjust(s, CountAdjustment::Increment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the slot state and properties from the slot counts.
|
||||
*/
|
||||
void
|
||||
remove(Slot const& s)
|
||||
{
|
||||
adjust(s, CountAdjustment::Decrement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the slot can become active.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
canActivate(Slot const& s) const
|
||||
{
|
||||
// Must be handshaked and in the right state
|
||||
XRPL_ASSERT(
|
||||
s.state() == Slot::State::Connected || s.state() == Slot::State::Accept,
|
||||
"xrpl::peer_finder::Counts::can_activate : valid input state");
|
||||
|
||||
if (s.fixed() || s.reserved())
|
||||
return true;
|
||||
|
||||
if (s.inbound())
|
||||
return inActive_ < inMax_;
|
||||
|
||||
return outActive_ < outMax_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of attempts needed to bring us to the max.
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
attemptsNeeded() const
|
||||
{
|
||||
if (attempts_ >= tuning::kMaxConnectAttempts)
|
||||
return 0;
|
||||
return tuning::kMaxConnectAttempts - attempts_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of outbound connection attempts.
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
attempts() const
|
||||
{
|
||||
return attempts_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of outbound slots.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
outMax() const
|
||||
{
|
||||
return outMax_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of outbound peers assigned an open slot.
|
||||
* Fixed peers do not count towards outbound slots used.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
outActive() const
|
||||
{
|
||||
return outActive_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of fixed connections.
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
fixed() const
|
||||
{
|
||||
return fixed_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of active fixed connections.
|
||||
*/
|
||||
[[nodiscard]] std::size_t
|
||||
fixedActive() const
|
||||
{
|
||||
return fixedActive_;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Called when the config is set or changed.
|
||||
*/
|
||||
void
|
||||
onConfig(Config const& config)
|
||||
{
|
||||
outMax_ = config.outPeers;
|
||||
if (config.wantIncoming)
|
||||
inMax_ = config.inPeers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of accepted connections that haven't handshaked.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
acceptCount() const
|
||||
{
|
||||
return acceptCount_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of connection attempts currently active.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
connectCount() const
|
||||
{
|
||||
return attempts_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of connections that are gracefully closing.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
closingCount() const
|
||||
{
|
||||
return closingCount_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of inbound slots.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
inMax() const
|
||||
{
|
||||
return inMax_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of inbound peers assigned an open slot.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
inboundActive() const
|
||||
{
|
||||
return inActive_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of active peers excluding fixed peers.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
totalActive() const
|
||||
{
|
||||
return inActive_ + outActive_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of unused inbound slots.
|
||||
* Fixed peers do not deduct from inbound slots or count towards totals.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
inboundSlotsFree() const
|
||||
{
|
||||
if (inActive_ < inMax_)
|
||||
return inMax_ - inActive_;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of unused outbound slots.
|
||||
* Fixed peers do not deduct from outbound slots or count towards totals.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
outboundSlotsFree() const
|
||||
{
|
||||
if (outActive_ < outMax_)
|
||||
return outMax_ - outActive_;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns true if the slot logic considers us "connected" to the network.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isConnectedToNetwork() const
|
||||
{
|
||||
// We will consider ourselves connected if we have reached
|
||||
// the number of outgoing connections desired, or if connect
|
||||
// automatically is false.
|
||||
//
|
||||
// Fixed peers do not count towards the active outgoing total.
|
||||
|
||||
return outMax_ <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output statistics.
|
||||
*/
|
||||
void
|
||||
onWrite(beast::PropertyStream::Map& map) const
|
||||
{
|
||||
map["accept"] = acceptCount();
|
||||
map["connect"] = connectCount();
|
||||
map["close"] = closingCount();
|
||||
map["in"] << inActive_ << "/" << inMax_;
|
||||
map["out"] << outActive_ << "/" << outMax_;
|
||||
map["fixed"] = fixedActive_;
|
||||
map["reserved"] = reserved_;
|
||||
map["total"] = active_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the state for diagnostics.
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
stateString() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << outActive_ << "/" << outMax_ << " out, " << inActive_ << "/" << inMax_ << " in, "
|
||||
<< connectCount() << " connecting, " << closingCount() << " closing";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
private:
|
||||
/**
|
||||
* Increments or decrements a counter based on the adjustment direction.
|
||||
*/
|
||||
template <typename T>
|
||||
static void
|
||||
adjustCounter(T& counter, CountAdjustment dir)
|
||||
{
|
||||
switch (dir)
|
||||
{
|
||||
case CountAdjustment::Increment:
|
||||
++counter;
|
||||
break;
|
||||
case CountAdjustment::Decrement:
|
||||
--counter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjusts counts based on the specified slot, in the direction indicated.
|
||||
//
|
||||
// IMPORTANT: All std::size_t counters MUST be adjusted via adjustCounter()
|
||||
// and NEVER via `+= n` where n = static_cast<int>(dir). When dir is
|
||||
// Decrement, n == -1; adding -1 to a std::size_t implicitly converts -1 to
|
||||
// SIZE_MAX, which UBSan flags as unsigned-integer-overflow and masks real
|
||||
// underflow bugs (decrementing a counter already at zero). Plain int
|
||||
// counters (acceptCount_, attempts_, closingCount_) are safe with += n.
|
||||
void
|
||||
adjust(Slot const& s, CountAdjustment const dir)
|
||||
{
|
||||
int const n = static_cast<int>(dir);
|
||||
if (s.fixed())
|
||||
adjustCounter(fixed_, dir);
|
||||
|
||||
if (s.reserved())
|
||||
adjustCounter(reserved_, dir);
|
||||
|
||||
switch (s.state())
|
||||
{
|
||||
case Slot::State::Accept:
|
||||
XRPL_ASSERT(s.inbound(), "xrpl::peer_finder::Counts::adjust : input is inbound");
|
||||
acceptCount_ += n;
|
||||
break;
|
||||
|
||||
case Slot::State::Connect:
|
||||
case Slot::State::Connected:
|
||||
XRPL_ASSERT(
|
||||
!s.inbound(),
|
||||
"xrpl::peer_finder::Counts::adjust : input is not "
|
||||
"inbound");
|
||||
attempts_ += n;
|
||||
break;
|
||||
|
||||
case Slot::State::Active:
|
||||
if (s.fixed())
|
||||
adjustCounter(fixedActive_, dir);
|
||||
if (!s.fixed() && !s.reserved())
|
||||
{
|
||||
if (s.inbound())
|
||||
{
|
||||
adjustCounter(inActive_, dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
adjustCounter(outActive_, dir);
|
||||
}
|
||||
}
|
||||
adjustCounter(active_, dir);
|
||||
break;
|
||||
|
||||
case Slot::State::Closing:
|
||||
closingCount_ += n;
|
||||
break;
|
||||
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
UNREACHABLE("xrpl::peer_finder::Counts::adjust : invalid input state");
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Outbound connection attempts.
|
||||
*/
|
||||
int attempts_{0};
|
||||
|
||||
/**
|
||||
* Active connections, including fixed and reserved.
|
||||
*/
|
||||
std::size_t active_{0};
|
||||
|
||||
/**
|
||||
* Total number of inbound slots.
|
||||
*/
|
||||
std::size_t inMax_{0};
|
||||
|
||||
/**
|
||||
* Number of inbound slots assigned to active peers.
|
||||
*/
|
||||
std::size_t inActive_{0};
|
||||
|
||||
/**
|
||||
* Maximum desired outbound slots.
|
||||
*/
|
||||
std::size_t outMax_{0};
|
||||
|
||||
/**
|
||||
* Active outbound slots.
|
||||
*/
|
||||
std::size_t outActive_{0};
|
||||
|
||||
/**
|
||||
* Fixed connections.
|
||||
*/
|
||||
std::size_t fixed_{0};
|
||||
|
||||
/**
|
||||
* Active fixed connections.
|
||||
*/
|
||||
std::size_t fixedActive_{0};
|
||||
|
||||
/**
|
||||
* Reserved connections.
|
||||
*/
|
||||
std::size_t reserved_{0};
|
||||
|
||||
// Number of inbound connections that are
|
||||
// not active or gracefully closing.
|
||||
int acceptCount_{0};
|
||||
|
||||
// Number of connections that are gracefully closing.
|
||||
int closingCount_{0};
|
||||
};
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
58
include/xrpl/peerfinder/detail/Fixed.h
Normal file
58
include/xrpl/peerfinder/detail/Fixed.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/peerfinder/Types.h>
|
||||
#include <xrpl/peerfinder/detail/Tuning.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* Metadata for a Fixed slot.
|
||||
*/
|
||||
class Fixed
|
||||
{
|
||||
public:
|
||||
explicit Fixed(clock_type& clock) : when_(clock.now())
|
||||
{
|
||||
}
|
||||
|
||||
Fixed(Fixed const&) = default;
|
||||
|
||||
/**
|
||||
* Returns the time after which we should allow a connection attempt.
|
||||
*/
|
||||
[[nodiscard]] clock_type::time_point const&
|
||||
when() const
|
||||
{
|
||||
return when_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates metadata to reflect a failed connection.
|
||||
*/
|
||||
void
|
||||
failure(clock_type::time_point const& now)
|
||||
{
|
||||
failures_ = std::min(failures_ + 1, tuning::kConnectionBackoff.size() - 1);
|
||||
when_ = now + std::chrono::minutes(tuning::kConnectionBackoff[failures_]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates metadata to reflect a successful connection.
|
||||
*/
|
||||
void
|
||||
success(clock_type::time_point const& now)
|
||||
{
|
||||
failures_ = 0;
|
||||
when_ = now;
|
||||
}
|
||||
|
||||
private:
|
||||
clock_type::time_point when_;
|
||||
std::size_t failures_{0};
|
||||
};
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
344
include/xrpl/peerfinder/detail/Handouts.h
Normal file
344
include/xrpl/peerfinder/detail/Handouts.h
Normal file
@@ -0,0 +1,344 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/container/aged_set.h>
|
||||
#include <xrpl/beast/net/IPAddress.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/peerfinder/Types.h>
|
||||
#include <xrpl/peerfinder/detail/SlotImp.h>
|
||||
#include <xrpl/peerfinder/detail/Tuning.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Try to insert one object in the target.
|
||||
* When an item is handed out it is moved to the end of the container.
|
||||
* @return The number of objects inserted
|
||||
*/
|
||||
// VFALCO TODO specialization that handles std::list for SequenceContainer
|
||||
// using splice for optimization over erase/push_back
|
||||
//
|
||||
template <class Target, class HopContainer>
|
||||
std::size_t
|
||||
handoutOne(Target& t, HopContainer& h)
|
||||
{
|
||||
XRPL_ASSERT(!t.full(), "xrpl::peer_finder::detail::handoutOne : target is not full");
|
||||
for (auto it = h.begin(); it != h.end(); ++it)
|
||||
{
|
||||
auto const& e = *it;
|
||||
if (t.tryInsert(e))
|
||||
{
|
||||
h.moveBack(it);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Distributes objects to targets according to business rules.
|
||||
* A best effort is made to evenly distribute items in the sequence
|
||||
* container list into the target sequence list.
|
||||
*/
|
||||
template <class TargetFwdIter, class SeqFwdIter>
|
||||
void
|
||||
handout(TargetFwdIter first, TargetFwdIter last, SeqFwdIter seqFirst, SeqFwdIter seqLast)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
std::size_t n(0);
|
||||
for (auto si = seqFirst; si != seqLast; ++si)
|
||||
{
|
||||
auto c = *si;
|
||||
bool allFull(true);
|
||||
for (auto ti = first; ti != last; ++ti)
|
||||
{
|
||||
auto& t = *ti;
|
||||
if (!t.full())
|
||||
{
|
||||
n += detail::handoutOne(t, c);
|
||||
allFull = false;
|
||||
}
|
||||
}
|
||||
if (allFull)
|
||||
return;
|
||||
}
|
||||
if (!n)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Receives handouts for redirecting a connection.
|
||||
* An incoming connection request is redirected when we are full on slots.
|
||||
*/
|
||||
class RedirectHandouts
|
||||
{
|
||||
public:
|
||||
template <class = void>
|
||||
explicit RedirectHandouts(SlotImp::ptr slot);
|
||||
|
||||
template <class = void>
|
||||
bool
|
||||
tryInsert(Endpoint const& ep);
|
||||
|
||||
[[nodiscard]] bool
|
||||
full() const
|
||||
{
|
||||
return list_.size() >= tuning::kRedirectEndpointCount;
|
||||
}
|
||||
|
||||
[[nodiscard]] SlotImp::ptr const&
|
||||
slot() const
|
||||
{
|
||||
return slot_;
|
||||
}
|
||||
|
||||
std::vector<Endpoint>&
|
||||
list()
|
||||
{
|
||||
return list_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<Endpoint> const&
|
||||
list() const
|
||||
{
|
||||
return list_;
|
||||
}
|
||||
|
||||
private:
|
||||
SlotImp::ptr slot_;
|
||||
std::vector<Endpoint> list_;
|
||||
};
|
||||
|
||||
template <class>
|
||||
RedirectHandouts::RedirectHandouts(SlotImp::ptr slot) : slot_(std::move(slot))
|
||||
{
|
||||
list_.reserve(tuning::kRedirectEndpointCount);
|
||||
}
|
||||
|
||||
template <class>
|
||||
bool
|
||||
RedirectHandouts::tryInsert(Endpoint const& ep)
|
||||
{
|
||||
if (full())
|
||||
return false;
|
||||
|
||||
// VFALCO NOTE This check can be removed when we provide the
|
||||
// addresses in a peer HTTP handshake instead of
|
||||
// the tmENDPOINTS message.
|
||||
//
|
||||
if (ep.hops > tuning::kMaxHops)
|
||||
return false;
|
||||
|
||||
// Don't send them our address
|
||||
if (ep.hops == 0)
|
||||
return false;
|
||||
|
||||
// Don't send them their own address
|
||||
if (slot_->remoteEndpoint().address() == ep.address.address())
|
||||
return false;
|
||||
|
||||
// Make sure the address isn't already in our list
|
||||
if (std::ranges::any_of(list_, [&ep](Endpoint const& other) {
|
||||
// Ignore port for security reasons
|
||||
return other.address.address() == ep.address.address();
|
||||
}))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
list_.emplace_back(ep.address, ep.hops);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Receives endpoints for a slot during periodic handouts.
|
||||
*/
|
||||
class SlotHandouts
|
||||
{
|
||||
public:
|
||||
template <class = void>
|
||||
explicit SlotHandouts(SlotImp::ptr slot);
|
||||
|
||||
template <class = void>
|
||||
bool
|
||||
tryInsert(Endpoint const& ep);
|
||||
|
||||
[[nodiscard]] bool
|
||||
full() const
|
||||
{
|
||||
return list_.size() >= tuning::kNumberOfEndpoints;
|
||||
}
|
||||
|
||||
void
|
||||
insert(Endpoint const& ep)
|
||||
{
|
||||
list_.push_back(ep);
|
||||
}
|
||||
|
||||
[[nodiscard]] SlotImp::ptr const&
|
||||
slot() const
|
||||
{
|
||||
return slot_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<Endpoint> const&
|
||||
list() const
|
||||
{
|
||||
return list_;
|
||||
}
|
||||
|
||||
private:
|
||||
SlotImp::ptr slot_;
|
||||
std::vector<Endpoint> list_;
|
||||
};
|
||||
|
||||
template <class>
|
||||
SlotHandouts::SlotHandouts(SlotImp::ptr slot) : slot_(std::move(slot))
|
||||
{
|
||||
list_.reserve(tuning::kNumberOfEndpoints);
|
||||
}
|
||||
|
||||
template <class>
|
||||
bool
|
||||
SlotHandouts::tryInsert(Endpoint const& ep)
|
||||
{
|
||||
if (full())
|
||||
return false;
|
||||
|
||||
if (ep.hops > tuning::kMaxHops)
|
||||
return false;
|
||||
|
||||
if (slot_->recent.filter(ep.address, ep.hops))
|
||||
return false;
|
||||
|
||||
// Don't send them their own address
|
||||
if (slot_->remoteEndpoint().address() == ep.address.address())
|
||||
return false;
|
||||
|
||||
// Make sure the address isn't already in our list
|
||||
if (std::ranges::any_of(list_, [&ep](Endpoint const& other) {
|
||||
// Ignore port for security reasons
|
||||
return other.address.address() == ep.address.address();
|
||||
}))
|
||||
return false;
|
||||
|
||||
list_.emplace_back(ep.address, ep.hops);
|
||||
|
||||
// Insert into this slot's recent table. Although the endpoint
|
||||
// didn't come from the slot, adding it to the slot's table
|
||||
// prevents us from sending it again until it has expired from
|
||||
// the other end's cache.
|
||||
//
|
||||
slot_->recent.insert(ep.address, ep.hops);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Receives handouts for making automatic connections.
|
||||
*/
|
||||
class ConnectHandouts
|
||||
{
|
||||
public:
|
||||
// Keeps track of addresses we have made outgoing connections
|
||||
// to, for the purposes of not connecting to them too frequently.
|
||||
using Squelches = beast::aged_set<beast::ip::Address>;
|
||||
|
||||
using list_type = std::vector<beast::ip::Endpoint>;
|
||||
|
||||
private:
|
||||
std::size_t needed_;
|
||||
Squelches& squelches_;
|
||||
list_type list_;
|
||||
|
||||
public:
|
||||
template <class = void>
|
||||
ConnectHandouts(std::size_t needed, Squelches& squelches);
|
||||
|
||||
template <class = void>
|
||||
bool
|
||||
tryInsert(beast::ip::Endpoint const& endpoint);
|
||||
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return list_.empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
full() const
|
||||
{
|
||||
return list_.size() >= needed_;
|
||||
}
|
||||
|
||||
bool
|
||||
tryInsert(Endpoint const& endpoint)
|
||||
{
|
||||
return tryInsert(endpoint.address);
|
||||
}
|
||||
|
||||
list_type&
|
||||
list()
|
||||
{
|
||||
return list_;
|
||||
}
|
||||
|
||||
[[nodiscard]] list_type const&
|
||||
list() const
|
||||
{
|
||||
return list_;
|
||||
}
|
||||
};
|
||||
|
||||
template <class>
|
||||
ConnectHandouts::ConnectHandouts(std::size_t needed, Squelches& squelches)
|
||||
: needed_(needed), squelches_(squelches)
|
||||
{
|
||||
list_.reserve(needed);
|
||||
}
|
||||
|
||||
template <class>
|
||||
bool
|
||||
ConnectHandouts::tryInsert(beast::ip::Endpoint const& endpoint)
|
||||
{
|
||||
if (full())
|
||||
return false;
|
||||
|
||||
// Make sure the address isn't already in our list
|
||||
if (std::ranges::any_of(list_, [&endpoint](beast::ip::Endpoint const& other) {
|
||||
// Ignore port for security reasons
|
||||
return other.address() == endpoint.address();
|
||||
}))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to squelch list so we don't try it too often.
|
||||
// If its already there, then make try_insert fail.
|
||||
auto const result(squelches_.insert(endpoint.address()));
|
||||
if (!result.second)
|
||||
return false;
|
||||
|
||||
list_.push_back(endpoint);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
564
include/xrpl/peerfinder/detail/Livecache.h
Normal file
564
include/xrpl/peerfinder/detail/Livecache.h
Normal file
@@ -0,0 +1,564 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/container/aged_map.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/PropertyStream.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/beast/utility/maybe_const.h>
|
||||
#include <xrpl/peerfinder/Types.h>
|
||||
#include <xrpl/peerfinder/detail/Tuning.h>
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
template <class>
|
||||
class Livecache;
|
||||
|
||||
namespace detail {
|
||||
|
||||
class LivecacheBase
|
||||
{
|
||||
public:
|
||||
explicit LivecacheBase() = default;
|
||||
|
||||
protected:
|
||||
struct Element : boost::intrusive::list_base_hook<>
|
||||
{
|
||||
Element(Endpoint endpoint) : endpoint(std::move(endpoint))
|
||||
{
|
||||
}
|
||||
|
||||
Endpoint endpoint;
|
||||
};
|
||||
|
||||
using list_type =
|
||||
boost::intrusive::make_list<Element, boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
public:
|
||||
/**
|
||||
* A list of Endpoint at the same hops
|
||||
* This is a lightweight wrapper around a reference to the underlying
|
||||
* container.
|
||||
*/
|
||||
template <bool IsConst>
|
||||
class Hop
|
||||
{
|
||||
public:
|
||||
// Iterator transformation to extract the endpoint from Element
|
||||
struct Transform
|
||||
{
|
||||
using first_argument = Element;
|
||||
using result_type = Endpoint;
|
||||
|
||||
explicit Transform() = default;
|
||||
|
||||
Endpoint const&
|
||||
operator()(Element const& e) const
|
||||
{
|
||||
return e.endpoint;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
using iterator = boost::transform_iterator<Transform, list_type::const_iterator>;
|
||||
|
||||
using const_iterator = iterator;
|
||||
|
||||
using reverse_iterator =
|
||||
boost::transform_iterator<Transform, list_type::const_reverse_iterator>;
|
||||
|
||||
using const_reverse_iterator = reverse_iterator;
|
||||
|
||||
[[nodiscard]] iterator
|
||||
begin() const
|
||||
{
|
||||
return iterator(list_.get().cbegin(), Transform());
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator
|
||||
cbegin() const
|
||||
{
|
||||
return iterator(list_.get().cbegin(), Transform());
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator
|
||||
end() const
|
||||
{
|
||||
return iterator(list_.get().cend(), Transform());
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator
|
||||
cend() const
|
||||
{
|
||||
return iterator(list_.get().cend(), Transform());
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator
|
||||
rbegin() const
|
||||
{
|
||||
return reverse_iterator(list_.get().crbegin(), Transform());
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator
|
||||
crbegin() const
|
||||
{
|
||||
return reverse_iterator(list_.get().crbegin(), Transform());
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator
|
||||
rend() const
|
||||
{
|
||||
return reverse_iterator(list_.get().crend(), Transform());
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator
|
||||
crend() const
|
||||
{
|
||||
return reverse_iterator(list_.get().crend(), Transform());
|
||||
}
|
||||
|
||||
// move the element to the end of the container
|
||||
void
|
||||
moveBack(const_iterator pos)
|
||||
{
|
||||
auto& e(const_cast<Element&>(*pos.base()));
|
||||
list_.get().erase(list_.get().iterator_to(e));
|
||||
list_.get().push_back(e);
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Hop(beast::MaybeConst<IsConst, list_type>::type& list) : list_(list)
|
||||
{
|
||||
}
|
||||
|
||||
friend class LivecacheBase;
|
||||
|
||||
std::reference_wrapper<typename beast::MaybeConst<IsConst, list_type>::type> list_;
|
||||
};
|
||||
|
||||
protected:
|
||||
// Work-around to call Hop's private constructor from Livecache
|
||||
template <bool IsConst>
|
||||
static Hop<IsConst>
|
||||
makeHop(beast::MaybeConst<IsConst, list_type>::type& list)
|
||||
{
|
||||
return Hop<IsConst>(list);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The Livecache holds the short-lived relayed Endpoint messages.
|
||||
*
|
||||
* Since peers only advertise themselves when they have open slots,
|
||||
* we want these messages to expire rather quickly after the peer becomes
|
||||
* full.
|
||||
*
|
||||
* Addresses added to the cache are not connection-tested to see if
|
||||
* they are connectable (with one small exception regarding neighbors).
|
||||
* Therefore, these addresses are not suitable for persisting across
|
||||
* launches or for bootstrapping, because they do not have verifiable
|
||||
* and locally observed uptime and connectability information.
|
||||
*/
|
||||
template <class Allocator = std::allocator<char>>
|
||||
class Livecache : protected detail::LivecacheBase
|
||||
{
|
||||
private:
|
||||
using cache_type = beast::aged_map<
|
||||
beast::ip::Endpoint,
|
||||
Element,
|
||||
std::chrono::steady_clock,
|
||||
std::less<beast::ip::Endpoint>,
|
||||
Allocator>;
|
||||
|
||||
beast::Journal journal_;
|
||||
cache_type cache_;
|
||||
|
||||
public:
|
||||
using allocator_type = Allocator;
|
||||
|
||||
/**
|
||||
* Create the cache.
|
||||
*/
|
||||
Livecache(clock_type& clock, beast::Journal journal, Allocator alloc = Allocator());
|
||||
|
||||
//
|
||||
// Iteration by hops
|
||||
//
|
||||
// The range [begin, end) provides a sequence of list_type
|
||||
// where each list contains endpoints at a given hops.
|
||||
//
|
||||
|
||||
class HopsT
|
||||
{
|
||||
private:
|
||||
// An endpoint at hops=0 represents the local node.
|
||||
// Endpoints coming in at maxHops are stored at maxHops +1,
|
||||
// but not given out (since they would exceed maxHops). They
|
||||
// are used for automatic connection attempts.
|
||||
//
|
||||
using Histogram = std::array<int, 1 + tuning::kMaxHops + 1>;
|
||||
using lists_type = std::array<list_type, 1 + tuning::kMaxHops + 1>;
|
||||
|
||||
template <bool IsConst>
|
||||
struct Transform
|
||||
{
|
||||
using first_argument = lists_type::value_type;
|
||||
using result_type = Hop<IsConst>;
|
||||
|
||||
explicit Transform() = default;
|
||||
|
||||
Hop<IsConst>
|
||||
operator()(beast::MaybeConst<IsConst, lists_type::value_type>::type& list) const
|
||||
{
|
||||
return makeHop<IsConst>(list);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
using iterator = boost::transform_iterator<Transform<false>, lists_type::iterator>;
|
||||
|
||||
using const_iterator =
|
||||
boost::transform_iterator<Transform<true>, lists_type::const_iterator>;
|
||||
|
||||
using reverse_iterator =
|
||||
boost::transform_iterator<Transform<false>, lists_type::reverse_iterator>;
|
||||
|
||||
using const_reverse_iterator =
|
||||
boost::transform_iterator<Transform<true>, lists_type::const_reverse_iterator>;
|
||||
|
||||
iterator
|
||||
begin()
|
||||
{
|
||||
return iterator(lists_.begin(), Transform<false>());
|
||||
}
|
||||
|
||||
[[nodiscard]] const_iterator
|
||||
begin() const
|
||||
{
|
||||
return const_iterator(lists_.cbegin(), Transform<true>());
|
||||
}
|
||||
|
||||
[[nodiscard]] const_iterator
|
||||
cbegin() const
|
||||
{
|
||||
return const_iterator(lists_.cbegin(), Transform<true>());
|
||||
}
|
||||
|
||||
iterator
|
||||
end()
|
||||
{
|
||||
return iterator(lists_.end(), Transform<false>());
|
||||
}
|
||||
|
||||
[[nodiscard]] const_iterator
|
||||
end() const
|
||||
{
|
||||
return const_iterator(lists_.cend(), Transform<true>());
|
||||
}
|
||||
|
||||
[[nodiscard]] const_iterator
|
||||
cend() const
|
||||
{
|
||||
return const_iterator(lists_.cend(), Transform<true>());
|
||||
}
|
||||
|
||||
reverse_iterator
|
||||
rbegin()
|
||||
{
|
||||
return reverse_iterator(lists_.rbegin(), Transform<false>());
|
||||
}
|
||||
|
||||
[[nodiscard]] const_reverse_iterator
|
||||
rbegin() const
|
||||
{
|
||||
return const_reverse_iterator(lists_.crbegin(), Transform<true>());
|
||||
}
|
||||
|
||||
[[nodiscard]] const_reverse_iterator
|
||||
crbegin() const
|
||||
{
|
||||
return const_reverse_iterator(lists_.crbegin(), Transform<true>());
|
||||
}
|
||||
|
||||
reverse_iterator
|
||||
rend()
|
||||
{
|
||||
return reverse_iterator(lists_.rend(), Transform<false>());
|
||||
}
|
||||
|
||||
[[nodiscard]] const_reverse_iterator
|
||||
rend() const
|
||||
{
|
||||
return const_reverse_iterator(lists_.crend(), Transform<true>());
|
||||
}
|
||||
|
||||
[[nodiscard]] const_reverse_iterator
|
||||
crend() const
|
||||
{
|
||||
return const_reverse_iterator(lists_.crend(), Transform<true>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffle each hop list.
|
||||
*/
|
||||
void
|
||||
shuffle();
|
||||
|
||||
[[nodiscard]] std::string
|
||||
histogram() const;
|
||||
|
||||
private:
|
||||
explicit HopsT(Allocator const& alloc);
|
||||
|
||||
void
|
||||
insert(Element& e);
|
||||
|
||||
// Reinsert e at a new hops
|
||||
void
|
||||
reinsert(Element& e, std::uint32_t hops);
|
||||
|
||||
void
|
||||
remove(Element& e);
|
||||
|
||||
friend class Livecache;
|
||||
lists_type lists_;
|
||||
Histogram hist_{};
|
||||
} hops;
|
||||
|
||||
/**
|
||||
* Returns `true` if the cache is empty.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return cache_.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entries in the cache.
|
||||
*/
|
||||
cache_type::size_type
|
||||
size() const
|
||||
{
|
||||
return cache_.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase entries whose time has expired.
|
||||
*/
|
||||
void
|
||||
expire();
|
||||
|
||||
/**
|
||||
* Creates or updates an existing Element based on a new message.
|
||||
*/
|
||||
void
|
||||
insert(Endpoint const& ep);
|
||||
|
||||
/**
|
||||
* Output statistics.
|
||||
*/
|
||||
void
|
||||
onWrite(beast::PropertyStream::Map& map);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Allocator>
|
||||
Livecache<Allocator>::Livecache(clock_type& clock, beast::Journal journal, Allocator alloc)
|
||||
: journal_(journal), cache_(clock, alloc), hops(alloc)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Allocator>
|
||||
void
|
||||
Livecache<Allocator>::expire()
|
||||
{
|
||||
std::size_t n(0);
|
||||
typename cache_type::time_point const expired(
|
||||
cache_.clock().now() - tuning::kLiveCacheSecondsToLive);
|
||||
for (auto iter(cache_.chronological.begin());
|
||||
iter != cache_.chronological.end() && iter.when() <= expired;)
|
||||
{
|
||||
Element& e(iter->second);
|
||||
hops.remove(e);
|
||||
iter = cache_.erase(iter);
|
||||
++n;
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
JLOG(journal_.debug()) << std::left << std::setw(18) << "Livecache expired " << n
|
||||
<< ((n > 1) ? " entries" : " entry");
|
||||
}
|
||||
}
|
||||
|
||||
template <class Allocator>
|
||||
void
|
||||
Livecache<Allocator>::insert(Endpoint const& ep)
|
||||
{
|
||||
// The caller already incremented hop, so if we got a
|
||||
// message at maxHops we will store it at maxHops + 1.
|
||||
// This means we won't give out the address to other peers
|
||||
// but we will use it to make connections and hand it out
|
||||
// when redirecting.
|
||||
//
|
||||
XRPL_ASSERT(
|
||||
ep.hops <= (tuning::kMaxHops + 1),
|
||||
"xrpl::peer_finder::Livecache::insert : maximum input hops");
|
||||
auto result = cache_.emplace(ep.address, ep);
|
||||
Element& e(result.first->second);
|
||||
if (result.second)
|
||||
{
|
||||
hops.insert(e);
|
||||
JLOG(journal_.debug()) << std::left << std::setw(18) << "Livecache insert " << ep.address
|
||||
<< " at hops " << ep.hops;
|
||||
return;
|
||||
}
|
||||
if (!result.second && (ep.hops > e.endpoint.hops))
|
||||
{
|
||||
// Drop duplicates at higher hops
|
||||
std::size_t const excess(ep.hops - e.endpoint.hops);
|
||||
JLOG(journal_.trace()) << std::left << std::setw(18) << "Livecache drop " << ep.address
|
||||
<< " at hops +" << excess;
|
||||
return;
|
||||
}
|
||||
|
||||
cache_.touch(result.first);
|
||||
|
||||
// Address already in the cache so update metadata
|
||||
if (ep.hops < e.endpoint.hops)
|
||||
{
|
||||
hops.reinsert(e, ep.hops);
|
||||
JLOG(journal_.debug()) << std::left << std::setw(18) << "Livecache update " << ep.address
|
||||
<< " at hops " << ep.hops;
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(journal_.trace()) << std::left << std::setw(18) << "Livecache refresh " << ep.address
|
||||
<< " at hops " << ep.hops;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Allocator>
|
||||
void
|
||||
Livecache<Allocator>::onWrite(beast::PropertyStream::Map& map)
|
||||
{
|
||||
typename cache_type::time_point const expired(
|
||||
cache_.clock().now() - tuning::kLiveCacheSecondsToLive);
|
||||
map["size"] = size();
|
||||
map["hist"] = hops.histogram();
|
||||
beast::PropertyStream::Set set("entries", map);
|
||||
for (auto iter(cache_.cbegin()); iter != cache_.cend(); ++iter)
|
||||
{
|
||||
auto const& e(iter->second);
|
||||
beast::PropertyStream::Map item(set);
|
||||
item["hops"] = e.endpoint.hops;
|
||||
item["address"] = e.endpoint.address.toString();
|
||||
std::stringstream ss;
|
||||
ss << (iter.when() - expired).count();
|
||||
item["expires"] = ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Allocator>
|
||||
void
|
||||
Livecache<Allocator>::HopsT::shuffle()
|
||||
{
|
||||
for (auto& list : lists_)
|
||||
{
|
||||
std::vector<std::reference_wrapper<Element>> v;
|
||||
v.reserve(list.size());
|
||||
std::ranges::copy(list, std::back_inserter(v));
|
||||
std::shuffle(v.begin(), v.end(), defaultPrng());
|
||||
list.clear();
|
||||
for (auto& e : v)
|
||||
list.push_back(e);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Allocator>
|
||||
std::string
|
||||
Livecache<Allocator>::HopsT::histogram() const
|
||||
{
|
||||
std::string s;
|
||||
for (auto const& h : hist_)
|
||||
{
|
||||
if (!s.empty())
|
||||
s += ", ";
|
||||
s += std::to_string(h);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
template <class Allocator>
|
||||
Livecache<Allocator>::HopsT::HopsT(Allocator const& alloc)
|
||||
{
|
||||
std::ranges::fill(hist_, 0);
|
||||
}
|
||||
|
||||
template <class Allocator>
|
||||
void
|
||||
Livecache<Allocator>::HopsT::insert(Element& e)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
e.endpoint.hops <= tuning::kMaxHops + 1,
|
||||
"xrpl::peer_finder::Livecache::HopsT::insert : maximum input hops");
|
||||
// This has security implications without a shuffle
|
||||
lists_[e.endpoint.hops].push_front(e);
|
||||
++hist_[e.endpoint.hops];
|
||||
}
|
||||
|
||||
template <class Allocator>
|
||||
void
|
||||
Livecache<Allocator>::HopsT::reinsert(Element& e, std::uint32_t numHops)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
numHops <= tuning::kMaxHops + 1,
|
||||
"xrpl::peer_finder::Livecache::HopsT::reinsert : maximum hops input");
|
||||
|
||||
auto& list = lists_[e.endpoint.hops];
|
||||
list.erase(list.iterator_to(e));
|
||||
|
||||
--hist_[e.endpoint.hops];
|
||||
|
||||
e.endpoint.hops = numHops;
|
||||
insert(e);
|
||||
}
|
||||
|
||||
template <class Allocator>
|
||||
void
|
||||
Livecache<Allocator>::HopsT::remove(Element& e)
|
||||
{
|
||||
--hist_[e.endpoint.hops];
|
||||
|
||||
auto& list = lists_[e.endpoint.hops];
|
||||
list.erase(list.iterator_to(e));
|
||||
}
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
1232
include/xrpl/peerfinder/detail/Logic.h
Normal file
1232
include/xrpl/peerfinder/detail/Logic.h
Normal file
File diff suppressed because it is too large
Load Diff
199
include/xrpl/peerfinder/detail/SlotImp.h
Normal file
199
include/xrpl/peerfinder/detail/SlotImp.h
Normal file
@@ -0,0 +1,199 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/container/aged_unordered_map.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/peerfinder/Types.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
class SlotImp : public Slot
|
||||
{
|
||||
public:
|
||||
using ptr = std::shared_ptr<SlotImp>;
|
||||
|
||||
// inbound
|
||||
SlotImp(
|
||||
beast::ip::Endpoint const& localEndpoint,
|
||||
beast::ip::Endpoint remoteEndpoint,
|
||||
bool fixed,
|
||||
clock_type& clock);
|
||||
|
||||
// outbound
|
||||
SlotImp(beast::ip::Endpoint remoteEndpoint, bool fixed, clock_type& clock);
|
||||
|
||||
bool
|
||||
inbound() const override
|
||||
{
|
||||
return inbound_;
|
||||
}
|
||||
|
||||
bool
|
||||
fixed() const override
|
||||
{
|
||||
return fixed_;
|
||||
}
|
||||
|
||||
bool
|
||||
reserved() const override
|
||||
{
|
||||
return reserved_;
|
||||
}
|
||||
|
||||
State
|
||||
state() const override
|
||||
{
|
||||
return state_;
|
||||
}
|
||||
|
||||
beast::ip::Endpoint const&
|
||||
remoteEndpoint() const override
|
||||
{
|
||||
return remoteEndpoint_;
|
||||
}
|
||||
|
||||
std::optional<beast::ip::Endpoint> const&
|
||||
localEndpoint() const override
|
||||
{
|
||||
return localEndpoint_;
|
||||
}
|
||||
|
||||
std::optional<PublicKey> const&
|
||||
publicKey() const override
|
||||
{
|
||||
return publicKey_;
|
||||
}
|
||||
|
||||
std::string
|
||||
prefix() const
|
||||
{
|
||||
return "[" + getFingerprint(remoteEndpoint(), publicKey()) + "] ";
|
||||
}
|
||||
|
||||
std::optional<std::uint16_t>
|
||||
listeningPort() const override
|
||||
{
|
||||
std::uint32_t const value = listeningPort_;
|
||||
if (value == kUnknownPort)
|
||||
return std::nullopt;
|
||||
return value;
|
||||
}
|
||||
|
||||
void
|
||||
setListeningPort(std::uint16_t port)
|
||||
{
|
||||
listeningPort_ = port;
|
||||
}
|
||||
|
||||
void
|
||||
localEndpoint(beast::ip::Endpoint const& endpoint)
|
||||
{
|
||||
localEndpoint_ = endpoint;
|
||||
}
|
||||
|
||||
void
|
||||
remoteEndpoint(beast::ip::Endpoint const& endpoint)
|
||||
{
|
||||
remoteEndpoint_ = endpoint;
|
||||
}
|
||||
|
||||
void
|
||||
publicKey(PublicKey const& key)
|
||||
{
|
||||
publicKey_ = key;
|
||||
}
|
||||
|
||||
void
|
||||
reserved(bool reserved)
|
||||
{
|
||||
reserved_ = reserved;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
state(State state);
|
||||
|
||||
void
|
||||
activate(clock_type::time_point const& now);
|
||||
|
||||
// "Memberspace"
|
||||
//
|
||||
// The set of all recent addresses that we have seen from this peer.
|
||||
// We try to avoid sending a peer the same addresses they gave us.
|
||||
//
|
||||
class RecentT
|
||||
{
|
||||
public:
|
||||
explicit RecentT(clock_type& clock);
|
||||
|
||||
/**
|
||||
* Called for each valid endpoint received for a slot.
|
||||
* We also insert messages that we send to the slot to prevent
|
||||
* sending a slot the same address too frequently.
|
||||
*/
|
||||
void
|
||||
insert(beast::ip::Endpoint const& ep, std::uint32_t hops);
|
||||
|
||||
/**
|
||||
* Returns `true` if we should not send endpoint to the slot.
|
||||
*/
|
||||
bool
|
||||
filter(beast::ip::Endpoint const& ep, std::uint32_t hops);
|
||||
|
||||
private:
|
||||
void
|
||||
expire();
|
||||
|
||||
friend class SlotImp;
|
||||
beast::aged_unordered_map<beast::ip::Endpoint, std::uint32_t> cache_;
|
||||
} recent;
|
||||
|
||||
void
|
||||
expire()
|
||||
{
|
||||
recent.expire();
|
||||
}
|
||||
|
||||
private:
|
||||
bool const inbound_;
|
||||
bool const fixed_;
|
||||
bool reserved_;
|
||||
State state_;
|
||||
beast::ip::Endpoint remoteEndpoint_;
|
||||
std::optional<beast::ip::Endpoint> localEndpoint_;
|
||||
std::optional<PublicKey> publicKey_;
|
||||
|
||||
static std::int32_t constexpr kUnknownPort = -1;
|
||||
std::atomic<std::int32_t> listeningPort_;
|
||||
|
||||
public:
|
||||
// DEPRECATED public data members
|
||||
|
||||
// Tells us if we checked the connection. Outbound connections
|
||||
// are always considered checked since we successfully connected.
|
||||
bool checked;
|
||||
|
||||
// Set to indicate if the connection can receive incoming at the
|
||||
// address advertised in mtENDPOINTS. Only valid if checked is true.
|
||||
bool canAccept;
|
||||
|
||||
// Set to indicate that a connection check for this peer is in
|
||||
// progress. Valid always.
|
||||
bool connectivityCheckInProgress;
|
||||
|
||||
// The time after which we will accept mtENDPOINTS from the peer
|
||||
// This is to prevent flooding or spamming. Receipt of mtENDPOINTS
|
||||
// sooner than the allotted time should impose a load charge.
|
||||
//
|
||||
clock_type::time_point whenAcceptEndpoints;
|
||||
};
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
49
include/xrpl/peerfinder/detail/Source.h
Normal file
49
include/xrpl/peerfinder/detail/Source.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/peerfinder/Types.h>
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* A static or dynamic source of peer addresses.
|
||||
* These are used as fallbacks when we are bootstrapping and don't have
|
||||
* a local cache, or when none of our addresses are functioning. Typically
|
||||
* sources will represent things like static text in the config file, a
|
||||
* separate local file with addresses, or a remote HTTPS URL that can
|
||||
* be updated automatically. Another solution is to use a custom DNS server
|
||||
* that hands out peer IP addresses when name lookups are performed.
|
||||
*/
|
||||
class Source
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The results of a fetch.
|
||||
*/
|
||||
struct Results
|
||||
{
|
||||
explicit Results() = default;
|
||||
|
||||
// error_code on a failure
|
||||
boost::system::error_code error;
|
||||
|
||||
// list of fetched endpoints
|
||||
IPAddresses addresses;
|
||||
};
|
||||
|
||||
virtual ~Source() = default;
|
||||
virtual std::string const&
|
||||
name() = 0;
|
||||
virtual void
|
||||
cancel()
|
||||
{
|
||||
}
|
||||
virtual void
|
||||
fetch(Results& results, beast::Journal journal) = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
25
include/xrpl/peerfinder/detail/SourceStrings.h
Normal file
25
include/xrpl/peerfinder/detail/SourceStrings.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/peerfinder/detail/Source.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* Provides addresses from a static set of strings.
|
||||
*/
|
||||
class SourceStrings : public Source
|
||||
{
|
||||
public:
|
||||
explicit SourceStrings() = default;
|
||||
|
||||
using Strings = std::vector<std::string>;
|
||||
|
||||
static std::shared_ptr<Source>
|
||||
make(std::string const& name, Strings const& strings);
|
||||
};
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
36
include/xrpl/peerfinder/detail/Store.h
Normal file
36
include/xrpl/peerfinder/detail/Store.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* Abstract persistence for PeerFinder data.
|
||||
*/
|
||||
class Store
|
||||
{
|
||||
public:
|
||||
virtual ~Store() = default;
|
||||
|
||||
// load the bootstrap cache
|
||||
using load_callback = std::function<void(beast::ip::Endpoint, int)>;
|
||||
virtual std::size_t
|
||||
load(load_callback const& cb) = 0;
|
||||
|
||||
// save the bootstrap cache
|
||||
struct Entry
|
||||
{
|
||||
explicit Entry() = default;
|
||||
|
||||
beast::ip::Endpoint endpoint;
|
||||
int valence{};
|
||||
};
|
||||
virtual void
|
||||
save(std::vector<Entry> const& v) = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
115
include/xrpl/peerfinder/detail/Tuning.h
Normal file
115
include/xrpl/peerfinder/detail/Tuning.h
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* Heuristically tuned constants.
|
||||
*/
|
||||
/** @{ */
|
||||
namespace xrpl::peer_finder::tuning {
|
||||
|
||||
//---------------------------------------------------------
|
||||
//
|
||||
// Automatic Connection Policy
|
||||
//
|
||||
//---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Time to wait between making batches of connection attempts
|
||||
*/
|
||||
static constexpr auto kSecondsPerConnect = 10;
|
||||
|
||||
/**
|
||||
* Maximum number of simultaneous connection attempts.
|
||||
*/
|
||||
static constexpr auto kMaxConnectAttempts = 20;
|
||||
|
||||
/**
|
||||
* The percentage of total peer slots that are outbound.
|
||||
* The number of outbound peers will be the larger of the
|
||||
* minOutCount and outPercent * Config::maxPeers specially
|
||||
* rounded.
|
||||
*/
|
||||
static constexpr auto kOutPercent = 15;
|
||||
|
||||
/**
|
||||
* A hard minimum on the number of outgoing connections.
|
||||
* This is enforced outside the Logic, so that the unit test
|
||||
* can use any settings it wants.
|
||||
*/
|
||||
static constexpr auto kMinOutCount = 10;
|
||||
|
||||
/**
|
||||
* The default value of Config::maxPeers.
|
||||
*/
|
||||
static constexpr auto kDefaultMaxPeers = 21;
|
||||
|
||||
/**
|
||||
* Max redirects we will accept from one connection.
|
||||
* Redirects are limited for security purposes, to prevent
|
||||
* the address caches from getting flooded.
|
||||
*/
|
||||
static constexpr auto kMaxRedirects = 30;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Fixed
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static std::array<int, 10> const kConnectionBackoff{{1, 1, 2, 3, 5, 8, 13, 21, 34, 55}};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Bootcache
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Threshold of cache entries above which we trim.
|
||||
static constexpr auto kBootcacheSize = 1000;
|
||||
|
||||
// The percentage of addresses we prune when we trim the cache.
|
||||
static constexpr auto kBootcachePrunePercent = 10;
|
||||
|
||||
// The cool down wait between database updates
|
||||
// Ideally this should be larger than the time it takes a full
|
||||
// peer to send us a set of addresses and then disconnect.
|
||||
//
|
||||
static std::chrono::seconds const kBootcacheCooldownTime(60);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Livecache
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Drop incoming messages with hops greater than this number
|
||||
constexpr std::uint32_t kMaxHops = 6;
|
||||
|
||||
// How many Endpoint to send in each mtENDPOINTS
|
||||
constexpr std::uint32_t kNumberOfEndpoints = 2 * kMaxHops;
|
||||
|
||||
// The most Endpoint we will accept in mtENDPOINTS
|
||||
constexpr std::uint32_t kNumberOfEndpointsMax =
|
||||
std::max<decltype(kNumberOfEndpoints)>(kNumberOfEndpoints * 2, 64);
|
||||
|
||||
// Number of addresses we provide when redirecting.
|
||||
constexpr std::uint32_t kRedirectEndpointCount = 10;
|
||||
|
||||
// How often we send or accept mtENDPOINTS messages per peer
|
||||
// (we use a prime number of purpose)
|
||||
constexpr std::chrono::seconds kSecondsPerMessage(151);
|
||||
|
||||
// How long an Endpoint will stay in the cache
|
||||
// This should be a small multiple of the broadcast frequency
|
||||
constexpr std::chrono::seconds kLiveCacheSecondsToLive(30);
|
||||
|
||||
// How much time to wait before trying an outgoing address again.
|
||||
// Note that we ignore the port for purposes of comparison.
|
||||
constexpr std::chrono::seconds kRecentAttemptDuration(60);
|
||||
|
||||
} // namespace xrpl::peer_finder::tuning
|
||||
/** @} */
|
||||
36
include/xrpl/peerfinder/make_Manager.h
Normal file
36
include/xrpl/peerfinder/make_Manager.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/insight/Collector.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/peerfinder/PeerfinderManager.h>
|
||||
#include <xrpl/peerfinder/Types.h>
|
||||
#include <xrpl/peerfinder/detail/Store.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl::peer_finder {
|
||||
|
||||
/**
|
||||
* @brief Create a new Manager.
|
||||
*
|
||||
* @param ioContext The io_context used to schedule asynchronous work.
|
||||
* @param clock The clock used for timekeeping.
|
||||
* @param journal The journal used for logging.
|
||||
* @param store The persistence backend for the bootstrap cache. The caller
|
||||
* retains ownership and must keep it alive (and opened) for the lifetime of
|
||||
* the returned Manager. This lets consumers supply their own Store
|
||||
* implementation (e.g. the SQLite-backed StoreSqdb in xrpld).
|
||||
* @param collector The collector used to report metrics.
|
||||
* @return The newly created Manager.
|
||||
*/
|
||||
std::unique_ptr<Manager>
|
||||
makeManager(
|
||||
boost::asio::io_context& ioContext,
|
||||
clock_type& clock,
|
||||
beast::Journal journal,
|
||||
Store& store,
|
||||
beast::insight::Collector::ptr const& collector);
|
||||
|
||||
} // namespace xrpl::peer_finder
|
||||
@@ -246,7 +246,15 @@ message TMGetObjectByHash {
|
||||
|
||||
message TMLedgerNode {
|
||||
required bytes nodedata = 1;
|
||||
optional bytes nodeid = 2; // missing for ledger base data
|
||||
|
||||
// Used when protocol version <2.3. Not set for ledger base data.
|
||||
optional bytes nodeid = 2;
|
||||
|
||||
// Used when protocol version >=2.3. Neither value is set for ledger base data.
|
||||
oneof reference {
|
||||
bytes id = 3; // Set for inner nodes.
|
||||
uint32 depth = 4; // Set for leaf nodes.
|
||||
}
|
||||
}
|
||||
|
||||
enum TMLedgerInfoType {
|
||||
@@ -293,14 +301,15 @@ message TMLedgerData {
|
||||
}
|
||||
|
||||
message TMPing {
|
||||
// Previously used - don't reuse.
|
||||
reserved 3, 4;
|
||||
|
||||
enum pingType {
|
||||
ptPING = 0; // we want a reply
|
||||
ptPONG = 1; // this is a reply
|
||||
}
|
||||
required pingType type = 1;
|
||||
optional uint32 seq = 2; // detect stale replies, ensure other side is reading
|
||||
optional uint64 pingTime = 3; // know when we think we sent the ping
|
||||
optional uint64 netTime = 4;
|
||||
optional uint32 seq = 2; // detect stale replies, ensure other side is reading
|
||||
}
|
||||
|
||||
message TMSquelch {
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace xrpl {
|
||||
* Command line Requests use apiCommandLineVersion.
|
||||
*/
|
||||
|
||||
namespace RPC {
|
||||
namespace rpc {
|
||||
|
||||
template <unsigned int Version>
|
||||
static constexpr std::integral_constant<unsigned, Version> kApiVersion = {};
|
||||
@@ -60,7 +60,7 @@ static_assert(kApiMaximumValidVersion >= kApiMaximumSupportedVersion);
|
||||
inline void
|
||||
setVersion(json::Value& parent, unsigned int apiVersion, bool betaEnabled)
|
||||
{
|
||||
XRPL_ASSERT(apiVersion != kApiInvalidVersion, "xrpl::RPC::setVersion : input is valid");
|
||||
XRPL_ASSERT(apiVersion != kApiInvalidVersion, "xrpl::rpc::setVersion : input is valid");
|
||||
|
||||
auto& retObj = parent[jss::version] = json::ValueType::Object;
|
||||
|
||||
@@ -99,12 +99,12 @@ setVersion(json::Value& parent, unsigned int apiVersion, bool betaEnabled)
|
||||
inline unsigned int
|
||||
getAPIVersionNumber(json::Value const& jv, bool betaEnabled)
|
||||
{
|
||||
static json::Value const kMinVersion(RPC::kApiMinimumSupportedVersion);
|
||||
static json::Value const kMinVersion(rpc::kApiMinimumSupportedVersion);
|
||||
json::Value const maxVersion(
|
||||
betaEnabled ? RPC::kApiBetaVersion : RPC::kApiMaximumSupportedVersion);
|
||||
betaEnabled ? rpc::kApiBetaVersion : rpc::kApiMaximumSupportedVersion);
|
||||
|
||||
if (!jv.isObject() || !jv.isMember(jss::api_version))
|
||||
return RPC::kApiVersionIfUnspecified;
|
||||
return rpc::kApiVersionIfUnspecified;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -113,33 +113,33 @@ getAPIVersionNumber(json::Value const& jv, bool betaEnabled)
|
||||
{
|
||||
case json::ValueType::Int:
|
||||
if (rawVersion.asInt() < 0)
|
||||
return RPC::kApiInvalidVersion;
|
||||
return rpc::kApiInvalidVersion;
|
||||
[[fallthrough]];
|
||||
case json::ValueType::UInt: {
|
||||
auto const apiVersion = rawVersion.asUInt();
|
||||
if (apiVersion < kMinVersion || apiVersion > maxVersion)
|
||||
return RPC::kApiInvalidVersion;
|
||||
return rpc::kApiInvalidVersion;
|
||||
return apiVersion;
|
||||
}
|
||||
default:
|
||||
return RPC::kApiInvalidVersion;
|
||||
return rpc::kApiInvalidVersion;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return RPC::kApiInvalidVersion;
|
||||
return rpc::kApiInvalidVersion;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace rpc
|
||||
|
||||
template <unsigned MinVer, unsigned MaxVer, typename Fn, typename... Args>
|
||||
void
|
||||
forApiVersions(Fn const& fn, Args&&... args)
|
||||
requires //
|
||||
(MaxVer >= MinVer) && //
|
||||
(MinVer >= RPC::kApiMinimumSupportedVersion) && //
|
||||
(RPC::kApiMaximumValidVersion >= MaxVer) && requires {
|
||||
(MinVer >= rpc::kApiMinimumSupportedVersion) && //
|
||||
(rpc::kApiMaximumValidVersion >= MaxVer) && requires {
|
||||
fn(std::integral_constant<unsigned int, MinVer>{}, std::forward<Args>(args)...);
|
||||
fn(std::integral_constant<unsigned int, MaxVer>{}, std::forward<Args>(args)...);
|
||||
}
|
||||
@@ -158,11 +158,11 @@ template <typename Fn, typename... Args>
|
||||
void
|
||||
forAllApiVersions(Fn const& fn, Args&&... args)
|
||||
requires requires {
|
||||
forApiVersions<RPC::kApiMinimumSupportedVersion, RPC::kApiMaximumValidVersion>(
|
||||
forApiVersions<rpc::kApiMinimumSupportedVersion, rpc::kApiMaximumValidVersion>(
|
||||
fn, std::forward<Args>(args)...);
|
||||
}
|
||||
{
|
||||
forApiVersions<RPC::kApiMinimumSupportedVersion, RPC::kApiMaximumValidVersion>(
|
||||
forApiVersions<rpc::kApiMinimumSupportedVersion, rpc::kApiMaximumValidVersion>(
|
||||
fn, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* Versioning information for this build.
|
||||
*/
|
||||
// VFALCO The namespace is deprecated
|
||||
namespace xrpl::BuildInfo {
|
||||
namespace xrpl::build_info {
|
||||
|
||||
/**
|
||||
* Server version.
|
||||
@@ -84,4 +84,4 @@ isXrpldVersion(std::uint64_t version);
|
||||
bool
|
||||
isNewerVersion(std::uint64_t version);
|
||||
|
||||
} // namespace xrpl::BuildInfo
|
||||
} // namespace xrpl::build_info
|
||||
|
||||
@@ -167,7 +167,7 @@ enum WarningCodeI {
|
||||
|
||||
// VFALCO NOTE these should probably not be in the RPC namespace.
|
||||
|
||||
namespace RPC {
|
||||
namespace rpc {
|
||||
|
||||
/**
|
||||
* Maps an rpc error code to its token, default message, and HTTP status.
|
||||
@@ -337,7 +337,7 @@ containsError(json::Value const& json);
|
||||
int
|
||||
errorCodeHttpStatus(ErrorCodeI code);
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace rpc
|
||||
|
||||
/**
|
||||
* Returns a single string with the contents of an RPC error.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <array>
|
||||
@@ -21,8 +22,6 @@
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class SeqProxy;
|
||||
/**
|
||||
* Keylet computation functions.
|
||||
*
|
||||
@@ -123,7 +122,7 @@ trustLine(AccountID const& id, Issue const& issue) noexcept
|
||||
*/
|
||||
/** @{ */
|
||||
Keylet
|
||||
offer(AccountID const& id, std::uint32_t seq) noexcept;
|
||||
offer(AccountID const& id, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
offer(uint256 const& key) noexcept
|
||||
@@ -136,7 +135,7 @@ offer(uint256 const& key) noexcept
|
||||
* The initial directory page for a specific quality
|
||||
*/
|
||||
Keylet
|
||||
quality(Keylet const& k, std::uint64_t q) noexcept;
|
||||
quality(Keylet const& k, std::uint64_t const q) noexcept;
|
||||
|
||||
/**
|
||||
* The directory for the next lower quality
|
||||
@@ -149,10 +148,7 @@ next(Keylet const& k);
|
||||
*/
|
||||
/** @{ */
|
||||
Keylet
|
||||
ticket(AccountID const& id, std::uint32_t ticketSeq);
|
||||
|
||||
Keylet
|
||||
ticket(AccountID const& id, SeqProxy ticketSeq);
|
||||
ticket(AccountID const& id, SeqProxy const& ticketSeq);
|
||||
|
||||
inline Keylet
|
||||
ticket(uint256 const& key)
|
||||
@@ -178,7 +174,7 @@ sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept;
|
||||
*/
|
||||
/** @{ */
|
||||
Keylet
|
||||
check(AccountID const& id, std::uint32_t seq) noexcept;
|
||||
check(AccountID const& id, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
check(uint256 const& key) noexcept
|
||||
@@ -225,10 +221,10 @@ ownerDir(AccountID const& id) noexcept;
|
||||
*/
|
||||
/** @{ */
|
||||
Keylet
|
||||
page(uint256 const& root, std::uint64_t index = 0) noexcept;
|
||||
page(uint256 const& root, std::uint64_t const index = 0) noexcept;
|
||||
|
||||
inline Keylet
|
||||
page(Keylet const& root, std::uint64_t index = 0) noexcept
|
||||
page(Keylet const& root, std::uint64_t const index = 0) noexcept
|
||||
{
|
||||
XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::keylet::page : valid root type");
|
||||
return page(root.key, index);
|
||||
@@ -239,13 +235,13 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
|
||||
* An escrow entry
|
||||
*/
|
||||
Keylet
|
||||
escrow(AccountID const& src, std::uint32_t seq) noexcept;
|
||||
escrow(AccountID const& src, SeqProxy const& seq) noexcept;
|
||||
|
||||
/**
|
||||
* A PaymentChannel
|
||||
*/
|
||||
Keylet
|
||||
payChannel(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
|
||||
payChannel(AccountID const& src, AccountID const& dst, SeqProxy const& seq) noexcept;
|
||||
|
||||
/**
|
||||
* NFT page keylets
|
||||
@@ -276,7 +272,7 @@ nftokenPage(Keylet const& k, uint256 const& token);
|
||||
* An offer from an account to buy or sell an NFT
|
||||
*/
|
||||
Keylet
|
||||
nftokenOffer(AccountID const& owner, std::uint32_t seq);
|
||||
nftokenOffer(AccountID const& owner, SeqProxy const& seq);
|
||||
|
||||
inline Keylet
|
||||
nftokenOffer(uint256 const& offer)
|
||||
@@ -316,17 +312,17 @@ bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType);
|
||||
|
||||
// `seq` is stored as `sfXChainClaimID` in the object
|
||||
Keylet
|
||||
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq);
|
||||
xChainClaimID(STXChainBridge const& bridge, std::uint64_t const seq);
|
||||
|
||||
// `seq` is stored as `sfXChainAccountCreateCount` in the object
|
||||
Keylet
|
||||
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq);
|
||||
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t const seq);
|
||||
|
||||
Keylet
|
||||
did(AccountID const& account) noexcept;
|
||||
|
||||
Keylet
|
||||
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept;
|
||||
oracle(AccountID const& account, std::uint32_t const documentID) noexcept;
|
||||
|
||||
Keylet
|
||||
credential(AccountID const& subject, AccountID const& issuer, Slice const& credType) noexcept;
|
||||
@@ -337,9 +333,6 @@ credential(uint256 const& key) noexcept
|
||||
return {ltCREDENTIAL, key};
|
||||
}
|
||||
|
||||
Keylet
|
||||
mptokenIssuance(std::uint32_t seq, AccountID const& issuer) noexcept;
|
||||
|
||||
Keylet
|
||||
mptokenIssuance(MPTID const& issuanceID) noexcept;
|
||||
|
||||
@@ -362,7 +355,7 @@ Keylet
|
||||
mptoken(uint256 const& issuanceKey, AccountID const& holder) noexcept;
|
||||
|
||||
Keylet
|
||||
vault(AccountID const& owner, std::uint32_t seq) noexcept;
|
||||
vault(AccountID const& owner, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
vault(uint256 const& vaultKey)
|
||||
@@ -371,7 +364,7 @@ vault(uint256 const& vaultKey)
|
||||
}
|
||||
|
||||
Keylet
|
||||
loanBroker(AccountID const& owner, std::uint32_t seq) noexcept;
|
||||
loanBroker(AccountID const& owner, SeqProxy const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
loanBroker(uint256 const& key)
|
||||
@@ -380,7 +373,7 @@ loanBroker(uint256 const& key)
|
||||
}
|
||||
|
||||
Keylet
|
||||
loan(uint256 const& loanBrokerID, std::uint32_t loanSeq) noexcept;
|
||||
loan(uint256 const& loanBrokerID, SeqProxy const& loanSeq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
loan(uint256 const& key)
|
||||
@@ -389,7 +382,7 @@ loan(uint256 const& key)
|
||||
}
|
||||
|
||||
Keylet
|
||||
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
|
||||
permissionedDomain(AccountID const& account, SeqProxy const& seq) noexcept;
|
||||
|
||||
Keylet
|
||||
permissionedDomain(uint256 const& domainID) noexcept;
|
||||
@@ -407,12 +400,6 @@ getQualityNext(uint256 const& uBase);
|
||||
std::uint64_t
|
||||
getQuality(uint256 const& uBase);
|
||||
|
||||
uint256
|
||||
getTicketIndex(AccountID const& account, std::uint32_t uSequence);
|
||||
|
||||
uint256
|
||||
getTicketIndex(AccountID const& account, SeqProxy ticketSeq);
|
||||
|
||||
template <class... KeyletParams>
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
|
||||
struct KeyletDesc
|
||||
@@ -426,6 +413,6 @@ struct KeyletDesc
|
||||
extern std::array<KeyletDesc<AccountID const&>, 6> const kDirectAccountKeylets;
|
||||
|
||||
MPTID
|
||||
makeMptID(std::uint32_t sequence, AccountID const& account);
|
||||
makeMptID(std::uint32_t const sequence, AccountID const& account);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -190,17 +190,6 @@ enum LedgerEntryType : std::uint16_t {
|
||||
LSF_FLAG(lsfMPTCanClawback, 0x00000040) \
|
||||
LSF_FLAG(lsfMPTCanHoldConfidentialBalance, 0x00000080)) \
|
||||
\
|
||||
LEDGER_OBJECT(MPTokenIssuanceMutable, \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanLock, 0x00000002) \
|
||||
LSF_FLAG(lsmfMPTCanEnableRequireAuth, 0x00000004) \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanEscrow, 0x00000008) \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanTrade, 0x00000010) \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanTransfer, 0x00000020) \
|
||||
LSF_FLAG(lsmfMPTCanEnableCanClawback, 0x00000040) \
|
||||
LSF_FLAG(lsmfMPTCannotEnableCanHoldConfidentialBalance, 0x00000080) \
|
||||
LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \
|
||||
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \
|
||||
\
|
||||
LEDGER_OBJECT(MPToken, \
|
||||
LSF_FLAG2(lsfMPTLocked, 0x00000001) \
|
||||
LSF_FLAG(lsfMPTAuthorized, 0x00000002) \
|
||||
@@ -294,6 +283,17 @@ getAllLedgerFlags()
|
||||
#pragma pop_macro("TO_MAP")
|
||||
#pragma pop_macro("ALL_LEDGER_FLAGS")
|
||||
|
||||
// MPTokenIssuance ImmutableFlags (sfImmutableFlags)
|
||||
inline constexpr std::uint32_t lsifMPTCanLock = 0x00000002;
|
||||
inline constexpr std::uint32_t lsifMPTRequireAuth = 0x00000004;
|
||||
inline constexpr std::uint32_t lsifMPTCanEscrow = 0x00000008;
|
||||
inline constexpr std::uint32_t lsifMPTCanTrade = 0x00000010;
|
||||
inline constexpr std::uint32_t lsifMPTCanTransfer = 0x00000020;
|
||||
inline constexpr std::uint32_t lsifMPTCanClawback = 0x00000040;
|
||||
inline constexpr std::uint32_t lsifMPTCanHoldConfidentialBalance = 0x00000080;
|
||||
inline constexpr std::uint32_t lsifMPTMetadata = 0x00010000;
|
||||
inline constexpr std::uint32_t lsifMPTTransferFee = 0x00020000;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -188,6 +188,6 @@ struct MultiApiJson
|
||||
|
||||
// Wrapper for Json for all supported API versions.
|
||||
using MultiApiJson =
|
||||
detail::MultiApiJson<RPC::kApiMinimumSupportedVersion, RPC::kApiMaximumValidVersion>;
|
||||
detail::MultiApiJson<rpc::kApiMinimumSupportedVersion, rpc::kApiMaximumValidVersion>;
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl::RPC {
|
||||
namespace xrpl::rpc {
|
||||
|
||||
/**
|
||||
* Adds common synthetic fields to transaction-related JSON responses
|
||||
@@ -16,4 +16,4 @@ void
|
||||
insertNFTSyntheticInJson(json::Value&, std::shared_ptr<STTx const> const&, TxMeta const&);
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl::RPC
|
||||
} // namespace xrpl::rpc
|
||||
|
||||
@@ -139,7 +139,7 @@ tenthBipsOfValue(T value, TenthBips<TBips> bips)
|
||||
return value * bips.value() / kTenthBipsPerUnity.value();
|
||||
}
|
||||
|
||||
namespace Lending {
|
||||
namespace lending {
|
||||
/**
|
||||
* The maximum management fee rate allowed by a loan broker in 1/10 bips.
|
||||
*
|
||||
@@ -236,7 +236,7 @@ static constexpr int kLoanPaymentsPerFeeIncrement = 5;
|
||||
* without an amendment
|
||||
*/
|
||||
static constexpr int kLoanMaximumPaymentsPerTransaction = 100;
|
||||
} // namespace Lending
|
||||
} // namespace lending
|
||||
|
||||
/**
|
||||
* The maximum length of a URI inside an NFT
|
||||
@@ -316,6 +316,17 @@ constexpr std::uint8_t kVaultDefaultIouScale = 6;
|
||||
*/
|
||||
constexpr std::uint8_t kVaultMaximumIouScale = 18;
|
||||
|
||||
/**
|
||||
* Vault ledger-entry schema versions. Assigned to newly created
|
||||
* Vaults once featureLendingProtocolV1_1 is enabled. Vaults created before
|
||||
* activation are left without LEVersion (implicit legacy version 0,
|
||||
* accrual-basis accounting).
|
||||
*/
|
||||
enum class VaultVersion : uint8_t {
|
||||
Legacy = 0,
|
||||
CashBasis,
|
||||
};
|
||||
|
||||
/**
|
||||
* Maximum recursion depth for vault shares being put as an asset inside
|
||||
* another vault; counted from 0
|
||||
|
||||
@@ -260,7 +260,7 @@ calcAccountID(PublicKey const& pk);
|
||||
|
||||
inline std::string
|
||||
getFingerprint(
|
||||
beast::IP::Endpoint const& address,
|
||||
beast::ip::Endpoint const& address,
|
||||
std::optional<PublicKey> const& publicKey = std::nullopt,
|
||||
std::optional<std::string> const& id = std::nullopt)
|
||||
{
|
||||
|
||||
@@ -90,7 +90,11 @@ public:
|
||||
operator=(STObject&& other);
|
||||
|
||||
STObject(SOTemplate const& type, SField const& name);
|
||||
STObject(SOTemplate const& type, SerialIter& sit, SField const& name);
|
||||
STObject(
|
||||
SOTemplate const& type,
|
||||
SerialIter& sit,
|
||||
SField const& name,
|
||||
bool requireCanonicalOrder = false);
|
||||
STObject(SerialIter& sit, SField const& name, int depth = 0);
|
||||
STObject(SerialIter&& sit, SField const& name);
|
||||
explicit STObject(SField const& name);
|
||||
@@ -123,7 +127,7 @@ public:
|
||||
set(SOTemplate const&);
|
||||
|
||||
bool
|
||||
set(SerialIter& u, int depth = 0);
|
||||
set(SerialIter& u, int depth = 0, bool requireCanonicalOrder = false);
|
||||
|
||||
[[nodiscard]] SerializedTypeID
|
||||
getSType() const override;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -108,6 +109,9 @@ public:
|
||||
[[nodiscard]] bool
|
||||
isType(Type const& pe) const;
|
||||
|
||||
[[nodiscard]] size_t
|
||||
getHash() const;
|
||||
|
||||
bool
|
||||
operator==(STPathElement const& t) const;
|
||||
|
||||
@@ -171,12 +175,23 @@ public:
|
||||
reserve(size_t s);
|
||||
};
|
||||
|
||||
template <class Hasher>
|
||||
void
|
||||
hash_append(Hasher& h, STPath const& p) noexcept
|
||||
{
|
||||
for (auto const& e : p)
|
||||
{
|
||||
beast::hash_append(h, e.getHash());
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// A set of zero or more payment paths
|
||||
class STPathSet final : public STBase, public CountedObject<STPathSet>
|
||||
{
|
||||
std::vector<STPath> value_;
|
||||
xrpl::hardened_hash_set<STPath> seenHashes_;
|
||||
|
||||
public:
|
||||
STPathSet() = default;
|
||||
@@ -205,9 +220,6 @@ public:
|
||||
std::vector<STPath>::const_reference
|
||||
operator[](std::vector<STPath>::size_type n) const;
|
||||
|
||||
std::vector<STPath>::reference
|
||||
operator[](std::vector<STPath>::size_type n);
|
||||
|
||||
[[nodiscard]] std::vector<STPath>::const_iterator
|
||||
begin() const;
|
||||
|
||||
@@ -227,6 +239,9 @@ public:
|
||||
void
|
||||
emplaceBack(Args&&... args);
|
||||
|
||||
[[nodiscard]] bool
|
||||
contains(STPath const& path) const;
|
||||
|
||||
private:
|
||||
STBase*
|
||||
copy(std::size_t n, void* buf) const override;
|
||||
@@ -515,12 +530,6 @@ STPathSet::operator[](std::vector<STPath>::size_type n) const
|
||||
return value_[n];
|
||||
}
|
||||
|
||||
inline std::vector<STPath>::reference
|
||||
STPathSet::operator[](std::vector<STPath>::size_type n)
|
||||
{
|
||||
return value_[n];
|
||||
}
|
||||
|
||||
inline std::vector<STPath>::const_iterator
|
||||
STPathSet::begin() const
|
||||
{
|
||||
@@ -549,6 +558,7 @@ inline void
|
||||
STPathSet::pushBack(STPath const& e)
|
||||
{
|
||||
value_.push_back(e);
|
||||
seenHashes_.emplace(value_.back());
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
@@ -556,6 +566,13 @@ inline void
|
||||
STPathSet::emplaceBack(Args&&... args)
|
||||
{
|
||||
value_.emplace_back(std::forward<Args>(args)...);
|
||||
seenHashes_.emplace(value_.back());
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathSet::contains(STPath const& path) const
|
||||
{
|
||||
return seenHashes_.contains(path);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -93,12 +93,6 @@ public:
|
||||
[[nodiscard]] SeqProxy
|
||||
getSeqProxy() const;
|
||||
|
||||
/**
|
||||
* Returns the first non-zero value of (Sequence, TicketSequence).
|
||||
*/
|
||||
[[nodiscard]] std::uint32_t
|
||||
getSeqValue() const;
|
||||
|
||||
[[nodiscard]] boost::container::flat_set<AccountID>
|
||||
getMentionedAccounts() const;
|
||||
|
||||
|
||||
@@ -54,6 +54,22 @@ class STValidation final : public STObject, public CountedObject<STValidation>
|
||||
NetClock::time_point seenTime_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @struct DeserializeOptions
|
||||
* @brief Options controlling deserialization of a STValidation.
|
||||
|
||||
* @var DeserializeOptions::checkSignature
|
||||
* Whether to verify the data was signed properly
|
||||
*
|
||||
* @var DeserializeOptions::requireCanonicalOrder
|
||||
* Whether to require the fields to be in canonical order
|
||||
*/
|
||||
struct DeserializeOptions
|
||||
{
|
||||
bool checkSignature;
|
||||
bool requireCanonicalOrder;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a STValidation from a peer from serialized data.
|
||||
*
|
||||
@@ -64,12 +80,12 @@ public:
|
||||
* that signed the validation. For manifest based
|
||||
* validators, this should be the NodeID of the master
|
||||
* public key.
|
||||
* @param checkSignature Whether to verify the data was signed properly
|
||||
* @param options Options controlling deserialization
|
||||
*
|
||||
* @note Throws if the object is not valid
|
||||
*/
|
||||
template <class LookupNodeID>
|
||||
STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool checkSignature);
|
||||
STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, DeserializeOptions options);
|
||||
|
||||
/**
|
||||
* Construct, sign and trust a new STValidation issued by this node.
|
||||
@@ -163,8 +179,8 @@ private:
|
||||
};
|
||||
|
||||
template <class LookupNodeID>
|
||||
STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool checkSignature)
|
||||
: STObject(validationFormat(), sit, sfValidation)
|
||||
STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, DeserializeOptions options)
|
||||
: STObject(validationFormat(), sit, sfValidation, options.requireCanonicalOrder)
|
||||
, signingPubKey_([this]() {
|
||||
auto const spk = getFieldVL(sfSigningPubKey);
|
||||
|
||||
@@ -175,7 +191,7 @@ STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool ch
|
||||
}())
|
||||
, nodeID_(lookupNodeID(signingPubKey_))
|
||||
{
|
||||
if (checkSignature && !isValid())
|
||||
if (options.checkSignature && !isValid())
|
||||
{
|
||||
JLOG(debugLog().error()) << "Invalid signature in validation: "
|
||||
<< getJson(JsonOptions::Values::None);
|
||||
|
||||
@@ -53,14 +53,29 @@ public:
|
||||
operator=(SeqProxy const& other) = default;
|
||||
|
||||
/**
|
||||
* Factory function to return a sequence-based SeqProxy
|
||||
* Factory function to return a sequence-based SeqProxy.
|
||||
* Outside of tests, this function should only be used for "secondary" transaction sequences,
|
||||
* e.g. `sfOfferSequence`, or sequence fields in an existing ledger object. DO NOT use this for
|
||||
* the "primary" sequence of a transaction, `sfSequence`.
|
||||
*/
|
||||
static constexpr SeqProxy
|
||||
sequence(std::uint32_t v)
|
||||
rawSequence(std::uint32_t v)
|
||||
{
|
||||
return SeqProxy{Type::Seq, v};
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function to return a ticket-based SeqProxy.
|
||||
* Outside of tests, this function should only be used for "secondary" transaction sequences,
|
||||
* e.g. `sfOfferSequence`, or sequence fields in an existing ledger object. DO NOT use this for
|
||||
* the "primary" ticket sequence of a transaction, `sfTicketSequence`.
|
||||
*/
|
||||
static constexpr SeqProxy
|
||||
rawTicket(std::uint32_t v)
|
||||
{
|
||||
return SeqProxy{Type::Ticket, v};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::uint32_t
|
||||
value() const
|
||||
{
|
||||
|
||||
@@ -152,7 +152,14 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
\
|
||||
TRANSACTION(MPTokenIssuanceSet, \
|
||||
TF_FLAG(tfMPTLock, 0x00000001) \
|
||||
TF_FLAG(tfMPTUnlock, 0x00000002), \
|
||||
TF_FLAG(tfMPTUnlock, 0x00000002) \
|
||||
TF_FLAG(tfMPTSetCanLock, 0x00000004) \
|
||||
TF_FLAG(tfMPTSetRequireAuth, 0x00000008) \
|
||||
TF_FLAG(tfMPTSetCanEscrow, 0x00000010) \
|
||||
TF_FLAG(tfMPTSetCanTrade, 0x00000020) \
|
||||
TF_FLAG(tfMPTSetCanTransfer, 0x00000040) \
|
||||
TF_FLAG(tfMPTSetCanClawback, 0x00000080) \
|
||||
TF_FLAG(tfMPTSetCanHoldConfidentialBalance, 0x00000100), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(NFTokenCreateOffer, \
|
||||
@@ -356,38 +363,26 @@ inline constexpr FlagValue tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment);
|
||||
inline constexpr FlagValue tfTrustSetPermissionMask =
|
||||
~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze);
|
||||
|
||||
// MPTokenIssuanceCreate MutableFlags:
|
||||
// Indicating specific fields or flags may be changed after issuance.
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanLock = lsmfMPTCanEnableCanLock;
|
||||
inline constexpr FlagValue tmfMPTCanEnableRequireAuth = lsmfMPTCanEnableRequireAuth;
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanEscrow = lsmfMPTCanEnableCanEscrow;
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanTrade = lsmfMPTCanEnableCanTrade;
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanTransfer = lsmfMPTCanEnableCanTransfer;
|
||||
inline constexpr FlagValue tmfMPTCanEnableCanClawback = lsmfMPTCanEnableCanClawback;
|
||||
inline constexpr FlagValue tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata;
|
||||
inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee;
|
||||
inline constexpr FlagValue tmfMPTCannotEnableCanHoldConfidentialBalance =
|
||||
lsmfMPTCannotEnableCanHoldConfidentialBalance;
|
||||
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask =
|
||||
~(tmfMPTCanEnableCanLock | tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanEscrow |
|
||||
tmfMPTCanEnableCanTrade | tmfMPTCanEnableCanTransfer | tmfMPTCanEnableCanClawback |
|
||||
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee |
|
||||
tmfMPTCannotEnableCanHoldConfidentialBalance);
|
||||
// MPTokenIssuanceCreate / MPTokenIssuanceSet ImmutableFlags:
|
||||
// Defines the immutable fields and flags specific to MPTokenIssuance.
|
||||
inline constexpr FlagValue tifMPTCanLock = lsifMPTCanLock;
|
||||
inline constexpr FlagValue tifMPTRequireAuth = lsifMPTRequireAuth;
|
||||
inline constexpr FlagValue tifMPTCanEscrow = lsifMPTCanEscrow;
|
||||
inline constexpr FlagValue tifMPTCanTrade = lsifMPTCanTrade;
|
||||
inline constexpr FlagValue tifMPTCanTransfer = lsifMPTCanTransfer;
|
||||
inline constexpr FlagValue tifMPTCanClawback = lsifMPTCanClawback;
|
||||
inline constexpr FlagValue tifMPTMetadata = lsifMPTMetadata;
|
||||
inline constexpr FlagValue tifMPTTransferFee = lsifMPTTransferFee;
|
||||
inline constexpr FlagValue tifMPTCanHoldConfidentialBalance = lsifMPTCanHoldConfidentialBalance;
|
||||
inline constexpr FlagValue tifMPTokenIssuanceImmutableMask =
|
||||
~(tifMPTCanLock | tifMPTRequireAuth | tifMPTCanEscrow | tifMPTCanTrade | tifMPTCanTransfer |
|
||||
tifMPTCanClawback | tifMPTMetadata | tifMPTTransferFee | tifMPTCanHoldConfidentialBalance);
|
||||
|
||||
// MPTokenIssuanceSet MutableFlags:
|
||||
// Enable mutable capability flags. These flags are one-way: once enabled,
|
||||
// the corresponding capability cannot be disabled by MPTokenIssuanceSet.
|
||||
|
||||
inline constexpr FlagValue tmfMPTSetCanLock = 0x00000001;
|
||||
inline constexpr FlagValue tmfMPTSetRequireAuth = 0x00000002;
|
||||
inline constexpr FlagValue tmfMPTSetCanEscrow = 0x00000004;
|
||||
inline constexpr FlagValue tmfMPTSetCanTrade = 0x00000008;
|
||||
inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000010;
|
||||
inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000020;
|
||||
inline constexpr FlagValue tmfMPTSetCanHoldConfidentialBalance = 0x00000040;
|
||||
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask =
|
||||
~(tmfMPTSetCanLock | tmfMPTSetRequireAuth | tmfMPTSetCanEscrow | tmfMPTSetCanTrade |
|
||||
tmfMPTSetCanTransfer | tmfMPTSetCanClawback | tmfMPTSetCanHoldConfidentialBalance);
|
||||
// MPTokenIssuanceSet set of flags that is used to enable capabilities on an MPTokenIssuance.
|
||||
// Used as `txFlags & tfMPTokenIssuanceSetEnableFlagMask` to extract the capability-enabling bits.
|
||||
inline constexpr FlagValue tfMPTokenIssuanceSetEnableFlagMask = tfMPTSetCanLock |
|
||||
tfMPTSetRequireAuth | tfMPTSetCanEscrow | tfMPTSetCanTrade | tfMPTSetCanTransfer |
|
||||
tfMPTSetCanClawback | tfMPTSetCanHoldConfidentialBalance;
|
||||
|
||||
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between accounts allowed a
|
||||
// TrustLine to be added to the issuer of that token without explicit permission from that issuer.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace Attestations {
|
||||
namespace attestations {
|
||||
|
||||
struct AttestationBase
|
||||
{
|
||||
@@ -227,7 +227,7 @@ struct CmpByCreateCount
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace Attestations
|
||||
}; // namespace attestations
|
||||
|
||||
// Result when checking when two attestation match.
|
||||
enum class AttestationMatch {
|
||||
@@ -241,7 +241,7 @@ enum class AttestationMatch {
|
||||
|
||||
struct XChainClaimAttestation
|
||||
{
|
||||
using TSignedAttestation = Attestations::AttestationClaim;
|
||||
using TSignedAttestation = attestations::AttestationClaim;
|
||||
static SField const& arrayFieldName;
|
||||
|
||||
AccountID keyAccount;
|
||||
@@ -297,7 +297,7 @@ struct XChainClaimAttestation
|
||||
|
||||
struct XChainCreateAccountAttestation
|
||||
{
|
||||
using TSignedAttestation = Attestations::AttestationCreateAccount;
|
||||
using TSignedAttestation = attestations::AttestationCreateAccount;
|
||||
static SField const& arrayFieldName;
|
||||
|
||||
AccountID keyAccount;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
@@ -59,7 +60,6 @@ XRPL_FIX (PreviousTxnID, Supported::Yes, VoteBehavior::DefaultNo
|
||||
XRPL_FIX (XChainRewardRounding, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (EmptyDID, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PriceOracle, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (AMMOverflowOffer, Supported::Yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (FillOrKill, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DID, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(XChainBridge, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
@@ -100,6 +100,7 @@ XRPL_RETIRE_FIX(1578)
|
||||
XRPL_RETIRE_FIX(1623)
|
||||
XRPL_RETIRE_FIX(1781)
|
||||
XRPL_RETIRE_FIX(AmendmentMajorityCalc)
|
||||
XRPL_RETIRE_FIX(AMMOverflowOffer)
|
||||
XRPL_RETIRE_FIX(CheckThreading)
|
||||
XRPL_RETIRE_FIX(DisallowIncomingV1)
|
||||
XRPL_RETIRE_FIX(InnerObjTemplate)
|
||||
|
||||
@@ -409,7 +409,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
{sfDomainID, SoeOptional},
|
||||
{sfMutableFlags, SoeDefault},
|
||||
{sfImmutableFlags, SoeDefault},
|
||||
{sfReferenceHolding, SoeOptional},
|
||||
{sfIssuerEncryptionKey, SoeOptional},
|
||||
{sfAuditorEncryptionKey, SoeOptional},
|
||||
@@ -510,6 +510,7 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
|
||||
{sfShareMPTID, SoeRequired},
|
||||
{sfWithdrawalPolicy, SoeRequired},
|
||||
{sfScale, SoeDefault},
|
||||
{sfLEVersion, SoeDefault},
|
||||
// no SharesTotal ever (use MPTIssuance.sfOutstandingAmount)
|
||||
// no PermissionedDomainID ever (use MPTIssuance.sfDomainID)
|
||||
}))
|
||||
|
||||
@@ -18,6 +18,7 @@ TYPED_SFIELD(sfMethod, UINT8, 2)
|
||||
TYPED_SFIELD(sfTransactionResult, UINT8, 3)
|
||||
TYPED_SFIELD(sfScale, UINT8, 4)
|
||||
TYPED_SFIELD(sfAssetScale, UINT8, 5)
|
||||
TYPED_SFIELD(sfLEVersion, UINT8, 6)
|
||||
|
||||
// 8-bit integers (uncommon)
|
||||
TYPED_SFIELD(sfTickSize, UINT8, 16)
|
||||
@@ -97,7 +98,7 @@ TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
|
||||
TYPED_SFIELD(sfMutableFlags, UINT32, 53)
|
||||
TYPED_SFIELD(sfImmutableFlags, UINT32, 53)
|
||||
TYPED_SFIELD(sfStartDate, UINT32, 54)
|
||||
TYPED_SFIELD(sfPaymentInterval, UINT32, 55)
|
||||
TYPED_SFIELD(sfGracePeriod, UINT32, 56)
|
||||
@@ -241,6 +242,7 @@ TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset
|
||||
|
||||
// int32
|
||||
TYPED_SFIELD(sfLoanScale, INT32, 1)
|
||||
TYPED_SFIELD(sfRemainingOwnerCountDelta, INT32, 2)
|
||||
|
||||
// currency amount (common)
|
||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||
@@ -280,6 +282,7 @@ TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
|
||||
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
|
||||
TYPED_SFIELD(sfFeeAmountDelta, AMOUNT, 34)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
|
||||
@@ -705,7 +705,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
|
||||
{sfMaximumAmount, SoeOptional},
|
||||
{sfMPTokenMetadata, SoeOptional},
|
||||
{sfDomainID, SoeOptional},
|
||||
{sfMutableFlags, SoeOptional},
|
||||
{sfImmutableFlags, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction type destroys a MPTokensIssuance instance */
|
||||
@@ -734,7 +734,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
|
||||
{sfDomainID, SoeOptional},
|
||||
{sfMPTokenMetadata, SoeOptional},
|
||||
{sfTransferFee, SoeOptional},
|
||||
{sfMutableFlags, SoeOptional},
|
||||
{sfImmutableFlags, SoeOptional},
|
||||
{sfIssuerEncryptionKey, SoeOptional},
|
||||
{sfAuditorEncryptionKey, SoeOptional},
|
||||
}))
|
||||
@@ -1085,7 +1085,7 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
|
||||
# include <xrpl/tx/transactors/token/ConfidentialMPTConvert.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_CONVERT, 85, ConfidentialMPTConvert,
|
||||
Delegation::Delegable,
|
||||
Delegation::NotDelegable,
|
||||
featureConfidentialTransfer,
|
||||
NoPriv,
|
||||
({
|
||||
@@ -1189,9 +1189,9 @@ TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
|
||||
({
|
||||
{sfCounterpartySponsor, SoeOptional},
|
||||
{sfSponsee, SoeOptional},
|
||||
{sfFeeAmount, SoeOptional},
|
||||
{sfFeeAmountDelta, SoeOptional},
|
||||
{sfMaxFee, SoeOptional},
|
||||
{sfRemainingOwnerCount, SoeOptional},
|
||||
{sfRemainingOwnerCountDelta, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
@@ -23,6 +23,16 @@ By default, `CODEGEN_VENV_DIR` points to `.venv` in the project root. The
|
||||
`setup_code_gen` target creates a venv there and installs the required packages.
|
||||
The `code_gen` target then uses the venv's Python interpreter to run generation.
|
||||
|
||||
Generation is pure Python, so the same targets are also available as a
|
||||
standalone project that needs neither the dependencies nor a compiler. This is
|
||||
what CI uses, and it is handy if you only want to regenerate these files:
|
||||
|
||||
```bash
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
cmake --build build/codegen --target setup_code_gen
|
||||
cmake --build build/codegen --target code_gen
|
||||
```
|
||||
|
||||
### Python Dependencies
|
||||
|
||||
The code generation requires the following Python packages (installed by `setup_code_gen`):
|
||||
|
||||
@@ -256,27 +256,27 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMutableFlags (SoeDefault)
|
||||
* @brief Get sfImmutableFlags (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getMutableFlags() const
|
||||
getImmutableFlags() const
|
||||
{
|
||||
if (hasMutableFlags())
|
||||
return this->sle_->at(sfMutableFlags);
|
||||
if (hasImmutableFlags())
|
||||
return this->sle_->at(sfImmutableFlags);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMutableFlags is present.
|
||||
* @brief Check if sfImmutableFlags is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMutableFlags() const
|
||||
hasImmutableFlags() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfMutableFlags);
|
||||
return this->sle_->isFieldPresent(sfImmutableFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -557,13 +557,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMutableFlags (SoeDefault)
|
||||
* @brief Set sfImmutableFlags (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setMutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setImmutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMutableFlags] = value;
|
||||
object_[sfImmutableFlags] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -287,6 +287,30 @@ public:
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfScale);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfLEVersion (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT8::type::value_type>
|
||||
getLEVersion() const
|
||||
{
|
||||
if (hasLEVersion())
|
||||
return this->sle_->at(sfLEVersion);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfLEVersion is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasLEVersion() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfLEVersion);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -508,6 +532,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfLEVersion (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
VaultBuilder&
|
||||
setLEVersion(std::decay_t<typename SF_UINT8::type::value_type> const& value)
|
||||
{
|
||||
object_[sfLEVersion] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed Vault wrapper.
|
||||
* @param index The ledger entry index.
|
||||
|
||||
@@ -19,7 +19,7 @@ class ConfidentialMPTConvertBuilder;
|
||||
* @brief Transaction: ConfidentialMPTConvert
|
||||
*
|
||||
* Type: ttCONFIDENTIAL_MPT_CONVERT (85)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureConfidentialTransfer
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
|
||||
@@ -178,29 +178,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMutableFlags (SoeOptional)
|
||||
* @brief Get sfImmutableFlags (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getMutableFlags() const
|
||||
getImmutableFlags() const
|
||||
{
|
||||
if (hasMutableFlags())
|
||||
if (hasImmutableFlags())
|
||||
{
|
||||
return this->tx_->at(sfMutableFlags);
|
||||
return this->tx_->at(sfImmutableFlags);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMutableFlags is present.
|
||||
* @brief Check if sfImmutableFlags is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMutableFlags() const
|
||||
hasImmutableFlags() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfMutableFlags);
|
||||
return this->tx_->isFieldPresent(sfImmutableFlags);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -302,13 +302,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMutableFlags (SoeOptional)
|
||||
* @brief Set sfImmutableFlags (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceCreateBuilder&
|
||||
setMutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setImmutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMutableFlags] = value;
|
||||
object_[sfImmutableFlags] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -163,29 +163,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMutableFlags (SoeOptional)
|
||||
* @brief Get sfImmutableFlags (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getMutableFlags() const
|
||||
getImmutableFlags() const
|
||||
{
|
||||
if (hasMutableFlags())
|
||||
if (hasImmutableFlags())
|
||||
{
|
||||
return this->tx_->at(sfMutableFlags);
|
||||
return this->tx_->at(sfImmutableFlags);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMutableFlags is present.
|
||||
* @brief Check if sfImmutableFlags is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMutableFlags() const
|
||||
hasImmutableFlags() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfMutableFlags);
|
||||
return this->tx_->isFieldPresent(sfImmutableFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,13 +341,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMutableFlags (SoeOptional)
|
||||
* @brief Set sfImmutableFlags (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceSetBuilder&
|
||||
setMutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setImmutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMutableFlags] = value;
|
||||
object_[sfImmutableFlags] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,29 +100,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfFeeAmount (SoeOptional)
|
||||
* @brief Get sfFeeAmountDelta (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getFeeAmount() const
|
||||
getFeeAmountDelta() const
|
||||
{
|
||||
if (hasFeeAmount())
|
||||
if (hasFeeAmountDelta())
|
||||
{
|
||||
return this->tx_->at(sfFeeAmount);
|
||||
return this->tx_->at(sfFeeAmountDelta);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfFeeAmount is present.
|
||||
* @brief Check if sfFeeAmountDelta is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasFeeAmount() const
|
||||
hasFeeAmountDelta() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfFeeAmount);
|
||||
return this->tx_->isFieldPresent(sfFeeAmountDelta);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,29 +152,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfRemainingOwnerCount (SoeOptional)
|
||||
* @brief Get sfRemainingOwnerCountDelta (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getRemainingOwnerCount() const
|
||||
protocol_autogen::Optional<SF_INT32::type::value_type>
|
||||
getRemainingOwnerCountDelta() const
|
||||
{
|
||||
if (hasRemainingOwnerCount())
|
||||
if (hasRemainingOwnerCountDelta())
|
||||
{
|
||||
return this->tx_->at(sfRemainingOwnerCount);
|
||||
return this->tx_->at(sfRemainingOwnerCountDelta);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfRemainingOwnerCount is present.
|
||||
* @brief Check if sfRemainingOwnerCountDelta is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasRemainingOwnerCount() const
|
||||
hasRemainingOwnerCountDelta() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfRemainingOwnerCount);
|
||||
return this->tx_->isFieldPresent(sfRemainingOwnerCountDelta);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -243,13 +243,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfFeeAmount (SoeOptional)
|
||||
* @brief Set sfFeeAmountDelta (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
setFeeAmountDelta(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfFeeAmount] = value;
|
||||
object_[sfFeeAmountDelta] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -265,13 +265,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfRemainingOwnerCount (SoeOptional)
|
||||
* @brief Set sfRemainingOwnerCountDelta (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setRemainingOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setRemainingOwnerCountDelta(std::decay_t<typename SF_INT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRemainingOwnerCount] = value;
|
||||
object_[sfRemainingOwnerCountDelta] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::Resource {
|
||||
namespace xrpl::resource {
|
||||
|
||||
/**
|
||||
* A consumption charge.
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
label() const;
|
||||
|
||||
/**
|
||||
* Return the cost of the charge in Resource::Manager units.
|
||||
* Return the cost of the charge in resource::Manager units.
|
||||
*/
|
||||
[[nodiscard]] value_type
|
||||
cost() const;
|
||||
@@ -60,4 +60,4 @@ private:
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, Charge const& v);
|
||||
|
||||
} // namespace xrpl::Resource
|
||||
} // namespace xrpl::resource
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::Resource {
|
||||
namespace xrpl::resource {
|
||||
|
||||
struct Entry;
|
||||
class Logic;
|
||||
@@ -96,4 +96,4 @@ private:
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, Consumer const& v);
|
||||
|
||||
} // namespace xrpl::Resource
|
||||
} // namespace xrpl::resource
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace xrpl::Resource {
|
||||
namespace xrpl::resource {
|
||||
|
||||
/**
|
||||
* The disposition of a consumer after applying a load charge.
|
||||
@@ -24,4 +24,4 @@ enum class Disposition {
|
||||
Drop
|
||||
};
|
||||
|
||||
} // namespace xrpl::Resource
|
||||
} // namespace xrpl::resource
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <xrpl/resource/Charge.h>
|
||||
|
||||
namespace xrpl::Resource {
|
||||
namespace xrpl::resource {
|
||||
|
||||
/**
|
||||
* Schedule of fees charged for imposing load on the server.
|
||||
@@ -13,6 +13,7 @@ extern Charge const kFeeRequestNoReply; // A request that we cannot satisfy.
|
||||
extern Charge const kFeeInvalidSignature; // An object whose signature we had to check that failed.
|
||||
extern Charge const kFeeUselessData; // Data we have no use for.
|
||||
extern Charge const kFeeInvalidData; // Data we have to verify before rejecting.
|
||||
extern Charge const kFeeMalformedData; // Data that no honest peer would send.
|
||||
|
||||
// RPC loads
|
||||
extern Charge const kFeeMalformedRpc; // An RPC request that we can immediately tell is invalid.
|
||||
@@ -31,4 +32,4 @@ extern Charge const kFeeWarning; // The cost of receiving a warning.
|
||||
extern Charge const kFeeDrop; // The cost of being dropped for excess load.
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl::Resource
|
||||
} // namespace xrpl::resource
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::Resource {
|
||||
namespace xrpl::resource {
|
||||
|
||||
/**
|
||||
* Data format for exchanging consumption information across peers.
|
||||
@@ -21,10 +21,10 @@ struct Gossip
|
||||
explicit Item() = default;
|
||||
|
||||
int balance{};
|
||||
beast::IP::Endpoint address;
|
||||
beast::ip::Endpoint address;
|
||||
};
|
||||
|
||||
std::vector<Item> items;
|
||||
};
|
||||
|
||||
} // namespace xrpl::Resource
|
||||
} // namespace xrpl::resource
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Resource::Manager
|
||||
# resource::Manager
|
||||
|
||||
The ResourceManager module has these responsibilities:
|
||||
|
||||
@@ -36,7 +36,7 @@ to the general public.
|
||||
## Consumer Types
|
||||
|
||||
Consumers are placed into three classifications (as identified by the
|
||||
Resource::Kind enumeration):
|
||||
resource::Kind enumeration):
|
||||
|
||||
- InBound,
|
||||
- OutBound, and
|
||||
@@ -72,6 +72,6 @@ drop connections to those IP addresses that occur commonly in the gossip.
|
||||
|
||||
## Access
|
||||
|
||||
In xrpld, the Application holds a unique instance of Resource::Manager,
|
||||
In xrpld, the Application holds a unique instance of resource::Manager,
|
||||
which may be retrieved by calling the method
|
||||
`Application::getResourceManager()`.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::Resource {
|
||||
namespace xrpl::resource {
|
||||
|
||||
/**
|
||||
* Tracks load and resource consumption.
|
||||
@@ -32,10 +32,10 @@ public:
|
||||
* IP if proxied.
|
||||
*/
|
||||
virtual Consumer
|
||||
newInboundEndpoint(beast::IP::Endpoint const& address) = 0;
|
||||
newInboundEndpoint(beast::ip::Endpoint const& address) = 0;
|
||||
virtual Consumer
|
||||
newInboundEndpoint(
|
||||
beast::IP::Endpoint const& address,
|
||||
beast::ip::Endpoint const& address,
|
||||
bool const proxy,
|
||||
std::string_view forwardedFor) = 0;
|
||||
|
||||
@@ -43,13 +43,13 @@ public:
|
||||
* Create a new endpoint keyed by outbound IP address and port.
|
||||
*/
|
||||
virtual Consumer
|
||||
newOutboundEndpoint(beast::IP::Endpoint const& address) = 0;
|
||||
newOutboundEndpoint(beast::ip::Endpoint const& address) = 0;
|
||||
|
||||
/**
|
||||
* Create a new unlimited endpoint keyed by forwarded IP.
|
||||
*/
|
||||
virtual Consumer
|
||||
newUnlimitedEndpoint(beast::IP::Endpoint const& address) = 0;
|
||||
newUnlimitedEndpoint(beast::ip::Endpoint const& address) = 0;
|
||||
|
||||
/**
|
||||
* Extract packaged consumer information for export.
|
||||
@@ -78,4 +78,4 @@ public:
|
||||
std::unique_ptr<Manager>
|
||||
makeManager(beast::insight::Collector::ptr const& collector, beast::Journal journal);
|
||||
|
||||
} // namespace xrpl::Resource
|
||||
} // namespace xrpl::resource
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::Resource {
|
||||
namespace xrpl::resource {
|
||||
|
||||
using clock_type = beast::AbstractClock<std::chrono::steady_clock>;
|
||||
|
||||
@@ -91,4 +91,4 @@ operator<<(std::ostream& os, Entry const& v)
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace xrpl::Resource
|
||||
} // namespace xrpl::resource
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user