Merge branch 'pratik/otel-phase5-docs-deployment' into pratik/otel-phase6-statsd

# Conflicts:
#	.cspell.config.yaml
This commit is contained in:
Pratik Mankawde
2026-08-20 12:10:49 +01:00
464 changed files with 27964 additions and 14307 deletions

View File

@@ -1,6 +1,6 @@
#pragma once
#include <boost/filesystem.hpp>
#include <filesystem>
namespace xrpl {
@@ -13,6 +13,6 @@ namespace xrpl {
* @throws runtime_error
*/
void
extractTarLz4(boost::filesystem::path const& src, boost::filesystem::path const& dst);
extractTarLz4(std::filesystem::path const& src, std::filesystem::path const& dst);
} // namespace xrpl

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
@@ -156,6 +157,19 @@ public:
}
/** @} */
/**
* Set every byte in the buffer to the given value.
*
* The size is unchanged, and this is a no-op on an empty buffer.
*
* @param value the byte to write to every position.
*/
void
fill(std::uint8_t value) noexcept
{
std::fill_n(p_.get(), size_, value);
}
/**
* Reset the buffer.
* All memory is deallocated. The resulting size is 0.
@@ -226,10 +240,4 @@ operator==(Buffer const& lhs, Buffer const& rhs) noexcept
return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
}
inline bool
operator!=(Buffer const& lhs, Buffer const& rhs) noexcept
{
return !(lhs == rhs);
}
} // namespace xrpl

View File

@@ -1,24 +1,79 @@
#pragma once
#include <boost/filesystem.hpp>
#include <boost/system/error_code.hpp>
#include <cstddef>
#include <filesystem>
#include <optional>
#include <string>
#include <system_error>
namespace xrpl {
std::string
getFileContents(
boost::system::error_code& ec,
boost::filesystem::path const& sourcePath,
std::error_code& ec,
std::filesystem::path const& sourcePath,
std::optional<std::size_t> maxSize = std::nullopt);
void
writeFileContents(
boost::system::error_code& ec,
boost::filesystem::path const& destPath,
std::error_code& ec,
std::filesystem::path const& destPath,
std::string const& contents);
/**
* Generate a unique, non-existing path under @p base whose filename starts with
* @p prefix and ends with a random hex suffix.
*
* Attempts up to @p maxAttempts paths. Throws `std::runtime_error` if a unique
* path cannot be found or if the filesystem returns an error while checking for
* existence.
*/
std::filesystem::path
uniqueRandomPath(
std::filesystem::path const& base,
std::string const& prefix = "",
std::size_t maxAttempts = 100);
/**
* RAII temporary directory.
*
* The directory and all its contents are deleted when
* the instance of `TempDir` is destroyed.
*/
class TempDir
{
std::filesystem::path path_;
public:
#if !GENERATING_DOCS
TempDir(TempDir const&) = delete;
TempDir&
operator=(TempDir const&) = delete;
#endif
/**
* Construct a temporary directory.
*/
TempDir();
/**
* Destroy a temporary directory.
*/
~TempDir();
/**
* Get the native path for the temporary directory.
*/
[[nodiscard]] std::string
path() const;
/**
* Get the native path for a file.
*
* The file does not need to exist.
*/
[[nodiscard]] std::string
file(std::string const& name) const;
};
} // namespace xrpl

View File

@@ -96,9 +96,6 @@ public:
SharedIntrusive&
operator=(SharedIntrusive const& rhs);
bool
operator!=(std::nullptr_t) const;
bool
operator==(std::nullptr_t) const;

View File

@@ -111,13 +111,6 @@ SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)
return *this;
}
template <class T>
bool
SharedIntrusive<T>::operator!=(std::nullptr_t) const
{
return this->get() != nullptr;
}
template <class T>
bool
SharedIntrusive<T>::operator==(std::nullptr_t) const

View File

@@ -3,8 +3,8 @@
#include <xrpl/beast/utility/Journal.h>
#include <boost/beast/core/string.hpp>
#include <boost/filesystem.hpp>
#include <filesystem>
#include <fstream>
#include <map>
#include <memory>
@@ -84,7 +84,7 @@ private:
* @return `true` if the file was opened.
*/
bool
open(boost::filesystem::path const& path);
open(std::filesystem::path const& path);
/**
* Close and re-open the system file associated with the log
@@ -133,7 +133,7 @@ private:
private:
std::unique_ptr<std::ofstream> stream_;
boost::filesystem::path path_;
std::filesystem::path path_;
};
std::mutex mutable mutex_;
@@ -152,7 +152,7 @@ public:
virtual ~Logs() = default;
bool
open(boost::filesystem::path const& pathToLogFile);
open(std::filesystem::path const& pathToLogFile);
beast::Journal::Sink&
get(std::string const& name);

View File

@@ -304,7 +304,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* on-ledger are non-negative. This is due to implementation details of
* several operations which use unsigned arithmetic internally. This is
* sufficient to represent all valid XRP values (where the absolute value
* can not exceed INITIAL_XRP: 10^17), and MPT values (where the absolute
* can not exceed kInitialXRP: 10^17), and MPT values (where the absolute
* value can not exceed maxMPTokenAmount: 2^63-1).
*
* ---- Mantissa Range Switching ----
@@ -449,12 +449,6 @@ public:
x.exponent_ == y.exponent_;
}
friend constexpr bool
operator!=(Number const& x, Number const& y) noexcept
{
return !(x == y);
}
friend constexpr bool
operator<(Number const& l, Number const& r) noexcept
{

View File

@@ -85,12 +85,6 @@ public:
}
};
inline bool
operator!=(SHAMapHash const& x, SHAMapHash const& y)
{
return !(x == y);
}
template <>
inline std::size_t
extract(SHAMapHash const& key)

View File

@@ -208,12 +208,6 @@ operator==(Slice const& lhs, Slice const& rhs) noexcept
return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
}
inline bool
operator!=(Slice const& lhs, Slice const& rhs) noexcept
{
return !(lhs == rhs);
}
inline bool
operator<(Slice const& lhs, Slice const& rhs) noexcept
{

View File

@@ -2,7 +2,6 @@
#include <xrpl/basics/Blob.h>
#include <boost/format.hpp>
#include <boost/utility/string_view.hpp>
#include <array>
@@ -125,9 +124,31 @@ struct ParsedUrl
bool
parseUrl(ParsedUrl& pUrl, std::string const& strUrl);
/**
* Remove leading and trailing ASCII whitespace.
*
* Whitespace is the fixed set " \t\n\v\f\r"; the current locale is not
* consulted, so the result depends only on the input.
*
* @param str The string to trim.
* @return @p str without leading or trailing whitespace.
*/
std::string
trimWhitespace(std::string str);
/**
* Fold ASCII upper case letters to lower case.
*
* Only 'A' through 'Z' are remapped; every other byte is left alone and the
* current locale is not consulted, so the result depends only on the input.
*
* @param str The string to fold.
* @return @p str with each ASCII upper case letter replaced by its lower case
* equivalent.
*/
std::string
toLower(std::string str);
std::optional<std::uint64_t>
toUInt64(std::string const& s);

View File

@@ -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);

View File

@@ -116,12 +116,6 @@ public:
{
return lhs.map == rhs.map && lhs.ait == rhs.ait && lhs.mit == rhs.mit;
}
friend bool
operator!=(Iterator const& lhs, Iterator const& rhs)
{
return !(lhs == rhs);
}
};
struct ConstIterator
@@ -189,12 +183,6 @@ public:
{
return lhs.map == rhs.map && lhs.ait == rhs.ait && lhs.mit == rhs.mit;
}
friend bool
operator!=(ConstIterator const& lhs, ConstIterator const& rhs)
{
return !(lhs == rhs);
}
};
private:

View File

@@ -1038,25 +1038,6 @@ public:
Compare,
OtherAllocator> const& other) const;
template <
bool OtherIsMulti,
bool OtherIsMap,
class OtherT,
class OtherDuration,
class OtherAllocator>
bool
operator!=(AgedOrderedContainer<
OtherIsMulti,
OtherIsMap,
Key,
OtherT,
OtherDuration,
Compare,
OtherAllocator> const& other) const
{
return !(this->operator==(other));
}
template <
bool OtherIsMulti,
bool OtherIsMap,

View File

@@ -1340,28 +1340,6 @@ public:
OtherAllocator> const& other) const
requires MaybeMulti;
template <
bool OtherIsMulti,
bool OtherIsMap,
class OtherKey,
class OtherT,
class OtherDuration,
class OtherHash,
class OtherAllocator>
bool
operator!=(AgedUnorderedContainer<
OtherIsMulti,
OtherIsMap,
OtherKey,
OtherT,
OtherDuration,
OtherHash,
KeyEqual,
OtherAllocator> const& other) const
{
return !(this->operator==(other));
}
private:
bool
wouldExceed(size_type additional) const

View File

@@ -82,13 +82,6 @@ public:
return node_ == other.node_;
}
template <typename M>
bool
operator!=(ListIterator<M> const& other) const noexcept
{
return !((*this) == other);
}
reference
operator*() const noexcept
{

View File

@@ -110,12 +110,6 @@ public:
operator==(Endpoint const& lhs, Endpoint const& rhs);
friend bool
operator<(Endpoint const& lhs, Endpoint const& rhs);
friend bool
operator!=(Endpoint const& lhs, Endpoint const& rhs)
{
return !(lhs == rhs);
}
friend bool
operator>(Endpoint const& lhs, Endpoint const& rhs)
{

View File

@@ -11,6 +11,7 @@
#include <cstddef>
#include <iterator>
#include <string>
#include <string_view>
#include <vector>
namespace beast::rfc2616 {
@@ -186,7 +187,7 @@ splitCommas(FwdIt first, FwdIt last)
template <class Result = std::vector<std::string>>
Result
splitCommas(boost::beast::string_view const& s)
splitCommas(std::string_view s)
{
return splitCommas(s.begin(), s.end());
}
@@ -229,12 +230,6 @@ public:
return other.it_ == it_ && other.end_ == end_ && other.value_.size() == value_.size();
}
bool
operator!=(ListIterator const& other) const
{
return !(*this == other);
}
reference
operator*() const
{

View File

@@ -8,7 +8,6 @@
#include <xrpl/beast/unit_test/runner.h>
#include <xrpl/beast/unit_test/suite_info.h>
#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>
#include <algorithm>
@@ -188,7 +187,7 @@ Reporter<Unused>::fmtdur(clock_type::duration const& d)
using namespace std::chrono;
auto const ms = duration_cast<milliseconds>(d);
if (ms < seconds{1})
return boost::lexical_cast<std::string>(ms.count()) + "ms";
return std::to_string(ms.count()) + "ms";
std::stringstream ss;
ss << std::fixed << std::setprecision(1) << (ms.count() / 1000.) << "s";
return ss.str();

View File

@@ -6,11 +6,10 @@
#include <xrpl/beast/unit_test/runner.h>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/throw_exception.hpp>
#include <exception>
#include <filesystem>
#include <memory>
#include <ostream>
#include <sstream>
@@ -27,10 +26,10 @@ makeReason(String const& reason, char const* file, int line)
std::string s(reason);
if (!s.empty())
s.append(": ");
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
s.append(fs::path{file}.filename().string());
s.append("(");
s.append(boost::lexical_cast<std::string>(line));
s.append(std::to_string(line));
s.append(")");
return s;
}

View File

@@ -1,71 +0,0 @@
#pragma once
#include <boost/filesystem.hpp>
#include <string>
namespace beast {
/**
* RAII temporary directory.
*
* The directory and all its contents are deleted when
* the instance of `temp_dir` is destroyed.
*/
class TempDir
{
boost::filesystem::path path_;
public:
#if !GENERATING_DOCS
TempDir(TempDir const&) = delete;
TempDir&
operator=(TempDir const&) = delete;
#endif
/**
* Construct a temporary directory.
*/
TempDir()
{
auto const dir = boost::filesystem::temp_directory_path();
do
{
path_ = dir / boost::filesystem::unique_path();
} while (boost::filesystem::exists(path_));
boost::filesystem::create_directory(path_);
}
/**
* Destroy a temporary directory.
*/
~TempDir()
{
// use non-throwing calls in the destructor
boost::system::error_code ec;
boost::filesystem::remove_all(path_, ec);
// TODO: warn/notify if ec set ?
}
/**
* Get the native path for the temporary directory
*/
[[nodiscard]] std::string
path() const
{
return path_.string();
}
/**
* Get the native path for the a file.
*
* The file does not need to exist.
*/
[[nodiscard]] std::string
file(std::string const& name) const
{
return (path_ / name).string();
}
};
} // namespace beast

View File

@@ -92,10 +92,4 @@ operator==(Condition const& lhs, Condition const& rhs)
lhs.fingerprint == rhs.fingerprint;
}
inline bool
operator!=(Condition const& lhs, Condition const& rhs)
{
return !(lhs == rhs);
}
} // namespace xrpl::cryptoconditions

View File

@@ -93,12 +93,6 @@ operator==(Fulfillment const& lhs, Fulfillment const& rhs)
lhs.fingerprint() == rhs.fingerprint();
}
inline bool
operator!=(Fulfillment const& lhs, Fulfillment const& rhs)
{
return !(lhs == rhs);
}
/**
* Determine whether the given fulfillment and condition match
*/

View File

@@ -2,7 +2,6 @@
#include <xrpl/basics/contract.h>
#include <boost/beast/core/string.hpp>
#include <boost/lexical_cast.hpp>
#include <algorithm>

View File

@@ -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";
@@ -118,7 +119,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";

View File

@@ -23,6 +23,7 @@
#include <map>
#include <memory>
#include <optional>
#include <ranges>
#include <sstream>
#include <string>
#include <string_view>
@@ -1748,7 +1749,13 @@ Consensus<Adaptor>::updateOurPositions(std::unique_ptr<std::stringstream> const&
JLOG(j_.info()) << ss.str();
CLOG(clog) << ss.str();
for (auto const& [t, v] : closeTimeVotes)
// Walk the votes highest-time first so that, among close times tied
// for the most votes, the earliest wins. The smaller value is the
// safer choice: without close-time consensus this round, the winner
// only updates our position for the next proposal, and a too-early
// time is bounded below by the prior ledger's close time. Only the
// tie-break changes; the bin with the most votes still wins.
for (auto const& [t, v] : std::views::reverse(closeTimeVotes))
{
JLOG(j_.debug()) << "CCTime: seq "
<< static_cast<std::uint32_t>(previousLedger_.seq()) + 1 << ": "

View File

@@ -8,7 +8,9 @@
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <map>
#include <optional>
#include <string>
namespace xrpl {
@@ -211,6 +213,75 @@ struct ConsensusCloseTimes
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
*/

View File

@@ -4,10 +4,9 @@
#include <xrpl/core/Job.h>
#include <xrpl/json/json_value.h>
#include <boost/filesystem.hpp>
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <string>
@@ -44,7 +43,7 @@ public:
*/
struct Setup
{
boost::filesystem::path perfLog;
std::filesystem::path perfLog;
// log_interval is in milliseconds to support faster testing.
milliseconds logInterval{seconds(1)};
};
@@ -149,7 +148,7 @@ public:
};
PerfLog::Setup
setupPerfLog(Section const& section, boost::filesystem::path const& configDir);
setupPerfLog(Section const& section, std::filesystem::path const& configDir);
std::unique_ptr<PerfLog>
makePerfLog(

View File

@@ -1,20 +1,19 @@
#pragma once
#include <boost/beast/core/string.hpp>
#include <functional>
#include <string>
#include <string_view>
namespace json {
class Value;
using Output = std::function<void(boost::beast::string_view const&)>;
using Output = std::function<void(std::string_view)>;
inline Output
stringOutput(std::string& s)
{
return [&](boost::beast::string_view const& b) { s.append(b.data(), b.size()); };
return [&](std::string_view b) { s.append(b.data(), b.size()); };
}
/**

View File

@@ -4,6 +4,7 @@
#include <xrpl/json/json_forwards.h>
#include <cstring>
#include <iterator>
#include <limits>
#include <map>
#include <string>
@@ -72,36 +73,18 @@ operator==(StaticString x, StaticString y)
return strcmp(x.cStr(), y.cStr()) == 0;
}
inline bool
operator!=(StaticString x, StaticString y)
{
return !(x == y);
}
inline bool
operator==(std::string const& x, StaticString y)
{
return strcmp(x.c_str(), y.cStr()) == 0;
}
inline bool
operator!=(std::string const& x, StaticString y)
{
return !(x == y);
}
inline bool
operator==(StaticString x, std::string const& y)
{
return y == x;
}
inline bool
operator!=(StaticString x, std::string const& y)
{
return !(y == x);
}
/**
* @brief Represents a <a HREF="http://www.json.org">JSON</a> value.
*
@@ -489,12 +472,6 @@ toJson(xrpl::Number const& number)
bool
operator==(Value const&, Value const&);
inline bool
operator!=(Value const& x, Value const& y)
{
return !(x == y);
}
bool
operator<(Value const&, Value const&);
@@ -548,6 +525,7 @@ public:
class ValueIteratorBase
{
public:
using iterator_category = std::bidirectional_iterator_tag;
using size_t = unsigned int;
using difference_type = int;
using SelfType = ValueIteratorBase;
@@ -562,12 +540,6 @@ public:
return isEqual(other);
}
bool
operator!=(SelfType const& other) const
{
return !isEqual(other);
}
/**
* Return either the index or the member name of the referenced value as a
* Value.

View File

@@ -49,12 +49,6 @@ public:
bool
operator==(const_iterator const& other) const;
bool
operator!=(const_iterator const& other) const
{
return !(*this == other);
}
reference
operator*() const;

View File

@@ -59,12 +59,6 @@ private:
return lhs.txId_ == rhs.txId_;
}
friend bool
operator!=(Key const& lhs, Key const& rhs)
{
return !(lhs == rhs);
}
[[nodiscard]] uint256 const&
getAccount() const
{

View File

@@ -59,12 +59,6 @@ public:
bool
operator==(ConstIterator const& other) const;
bool
operator!=(ConstIterator const& other) const
{
return !(*this == other);
}
reference
operator*() const;

View File

@@ -35,6 +35,11 @@ enum class SkipEntry : bool { No = false, Yes };
//
//------------------------------------------------------------------------------
/**
* Whether an expiration check should be inclusive or exclusive.
*/
enum class ExpiryComparison { Inclusive, Exclusive };
/**
* Determines whether the given expiration time has passed.
*
@@ -54,11 +59,16 @@ enum class SkipEntry : bool { No = false, Yes };
*
* @param view The ledger whose parent time is used as the clock.
* @param exp The optional expiration time we want to check.
* @param comparison Whether the boundary is inclusive (`now >= exp`, the
* default) or exclusive (`now > exp`).
*
* @return `true` if `exp` is in the past; `false` otherwise.
*/
[[nodiscard]] bool
hasExpired(ReadView const& view, std::optional<std::uint32_t> const& exp);
hasExpired(
ReadView const& view,
std::optional<std::uint32_t> const& exp,
ExpiryComparison comparison = ExpiryComparison::Inclusive);
// Note, depth parameter is used to limit the recursion depth
[[nodiscard]] bool
@@ -68,6 +78,13 @@ isVaultPseudoAccountFrozen(
MPTIssue const& mptShare,
std::uint8_t depth);
[[nodiscard]] bool
isVaultPseudoAccountFrozen(
ReadView const& view,
AccountID const& account,
SLE const& issuanceSle,
std::uint8_t depth);
[[nodiscard]] bool
isLPTokenFrozen(
ReadView const& view,
@@ -75,6 +92,26 @@ isLPTokenFrozen(
Asset const& asset,
Asset const& asset2);
/**
* Check whether an AMM LPToken may be transferred between @p from and @p to.
*
* @p lpTokenIssuer is the issuer of the LPToken being moved. If it is not an
* AMM account the token is not an LPToken and the transfer is unconditionally
* permitted. Otherwise, for each MPT pool asset of that AMM, canTransfer() must
* permit the transfer (which exempts the MPT issuer). Non-MPT pool assets are
* always transferable by this check, so it is implicitly gated by
* featureMPTokensV2 (MPTs can only be AMM pool assets once V2 is enabled).
*
* @return tesSUCCESS if permitted, otherwise the canTransfer() failure code
* (e.g. tecNO_AUTH) of the first MPT pool asset that disallows it.
*/
[[nodiscard]] TER
canTransferLPToken(
ReadView const& view,
AccountID const& from,
AccountID const& to,
AccountID const& lpTokenIssuer);
// Return the list of enabled amendments
[[nodiscard]] std::set<uint256>
getEnabledAmendments(ReadView const& view);

View File

@@ -85,9 +85,6 @@ public:
bool
operator==(Iterator const& other) const;
bool
operator!=(Iterator const& other) const;
// Can throw
reference
operator*() const;

View File

@@ -64,13 +64,6 @@ ReadViewFwdRange<ValueType>::Iterator::operator==(Iterator const& other) const
return impl_ == other.impl_;
}
template <class ValueType>
bool
ReadViewFwdRange<ValueType>::Iterator::operator!=(Iterator const& other) const
{
return !(*this == other);
}
template <class ValueType>
auto
ReadViewFwdRange<ValueType>::Iterator::operator*() const -> reference

View File

@@ -226,7 +226,7 @@ getAMMOfferStartWithTakerGets(
auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerGets is XRP.
// This has the most impact when takerGets is integral.
auto const takerGets =
toAmount<TOut>(getAsset(pool.out), nTakerGetsProposed, Number::RoundingMode::Downward);
return TAmounts<TIn, TOut>{swapAssetOut(pool, takerGets, tfee), takerGets};
@@ -294,7 +294,7 @@ getAMMOfferStartWithTakerPays(
auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerPays is XRP.
// This has the most impact when takerPays is integral.
auto const takerPays =
toAmount<TIn>(getAsset(pool.in), nTakerPaysProposed, Number::RoundingMode::Downward);
return TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
@@ -313,11 +313,11 @@ getAMMOfferStartWithTakerPays(
* is equal to LOB quality (in this case AMM offer quality is
* better than LOB quality) or AMM offer is equal to LOB quality
* (in this case SPQ is better than LOB quality).
* Pre-amendment code calculates takerPays first. If takerGets is XRP,
* it is rounded down, which results in worse offer quality than
* LOB quality, and the offer might fail to generate.
* Post-amendment code calculates the XRP offer side first. The result
* is rounded down, which makes the offer quality better.
* Pre-amendment code calculates takerPays first. If takerGets is the
* economically coarser integral side, it is rounded down, which results in
* worse offer quality than LOB quality, and the offer might fail to generate.
* Post-amendment code calculates the economically coarser integral offer side
* first. The result is rounded down, which makes the offer quality better.
* It might not be possible to match either SPQ or AMM offer to LOB
* quality. This generally happens at higher fees.
* @param pool AMM pool balances
@@ -396,10 +396,18 @@ changeSpotPriceQuality(
return std::nullopt;
}
// Generate the offer starting with XRP side. Return seated offer amounts
// if the offer can be generated, otherwise nullopt.
auto amounts = [&]() {
if (isXRP(getAsset(pool.out)))
bool const inIntegral = getAsset(pool.in).integral();
bool const outIntegral = getAsset(pool.out).integral();
// Preserve historical behavior for fractional pairs and XRP/IOU-style
// one-integral-side pairs. For two integral assets, pick the side whose
// minimum unit is economically coarser at this quality.
//
// Quality::rate() is input units per output unit, so one output unit is
// coarser when it costs at least one input unit. Ties use takerGets,
// matching the historical XRP-output behavior.
if (outIntegral && (!inIntegral || Number(quality.rate()) >= 1))
return getAMMOfferStartWithTakerGets(pool, quality, tfee);
return getAMMOfferStartWithTakerPays(pool, quality, tfee);
}();

View File

@@ -7,6 +7,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
@@ -34,7 +35,7 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j);
// Amendment and parameters checks for sfCredentialIDs field
NotTEC
checkFields(STTx const& tx, beast::Journal j);
checkFields(STTx const& tx, Rules const& rules, beast::Journal j);
// Accessing the ledger to check if provided credentials are valid. Do not use
// in doApply (only in preclaim) since it does not remove expired credentials.

View File

@@ -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,

View File

@@ -7,6 +7,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
#include <xrpl/protocol/Protocol.h>
@@ -21,6 +22,7 @@
#include <cstdint>
#include <expected>
#include <optional>
#include <string_view>
#include <utility>
@@ -58,6 +60,42 @@ canApplyToBrokerCover(
bool
checkLendingProtocolDependencies(Rules const& rules, STTx const& tx);
/**
* The accounts and asset that LoanManage::defaultLoan's fixCleanup3_4_0
* freeze/lock exemption applies to.
*
* `defaultLoan` moves funds from the LoanBroker pseudo-account to the Vault
* pseudo-account via `accountSend`. Since neither is the vault asset's
* issuer, this is a third-party transfer that transits through the issuer in
* two hops (broker -> issuer, issuer -> vault; see
* `directSendNoLimitIOU`/`directSendNoLimitMPT`), so the exemption must cover
* both the issuer/broker and issuer/vault pairs, not a direct broker/vault
* pair. `asset` scopes it further to the vault's own currency/MPT issuance,
* so an unrelated one the same accounts happen to hold is still protected.
*/
struct LoanDefaultFreezeExemptAccounts
{
AccountID issuer;
AccountID broker;
AccountID vault;
Asset asset;
};
/**
* Resolves the accounts and asset a LoanManage default transaction is
* exempt from freeze/lock for.
*
* @param view Ledger view used to resolve the Loan -> LoanBroker -> Vault
* chain.
* @param tx The transaction under invariant review.
* @return The exempt accounts and asset if `tx` is a `ttLOAN_MANAGE`
* transaction with the `tfLoanDefault` flag set, `fixCleanup3_4_0` is
* enabled, and the loan/broker/vault objects it references can all be
* resolved; `std::nullopt` otherwise.
*/
[[nodiscard]] std::optional<LoanDefaultFreezeExemptAccounts>
getLoanDefaultFreezeExemptAccounts(ReadView const& view, STTx const& tx);
static constexpr std::uint32_t kSecondsInYear = 365 * 24 * 60 * 60;
Number

View File

@@ -29,6 +29,9 @@ namespace xrpl {
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] bool
isGlobalFrozen(SLE const& issuanceSle);
/**
* Returns true if @p account's MPToken for @p mptIssue carries the
* individual-lock flag (lsfMPTLocked).
@@ -40,9 +43,29 @@ isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
* receive tokens — it combines isIndividualFrozen, isGlobalFrozen, and
* isVaultPseudoAccountFrozen into a single complete check.
*/
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] bool
isIndividualFrozen(SLE const& mptSle);
/**
* Returns true if @p account cannot send or receive tokens of @p mptIssue
* because a freeze applies. This is the complete check callers should use
* before moving MPT value: it combines @ref isGlobalFrozen (issuance-level
* lock), @ref isIndividualFrozen (per-holder lock bit), and the transitive
* vault pseudo-account check (if @p mptIssue is a vault share, the underlying
* asset is checked, and so on recursively up to @c maxAssetCheckDepth).
*
* The @c SLE overload takes an already-loaded ltMPTOKEN or ltMPTOKEN_ISSUANCE
* ledger entry; for ltMPTOKEN it can skip the per-holder individual-lock lookup.
* @ref isAnyFrozen answers the same question for a set of accounts and returns true
* if the freeze applies to any of them.
*
* @param depth Current recursion depth for the vault-share walk. Callers
* outside this module should leave it at the default.
*/
[[nodiscard]] bool
isFrozen(
ReadView const& view,
@@ -50,6 +73,18 @@ isFrozen(
MPTIssue const& mptIssue,
std::uint8_t depth = 0);
/**
* SLE overload: pass an already-loaded ltMPTOKEN (holder row) or
* ltMPTOKEN_ISSUANCE to reuse it for the freeze checks and avoid re-reading
* the same object. For an ltMPTOKEN, @p sle is used directly for the
* individual-lock check and the issuance is read once for global-freeze and
* vault-pseudo-account. For an ltMPTOKEN_ISSUANCE, @p sle is used directly
* for global-freeze and vault-pseudo-account, and the caller's holder row is
* read for the individual-lock check.
*/
[[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, SLE const& sle, std::uint8_t depth = 0);
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
@@ -261,6 +296,14 @@ checkCreateMPT(
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
SLE::ref sponsorSle,
std::uint32_t flags,
beast::Journal j);
TER
checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
beast::Journal j);
//------------------------------------------------------------------------------

View File

@@ -1,15 +1,20 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <cstdint>
#include <optional>
namespace xrpl {
class STTx;
/**
* From the perspective of a vault, return the number of shares to give
* depositor when they offer a fixed amount of assets. Note, since shares are
@@ -52,6 +57,38 @@ enum class TruncateShares : bool { No = false, Yes = true };
*/
enum class WaiveUnrealizedLoss : bool { No = false, Yes = true };
/**
* Returns the effective total of assets backing outstanding shares for the
* purposes of a withdrawal, i.e. sfAssetsTotal, discounted by sfLossUnrealized
* unless waived. This is the numerator used by both withdraw conversion
* helpers (assetsToSharesWithdraw and sharesToAssetsWithdraw) to compute the
* share/asset exchange rate.
*
* @param vault The vault SLE.
* @param waive Whether to waive (i.e. not subtract) the vault's unrealized
* loss.
*/
[[nodiscard]] Number
assetsTotalForWithdrawal(SLE::const_ref vault, WaiveUnrealizedLoss waive);
/**
* Returns whether debiting `amount` from `total` — the current value of a
* vault's sfAssetsTotal or sfAssetsAvailable field — would canonicalize back
* to the exact same STAmount value it started at. This happens when a
* genuinely non-zero debit is dust relative to a `total` large enough to
* exceed STAmount's significant-digit precision: the shares still move, but
* the stored total doesn't change, which otherwise trips the ValidVault
* invariant after the fact instead of failing cleanly upfront.
*
* @param asset The vault's underlying asset, used to canonicalize both sides
* the same way the ledger will when the field is stored.
* @param total The field's current value.
* @param amount The amount to debit. A value of zero always returns false;
* that case is rejected separately and unconditionally.
*/
[[nodiscard]] bool
debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount);
/**
* From the perspective of a vault, return the number of shares to demand from
* the depositor when they ask to withdraw a fixed amount of assets. Since
@@ -123,4 +160,82 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref
[[nodiscard]] VaultVersion
getVaultVersion(SLE::const_ref vault);
/**
* Resolves the VaultKind of a vault SLE. Returns VaultKind::ClosedEnded when
* sfVaultKind is present and equal to that value; anything else (including an
* absent field or an unrecognised value) is treated as VaultKind::OpenEnded.
*
* @param vault The vault SLE.
*/
[[nodiscard]] VaultKind
getVaultKind(SLE::const_ref vault);
/**
* Reads sfVaultKind from a transaction. An absent field resolves to
* VaultKind::OpenEnded (matching the on-ledger default); any unrecognised
* value is also treated as VaultKind::OpenEnded, mirroring the SLE overload.
* Callers that need to reject out-of-range values (e.g. preflight) should
* gate on isValidVaultKind() first.
*
* @param tx The transaction.
*/
[[nodiscard]] VaultKind
getVaultKind(STTx const& tx);
/**
* Returns true iff sfVaultKind is either absent from @p tx or is present and
* equal to a recognised VaultKind enumerator. Intended for use in preflight
* to reject malformed transactions before decoding with getVaultKind().
*
* @param tx The transaction.
*/
[[nodiscard]] bool
isValidVaultKind(STTx const& tx);
/**
* Returns true iff the (SubscriptionDate, RedemptionDate) gap of a
* closed-ended vault satisfies
* kMinInvestmentPeriod <= (red - sub) < kMaxInvestmentPeriod. The arithmetic
* is performed in std::int64_t so that @p sub near UINT32_MAX does not
* overflow. Shared by VaultCreate::preflight and the ValidVault invariant.
*
* @param sub The value of sfSubscriptionDate.
* @param red The value of sfRedemptionDate.
*/
[[nodiscard]] bool
isValidClosedEndedGap(std::uint32_t sub, std::uint32_t red);
/**
* Returns the current lifecycle phase of a vault. Open-ended
* vaults are always NoPhase. For closed-ended vaults the phase is derived
* from the parent ledger close time and the vault's immutable
* SubscriptionDate and RedemptionDate.
*
* @param view The ledger view whose parent close time is used as the clock.
* @param vault The vault SLE.
*/
[[nodiscard]] VaultPhase
getVaultPhase(ReadView const& view, SLE::const_ref vault);
/**
* Raw-fields overload of getVaultPhase. Derives the phase from an already
* decomposed vault snapshot: an absent or non-ClosedEnded @p vaultKind
* resolves to VaultPhase::NoPhase; otherwise the phase is computed from
* @p subscriptionDate and @p redemptionDate against the view's parent
* close time using the same boundary semantics as the SLE overload
* (Subscription is inclusive of now == SubscriptionDate; Investment starts
* strictly after).
*
* @param view The ledger view whose parent close time is used as the clock.
* @param vaultKind The value of sfVaultKind, or nullopt if absent.
* @param subscriptionDate The value of sfSubscriptionDate, or nullopt if absent.
* @param redemptionDate The value of sfRedemptionDate, or nullopt if absent.
*/
[[nodiscard]] VaultPhase
getVaultPhase(
ReadView const& view,
std::optional<std::uint8_t> vaultKind,
std::optional<std::uint32_t> subscriptionDate,
std::optional<std::uint32_t> redemptionDate);
} // namespace xrpl

View File

@@ -8,11 +8,11 @@
#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/format.hpp>
#include <openssl/err.h>
#include <openssl/tls1.h>
#include <format>
#include <stdexcept>
#include <string>
#include <type_traits>
@@ -38,8 +38,8 @@ public:
if (ec && sslVerifyDir.empty())
{
Throw<std::runtime_error>(boost::str(
boost::format("Failed to set_default_verify_paths: %s") % ec.message()));
Throw<std::runtime_error>(
std::format("Failed to set_default_verify_paths: {}", ec.message()));
}
}
else
@@ -54,7 +54,7 @@ public:
if (ec)
{
Throw<std::runtime_error>(
boost::str(boost::format("Failed to add verify path: %s") % ec.message()));
std::format("Failed to add verify path: {}", ec.message()));
}
}
}

View File

@@ -1,10 +1,10 @@
syntax = "proto2";
package protocol;
// Unused numbers in the list below may have been used previously. Please don't
// reassign them for reuse unless you are 100% certain that there won't be a
// conflict. Even if you're sure, it's probably best to assign a new type.
enum MessageType {
// Previously used - don't reuse.
reserved 0 to 1, 4, 6 to 14, 16 to 29, 36 to 40, 43 to 54, 61 to 62;
mtMANIFESTS = 2;
mtPING = 3;
mtCLUSTER = 5;
@@ -17,7 +17,6 @@ enum MessageType {
mtHAVE_SET = 35;
mtVALIDATION = 41;
mtGET_OBJECTS = 42;
mtVALIDATOR_LIST = 54;
mtSQUELCH = 55;
mtVALIDATOR_LIST_COLLECTION = 56;
mtPROOF_PATH_REQ = 57;
@@ -191,14 +190,6 @@ message TMHaveTransactionSet {
required bytes hash = 2;
}
// Validator list (UNL)
message TMValidatorList {
required bytes manifest = 1;
required bytes blob = 2;
required bytes signature = 3;
required uint32 version = 4;
}
// Validator List v2
message ValidatorBlobInfo {
optional bytes manifest = 1;
@@ -333,14 +324,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 {

View File

@@ -47,7 +47,7 @@ ammLPTIssue(Asset const& asset1, Asset const& asset2, AccountID const& ammAccoun
/**
* Validate the amount.
* If validZero is false and amount is beast::zero then invalid amount.
* If validZero is false and amount is beast::kZero then invalid amount.
* Return error code if invalid amount.
* If pair then validate amount's issue matches one of the pair's issue.
*/

View File

@@ -154,7 +154,7 @@ T
toAmount(Asset const& asset, Number const& n, Number::RoundingMode mode = Number::getround())
{
SaveNumberRoundMode const rm(Number::getround());
if (isXRP(asset))
if (asset.integral())
Number::setround(mode);
if constexpr (std::is_same_v<IOUAmount, T>)

View File

@@ -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

View File

@@ -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;
//------------------------------------------------------------------------------
/**

View File

@@ -9,6 +9,7 @@
#include <mpt_protocol.h>
#include <secp256k1_mpt.h>
#include <chrono>
#include <cstddef>
#include <cstdint>
@@ -327,6 +328,36 @@ enum class VaultVersion : uint8_t {
CashBasis,
};
/**
* Vault kind. Distinguishes closed-ended vaults from the default open-ended
* kind. Persisted as sfVaultKind (UINT8); absent means OpenEnded.
*/
enum class VaultKind : std::uint8_t {
OpenEnded = 0,
ClosedEnded = 1,
};
/**
* Lifecycle phase of a vault. Open-ended vaults are always NoPhase; the other
* three values are the phases of a closed-ended vault.
*/
enum class VaultPhase : std::uint8_t {
NoPhase = 0,
Subscription,
Investment,
Redemption,
};
/**
* Bounds on the length of a closed-ended vault's Investment phase
* (RedemptionDate - SubscriptionDate). At vault creation the gap must satisfy
* kMinInvestmentPeriod <= gap < kMaxInvestmentPeriod.
*/
constexpr std::uint32_t kMinInvestmentPeriod =
std::chrono::seconds{std::chrono::minutes{1}}.count();
// This is 946708560 seconds which 30 x 365.2425 days (the average length of a Gregorian year).
constexpr std::uint32_t kMaxInvestmentPeriod = std::chrono::seconds{std::chrono::years{30}}.count();
/**
* Maximum recursion depth for vault shares being put as an asset inside
* another vault; counted from 0

View File

@@ -75,13 +75,6 @@ operator==(TAmounts<In, Out> const& lhs, TAmounts<In, Out> const& rhs) noexcept
return lhs.in == rhs.in && lhs.out == rhs.out;
}
template <class In, class Out>
bool
operator!=(TAmounts<In, Out> const& lhs, TAmounts<In, Out> const& rhs) noexcept
{
return !(lhs == rhs);
}
//------------------------------------------------------------------------------
// XRPL specific constant used for parsing qualities and other things
@@ -271,12 +264,6 @@ public:
return lhs.value_ == rhs.value_;
}
friend bool
operator!=(Quality const& lhs, Quality const& rhs) noexcept
{
return !(lhs == rhs);
}
friend std::ostream&
operator<<(std::ostream& os, Quality const& quality)
{

View File

@@ -60,6 +60,15 @@ public:
std::optional<Number>
outFromAvgQ(Quality const& quality);
/**
* Return whether `out` produces at least the requested
* average quality.
* @param quality requested average quality (quality limit)
* @param out output amount to test
*/
[[nodiscard]] bool
satisfiesAvgQ(Quality const& quality, Number const& out) const;
/**
* Return true if the quality function is constant
*/

View File

@@ -98,9 +98,6 @@ public:
*/
bool
operator==(Rules const&) const;
bool
operator!=(Rules const& other) const;
};
std::optional<Rules> const&

View File

@@ -642,12 +642,6 @@ operator==(STAmount const& lhs, STAmount const& rhs);
bool
operator<(STAmount const& lhs, STAmount const& rhs);
inline bool
operator!=(STAmount const& lhs, STAmount const& rhs)
{
return !(lhs == rhs);
}
inline bool
operator>(STAmount const& lhs, STAmount const& rhs)
{

View File

@@ -133,9 +133,6 @@ public:
bool
operator==(STArray const& s) const;
bool
operator!=(STArray const& s) const;
iterator
erase(iterator pos);
@@ -283,12 +280,6 @@ STArray::operator==(STArray const& s) const
return v_ == s.v_;
}
inline bool
STArray::operator!=(STArray const& s) const
{
return v_ != s.v_;
}
inline STArray::iterator
STArray::erase(iterator pos)
{

View File

@@ -140,8 +140,6 @@ public:
bool
operator==(STBase const& t) const;
bool
operator!=(STBase const& t) const;
template <class D>
D&

View File

@@ -93,12 +93,6 @@ operator==(STCurrency const& lhs, STCurrency const& rhs)
return lhs.currency() == rhs.currency();
}
inline bool
operator!=(STCurrency const& lhs, STCurrency const& rhs)
{
return !operator==(lhs, rhs);
}
inline bool
operator<(STCurrency const& lhs, STCurrency const& rhs)
{

View File

@@ -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;
@@ -428,8 +432,6 @@ public:
bool
operator==(STObject const& o) const;
bool
operator!=(STObject const& o) const;
class FieldErr;
@@ -663,36 +665,6 @@ public:
return !lhs.engaged() || *lhs == *rhs;
}
friend bool
operator!=(OptionalProxy const& lhs, std::nullopt_t) noexcept
{
return !(lhs == std::nullopt);
}
friend bool
operator!=(std::nullopt_t, OptionalProxy const& rhs) noexcept
{
return !(rhs == std::nullopt);
}
friend bool
operator!=(OptionalProxy const& lhs, optional_type const& rhs) noexcept
{
return !(lhs == rhs);
}
friend bool
operator!=(optional_type const& lhs, OptionalProxy const& rhs) noexcept
{
return !(lhs == rhs);
}
friend bool
operator!=(OptionalProxy const& lhs, OptionalProxy const& rhs) noexcept
{
return !(lhs == rhs);
}
// Emulate std::optional::value_or
[[nodiscard]] value_type
valueOr(value_type val) const;
@@ -1198,12 +1170,6 @@ STObject::setFieldH160(SField const& field, BaseUInt<160, Tag> const& v)
}
}
inline bool
STObject::operator!=(STObject const& o) const
{
return !(*this == o);
}
template <typename T, typename V>
V
STObject::getFieldByValue(SField const& field) const

View File

@@ -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,11 +109,11 @@ public:
[[nodiscard]] bool
isType(Type const& pe) const;
bool
operator==(STPathElement const& t) const;
[[nodiscard]] size_t
getHash() const;
bool
operator!=(STPathElement const& t) const;
operator==(STPathElement const& t) const;
private:
static std::size_t
@@ -171,12 +172,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 +217,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 +236,9 @@ public:
void
emplaceBack(Args&&... args);
[[nodiscard]] bool
contains(STPath const& path) const;
private:
STBase*
copy(std::size_t n, void* buf) const override;
@@ -417,12 +429,6 @@ STPathElement::operator==(STPathElement const& t) const
accountID_ == t.accountID_ && assetID_ == t.assetID_ && issuerID_ == t.issuerID_;
}
inline bool
STPathElement::operator!=(STPathElement const& t) const
{
return !operator==(t);
}
// ------------ STPath ------------
inline STPath::STPath(std::vector<STPathElement> p) : path_(std::move(p))
@@ -515,12 +521,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 +549,7 @@ inline void
STPathSet::pushBack(STPath const& e)
{
value_.push_back(e);
seenHashes_.emplace(value_.back());
}
template <typename... Args>
@@ -556,6 +557,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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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
{
@@ -108,12 +123,6 @@ public:
return (lhs.value() == rhs.value());
}
friend constexpr bool
operator!=(SeqProxy lhs, SeqProxy rhs)
{
return !(lhs == rhs);
}
friend constexpr bool
operator<(SeqProxy lhs, SeqProxy rhs)
{

View File

@@ -265,20 +265,10 @@ public:
return v == data_;
}
bool
operator!=(Blob const& v) const
{
return v != data_;
}
bool
operator==(Serializer const& v) const
{
return v.data_ == data_;
}
bool
operator!=(Serializer const& v) const
{
return v.data_ != data_;
}
static int
decodeLengthLength(int b1);

View File

@@ -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.
@@ -430,8 +425,7 @@ inline constexpr FlagValue tfDepositSubTx =
ASF_FLAG(asfDefaultRipple, 8) \
ASF_FLAG(asfDepositAuth, 9) \
ASF_FLAG(asfAuthorizedNFTokenMinter, 10) \
/* 11 is reserved for Hooks amendment */ \
/* ASF_FLAG(asfTshCollect, 11) */ \
/* 11 is unused */ \
ASF_FLAG(asfDisallowIncomingNFTokenOffer, 12) \
ASF_FLAG(asfDisallowIncomingCheck, 13) \
ASF_FLAG(asfDisallowIncomingPayChan, 14) \

View File

@@ -258,13 +258,6 @@ public:
return value_ == other;
}
template <Compatible<ValueUnit> Other>
constexpr bool
operator!=(ValueUnit<unit_type, Other> const& other) const
{
return !operator==(other);
}
constexpr bool
operator<(ValueUnit const& other) const
{

View File

@@ -152,10 +152,4 @@ operator==(STVar const& lhs, STVar const& rhs)
return lhs.get().isEquivalent(rhs.get());
}
inline bool
operator!=(STVar const& lhs, STVar const& rhs)
{
return !(lhs == rhs);
}
} // namespace xrpl::detail

View File

@@ -59,7 +59,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 +99,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)

View File

@@ -404,7 +404,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},
@@ -506,6 +506,9 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfWithdrawalPolicy, SoeRequired},
{sfScale, SoeDefault},
{sfLEVersion, SoeDefault},
{sfVaultKind, SoeDefault},
{sfSubscriptionDate, SoeOptional},
{sfRedemptionDate, SoeOptional},
// no SharesTotal ever (use MPTIssuance.sfOutstandingAmount)
// no PermissionedDomainID ever (use MPTIssuance.sfDomainID)
}))

View File

@@ -23,9 +23,11 @@ TYPED_SFIELD(sfLEVersion, UINT8, 6)
// 8-bit integers (uncommon)
TYPED_SFIELD(sfTickSize, UINT8, 16)
TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17)
TYPED_SFIELD(sfHookResult, UINT8, 18)
// 18 unused
TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19)
TYPED_SFIELD(sfWithdrawalPolicy, UINT8, 20)
TYPED_SFIELD(sfContractResult, UINT8, 21)
TYPED_SFIELD(sfVaultKind, UINT8, 22)
// 16-bit integers (common)
TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::kSmdNever)
@@ -37,10 +39,7 @@ TYPED_SFIELD(sfDiscountedFee, UINT16, 6)
// 16-bit integers (uncommon)
TYPED_SFIELD(sfVersion, UINT16, 16)
TYPED_SFIELD(sfHookStateChangeCount, UINT16, 17)
TYPED_SFIELD(sfHookEmitCount, UINT16, 18)
TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
// 17 to 20 unused
TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22) // 1/10 basis points (bips)
@@ -91,14 +90,12 @@ TYPED_SFIELD(sfTicketSequence, UINT32, 41)
TYPED_SFIELD(sfNFTokenTaxon, UINT32, 42)
TYPED_SFIELD(sfMintedNFTokens, UINT32, 43)
TYPED_SFIELD(sfBurnedNFTokens, UINT32, 44)
TYPED_SFIELD(sfHookStateCount, UINT32, 45)
TYPED_SFIELD(sfEmitGeneration, UINT32, 46)
// 47 reserved for Hooks
// 45 to 47 unused
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)
@@ -120,6 +117,8 @@ TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 71)
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 72)
TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
TYPED_SFIELD(sfSubscriptionDate, UINT32, 75)
TYPED_SFIELD(sfRedemptionDate, UINT32, 76)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -137,9 +136,7 @@ TYPED_SFIELD(sfNFTokenOfferNode, UINT64, 12)
TYPED_SFIELD(sfEmitBurden, UINT64, 13)
// 64-bit integers (uncommon)
TYPED_SFIELD(sfHookOn, UINT64, 16)
TYPED_SFIELD(sfHookInstructionCount, UINT64, 17)
TYPED_SFIELD(sfHookReturnCode, UINT64, 18)
// 16 to 18 unused
TYPED_SFIELD(sfReferenceCount, UINT64, 19)
TYPED_SFIELD(sfXChainClaimID, UINT64, 20)
TYPED_SFIELD(sfXChainAccountCreateCount, UINT64, 21)
@@ -203,10 +200,7 @@ TYPED_SFIELD(sfPreviousPageMin, UINT256, 26)
TYPED_SFIELD(sfNextPageMin, UINT256, 27)
TYPED_SFIELD(sfNFTokenBuyOffer, UINT256, 28)
TYPED_SFIELD(sfNFTokenSellOffer, UINT256, 29)
TYPED_SFIELD(sfHookStateKey, UINT256, 30)
TYPED_SFIELD(sfHookHash, UINT256, 31)
TYPED_SFIELD(sfHookNamespace, UINT256, 32)
TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
// 30 to 33 unused
TYPED_SFIELD(sfDomainID, UINT256, 34)
TYPED_SFIELD(sfVaultID, UINT256, 35,
SField::kSmdPseudoAccount | SField::kSmdDefault)
@@ -237,8 +231,9 @@ TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15, SField::kSmdNeedsAsset
TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16)
TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset | SField::kSmdDefault)
// int32
// 32-bit signed (common)
TYPED_SFIELD(sfLoanScale, INT32, 1)
TYPED_SFIELD(sfRemainingOwnerCountDelta, INT32, 2)
// currency amount (common)
TYPED_SFIELD(sfAmount, AMOUNT, 1)
@@ -260,15 +255,13 @@ TYPED_SFIELD(sfMinimumOffer, AMOUNT, 16)
TYPED_SFIELD(sfRippleEscrow, AMOUNT, 17)
TYPED_SFIELD(sfDeliveredAmount, AMOUNT, 18)
TYPED_SFIELD(sfNFTokenBrokerFee, AMOUNT, 19)
// Reserve 20 & 21 for Hooks.
// 20 to 21 unused
// currency amount (fees)
TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
// currency amount (AMM)
// currency amount (more)
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
@@ -278,6 +271,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)
@@ -302,10 +296,7 @@ TYPED_SFIELD(sfMasterSignature, VL, 18, SField::kSmdDefault, SFi
TYPED_SFIELD(sfUNLModifyValidator, VL, 19)
TYPED_SFIELD(sfValidatorToDisable, VL, 20)
TYPED_SFIELD(sfValidatorToReEnable, VL, 21)
TYPED_SFIELD(sfHookStateData, VL, 22)
TYPED_SFIELD(sfHookReturnString, VL, 23)
TYPED_SFIELD(sfHookParameterName, VL, 24)
TYPED_SFIELD(sfHookParameterValue, VL, 25)
// 22 to 25 unused
TYPED_SFIELD(sfDIDDocument, VL, 26)
TYPED_SFIELD(sfData, VL, 27)
TYPED_SFIELD(sfAssetClass, VL, 28)
@@ -343,7 +334,7 @@ TYPED_SFIELD(sfHolder, ACCOUNT, 11)
TYPED_SFIELD(sfDelegate, ACCOUNT, 12)
// account (uncommon)
TYPED_SFIELD(sfHookAccount, ACCOUNT, 16)
// 16 unused
TYPED_SFIELD(sfOtherChainSource, ACCOUNT, 18)
TYPED_SFIELD(sfOtherChainDestination, ACCOUNT, 19)
TYPED_SFIELD(sfAttestationSignerAccount, ACCOUNT, 20)
@@ -396,7 +387,7 @@ UNTYPED_SFIELD(sfMemo, OBJECT, 10)
UNTYPED_SFIELD(sfSignerEntry, OBJECT, 11)
UNTYPED_SFIELD(sfNFToken, OBJECT, 12)
UNTYPED_SFIELD(sfEmitDetails, OBJECT, 13)
UNTYPED_SFIELD(sfHook, OBJECT, 14)
// 14 unused
UNTYPED_SFIELD(sfPermission, OBJECT, 15)
// inner object (uncommon)
@@ -404,11 +395,7 @@ UNTYPED_SFIELD(sfSigner, OBJECT, 16)
// 17 unused
UNTYPED_SFIELD(sfMajority, OBJECT, 18)
UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19)
UNTYPED_SFIELD(sfEmittedTxn, OBJECT, 20)
UNTYPED_SFIELD(sfHookExecution, OBJECT, 21)
UNTYPED_SFIELD(sfHookDefinition, OBJECT, 22)
UNTYPED_SFIELD(sfHookParameter, OBJECT, 23)
UNTYPED_SFIELD(sfHookGrant, OBJECT, 24)
// 20 to 24 unused
UNTYPED_SFIELD(sfVoteEntry, OBJECT, 25)
UNTYPED_SFIELD(sfAuctionSlot, OBJECT, 26)
UNTYPED_SFIELD(sfAuthAccount, OBJECT, 27)
@@ -436,16 +423,14 @@ UNTYPED_SFIELD(sfSufficient, ARRAY, 7)
UNTYPED_SFIELD(sfAffectedNodes, ARRAY, 8)
UNTYPED_SFIELD(sfMemos, ARRAY, 9)
UNTYPED_SFIELD(sfNFTokens, ARRAY, 10)
UNTYPED_SFIELD(sfHooks, ARRAY, 11)
// 11 unused
UNTYPED_SFIELD(sfVoteSlots, ARRAY, 12)
UNTYPED_SFIELD(sfAdditionalBooks, ARRAY, 13)
// array of objects (uncommon)
UNTYPED_SFIELD(sfMajorities, ARRAY, 16)
UNTYPED_SFIELD(sfDisabledValidators, ARRAY, 17)
UNTYPED_SFIELD(sfHookExecutions, ARRAY, 18)
UNTYPED_SFIELD(sfHookParameters, ARRAY, 19)
UNTYPED_SFIELD(sfHookGrants, ARRAY, 20)
// 18 to 20 unused
UNTYPED_SFIELD(sfXChainClaimAttestations, ARRAY, 21)
UNTYPED_SFIELD(sfXChainCreateAccountAttestations, ARRAY, 22)
// 23 unused

View File

@@ -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},
}))
@@ -862,6 +862,9 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
{sfWithdrawalPolicy, SoeOptional},
{sfData, SoeOptional},
{sfScale, SoeOptional},
{sfVaultKind, SoeOptional},
{sfSubscriptionDate, SoeOptional},
{sfRedemptionDate, SoeOptional},
}))
/** This transaction updates a single asset vault. */
@@ -1085,7 +1088,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 +1192,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.

View File

@@ -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;
}

View File

@@ -311,6 +311,78 @@ public:
{
return this->sle_->isFieldPresent(sfLEVersion);
}
/**
* @brief Get sfVaultKind (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT8::type::value_type>
getVaultKind() const
{
if (hasVaultKind())
return this->sle_->at(sfVaultKind);
return std::nullopt;
}
/**
* @brief Check if sfVaultKind is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasVaultKind() const
{
return this->sle_->isFieldPresent(sfVaultKind);
}
/**
* @brief Get sfSubscriptionDate (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSubscriptionDate() const
{
if (hasSubscriptionDate())
return this->sle_->at(sfSubscriptionDate);
return std::nullopt;
}
/**
* @brief Check if sfSubscriptionDate is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSubscriptionDate() const
{
return this->sle_->isFieldPresent(sfSubscriptionDate);
}
/**
* @brief Get sfRedemptionDate (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getRedemptionDate() const
{
if (hasRedemptionDate())
return this->sle_->at(sfRedemptionDate);
return std::nullopt;
}
/**
* @brief Check if sfRedemptionDate is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasRedemptionDate() const
{
return this->sle_->isFieldPresent(sfRedemptionDate);
}
};
/**
@@ -543,6 +615,39 @@ public:
return *this;
}
/**
* @brief Set sfVaultKind (SoeDefault)
* @return Reference to this builder for method chaining.
*/
VaultBuilder&
setVaultKind(std::decay_t<typename SF_UINT8::type::value_type> const& value)
{
object_[sfVaultKind] = value;
return *this;
}
/**
* @brief Set sfSubscriptionDate (SoeOptional)
* @return Reference to this builder for method chaining.
*/
VaultBuilder&
setSubscriptionDate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSubscriptionDate] = value;
return *this;
}
/**
* @brief Set sfRedemptionDate (SoeOptional)
* @return Reference to this builder for method chaining.
*/
VaultBuilder&
setRedemptionDate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfRedemptionDate] = value;
return *this;
}
/**
* @brief Build and return the completed Vault wrapper.
* @param index The ledger entry index.

View File

@@ -19,7 +19,7 @@ class ConfidentialMPTConvertBuilder;
* @brief Transaction: ConfidentialMPTConvert
*
* Type: ttCONFIDENTIAL_MPT_CONVERT (85)
* Delegable: Delegation::Delegable
* Delegable: Delegation::NotDelegable
* Amendment: featureConfidentialTransfer
* Privileges: NoPriv
*

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -214,6 +214,84 @@ public:
{
return this->tx_->isFieldPresent(sfScale);
}
/**
* @brief Get sfVaultKind (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT8::type::value_type>
getVaultKind() const
{
if (hasVaultKind())
{
return this->tx_->at(sfVaultKind);
}
return std::nullopt;
}
/**
* @brief Check if sfVaultKind is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasVaultKind() const
{
return this->tx_->isFieldPresent(sfVaultKind);
}
/**
* @brief Get sfSubscriptionDate (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSubscriptionDate() const
{
if (hasSubscriptionDate())
{
return this->tx_->at(sfSubscriptionDate);
}
return std::nullopt;
}
/**
* @brief Check if sfSubscriptionDate is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSubscriptionDate() const
{
return this->tx_->isFieldPresent(sfSubscriptionDate);
}
/**
* @brief Get sfRedemptionDate (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getRedemptionDate() const
{
if (hasRedemptionDate())
{
return this->tx_->at(sfRedemptionDate);
}
return std::nullopt;
}
/**
* @brief Check if sfRedemptionDate is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasRedemptionDate() const
{
return this->tx_->isFieldPresent(sfRedemptionDate);
}
};
/**
@@ -338,6 +416,39 @@ public:
return *this;
}
/**
* @brief Set sfVaultKind (SoeOptional)
* @return Reference to this builder for method chaining.
*/
VaultCreateBuilder&
setVaultKind(std::decay_t<typename SF_UINT8::type::value_type> const& value)
{
object_[sfVaultKind] = value;
return *this;
}
/**
* @brief Set sfSubscriptionDate (SoeOptional)
* @return Reference to this builder for method chaining.
*/
VaultCreateBuilder&
setSubscriptionDate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSubscriptionDate] = value;
return *this;
}
/**
* @brief Set sfRedemptionDate (SoeOptional)
* @return Reference to this builder for method chaining.
*/
VaultCreateBuilder&
setRedemptionDate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfRedemptionDate] = value;
return *this;
}
/**
* @brief Build and return the VaultCreate wrapper.
* @param publicKey The public key for signing.

View File

@@ -2,6 +2,9 @@
#include <array>
#include <cstdint>
#include <format>
#include <string>
#include <string_view>
namespace xrpl {
@@ -9,9 +12,29 @@ namespace xrpl {
// These pragmas are built at startup and applied to all database
// connections, unless otherwise noted.
inline constexpr char const* kCommonDbPragmaJournal{"PRAGMA journal_mode=%s;"};
inline constexpr char const* kCommonDbPragmaSync{"PRAGMA synchronous=%s;"};
inline constexpr char const* kCommonDbPragmaTemp{"PRAGMA temp_store=%s;"};
//
// They are exposed as functions rather than as format-string constants so
// that the un-substituted template can never reach sqlite: an unrecognized
// pragma value is silently ignored, so forgetting to interpolate would
// leave the setting at its default instead of failing loudly.
[[nodiscard]] inline std::string
commonDbPragmaJournal(std::string_view journalMode)
{
return std::format("PRAGMA journal_mode={};", journalMode);
}
[[nodiscard]] inline std::string
commonDbPragmaSync(std::string_view synchronous)
{
return std::format("PRAGMA synchronous={};", synchronous);
}
[[nodiscard]] inline std::string
commonDbPragmaTemp(std::string_view tempStore)
{
return std::format("PRAGMA temp_store={};", tempStore);
}
// A warning will be logged if any lower-safety sqlite tuning settings
// are used and at least this much ledger history is configured. This
// includes full history nodes. This is because such a large amount of

View File

@@ -6,13 +6,12 @@
#include <xrpl/core/StartUpType.h>
#include <xrpl/rdb/SociDB.h>
#include <boost/filesystem/path.hpp>
#include <soci/statement.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
@@ -80,7 +79,7 @@ public:
StartUpType startUp = StartUpType::Normal;
bool standAlone = false;
boost::filesystem::path dataDir;
std::filesystem::path dataDir;
// Indicates whether or not to return the `globalPragma`
// from commonPragma()
bool useGlobalPragma = false;
@@ -143,7 +142,7 @@ public:
template <std::size_t N, std::size_t M>
DatabaseCon(
boost::filesystem::path const& dataDir,
std::filesystem::path const& dataDir,
std::string const& dbName,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,
@@ -155,7 +154,7 @@ public:
// Use this constructor to setup checkpointing
template <std::size_t N, std::size_t M>
DatabaseCon(
boost::filesystem::path const& dataDir,
std::filesystem::path const& dataDir,
std::string const& dbName,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,
@@ -190,7 +189,7 @@ private:
template <std::size_t N, std::size_t M>
DatabaseCon(
boost::filesystem::path const& pPath,
std::filesystem::path const& pPath,
std::vector<std::string> const* commonPragma,
std::array<std::string, N> const& pragma,
std::array<char const*, M> const& initSQL,

View File

@@ -14,7 +14,6 @@
#include <xrpl/protocol/TxMeta.h>
#include <xrpl/protocol/TxSearched.h>
#include <boost/filesystem.hpp>
#include <boost/variant.hpp>
#include <concepts>

View File

@@ -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.

View File

@@ -11,6 +11,7 @@
#include <xrpl/server/Manifest.h>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
@@ -22,6 +23,39 @@ namespace xrpl {
// Operations that clients may wish to perform against the network
// Master operational handler, server sequencer, network tracker
/**
* Maximum number of subscriptions a single client connection may hold at once.
*
* Applies to the account, real-time account, and account-history subscriptions
* tracked on one InfoSub (the sets counted by totalSubscriptionCount), bounding
* the disconnect-time cleanup of those sets. Book subscriptions are tracked
* separately (OrderBookDB) and are not counted here. Generous enough for
* legitimate power users such as block explorers.
*/
constexpr std::size_t kMaxSubscriptionsPerConnection = 100'000;
/**
* Whether adding @p additional subscriptions to a connection already holding
* @p current would exceed the cap.
*
* Pure arithmetic split out so it can be unit-tested without a live
* connection. The first term avoids underflow in the subtraction.
*
* @param current Subscriptions already tracked on the connection.
* @param additional Subscriptions a request would add.
* @param cap The effective per-connection cap. Defaults to the
* built-in limit; callers may pass a configured override.
* @return true if the request must be rejected to stay within the cap.
*/
[[nodiscard]] constexpr bool
exceedsSubscriptionCap(
std::size_t current,
std::size_t additional,
std::size_t cap = kMaxSubscriptionsPerConnection)
{
return additional > cap || current > cap - additional;
}
class InfoSubRequest : public CountedObject<InfoSubRequest>
{
public:
@@ -44,12 +78,12 @@ public:
* map.
*
* @note Lifetime contract: every `InfoSub` instance MUST be destroyed
* before the backing `Source`. NetworkOPsImp shutdown drops all
* subscriber strong refs before its own teardown to satisfy this.
* before the backing `Source`. NetworkOPsImp shutdown drops all
* subscriber strong refs before its own teardown to satisfy this.
* @note Thread-safety: per-instance state is guarded by `lock_`. The
* destructor reads tracking sets without taking `lock_` because
* the strong-pointer ref-count is zero at destruction time, so
* no other thread can be calling the public mutators.
* destructor reads tracking sets without taking `lock_` because
* the strong-pointer ref-count is zero at destruction time, so
* no other thread can be calling the public mutators.
*/
class InfoSub : public CountedObject<InfoSub>
{
@@ -117,6 +151,34 @@ public:
AccountID const& account,
bool historyOnly) = 0;
/**
* Schedule the server-side teardown of a disconnecting connection's
* account subscriptions off the destructor thread.
*
* The implementation posts a low-priority JobQueue task that erases the
* entries in bounded chunks, so `~InfoSub` returns immediately instead
* of running the erase loop inline. The sets are taken by value so the
* job owns its copies and never references the destroyed `InfoSub`.
* Cleanup is keyed on `seq` (unique per connection), so deferring it
* cannot disturb a reconnected client reusing the same accounts.
*
* @param seq The disconnecting connection's unique subscription id.
* @param rtAccounts Real-time account subscriptions to remove.
* @param normalAccounts Normal account subscriptions to remove.
* @param historyAccounts Account-history subscriptions to remove.
*
* @note The implementing `Source` must outlive any job it posts. If the
* JobQueue is already stopping (process shutdown), the job is not
* enqueued; the cleanup is skipped because the server-side maps
* are about to be destroyed and no publishing can run.
*/
virtual void
scheduleAccountCleanup(
std::uint64_t seq,
hash_set<AccountID> rtAccounts,
hash_set<AccountID> normalAccounts,
hash_set<AccountID> historyAccounts) = 0;
// VFALCO TODO Document the bool return value
virtual bool
subLedger(ref ispListener, json::Value& jvResult) = 0;
@@ -153,12 +215,12 @@ public:
* @param ispListener The subscriber requesting removal.
* @param book The order book to unsubscribe from.
* @return true if the entry was present and removed, false if the
* subscriber was not subscribed to @p book.
* subscriber was not subscribed to @p book.
*
* @note Thread-safety: acquires subLock_ internally.
* @note Thread-safety: acquires bookLock_ internally.
* @note Do NOT call from ~InfoSub(). Use unsubBookInternal instead
* to avoid a redundant write-back to bookSubscriptions_ on a
* partially-destroyed object.
* to avoid a redundant write-back to bookSubscriptions_ on a
* partially-destroyed object.
*/
virtual bool
unsubBook(ref ispListener, Book const&) = 0;
@@ -173,9 +235,9 @@ public:
* @param uListener The sequence number of the subscriber being torn down.
* @param book The order book entry to remove.
* @return true if the entry was present and removed, false otherwise
* (e.g., already removed by a concurrent RPC unsubscribe).
* (e.g., already removed by a concurrent RPC unsubscribe).
*
* @note Thread-safety: acquires subLock_ internally.
* @note Thread-safety: acquires bookLock_ internally.
*/
virtual bool
unsubBookInternal(std::uint64_t uListener, Book const&) = 0;
@@ -221,8 +283,8 @@ public:
/**
* Journal used by InfoSub for diagnostics that occur after the
* owning subsystem (e.g. application-level Logs) is the only
* surviving sink — primarily destructor-time cleanup failures.
* owning subsystem (e.g. application-level Logs) is the only
* surviving sink — primarily destructor-time cleanup failures.
*/
[[nodiscard]] virtual beast::Journal const&
journal() const = 0;
@@ -243,6 +305,56 @@ public:
[[nodiscard]] std::uint64_t
getSeq() const;
/**
* Return the number of subscriptions currently tracked on this
* connection.
*
* The combined size of the per-connection account, real-time account, and
* account-history subscription sets. `doSubscribe` reads this to enforce
* the per-connection subscription cap before admitting more.
*
* @return The total tracked subscription count for this connection.
*
* @note Thread-safe: takes `lock_` for the read; read-only.
*/
[[nodiscard]] std::size_t
totalSubscriptionCount() const;
/**
* Enforce the cap and reserve a request's net-new accounts, atomically.
*
* Under one hold of `lock_`: count the net-new entries in the two sets,
* check the total against @p cap, and insert them only if it fits.
* All-or-nothing. Doing check and insert together stops two concurrent
* requests sharing an InfoSub (the admin subscribe-by-url path) from both
* passing the check before either records its accounts. The server-side
* maps are populated afterwards by subAccount, whose re-insert is a no-op.
*
* @param proposedAccounts Real-time (accounts_proposed) ids to reserve.
* @param normalAccounts Normal (accounts) ids to reserve.
* @param cap The effective per-connection cap.
* @return true if reserved; false if the request must be rejected.
* @note Thread-safe: takes `lock_`.
*/
[[nodiscard]] bool
tryReserveAccountSubscriptions(
hash_set<AccountID> const& proposedAccounts,
hash_set<AccountID> const& normalAccounts,
std::size_t cap);
/**
* Whether this connection already tracks an account-history for @p account.
*
* `doSubscribe` reads this to charge the cap for an account_history_tx_stream
* only when it is net-new, matching the account branches.
*
* @param account The account an account_history_tx_stream would add.
* @return true if @p account is already in the account-history set.
* @note Thread-safe: takes `lock_`; read-only.
*/
[[nodiscard]] bool
hasAccountHistorySubscription(AccountID const& account) const;
void
onSendEmpty();
@@ -302,7 +414,9 @@ public:
getApiVersion() const noexcept;
protected:
std::mutex lock_;
// Mutable so the read-only totalSubscriptionCount() accessor can lock it
// from a const method; locking semantics are otherwise unchanged.
mutable std::mutex lock_;
private:
Consumer consumer_;

View File

@@ -3,12 +3,14 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base64.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SecretKey.h>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
@@ -43,12 +45,15 @@ namespace xrpl {
dynamically generates the signatureless form when it needs to verify
the signature.
An instance of ManifestCache stores, for each trusted validator, (a) its
An instance of ManifestCache stores, for each known validator, (a) its
master public key, and (b) the most senior of all valid manifests it has
seen for that validator, if any. On startup, the [validator_token] config
entry (which contains the manifest for this validator) is decoded and
added to the manifest cache. Other manifests are added as "gossip"
received from xrpld peers.
received from xrpld peers, including ones for validators this node does not
trust. Manifests for untrusted validators are capped (kMaxUntrustedCount)
so peer gossip cannot grow the cache without bound; trusted validators are
not capped. Entries are never evicted, so a stored revocation is permanent.
When an ephemeral key is compromised, a new signing key pair is created,
along with a new manifest vouching for it (with a higher sequence number),
@@ -164,6 +169,100 @@ struct Manifest
std::string
to_string(Manifest const& m);
/**
* Largest a valid manifest can be, in decoded bytes.
*
* A manifest has a fixed set of fields. Each is serialized as a field header
* (1-2 bytes), an optional length prefix (1 byte for these sizes), and the
* field body. Taking every field at its largest gives the maximum below, so
* anything larger cannot be a valid manifest.
*
* Field header + length + body = bytes
* sfVersion (U16) 2 0 2 4
* sfSequence (U32) 1 0 4 5
* sfPublicKey (33) 1 1 33 35
* sfSigningPubKey (33) 1 1 33 35
* sfSignature (72) 1 1 72 74
* sfMasterSignature (72) 2 1 72 75
* sfDomain (128) 1 1 128 130
* -----
* 358
*/
constexpr std::size_t kMaxManifestBytes = 358;
/**
* Largest a valid manifest can be, in base64 characters.
*
* base64 encodes 3 bytes as 4 characters, so this is the encoded form of
* @ref kMaxManifestBytes. Callers that receive a base64 manifest should
* reject anything longer than this before decoding, to avoid allocating
* memory for an oversized input.
*/
constexpr std::size_t kMaxManifestBase64 = base64::encodedSize(kMaxManifestBytes);
/**
* Default number of untrusted manifests to store in cache and allowed
* in one Manifest message.
*
* Bounds unlisted validators two ways. In the cache, a manifest for a
* brand-new unlisted key is rejected once this many are held, so peer gossip
* cannot grow the cache without end. In a TMManifests message, this many are
* sent and processed, so a peer sending its whole cache cannot force unbounded
* work.
*
* Operators can override this with `[overlay] max_untrusted_count`. Both users
* read the configured value and fall back to this default.
*/
constexpr std::size_t kMaxUntrustedCount = 300;
/**
* Default number of trusted manifests allowed in a Manifest message.
* Not used atm while creating the message, but used to calculate the higher limit on
* received message size. Introduced to maintain consistency. Future implementation
* will use this limit.
*
* Trusted manifests are never dropped: every one this node holds is sent, and
* every one received is processed, since dropping one would delay a validator
* key rotation. This count only sizes the largest message accepted, so it must
* stay above any realistic validator list. Cap can be increased in the config
* file if messages get rejected with actual trusted manifest count crossing
* configured(or else default) value.
* Operators can override this with `[overlay] max_trusted_count`.
*/
constexpr std::size_t kMaxTrustedCount = 300;
/**
* Number of untrusted manifests to store in cache and allowed
* in one Manifest message..
*
* Returns the operator's override when one is configured, otherwise
* @ref kMaxUntrustedCount. Config stores an override rather than the default
* itself because the core module cannot depend on this module.
*
* @param configured The value from `[overlay] max_untrusted_count`, or
* `std::nullopt` when the operator did not set it.
*/
constexpr std::size_t
untrustedManifestCount(std::optional<std::size_t> const& configured)
{
return configured.value_or(kMaxUntrustedCount);
}
/**
* Number of trusted manifests allowed in a Manifest message.
*
* Not a cap on how many are sent or processed; see @ref kMaxTrustedCount.
* but used to calculate the higher limit on received message size.
*
* @param configured The value from `[overlay] max_trusted_count`, or
* `std::nullopt` when the operator did not set it.
*/
constexpr std::size_t
trustedManifestCount(std::optional<std::size_t> const& configured)
{
return configured.value_or(kMaxTrustedCount);
}
/**
* Constructs Manifest from serialized string
*
@@ -172,7 +271,7 @@ to_string(Manifest const& m);
* @return `std::nullopt` if string is invalid
*
* @note This does not verify manifest signatures.
* `Manifest::verify` should be called after constructing manifest.
* `Manifest::verify` should be called after constructing manifest.
*/
/** @{ */
std::optional<Manifest>
@@ -207,12 +306,6 @@ operator==(Manifest const& lhs, Manifest const& rhs)
lhs.serialized == rhs.serialized;
}
inline bool
operator!=(Manifest const& lhs, Manifest const& rhs)
{
return !(lhs == rhs);
}
struct ValidatorToken
{
std::string manifest;
@@ -225,30 +318,17 @@ loadValidatorToken(
beast::Journal journal = beast::Journal(beast::Journal::getNullSink()));
enum class ManifestDisposition {
/**
* Manifest is valid
*/
Accepted = 0,
Accepted = 0, ///< Manifest is valid
/**
* Sequence is too old
*/
Stale,
Stale, ///< Sequence is too old
/**
* The master key is not acceptable to us
*/
BadMasterKey,
BadMasterKey, ///< The master key is not acceptable to us
/**
* The ephemeral key is not acceptable to us
*/
BadEphemeralKey,
BadEphemeralKey, ///< The ephemeral key is not acceptable to us
/**
* Timely, but invalid signature
*/
Invalid
Invalid, ///< Timely, but invalid signature
UntrustedCapacity ///< Unlisted and limit reached
};
inline std::string
@@ -266,11 +346,25 @@ to_string(ManifestDisposition m)
return "badEphemeralKey";
case ManifestDisposition::Invalid:
return "invalid";
case ManifestDisposition::UntrustedCapacity:
return "untrustedCapacity";
default:
return "unknown";
}
}
/**
* Whether a manifest counts against the 'untrusted' cache cap.
*
* Passed to `ManifestCache::applyManifest` with no default, so every caller
* must choose. `Capped` is the safe, flood-resistant value; only listed or
* configured keys should use `Uncapped`.
*/
enum class ManifestRateLimitCapPolicy : std::uint8_t {
Capped, ///< Subject to the untrusted cap (unlisted peer gossip)
Uncapped ///< Bypasses the cap (listed/trusted or config manifests)
};
class DatabaseCon;
/**
@@ -294,8 +388,51 @@ private:
std::atomic<std::uint32_t> seq_{0};
/**
* Master keys of cached manifests for validators this node does not list.
*
* One entry per capped key in `map_`; its size enforces the cap below.
* A key is added when first cached under `Capped` and removed when it
* becomes listed (see `promoteToTrusted`) or an `Uncapped` update arrives,
* never re-added on de-listing. Uncapped keys are not tracked here.
*/
hash_set<PublicKey> untrustedKeys_;
/**
* Maximum number of untrusted master keys kept in the cache.
*
* Once reached, a manifest for a brand-new unlisted key is rejected. Set
* from the config, defaulting to @ref kMaxUntrustedCount.
*/
std::size_t const maxUntrustedCount_;
/**
* Running count of manifests rejected because the untrusted cap was full.
*
* Drives throttled logging (see `kUntrustedRejectCount`). Atomic because
* `applyManifest` may run concurrently.
*/
std::atomic<std::uint64_t> untrustedRejectCount_{0};
/**
* Number of cap rejections between summary warnings.
*
* @see untrustedRejectCount_
*/
static constexpr std::uint64_t kUntrustedRejectCount = 10000;
public:
explicit ManifestCache(beast::Journal j = beast::Journal(beast::Journal::getNullSink())) : j_(j)
/**
* @param j Journal for logging.
*
* @param maxUntrustedCount Untrusted master keys to keep. Pass the
* configured value; defaults to @ref kMaxUntrustedCount. Taken as a
* parameter because this module cannot depend on the config.
*/
explicit ManifestCache(
beast::Journal j = beast::Journal(beast::Journal::getNullSink()),
std::size_t maxUntrustedCount = kMaxUntrustedCount)
: j_(j), maxUntrustedCount_(maxUntrustedCount)
{
}
@@ -378,17 +515,44 @@ public:
/**
* Add manifest to cache.
*
* A brand-new unlisted key is rejected once the untrusted cap is full;
* updates to a cached key and `Uncapped` manifests bypass the cap. The
* caller decides `cap` before calling so the cache lock is not held while
* consulting the validator list, which would risk a lock-ordering deadlock.
*
* @param m Manifest to add
*
* @return `ManifestDisposition::accepted` if successful, or
* `stale` or `invalid` otherwise
* @param cap `Uncapped` skips the untrusted cap; use it for keys that are
* listed, configured, or loaded from the DB. Note `Uncapped` does not
* assert the key is currently trusted (a DB entry may predate a
* de-listing). Callers must state this explicitly so a manifest is
* never left uncapped by omission.
*
* @return `Accepted` if stored, `Stale` if superseded, `Invalid`/
* `BadEphemeralKey` if malformed, or `UntrustedCapacity` if the
* untrusted cap is full.
*
* @par Thread Safety
*
* May be called concurrently
*/
ManifestDisposition
applyManifest(Manifest m);
applyManifest(Manifest m, ManifestRateLimitCapPolicy cap);
/**
* Stop counting a master key against the untrusted cap.
*
* Called when a cached untrusted key becomes listed, freeing its slot.
* Idempotent and a no-op for keys that were never counted.
*
* @param pk Master public key that is now listed/trusted
*
* @par Thread Safety
*
* May be called concurrently
*/
void
promoteToTrusted(PublicKey const& pk);
/**
* Populate manifest cache with manifests in database and config.

View File

@@ -4,8 +4,6 @@
#include <xrpl/rdb/DatabaseCon.h>
#include <xrpl/rdb/SociDB.h>
#include <boost/filesystem.hpp>
#include <string>
namespace xrpl {

View File

@@ -10,6 +10,10 @@
#include <xrpl/rdb/DatabaseCon.h>
#include <xrpl/server/Manifest.h>
// boost::optional (not std::optional) appears in the declarations below,
// because SOCI's into()/use() bindings only support boost::optional.
#include <boost/optional/optional.hpp>
#include <functional>
#include <memory>
#include <string>

View File

@@ -25,6 +25,7 @@
#include <iterator>
#include <list>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
@@ -62,8 +63,7 @@ private:
bool pingActive_ = false;
boost::beast::websocket::ping_data payload_;
error_code ec_;
std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)>
controlCallback_;
std::function<void(boost::beast::websocket::frame_type, std::string_view)> controlCallback_;
public:
template <class Body, class Headers>
@@ -151,7 +151,7 @@ protected:
onPing(error_code const& ec);
void
onPingPong(boost::beast::websocket::frame_type kind, boost::beast::string_view payload);
onPingPong(boost::beast::websocket::frame_type kind, std::string_view payload);
void
onTimer(error_code ec);
@@ -189,9 +189,9 @@ BaseWSPeer<Handler, Impl>::run()
impl().ws_.set_option(port().pmdOptions);
// Must manage the control callback memory outside of the `control_callback`
// function
controlCallback_ = [this](
boost::beast::websocket::frame_type kind,
boost::beast::string_view payload) { onPingPong(kind, payload); };
controlCallback_ = [this](boost::beast::websocket::frame_type kind, std::string_view payload) {
onPingPong(kind, payload);
};
impl().ws_.control_callback(controlCallback_);
startTimer();
closeOnTimer_ = true;
@@ -430,11 +430,11 @@ template <class Handler, class Impl>
void
BaseWSPeer<Handler, Impl>::onPingPong(
boost::beast::websocket::frame_type kind,
boost::beast::string_view payload)
std::string_view payload)
{
if (kind == boost::beast::websocket::frame_type::pong)
{
boost::beast::string_view const p(payload_.begin());
std::string_view const p(payload_.begin(), payload_.size());
if (payload == p)
{
closeOnTimer_ = false;

View File

@@ -484,31 +484,36 @@ private:
// returns the first item at or below this node
SHAMapLeafNode*
firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch = 0) const;
firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch = 0u) const;
// returns the last item at or below this node
SHAMapLeafNode*
lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch = kBranchFactor) const;
lastBelow(
SHAMapTreeNodePtr node,
SharedPtrNodeStack& stack,
unsigned int branch = kBranchFactor) const;
// direction in which belowHelper scans an inner node's branches
enum class BelowDirection { First, Last };
// helper function for firstBelow and lastBelow
SHAMapLeafNode*
belowHelper(
SHAMapTreeNodePtr node,
SharedPtrNodeStack& stack,
int branch,
std::tuple<int, std::function<bool(int)>, std::function<void(int&)>> const& loopParams)
const;
unsigned int branch,
BelowDirection direction) const;
// Simple descent
// Get a child of the specified node
SHAMapTreeNode*
descend(SHAMapInnerNode*, int branch) const;
descend(SHAMapInnerNode*, unsigned int branch) const;
SHAMapTreeNode*
descendThrow(SHAMapInnerNode*, int branch) const;
descendThrow(SHAMapInnerNode*, unsigned int branch) const;
SHAMapTreeNodePtr
descend(SHAMapInnerNode&, int branch) const;
descend(SHAMapInnerNode&, unsigned int branch) const;
SHAMapTreeNodePtr
descendThrow(SHAMapInnerNode&, int branch) const;
descendThrow(SHAMapInnerNode&, unsigned int branch) const;
// Descend with filter
// If pending, callback is called as if it called fetchNodeNT
@@ -516,7 +521,7 @@ private:
SHAMapTreeNode*
descendAsync(
SHAMapInnerNode* parent,
int branch,
unsigned int branch,
SHAMapSyncFilter const* filter,
bool& pending,
descendCallback&&) const;
@@ -525,13 +530,13 @@ private:
descend(
SHAMapInnerNode* parent,
SHAMapNodeID const& parentID,
int branch,
unsigned int branch,
SHAMapSyncFilter const* filter) const;
// Non-storing
// Does not hook the returned node to its parent
SHAMapTreeNodePtr
descendNoStore(SHAMapInnerNode&, int branch) const;
descendNoStore(SHAMapInnerNode&, unsigned int branch) const;
/**
* If there is only one leaf below this node, get its contents
@@ -581,8 +586,8 @@ private:
using StackEntry = std::tuple<
SHAMapInnerNode*, // pointer to the node
SHAMapNodeID, // the node's ID
int, // while child we check first
int, // which child we check next
unsigned int, // which child we check first
unsigned int, // which child we check next
bool>; // whether we've found any missing children yet
// We explicitly choose to specify the use of std::deque here, because
@@ -596,7 +601,7 @@ private:
using DeferredNode = std::tuple<
SHAMapInnerNode*, // parent node
SHAMapNodeID, // parent node ID
int, // branch
unsigned int, // branch
SHAMapTreeNodePtr>; // node
int deferred;
@@ -789,12 +794,6 @@ operator==(SHAMap::ConstIterator const& x, SHAMap::ConstIterator const& y)
return x.item_ == y.item_;
}
inline bool
operator!=(SHAMap::ConstIterator const& x, SHAMap::ConstIterator const& y)
{
return !(x == y);
}
inline SHAMap::ConstIterator
SHAMap::begin() const
{

View File

@@ -62,8 +62,8 @@ private:
*
* @param i index of the requested child
*/
std::optional<int>
getChildIndex(int i) const;
std::optional<unsigned int>
getChildIndex(unsigned int i) const;
/**
* Call the `f` callback for all 16 (branchFactor) branches - even if
@@ -125,28 +125,28 @@ public:
isEmpty() const;
bool
isEmptyBranch(int m) const;
isEmptyBranch(unsigned int branch) const;
int
unsigned int
getBranchCount() const;
SHAMapHash const&
getChildHash(int m) const;
getChildHash(unsigned int branch) const;
void
setChild(int m, SHAMapTreeNodePtr child);
setChild(unsigned int branch, SHAMapTreeNodePtr child);
void
shareChild(int m, SHAMapTreeNodePtr const& child);
shareChild(unsigned int branch, SHAMapTreeNodePtr const& child);
SHAMapTreeNode*
getChildPointer(int branch);
getChildPointer(unsigned int branch);
SHAMapTreeNodePtr
getChild(int branch);
getChild(unsigned int branch);
SHAMapTreeNodePtr
canonicalizeChild(int branch, SHAMapTreeNodePtr node);
canonicalizeChild(unsigned int branch, SHAMapTreeNodePtr node);
// sync functions
bool
@@ -190,12 +190,12 @@ SHAMapInnerNode::isEmpty() const
}
inline bool
SHAMapInnerNode::isEmptyBranch(int m) const
SHAMapInnerNode::isEmptyBranch(unsigned int branch) const
{
return (isBranch_ & (1 << m)) == 0;
return (isBranch_ & (1u << branch)) == 0u;
}
inline int
inline unsigned int
SHAMapInnerNode::getBranchCount() const
{
return popcnt16(isBranch_);

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/base_uint.h>
#include <compare>
#include <cstddef>
#include <optional>
#include <ostream>
@@ -52,7 +53,21 @@ public:
}
[[nodiscard]] SHAMapNodeID
getChildNodeID(unsigned int m) const;
getChildNodeID(unsigned int branch) const;
/**
* Test whether this node ID lies on the path to the given leaf key
*
* A node at depth d identifies the tree path spelled by the first d
* nibbles of its key, so any leaf beneath it must agree on that prefix.
* A node ID that fails this test names a different subtree than the one
* it was built for.
*
* @param key the key of a leaf below this node
* @return whether this node ID is a prefix of the leaf key
*/
[[nodiscard]] bool
isPrefixOf(uint256 const& key) const;
/**
* Create a SHAMapNodeID of a node with the depth of the node and
@@ -63,47 +78,34 @@ public:
* @return SHAMapNodeID of the node
*/
static SHAMapNodeID
createID(int depth, uint256 const& key);
createID(unsigned int depth, uint256 const& key);
// FIXME-C++20: use spaceship and operator synthesis
/**
* Comparison operators
*
* <, >, <= and >= are synthesized from the spaceship. It is written out
* rather than defaulted because the ordering is by depth first, and the
* members are not declared in that order.
*/
bool
operator<(SHAMapNodeID const& n) const
std::strong_ordering
operator<=>(SHAMapNodeID const& n) const
{
return std::tie(depth_, id_) < std::tie(n.depth_, n.id_);
}
bool
operator>(SHAMapNodeID const& n) const
{
return n < *this;
}
bool
operator<=(SHAMapNodeID const& n) const
{
return !(n < *this);
}
bool
operator>=(SHAMapNodeID const& n) const
{
return !(*this < n);
return std::tie(depth_, id_) <=> std::tie(n.depth_, n.id_);
}
/**
* Equality, which the spaceship above does not provide.
*
* Only a *defaulted* operator<=> implicitly declares a defaulted
* operator==; the one above is user-provided, so == has to be written.
* It cannot be defaulted either, because a defaulted == would also compare
* the CountedObject base, which is not equality comparable.
*/
bool
operator==(SHAMapNodeID const& n) const
{
return (depth_ == n.depth_) && (id_ == n.id_);
}
bool
operator!=(SHAMapNodeID const& n) const
{
return !(*this == n);
}
};
inline std::string

View File

@@ -219,11 +219,11 @@ public:
*
* @param i index of the requested child
*/
[[nodiscard]] std::optional<int>
getChildIndex(std::uint16_t isBranch, int i) const;
[[nodiscard]] std::optional<unsigned int>
getChildIndex(std::uint16_t isBranch, unsigned int i) const;
};
[[nodiscard]] inline int
[[nodiscard]] inline unsigned int
popcnt16(std::uint16_t a)
{
#if __cpp_lib_bitops
@@ -234,11 +234,11 @@ popcnt16(std::uint16_t a)
// fallback to table lookup
static constexpr auto tbl = []() {
std::array<std::uint8_t, 256> ret{};
for (int i = 0; i != 256; ++i)
for (auto i = 0u; i != 256u; ++i)
{
for (int j = 0; j != 8; ++j)
for (auto j = 0u; j != 8u; ++j)
{
if (i & (1 << j))
if (i & (1u << j))
ret[i]++;
}
}

View File

@@ -22,6 +22,11 @@ static_assert(
static_assert(
kBoundaries.back() == SHAMapInnerNode::kBranchFactor,
"Last element of boundaries must be number of children in a dense array");
static_assert(
kBoundaries.front() >= 1,
"TaggedPointer.ipp subtracts 1 from a numAllocated value derived from "
"kBoundaries, as an unsigned quantity, in several places; the smallest "
"boundary must stay non-zero or those subtractions underflow.");
// Terminology: A chunk is the memory being allocated from a block. A block
// contains multiple chunks. This is the terminology the boost documentation
@@ -148,16 +153,16 @@ TaggedPointer::iterChildren(std::uint16_t isBranch, F&& f) const
if (numAllocated == SHAMapInnerNode::kBranchFactor)
{
// dense case
for (int i = 0; i < SHAMapInnerNode::kBranchFactor; ++i)
for (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++i)
f(hashes[i]);
}
else
{
// sparse case
int curHashI = 0;
for (int i = 0; i < SHAMapInnerNode::kBranchFactor; ++i)
auto curHashI = 0u;
for (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++i)
{
if ((1 << i) & isBranch)
if ((1u << i) & isBranch)
{
f(hashes[curHashI++]);
}
@@ -176,9 +181,9 @@ TaggedPointer::iterNonEmptyChildIndexes(std::uint16_t isBranch, F&& f) const
if (capacity() == SHAMapInnerNode::kBranchFactor)
{
// dense case
for (int i = 0; i < SHAMapInnerNode::kBranchFactor; ++i)
for (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++i)
{
if ((1 << i) & isBranch)
if ((1u << i) & isBranch)
{
f(i, i);
}
@@ -187,10 +192,10 @@ TaggedPointer::iterNonEmptyChildIndexes(std::uint16_t isBranch, F&& f) const
else
{
// sparse case
int curHashI = 0;
for (int i = 0; i < SHAMapInnerNode::kBranchFactor; ++i)
auto curHashI = 0u;
for (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++i)
{
if ((1 << i) & isBranch)
if ((1u << i) & isBranch)
{
f(i, curHashI++);
}
@@ -216,14 +221,14 @@ TaggedPointer::destroyHashesAndChildren()
deallocateArrays(tag, ptr);
}
inline std::optional<int>
TaggedPointer::getChildIndex(std::uint16_t isBranch, int i) const
inline std::optional<unsigned int>
TaggedPointer::getChildIndex(std::uint16_t isBranch, unsigned int i) const
{
if (isDense())
return i;
// Sparse case
if ((isBranch & (1 << i)) == 0)
if ((isBranch & (1u << i)) == 0u)
{
// Empty branch. Sparse children do not store empty branches
return {};
@@ -273,10 +278,10 @@ inline TaggedPointer::TaggedPointer(
*this = std::move(other);
auto [srcDstNumAllocated, srcDstHashes, srcDstChildren] = getHashesAndChildren();
bool const srcDstIsDense = isDense();
int srcDstIndex = 0;
for (int i = 0; i < SHAMapInnerNode::kBranchFactor; ++i)
auto srcDstIndex = 0u;
for (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++i)
{
auto const mask = (1 << i);
auto const mask = (1u << i);
bool const inSrc = (srcBranches & mask) != 0;
bool const inDst = (dstBranches & mask) != 0;
if (inSrc && inDst)
@@ -298,13 +303,13 @@ inline TaggedPointer::TaggedPointer(
// sparse
// need to shift all the elements to the left by
// one
for (int c = srcDstIndex; c < srcDstNumAllocated - 1; ++c)
for (auto c = srcDstIndex; c + 1 < srcDstNumAllocated; ++c)
{
srcDstHashes[c] = srcDstHashes[c + 1];
srcDstChildren[c] = std::move(srcDstChildren[c + 1]);
}
srcDstHashes[srcDstNumAllocated - 1].zero();
srcDstChildren[srcDstNumAllocated - 1].reset();
srcDstHashes[srcDstNumAllocated - 1u].zero();
srcDstChildren[srcDstNumAllocated - 1u].reset();
// do not increment the index
}
}
@@ -321,7 +326,7 @@ inline TaggedPointer::TaggedPointer(
// sparse
// need to create a hole by shifting all the elements to the
// right by one
for (int c = srcDstNumAllocated - 1; c > srcDstIndex; --c)
for (auto c = srcDstNumAllocated - 1u; c > srcDstIndex; --c)
{
srcDstHashes[c] = srcDstHashes[c - 1];
srcDstChildren[c] = std::move(srcDstChildren[c - 1]);
@@ -352,10 +357,10 @@ inline TaggedPointer::TaggedPointer(
auto [srcNumAllocated, srcHashes, srcChildren] = src.getHashesAndChildren();
bool const srcIsDense = src.isDense();
bool const dstIsDense = dst.isDense();
int srcIndex = 0, dstIndex = 0;
for (int i = 0; i < SHAMapInnerNode::kBranchFactor; ++i)
auto srcIndex = 0u, dstIndex = 0u;
for (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++i)
{
auto const mask = (1 << i);
auto const mask = (1u << i);
bool const inSrc = (srcBranches & mask) != 0;
bool const inDst = (dstBranches & mask) != 0;
if (inSrc && inDst)
@@ -409,7 +414,7 @@ inline TaggedPointer::TaggedPointer(
!dstIsDense || dstIndex == dstNumAllocated,
"xrpl::TaggedPointer::TaggedPointer(TaggedPointer&& ...) : "
"non-sparse or valid sparse");
for (int i = dstIndex; i < dstNumAllocated; ++i)
for (auto i = dstIndex; i < dstNumAllocated; ++i)
{
new (&dstHashes[i]) SHAMapHash{};
new (&dstChildren[i]) SHAMapTreeNodePtr{};
@@ -448,9 +453,9 @@ inline TaggedPointer::TaggedPointer(
new (&newChildren[branchNum]) SHAMapTreeNodePtr{std::move(oldChildren[indexNum])};
});
// Run the constructors for the remaining elements
for (int i = 0; i < SHAMapInnerNode::kBranchFactor; ++i)
for (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++i)
{
if (((1 << i) & isBranch) != 0)
if (((1u << i) & isBranch) != 0u)
continue;
new (&newHashes[i]) SHAMapHash{};
new (&newChildren[i]) SHAMapTreeNodePtr{};
@@ -459,7 +464,7 @@ inline TaggedPointer::TaggedPointer(
else
{
// new arrays are sparse, old arrays may be sparse or dense
int curCompressedIndex = 0;
auto curCompressedIndex = 0u;
iterNonEmptyChildIndexes(isBranch, [&](auto branchNum, auto indexNum) {
new (&newHashes[curCompressedIndex]) SHAMapHash{oldHashes[indexNum]};
new (&newChildren[curCompressedIndex])
@@ -467,7 +472,7 @@ inline TaggedPointer::TaggedPointer(
++curCompressedIndex;
});
// Run the constructors for the remaining elements
for (int i = curCompressedIndex; i < newNumAllocated; ++i)
for (auto i = curCompressedIndex; i < newNumAllocated; ++i)
{
new (&newHashes[i]) SHAMapHash{};
new (&newChildren[i]) SHAMapTreeNodePtr{};

View File

@@ -17,7 +17,6 @@
#include <cstddef>
#include <functional>
#include <optional>
#include <utility>
namespace xrpl {
@@ -130,16 +129,6 @@ public:
view_->rawDestroyXRP(fee);
}
/**
* Applies all invariant checkers one by one.
*
* @param result the result generated by processing this transaction.
* @param fee the fee charged for this transaction
* @return the result code that should be returned for this transaction.
*/
TER
checkInvariants(TER const result, XRPAmount const fee);
ApplyViewContext
getApplyViewContext()
{
@@ -150,13 +139,6 @@ public:
}
private:
static TER
failInvariantCheck(TER const result);
template <std::size_t... Is>
TER
checkInvariantsHelper(TER const result, XRPAmount const fee, std::index_sequence<Is...>);
OpenView& base_;
ApplyFlags flags_;
std::optional<ApplyViewImpl> view_;

View File

@@ -20,6 +20,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/applySteps.h>
#include <xrpl/tx/invariants/InvariantRunner.h>
#include <cstddef>
#include <cstdint>
@@ -147,7 +148,7 @@ struct FeePayer
FeePayerType type{FeePayerType::Account};
};
class Transactor
class Transactor : public TxInvariantCheck
{
protected:
ApplyContext& ctx_;
@@ -158,7 +159,7 @@ protected:
XRPAmount preFeeBalance_{}; // Balance before fees.
public:
virtual ~Transactor() = default;
~Transactor() override = default;
Transactor(Transactor const&) = delete;
Transactor&
operator=(Transactor const&) = delete;
@@ -183,20 +184,50 @@ public:
return ctx_.view();
}
/**
* Which invariant layers to check.
*
* Full runs the protocol invariants plus the transaction-specific
* check. This is always the scope of the initial pass, even when the
* tentative TER is a tec: a bug or exploit could still mutate ledger
* state, so transaction-specific invariants must run for failed
* transactions too.
*
* ProtocolOnly runs only the protocol invariants and is used
* exclusively for the second invariant pass that follows a
* fee-claim reset — specifically, the reset that
* Transactor::operator() performs when the initial invariant pass
* returns tecINVARIANT_FAILED, rolling the transaction's effects back
* to a fee-claim-only state. In that reduced state the
* transaction-specific post-conditions no longer apply, but the
* protocol invariants must still hold against the fee claim itself.
* ProtocolOnly is not intended for other context discards (e.g. the
* reset used to handle tecOVERSIZE/tecKILLED/etc. in
* processPersistentChanges, or the ctx_.discard() done under
* TapFailHard); those paths do not re-run invariants at all.
*/
enum class InvariantScope { Full, ProtocolOnly };
/**
* Check all invariants for the current transaction.
*
* Runs transaction-specific invariants first (visitInvariantEntry +
* finalizeInvariants), then protocol-level invariants. Both layers
* always run; the worst failure code is returned.
* Delegates to the free xrpl::checkInvariants runner. When @p scope is
* InvariantScope::Full, this transactor is passed so both layers
* share a single walk of the modified ledger entries. A failure in
* either layer fails the transaction the same way: tecINVARIANT_FAILED
* on the first pass, which the caller may respond to by rolling the
* transaction back to a fee-claim state and re-invoking this with
* InvariantScope::ProtocolOnly; a failure on that post-reset pass
* escalates to tefINVARIANT_FAILED.
*
* @param result the tentative TER from transaction processing.
* @param fee the fee consumed by the transaction.
* @param scope which invariant layers to check.
*
* @return the final TER after all invariant checks.
*/
[[nodiscard]] TER
checkInvariants(TER result, XRPAmount fee);
checkInvariants(TER result, XRPAmount fee, InvariantScope scope);
/////////////////////////////////////////////////////
/*
@@ -538,20 +569,30 @@ private:
preflightUniversal(PreflightContext const& ctx);
/**
* Check transaction-specific invariants only.
*
* Walks every modified ledger entry via visitInvariantEntry, then
* calls finalizeInvariants on the derived transactor. Returns
* tecINVARIANT_FAILED if any transaction invariant is violated.
*
* @param result the tentative TER from transaction processing.
* @param fee the fee consumed by the transaction.
*
* @return the original result if all invariants pass, or
* tecINVARIANT_FAILED otherwise.
* Bridges the two-phase TxInvariantCheck interface to this transactor's
* visitInvariantEntry/finalizeInvariants hooks. Declared private (rather
* than protected, like the hooks they forward to) so that neither this
* transactor nor any subclass can call them directly through a
* Transactor& — only through the TxInvariantCheck& that the free
* xrpl::checkInvariants runner holds, which is where the two-phase
* ordering is enforced.
*/
[[nodiscard]] TER
checkTransactionInvariants(TER result, XRPAmount fee);
void
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) final
{
visitInvariantEntry(isDelete, before, after);
}
[[nodiscard]] bool
finalize(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) final
{
return finalizeInvariants(tx, result, fee, view, j);
}
};
inline bool

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h>
@@ -11,6 +12,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <map>
#include <optional>
#include <vector>
namespace xrpl {
@@ -69,7 +71,9 @@ private:
IssuerChanges const& changes,
STTx const& tx,
beast::Journal const& j,
bool enforce);
bool enforce,
bool fixOverrideFreeze,
std::optional<LoanDefaultFreezeExemptAccounts> const& loanDefaultAccounts);
static bool
validateFrozenState(
@@ -78,7 +82,9 @@ private:
STTx const& tx,
beast::Journal const& j,
bool enforce,
bool globalFreeze);
bool globalFreeze,
bool fixOverrideFreeze,
std::optional<LoanDefaultFreezeExemptAccounts> const& loanDefaultAccounts);
};
} // namespace xrpl

View File

@@ -198,7 +198,7 @@ public:
/**
* @brief Invariant: An account XRP balance must be in XRP and take a value
* between 0 and INITIAL_XRP drops, inclusive.
* between 0 and kInitialXRP drops, inclusive.
*
* We iterate all account roots modified by the transaction and ensure that
* their XRP balances are reasonable.
@@ -290,7 +290,7 @@ public:
/**
* @brief Invariant: an escrow entry must take a value between 0 and
* INITIAL_XRP drops exclusive.
* kInitialXRP drops exclusive.
*/
class NoZeroEscrow
{

View File

@@ -0,0 +1,140 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <functional>
#include <optional>
namespace xrpl {
/**
* @brief Runtime interface for a transaction-specific invariant check.
*
* The free checkInvariants runner drives two layers of checks over a single
* walk of the modified ledger entries:
*
* - Protocol checks are the concrete types in InvariantChecks, held in a
* std::tuple and dispatched statically by a compile-time fold (no
* virtual calls). They are duck-typed against the two-phase contract
* described below; see InvariantChecker_PROTOTYPE in InvariantCheck.h.
* - The transaction-specific check is injected at runtime through this
* interface, so the runner can call it without depending on the concrete
* transactor type. Transactor implements this interface directly (see
* Transactor.h) so that the interface's access can stay narrower than
* Transactor's own public surface: calling through a TxInvariantCheck&
* (all the runner ever holds) is public, but calling through a
* Transactor& is not, since Transactor overrides these as private
* (forwarding to its own protected visitInvariantEntry/finalizeInvariants).
*
* Both layers honour the same two-phase protocol:
*
* Phase 1 — state collection (visitEntry). Called once for each ledger
* entry created, modified, or deleted by the transaction. Implementations
* accumulate whatever state they need to evaluate their post-conditions.
* Must not throw.
*
* Phase 2 — condition evaluation (finalize). Called once after every
* modified entry has been visited. Returns true if all post-conditions
* hold, false to fail the transaction.
*
* Rule: invariants must run regardless of transaction result. finalize
* MUST perform meaningful checks even when the transaction has failed
* (when result is not tesSUCCESS). A bug or exploit could cause a failed
* transaction to mutate ledger state in unexpected ways; invariants are the
* last line of defense.
*
* The typical pattern: an invariant that expects a domain-specific state
* change (e.g. a Vault being created) should expect that change only when
* the transaction succeeded. A failed VaultCreate must not have created a
* Vault.
*
* Rule: privilege-gated checks apply to failed transactions too. Failed
* transactions carry no privileges. Any privilege-gated assertion must
* therefore also be enforced for failed transactions.
*/
class TxInvariantCheck
{
public:
virtual ~TxInvariantCheck() = default;
/**
* @brief Called for each ledger entry modified by the transaction.
*
* @param isDelete true if the SLE is being deleted.
* @param before the entry's state before the transaction (nullptr for
* newly created entries).
* @param after the entry's state after the transaction. For deletions
* this is the SLE being erased; use @p isDelete rather than
* a null @p after to detect deletions. @p after is
* never null.
*/
virtual void
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) = 0;
/**
* @brief Called after all entries have been visited.
*
* @param tx the transaction being applied.
* @param result the tentative TER result of the transaction.
* @param fee the fee consumed by the transaction.
* @param view read-only view of the ledger after the transaction.
* @param j journal for logging invariant failures.
* @return true if all invariants hold; false to fail with
* tecINVARIANT_FAILED / tefINVARIANT_FAILED.
*/
[[nodiscard]] virtual bool
finalize(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) = 0;
};
/**
* @brief Run all protocol invariant checks plus the transaction-specific check
* in a single pass over the modified entries.
*
* Both layers share one walk of the modified-entry set: @p txCheck's
* visitEntry accumulates state on the same traversal that drives the
* protocol checkers, then both layers' finalize run on the complete state.
*
* Any failure (a finalize returning false or an exception anywhere in the
* check) returns failInvariantCheck(result). On the first pass that yields
* tecINVARIANT_FAILED, which the transactor treats as a signal to roll the
* transaction's effects back to a fee-claim-only state and re-run this
* runner against the reduced state (see Transactor::InvariantScope). If
* that second pass also fails, the result escalates to tefINVARIANT_FAILED,
* which excludes the transaction from the ledger entirely.
*
* The whole traversal — both layers' visitEntry calls and both layers'
* finalize calls — runs under a single try/catch. There is no per-layer
* isolation: an exception anywhere aborts the remaining traversal and
* finalize calls and fails the transaction.
*
* @param ctx the apply context for the current transaction.
* @param result the tentative TER from transaction processing.
* @param fee the fee consumed by the transaction.
* @param txCheck the transaction-specific invariant check.
* @return the final TER after all invariant checks.
*/
[[nodiscard]] TER
checkInvariants(
ApplyContext& ctx,
TER result,
XRPAmount fee,
std::optional<std::reference_wrapper<TxInvariantCheck>> txCheck);
[[nodiscard]] inline TER
checkInvariants(ApplyContext& ctx, TER result, XRPAmount fee)
{
return checkInvariants(ctx, result, fee, std::nullopt);
}
} // namespace xrpl

View File

@@ -16,6 +16,8 @@ namespace xrpl {
* @brief Invariants: Loans are internally consistent
*
* 1. If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0`
* 2. A newly-created Loan against a closed-ended vault must satisfy
* `StartDate + PaymentInterval * PaymentRemaining < Vault.RedemptionDate`.
*
*/
class ValidLoan

View File

@@ -38,7 +38,17 @@ namespace xrpl {
* - vault set must not alter the vault assets or shares balance
* - no vault transaction can change loss unrealized (it's updated by loan
* transactions)
* - a created closed-ended vault must satisfy
* MIN_INVESTMENT_PERIOD <= RedemptionDate - SubscriptionDate <
* MAX_INVESTMENT_PERIOD
* - vault deposit may only succeed when the vault phase is NoPhase or
* Subscription
* - vault withdrawal may not succeed when the vault phase is Investment
* - closed-ended loan origination (ttLOAN_SET) may only succeed when the
* vault phase is Investment
*
* Immutability of VaultKind, SubscriptionDate and RedemptionDate is enforced
* by NoModifiedUnmodifiableFields (see InvariantCheck.cpp).
*/
class ValidVault
{
@@ -55,6 +65,9 @@ class ValidVault
Number assetsAvailable = 0;
Number assetsMaximum = 0;
Number lossUnrealized = 0;
std::optional<std::uint8_t> vaultKind;
std::optional<std::uint32_t> subscriptionDate;
std::optional<std::uint32_t> redemptionDate;
Vault static make(SLE const&);
};
@@ -153,6 +166,17 @@ private:
[[nodiscard]] static bool
isVaultEmpty(Vault const& vault);
/**
* @brief Invariant check for @c ttLOAN_SET.
*
* For a closed-ended vault, a loan may only be originated while the vault is in the Investment
* phase (strictly past @c SubscriptionDate and before @c RedemptionDate). Open-ended vaults (@c
* NoPhase) are unaffected. The complementary maturity bound (final payment strictly precedes @c
* RedemptionDate) is enforced by @c ValidLoan.
*/
[[nodiscard]] bool
finalizeLoanSet(ReadView const& view, beast::Journal const& j) const;
public:
// Compute the coarsest scale required to represent all numbers
[[nodiscard]] static std::int32_t

View File

@@ -6,7 +6,6 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/tx/transactors/dex/AMMContext.h>
#include <cstdint>
@@ -124,17 +123,12 @@ private:
generateFibSeqOffer(TAmounts<TIn, TOut> const& balances) const;
/**
* Generate max offer.
* If `fixAMMOverflowOffer` is active, the offer is generated as:
* Generate max offer. The offer is generated as:
* takerGets = 99% * balances.out takerPays = swapOut(takerGets).
* Return nullopt if takerGets is 0 or takerGets == balances.out.
*
* If `fixAMMOverflowOffer` is not active, the offer is generated as:
* takerPays = max input amount;
* takerGets = swapIn(takerPays).
*/
[[nodiscard]] std::optional<AMMOffer<TIn, TOut>>
maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const;
maxOffer(TAmounts<TIn, TOut> const& balances) const;
};
} // namespace xrpl

View File

@@ -274,19 +274,6 @@ public:
return lhs.equal(rhs);
}
/**
* Return true if lhs != rhs.
*
* @param lhs Step to compare.
* @param rhs Step to compare.
* @return true if lhs != rhs.
*/
friend bool
operator!=(Step const& lhs, Step const& rhs)
{
return !(lhs == rhs);
}
/**
* Streaming operator for a Step.
*/

View File

@@ -1,6 +1,7 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
@@ -373,7 +374,7 @@ qualityUpperBound(ReadView const& v, Strand const& strand)
* increases quality of AMM steps, increasing the strand's composite
* quality as the result.
*/
template <typename TOutAmt>
template <StepAmount TOutAmt>
inline TOutAmt
limitOut(
ReadView const& v,
@@ -411,21 +412,29 @@ limitOut(
auto const out = qf->outFromAvgQ(limitQuality);
if (!out)
return remainingOut;
if constexpr (std::is_same_v<TOutAmt, XRPAmount>)
if constexpr (std::is_same_v<TOutAmt, XRPAmount> || std::is_same_v<TOutAmt, MPTAmount>)
{
return XRPAmount{*out};
auto const roundedOut = TOutAmt{*out};
// Integral outputs that round above the continuous target can
// realize worse average quality than the requested limit. Keep the
// default rounded value when it still satisfies the limit, since it
// is the largest matching offer; otherwise round down.
if (v.rules().enabled(featureMPTokensV2) && roundedOut > *out &&
!qf->satisfiesAvgQ(limitQuality, roundedOut))
{
NumberRoundModeGuard const g(Number::RoundingMode::Downward);
return TOutAmt{*out};
}
return roundedOut;
}
else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
{
return IOUAmount{*out};
}
else if constexpr (std::is_same_v<TOutAmt, MPTAmount>)
{
return MPTAmount{*out};
}
else
{
return STAmount{remainingOut.asset(), out->mantissa(), out->exponent()};
static constexpr bool kAlwaysFalse = !std::is_same_v<TOutAmt, TOutAmt>;
static_assert(kAlwaysFalse, "Unhandled StepAmount type");
}
}();
// A tiny difference could be due to the round off

View File

@@ -118,6 +118,7 @@ public:
Sandbox& view,
SLE const& ammSle,
AccountID const account,
std::optional<AccountID> const& clawbackIssuer,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
@@ -138,6 +139,11 @@ public:
* @param view
* @param ammSle AMM ledger entry
* @param ammAccount AMM account
* @param clawbackIssuer when set (AMMClawback path), the issuer performing
* the clawback. A recreated MPToken is only auto-authorized when the
* asset's issuer matches this account, so a clawback cannot grant
* authorization on behalf of a different (paired-asset) issuer.
* @param account LP account
* @param amountBalance current LP asset1 balance
* @param amountWithdraw asset1 withdraw amount
* @param amount2Withdraw asset2 withdraw amount
@@ -153,6 +159,7 @@ public:
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
std::optional<AccountID> const& clawbackIssuer,
AccountID const& account,
STAmount const& amountBalance,
STAmount const& amountWithdraw,

Some files were not shown because too many files have changed in this diff Show More