Compare commits

..
Author SHA1 Message Date
Bart 8fabef3b50 fix: Fail when no config file is read, and report the search
When Config::setup found no config file, it left the config file
pointing at the last name it tried in the last directory. The read
failed there, so the operator was told only that /etc/xrpld/rippled.cfg
was missing. setup then tried to create /var/lib/xrpld, and that throw
reached an uncaught main and aborted the process. A config file named
with --conf and unreadable produced only a warning, and setup carried on
with no settings at all.

The config file is now a local std::optional that setup passes to load,
rather than a configFile_ member. A file that is there but cannot be
read throws, as one with invalid contents already did, so load needs no
return value. When the search found nothing, setup writes every
directory it tried to stderr, skips the data directory, and returns
false. It is [[nodiscard]] bool.

So a false return means one thing only. The search found no config file
at all, which is fatal only without a positional argument, the same
condition that chooses between starting a server and running an RPC
command. That rule is missingConfigIsFatal in the new Main.h. setup
still sets up the defaults and the SSL context in that case, so the RPC
client runs; rpc_call::fromCommandLine takes its destination from
--rpc_ip.

An unreadable config file used to be fatal only when --conf named it. A
file the search found and could not read left RPC mode running on
defaults, so the same file gave exit 1 under --conf and exit 73 when
found by the search. Throwing removes that difference.

--vacuum is excluded from that condition. It returns before the RPC
branch and reads the config itself, so a positional argument beside it
used to skip the check. On a host with no config file, xrpld --vacuum
server_info ran vacuum unconfigured and died with "exception
database_path must be set. in function run" and exit 255. It now reports
the search and exits 1, as --vacuum alone already did.

run() wraps its setup call to catch what setup throws, print "xrpld:
invalid configuration: <reason>", and exit 1.

Also fixes the read-failure message, which wrapped the path in single
quotes on top of the double quotes that streaming a
std::filesystem::path adds, and ran the error number onto a period.
Every message here is built with std::format. The data directory throw
names the reason from its error_code, which was captured and then
discarded.

Config_test asserts the result of every setup call, covers a config file
that cannot be read, and covers the case where no directory holds one.
The second captures stderr and asserts all three searched directories
are named. The first asserts the message thrown and that no data
directory is left beside the file.

The guard that skips the data directory when nothing was read is not
reachable from a suite. Its one remaining path is a search that found
nothing, where the data directory is the last candidate's, /var/lib
under the system name, which a test cannot redirect. A built binary
covers it. On a host with no config file the run reports the searched
directories and exits 1, creating no data directory.

missingConfigIsFatal is a header-only predicate so that xrpld_tests can
reach it without linking server code, and its four combinations are
covered there.

run() itself is not reachable from a suite, so it is verified against a
built binary. An unreadable config file exits 1 whether --conf named it
or the search found it, and with or without a positional argument. With
no config file anywhere and a positional argument, the run still reaches
the RPC client.
2026-10-03 09:32:29 -04:00
Bart f95f7889f9 refactor: Search config directories over a candidate list
Config::setup searched the working directory, the XDG config directory
and the system config directory with a do/while(false) block and breaks,
trying both file names in each.

Replace it with a list of `Candidate` values, each naming a config
directory and its data directory, and a loop that takes the first
holding a config file. Add a docstring for the search order.

The directories chosen and the file reported are unchanged. The last
candidate is now also checked under the legacy name, which the old code
assigned without checking, so one more filesystem check runs when
nothing is found.
2026-10-02 21:19:48 +00:00
307 changed files with 7139 additions and 9197 deletions

No files matched your search

-1
View File
@@ -255,7 +255,6 @@ words:
- queuable
- Raphson
- rcflags
- reencrypted
- replayer
- repodata
- repomd
-12
View File
@@ -5,18 +5,6 @@
# This file is sorted in reverse chronological order, with the most recent commits at the top.
# The commits listed here are ignored by git blame, which is useful for formatting-only commits that would otherwise obscure the history of changes to a file.
# chore: CamelCase for typedef/using in `clang-tidy` (#8177)
97a1824537d20d82d25bf151a37a7a4532ebf085
# chore: Rename CamelCase namespaces to snake_case (#7933)
06488c1318d96f56d0536251bee08ac85fa7fdd3
# style: Unify style for all Doxygen comments (#7776)
73b6852a122854140336e6e6bc30a3a4b41aa5fd
# style: More clang-tidy identifier renaming (#7290)
a830ab10efed8d3e59ef2fc15d66efdf9c6bb0d8
# refactor: Rename static constants (#7120)
5b6e8b6f93b19c1e3f6a3467a25639031d9d9a53
# chore: More fixes for bad renames (#7092)
7afdd71a54d562b32a50b29a5aa00bb997dc9053
# refactor: Enable clang-tidy `readability-identifier-naming` check (#6571)
8995564ed6b9e453e144bb663303072a3c1ba305
# refactor: Enable remaining clang-tidy `cppcoreguidelines` checks (#6538)
@@ -194,6 +194,7 @@ tests.libxrpl > xrpl.resource
tests.libxrpl > xrpl.server
tests.libxrpl > xrpl.shamap
tests.libxrpl > xrpl.tx
tests.xrpld > xrpld.app
tests.xrpld > xrpld.rpc
tests.xrpld > xrpl.json
tests.xrpld > xrpl.protocol
-5
View File
@@ -30,11 +30,6 @@ Version 3.5.0 is not yet released.
- `subscribe`, `unsubscribe`: Added an optional `mpt_issuances` request field, an array of MPT issuance IDs (hex strings). Subscribers receive a message with `type` `mptTransaction` for each validated transaction whose metadata affects a subscribed issuance; the message has the same fields as the `transactions` stream. MPT issuance subscriptions count toward the per-connection subscription limit. An empty array, a non-array value, or an invalid ID returns `invalidParams`. ([#5671](https://github.com/XRPLF/rippled/pull/5671))
### Bugfixes in 3.5.0
- `channel_authorize`: The `channel_id` field now returns an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
- `channel_verify`: The `channel_id` and `signature` fields now return an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
## XRP Ledger server version 3.4.0
Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta releases.
@@ -31,7 +31,7 @@ namespace xrpl::ledger_entries {
// builder's STObject and the wrapper's SLE.
TEST(${name}Tests, BuilderSettersRoundTrip)
{
constexpr UInt256 index{1};
UInt256 const index{1u};
% for field in fields:
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
@@ -85,7 +85,7 @@ TEST(${name}Tests, BuilderSettersRoundTrip)
// from that SLE, build a new wrapper, and verify all fields (and validate()).
TEST(${name}Tests, BuilderFromSleRoundTrip)
{
constexpr UInt256 index{2};
UInt256 const index{2u};
% for field in fields:
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
@@ -146,7 +146,7 @@ TEST(${name}Tests, BuilderFromSleRoundTrip)
// 3) Verify wrapper throws when constructed from wrong ledger entry type.
TEST(${name}Tests, WrapperThrowsOnWrongEntryType)
{
constexpr UInt256 index{3};
UInt256 const index{3u};
// Build a valid ledger entry of a different type
// Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq
@@ -177,7 +177,7 @@ TEST(${name}Tests, WrapperThrowsOnWrongEntryType)
// 4) Verify builder throws when constructed from wrong ledger entry type.
TEST(${name}Tests, BuilderThrowsOnWrongEntryType)
{
constexpr UInt256 index{4};
UInt256 const index{4u};
// Build a valid ledger entry of a different type
% if wrong_le_include == "Ticket":
@@ -207,7 +207,7 @@ TEST(${name}Tests, BuilderThrowsOnWrongEntryType)
// 5) Build with only required fields and verify optional fields return nullopt.
TEST(${name}Tests, OptionalFieldsReturnNullopt)
{
constexpr UInt256 index{3};
UInt256 const index{3u};
% for field in required_fields:
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
Generated
+6 -6
View File
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1791130267,
"narHash": "sha256-1sjwcQMcgAbmiip/VivBSiK8p2bTkQGOvCuO7vnwfx4=",
"lastModified": 1781173989,
"narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9013764fcc0ea99fa16cf7aa7decf8a5c3889dd2",
"rev": "8c91a71d13451abc40eb9dae8910f972f979852f",
"type": "github"
},
"original": {
@@ -47,11 +47,11 @@
]
},
"locked": {
"lastModified": 1791189933,
"narHash": "sha256-Y8eN7ki0Urx4Ojf4w1xSpWg8uk/3lJqK+E0Oth77rH0=",
"lastModified": 1784611586,
"narHash": "sha256-OfqgY+0hp/zseZB7uyH0U8kIDPS4scZZCyAurEplvG0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "e60029353d0c48d216bc4b065168ccd4079c166f",
"rev": "14f58845249f3552a89b07772626b8d3c632fa86",
"type": "github"
},
"original": {
+56 -147
View File
@@ -1,31 +1,29 @@
#pragma once
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/beast/type_name.h>
#include <boost/core/type_name.hpp>
#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace xrpl {
/**
* Manages all counted object types.
*
* Counters register themselves on a lock-free intrusive list maintained
* by this object when constructed. Because counters are never destroyed
* or removed, the ABA problem does not apply.
*
* The registry is iterable as a forward range.
*/
class CountedObjects
{
public:
static CountedObjects&
getInstance() noexcept;
using Entry = std::pair<std::string, int>;
using List = std::vector<Entry>;
[[nodiscard]] List
getCounts(int minimumThreshold) const;
public:
/**
* Implementation for @ref CountedObject.
@@ -35,147 +33,68 @@ public:
class Counter
{
public:
Counter(std::string name) noexcept;
Counter(std::string name) noexcept : name_(std::move(name)), count_(0)
{
// Insert ourselves at the front of the lock-free linked list
CountedObjects& instance = CountedObjects::getInstance();
Counter* head = nullptr;
// Counters are intrusive list nodes whose addresses are published
// in the registry; they must never be copied or moved. The atomic
// members already force this, but we make it explicit.
Counter(Counter const&) = delete;
Counter&
operator=(Counter const&) = delete;
Counter(Counter&&) = delete;
Counter&
operator=(Counter&&) = delete;
do
{
head = instance.head_.load();
next_ = head;
} while (instance.head_.exchange(this) != head);
std::uint32_t
++instance.count_;
}
~Counter() noexcept = default;
int
increment() noexcept
{
auto const newCount = count_.fetch_add(1, std::memory_order::relaxed) + 1;
XRPL_ASSERT(newCount != 0, "xrpl::CountedObjects::Counter::increment : no overflow");
auto maxCount = maxCount_.load(std::memory_order::relaxed);
while (newCount > maxCount &&
!maxCount_.compare_exchange_weak(maxCount, newCount, std::memory_order::relaxed))
{
}
return newCount;
return ++count_;
}
std::uint32_t
int
decrement() noexcept
{
auto const prev = count_.fetch_sub(1, std::memory_order::relaxed);
XRPL_ASSERT(prev != 0, "xrpl::CountedObjects::Counter::decrement : no underflow");
return prev - 1;
return --count_;
}
[[nodiscard]] std::uint32_t
count() const noexcept
[[nodiscard]] int
getCount() const noexcept
{
return count_.load(std::memory_order::relaxed);
return count_.load();
}
[[nodiscard]] std::uint32_t
max() const noexcept
[[nodiscard]] Counter*
getNext() const noexcept
{
return std::max(
count_.load(std::memory_order::relaxed),
maxCount_.load(std::memory_order::relaxed));
return next_;
}
[[nodiscard]] std::string const&
name() const noexcept
getName() const noexcept
{
return name_;
}
private:
friend class CountedObjects;
Counter* next_ = nullptr;
std::atomic<std::uint32_t> count_ = 0;
std::atomic<std::uint32_t> maxCount_ = 0;
std::string const name_;
std::atomic<int> count_;
Counter* next_;
};
class Iterator
{
public:
using value_type = Counter const;
using reference = value_type&;
using pointer = value_type*;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
explicit Iterator(Counter* c = nullptr) noexcept : current_(c)
{
}
reference
operator*() const noexcept
{
return *current_;
}
pointer
operator->() const noexcept
{
return current_;
}
Iterator&
operator++() noexcept
{
current_ = current_->next_;
return *this;
}
Iterator
operator++(int) noexcept
{
auto tmp = *this;
++*this;
return tmp;
}
bool
operator==(Iterator const&) const noexcept = default;
private:
Counter* current_;
};
constexpr CountedObjects() noexcept = default;
[[nodiscard]] auto
begin() const noexcept
{
return Iterator{head_.load(std::memory_order::acquire)};
}
[[nodiscard]] auto
end() const noexcept
{
return Iterator{};
}
private:
std::atomic<Counter*> head_ = nullptr;
CountedObjects() noexcept;
~CountedObjects() noexcept = default;
private:
std::atomic<int> count_;
std::atomic<Counter*> head_;
};
/** The global counted object registry. */
inline constinit CountedObjects gCountedObjects;
inline CountedObjects::Counter::Counter(std::string name) noexcept
: next_(gCountedObjects.head_.load(std::memory_order::relaxed)), name_(std::move(name))
{
while (!gCountedObjects.head_.compare_exchange_weak(
next_, this, std::memory_order::release, std::memory_order::relaxed))
;
}
//------------------------------------------------------------------------------
/**
@@ -184,37 +103,27 @@ inline CountedObjects::Counter::Counter(std::string name) noexcept
* Derived classes have their instances counted automatically. This is used
* for reporting purposes.
*
* The constructors are private and `Object` is befriended so that the
* CRTP parameter must be the deriving class itself: a copy-paste error
* like `class B : public CountedObject<A>` fails to compile instead of
* silently polluting A's count.
*
* @note This class has no move operations by design: a derived class's
* move constructor falls back to the copy constructor for this
* base, so the newly created instance is counted. This keeps the
* invariant that count is the number of outstanding subobjects.
*
* @warning Counted objects constructed during dynamic initialization of
* other translation units may have their increments discarded when
* counter itself is dynamically initialized. Do not create counted
* objects before main() begins.
*
* @ingroup basics
*/
template <class Object>
requires std::is_class_v<Object>
class CountedObject
{
static inline CountedObjects::Counter counter{boost::core::type_name<Object>()};
private:
static auto&
getCounter() noexcept
{
static CountedObjects::Counter kC{beast::typeName<Object>()};
return kC;
}
CountedObject() noexcept
{
counter.increment();
getCounter().increment();
}
CountedObject(CountedObject const&) noexcept
{
counter.increment();
getCounter().increment();
}
CountedObject&
@@ -223,7 +132,7 @@ class CountedObject
public:
~CountedObject() noexcept
{
counter.decrement();
getCounter().decrement();
}
friend Object;
+12 -12
View File
@@ -110,10 +110,10 @@ static_assert(
*
* However, it does not have sufficient precision to represent the full integer
* range of int64_t values (-2^63 to 2^63-1), which are needed for XRP and MPT
* values. The implementation of SingleAssetVault, LendingProtocol, and
* MPTokensV2 need to represent those integer values accurately and precisely,
* both for the STNumber field type, and for internal calculations. That
* necessitated the "large" scale.
* values. The implementation of SingleAssetVault, and LendingProtocol need to
* represent those integer values accurately and precisely, both for the
* STNumber field type, and for internal calculations. That necessitated the
* "large" scale.
*
* The "Large" scales are intended to represent all values that can be represented
* by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max
@@ -134,8 +134,8 @@ struct MantissaRange final
// NOLINTBEGIN(readability-enum-initial-value)
// The values don't matter, except for Large
enum class MantissaScale {
// Small can be removed when any of featureSingleAssetVault, featureLendingProtocol, or
// featureMPTokensV2 are retired
// Small can be removed when either featureSingleAssetVault or featureLendingProtocol are
// retired
Small,
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
LargeLegacy,
@@ -311,10 +311,10 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
*
* The mantissa range may be changed at runtime via setMantissaScale(). The
* default mantissa range is "large". The range is updated whenever transaction
* processing begins, based on whether SingleAssetVault, LendingProtocol, or
* MPTokensV2 are enabled. If any is enabled, the mantissa range is set to
* "large". If not, it is set to "small", preserving backward compatibility and
* correct "amendment-gating".
* processing begins, based on whether SingleAssetVault or LendingProtocol are
* enabled. If either is enabled, the mantissa range is set to "large". If not,
* it is set to "small", preserving backward compatibility and correct
* "amendment-gating".
*
* It is extremely unlikely that any more calls to setMantissaScale() will be
* needed outside of unit tests.
@@ -344,8 +344,8 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* set/getMantissaScale() functions may be most appropriate. However, if the
* test has anything to do with transaction processing, it should enable or
* disable the amendments that control the mantissa range choice
* (SingleAssetVault, LendingProtocol, and MPTokensV2), and/or check if any of
* those amendments are enabled to determine which result to expect.
* (SingleAssetVault and LendingProtocol), and/or check if either of those
* amendments are enabled to determine which result to expect.
*/
class Number final
{
File diff suppressed because it is too large. Load diff
-23
View File
@@ -253,27 +253,4 @@ makeSlice(std::basic_string_view<char, Traits> s)
return Slice(s.data(), s.size());
}
/**
* Checks whether two byte ranges share at least one byte of storage.
*
* Each slice is treated as the half-open range [data, data + size), so
* ranges that merely touch do not overlap. An empty slice overlaps
* nothing, wherever it points.
*
* @param a The first range.
* @param b The second range.
* @return true if the ranges overlap, false otherwise.
*/
[[nodiscard]] inline bool
overlap(Slice a, Slice b) noexcept
{
if (a.empty() || b.empty())
return false;
auto const a0 = reinterpret_cast<std::uintptr_t>(a.data());
auto const b0 = reinterpret_cast<std::uintptr_t>(b.data());
return a0 < b0 + b.size() && b0 < a0 + a.size();
}
} // namespace xrpl
+1 -2
View File
@@ -1,7 +1,6 @@
#pragma once
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <boost/utility/string_view.hpp>
@@ -29,7 +28,7 @@ namespace xrpl {
* @return The input, encoded as a blob literal.
*/
std::string
sqlBlobLiteral(Slice blob);
sqlBlobLiteral(Blob const& blob);
namespace detail {
File diff suppressed because it is too large. Load diff
+2 -3
View File
@@ -1,8 +1,7 @@
#pragma once
#include <xrpl/basics/sanitizers.h>
#include <boost/core/type_name.hpp>
#include <xrpl/beast/type_name.h>
#include <exception>
#include <string>
@@ -56,7 +55,7 @@ Throw(Args&&... args)
std::is_convertible_v<E*, std::exception*>, "Exception must derive from std::exception.");
E e(std::forward<Args>(args)...);
logThrow("Throwing exception of type " + boost::core::type_name<E>() + ": " + e.what());
logThrow(std::string("Throwing exception of type " + beast::typeName<E>() + ": ") + e.what());
throw std::move(e);
}
-200
View File
@@ -1,200 +0,0 @@
#pragma once
#include <concepts>
#include <type_traits>
#include <utility>
namespace xrpl {
/**
* Opt-in bitwise operators for scoped enumerations.
*
* Provides `&`, `|`, `^`, `~` and the corresponding compound assignment forms
* for any scoped enumeration with an unsigned underlying type that opts in by
* specializing @ref OptIn:
*
* @code
* namespace xrpl {
*
* enum class MyFlags : std::uint32_t { a = 1, b = 2, c = 4 };
*
* template <>
* struct enum_bitops::OptIn<MyFlags> : std::true_type {};
*
* } // namespace xrpl
* @endcode
*
* The operators are constexpr and noexcept, and return the enumeration type.
*
* @par Where the specialization may be declared
* In namespace xrpl, or at global scope with full qualification, and after
* the enumeration is defined but before the operators are first used. This
* cannot be done in a nested namespace or at class scope; for enumerations
* nested in a class this means after the class definition.
*
* @par Where the operators are found
* The operators are declared in namespace xrpl, so argument-dependent lookup
* will find them only for enumerations declared in xrpl or nested in a class
* declared directly in xrpl. Enumerations in a nested namespace will only be
* found from code inside xrpl, and only if no enclosing scope declares an
* operator of the same name; elsewhere they require using-declarations.
*/
namespace enum_bitops {
/**
* Types that may be opted in to the bitwise operators.
*
* Satisfied by a scoped enumeration whose underlying type is an unsigned
* integer type.
*
* @tparam T The type to test.
*/
template <typename T>
concept Eligible = std::is_scoped_enum_v<T> && std::unsigned_integral<std::underlying_type_t<T>>;
/**
* Opt-in switch for the bitwise operators.
*
* The primary template derives from std::false_type. Specialize it to derive
* from std::true_type to enable the operators for an @ref Eligible enumeration.
*
* @tparam T The scoped enumeration to opt in.
*/
template <Eligible T>
struct OptIn : std::false_type
{
};
/**
* Enumerations for which the bitwise operators are enabled.
*
* Satisfied when @p T satisfies @ref Eligible and @ref OptIn has been
* specialized for it to derive from std::true_type.
*
* @tparam T The type to test.
*/
template <typename T>
concept Candidate = Eligible<T> && OptIn<T>::value;
} // namespace enum_bitops
// These are declared in xrpl, not in enum_bitops, so that argument-dependent
// lookup finds them for enumerations whose associated namespace is xrpl.
/**
* @name Bitwise operators for opted-in scoped enumerations
*
* Each operator applies the corresponding built-in operator to the
* underlying values and converts the result back to the enumeration type.
* Both operands must have the same enumeration type; there is no implicit
* conversion to or from the underlying type.
*/
/** @{ */
/**
* Bitwise AND.
*
* @param lhs The left operand.
* @param rhs The right operand.
* @return The bits set in both @p lhs and @p rhs.
*/
template <enum_bitops::Candidate T>
constexpr T
operator&(T lhs, T rhs) noexcept
{
return static_cast<T>(std::to_underlying(lhs) & std::to_underlying(rhs));
}
/**
* Bitwise OR.
*
* @param lhs The left operand.
* @param rhs The right operand.
* @return The bits set in @p lhs, in @p rhs, or in both.
*/
template <enum_bitops::Candidate T>
constexpr T
operator|(T lhs, T rhs) noexcept
{
return static_cast<T>(std::to_underlying(lhs) | std::to_underlying(rhs));
}
/**
* Bitwise exclusive OR.
*
* @param lhs The left operand.
* @param rhs The right operand.
* @return The bits set in exactly one of @p lhs and @p rhs.
*/
template <enum_bitops::Candidate T>
constexpr T
operator^(T lhs, T rhs) noexcept
{
return static_cast<T>(std::to_underlying(lhs) ^ std::to_underlying(rhs));
}
/**
* Bitwise complement.
*
* The result has every bit of the underlying type that is clear in
* @p val, including bits that no enumerator names. It is intended for
* clearing flags, as in `flags & ~flag`.
*
* @param val The operand.
* @return The complement of @p val.
*/
template <enum_bitops::Candidate T>
constexpr T
operator~(T val) noexcept
{
return static_cast<T>(~std::to_underlying(val));
}
/**
* Bitwise AND assignment.
*
* @param lhs The value to modify.
* @param rhs The right operand.
* @return A reference to @p lhs.
*/
template <enum_bitops::Candidate T>
constexpr T&
operator&=(T& lhs, T rhs) noexcept
{
lhs = lhs & rhs;
return lhs;
}
/**
* Bitwise OR assignment.
*
* @param lhs The value to modify.
* @param rhs The right operand.
* @return A reference to @p lhs.
*/
template <enum_bitops::Candidate T>
constexpr T&
operator|=(T& lhs, T rhs) noexcept
{
lhs = lhs | rhs;
return lhs;
}
/**
* Bitwise exclusive OR assignment.
*
* @param lhs The value to modify.
* @param rhs The right operand.
* @return A reference to @p lhs.
*/
template <enum_bitops::Candidate T>
constexpr T&
operator^=(T& lhs, T rhs) noexcept
{
lhs = lhs ^ rhs;
return lhs;
}
/** @} */
} // namespace xrpl
+46 -151
View File
@@ -2,212 +2,107 @@
#include <xrpl/beast/utility/instrumentation.h> // IWYU pragma: keep
#include <concepts>
#include <cstdint>
#include <limits>
#include <memory>
#include <type_traits>
#include <utility>
namespace xrpl {
/** Every value of @p Src can be represented by @p Dest.
// safe_cast adds compile-time checks to a static_cast to ensure that
// the destination can hold all values of the source. This is particularly
// handy when the source or destination is an enumeration type.
Given two integral types, the cast is safe when the destination can
hold every possible value of the source.
template <class Src, class Dest>
concept SafeToCast = (std::is_integral_v<Src> && std::is_integral_v<Dest>) &&
(std::is_signed_v<Src> || std::is_unsigned_v<Dest>) &&
(std::is_signed_v<Src> != std::is_signed_v<Dest> ? sizeof(Dest) > sizeof(Src)
: sizeof(Dest) >= sizeof(Src));
Comparing the bounds requires care: we use @c std::cmp_less_equal and
@c std::cmp_greater_equal; the plain relational operators would apply
arithmetic conversions, resulting in incorrect results when comparing
across signedness.
Because @c std::cmp_* requires standard signed or unsigned integer
arguments, which excludes character types and bool, we first widen
all type bounds to the maximum-width integer type while preserving
signedness.
@note Extended integer types, like __int128 on gcc, cannot be safely
widened to a standard integer type, so the concept will reject
them.
*/
template <typename Src, typename Dest>
concept SafeToCast = std::is_integral_v<Src> && std::is_integral_v<Dest> && []() consteval {
using WideSrc = std::conditional_t<std::is_signed_v<Src>, std::intmax_t, std::uintmax_t>;
using WideDest = std::conditional_t<std::is_signed_v<Dest>, std::intmax_t, std::uintmax_t>;
// Note: this guard must be an evaluated branch, and not a
// static_assert. The lambda body is outside the immediate
// context, so a substitution-time failure here would be a
// hard error rather than leaving the concept unsatisfied.
if constexpr (sizeof(Src) > sizeof(WideSrc) || sizeof(Dest) > sizeof(WideDest))
{
return false;
}
else
{
return std::cmp_less_equal(
static_cast<WideDest>(std::numeric_limits<Dest>::min()),
static_cast<WideSrc>(std::numeric_limits<Src>::min())) &&
std::cmp_greater_equal(
static_cast<WideDest>(std::numeric_limits<Dest>::max()),
static_cast<WideSrc>(std::numeric_limits<Src>::max()));
}
}();
/** Compile-time-checked static_cast that rejects non-value preserving casts.
@note There is deliberately no enum-to-enum overload, and no overload
returning the underlying type of an enum. For the latter, use
@c std::to_underlying.
*/
/** @{ */
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
template <class Dest, class Src>
constexpr Dest
safeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
{
static_assert(
SafeToCast<Src, Dest>, "This cast is not value-preserving. Please use unsafeCast instead.");
std::is_signed_v<Dest> || std::is_unsigned_v<Src>, "Cannot cast signed to unsigned");
constexpr unsigned kNotSame = std::is_signed_v<Dest> != std::is_signed_v<Src>;
static_assert(
sizeof(Dest) >= sizeof(Src) + kNotSame,
"Destination is too small to hold all values of source");
return static_cast<Dest>(s);
}
template <typename Dest, typename Src>
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
template <class Dest, class Src>
constexpr Dest
safeCast(Src s) noexcept
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
{
return static_cast<Dest>(safeCast<std::underlying_type_t<Dest>>(s));
}
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
template <class Dest, class Src>
constexpr Dest
safeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
{
return safeCast<Dest>(std::to_underlying(s));
return safeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
}
/** @} */
/** Integral-to-integral cast that is known to be narrowing or sign-erasing.
// unsafe_cast explicitly flags a static_cast as not necessarily able to hold
// all values of the source. It includes a compile-time check so that if
// underlying types become safe, it can be converted to a safe_cast.
This explicitly flags a conversion that can lose information for some
values of the source type, where the call site accepts that loss (or
truncation is the intended behavior).
The compile-time check ensures the cast remains "unsafe": if the types
involved later change such that the conversion becomes inherently
value-preserving, the static assertion fires with instructions to
migrate the call site to @ref safeCast.
If the conversion's safety depends on a runtime precondition rather
than on the types, or varies across instantiations of generic code,
use @ref checkedCast instead.
*/
/** @{ */
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
template <class Dest, class Src>
constexpr Dest
unsafeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
{
static_assert(
!SafeToCast<Src, Dest>, "This cast is value-preserving. Please use safeCast instead.");
!SafeToCast<Src, Dest>,
"Only unsafe if casting signed to unsigned or "
"destination is too small");
return static_cast<Dest>(s);
}
template <typename Dest, typename Src>
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
template <class Dest, class Src>
constexpr Dest
unsafeCast(Src s) noexcept
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
{
return static_cast<Dest>(unsafeCast<std::underlying_type_t<Dest>>(s));
}
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
template <class Dest, class Src>
constexpr Dest
unsafeCast(Src s) noexcept
{
return unsafeCast<Dest>(std::to_underlying(s));
}
/** @} */
/** Integral-to-integral cast when the caller has performed a bounds check.
This documents that a runtime precondition or external invariant, which
is not necessarily visible to the compiler, guarantees that the requested
conversion is value-preserving for the values that can actually occur.
This is primarily meant for generic code, where the same expression may
be value-preserving for one instantiation but not for another, making
both @ref safeCast and @ref unsafeCast unusable.
Unlike @ref safeCast and @ref unsafeCast, this imposes no static check
on the type relationship: the caller's claim is about runtime values,
not about types.
*/
/** @{ */
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
constexpr Dest
checkedCast(Src s) noexcept
{
return static_cast<Dest>(s);
}
template <typename Dest, typename Src>
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
constexpr Dest
checkedCast(Src s) noexcept
{
return static_cast<Dest>(checkedCast<std::underlying_type_t<Dest>>(s));
}
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
constexpr Dest
checkedCast(Src s) noexcept
{
return checkedCast<Dest>(std::to_underlying(s));
return unsafeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
}
/** @} */
/** Downcast within a class hierarchy, verified in debug builds.
Performs a static_cast down a hierarchy, but in debug builds verifies
via dynamic_cast that the object's dynamic type actually permits the
downcast. Both build modes execute the same conversion; debug builds
merely add the check.
The pointer form passes null through unchanged, as dynamic_cast does.
@note The check requires @p Src to be polymorphic; in release builds
an invalid downcast is undefined behavior on use, exactly as
with a bare static_cast.
*/
/** @{ */
template <class Dest, class Src>
requires(
std::is_pointer_v<Dest> && std::is_polymorphic_v<Src> &&
std::derived_from<std::remove_pointer_t<Dest>, Src> &&
(std::is_const_v<std::remove_pointer_t<Dest>> || !std::is_const_v<Src>) &&
(std::is_volatile_v<std::remove_pointer_t<Dest>> || !std::is_volatile_v<Src>))
requires std::is_pointer_v<Dest>
inline Dest
safeDowncast(Src* s) noexcept
{
XRPL_ASSERT(
s == nullptr || dynamic_cast<Dest>(s) != nullptr, "xrpl::safeDowncast : valid downcast");
#ifdef NDEBUG
return static_cast<Dest>(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
#else
auto* result = dynamic_cast<Dest>(s);
XRPL_ASSERT(result != nullptr, "xrpl::safeDowncast : pointer downcast is valid");
return result;
#endif
}
template <class Dest, class Src>
requires(
std::is_lvalue_reference_v<Dest> && std::is_polymorphic_v<Src> &&
std::derived_from<std::remove_reference_t<Dest>, Src>)
requires std::is_lvalue_reference_v<Dest>
inline Dest
safeDowncast(Src& s) noexcept
{
return *safeDowncast<std::add_pointer_t<std::remove_reference_t<Dest>>>(std::addressof(s));
#ifndef NDEBUG
XRPL_ASSERT(
dynamic_cast<std::add_pointer_t<std::remove_reference_t<Dest>>>(&s) != nullptr,
"xrpl::safeDowncast : reference downcast is valid");
#endif
return static_cast<Dest>(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
}
/** @} */
} // namespace xrpl
+152 -208
View File
@@ -4,215 +4,88 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <boost/predef/architecture.h>
#include <atomic>
#include <concepts>
#include <limits>
#include <type_traits>
#if BOOST_ARCH_X86
#ifndef __aarch64__
#include <immintrin.h>
#endif
namespace xrpl {
/** An unsigned integral type suitable for use as a spinlock.
The type must be always lock-free when wrapped in std::atomic, so
that lock operations cannot themselves take a (library-level) lock.
*/
template <typename T>
concept SpinlockValueType = std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free;
/** A spinlock value type that additionally supports the atomic bitwise
operations required to pack multiple locks into a single integer.
*/
template <typename T>
concept PackedSpinlockValueType = SpinlockValueType<T> && requires(std::atomic<T>& a, T v) {
{ a.fetch_or(v) } -> std::same_as<T>;
{ a.fetch_and(v) } -> std::same_as<T>;
};
namespace detail {
/** Inform the processor that we are in a tight spin-wait loop.
Spinlocks caught in tight loops can result in the processor's pipeline
filling up with comparison operations, resulting in a misprediction at
the time the lock is finally acquired, necessitating pipeline flushing
which is ridiculously expensive and results in very high latency.
This function instructs the processor to "pause" for some architecture
specific amount of time, to prevent this.
/**
* Inform the processor that we are in a tight spin-wait loop.
*
* Spinlocks caught in tight loops can result in the processor's pipeline
* filling up with comparison operations, resulting in a misprediction at
* the time the lock is finally acquired, necessitating pipeline flushing
* which is ridiculously expensive and results in very high latency.
*
* This function instructs the processor to "pause" for some architecture
* specific amount of time, to prevent this.
*/
inline void
spinPause() noexcept
{
#if BOOST_ARCH_X86
_mm_pause();
#elif BOOST_ARCH_ARM
asm volatile("yield" ::: "memory");
#ifdef __aarch64__
asm volatile("yield");
#else
#error No implementation available for spinPause to use
_mm_pause();
#endif
}
} // namespace detail
//------------------------------------------------------------------------------
/** Attempt to acquire a spinlock without blocking.
@note This interface is primarily intended for one-shot attempts to
acquire the lock. Avoid calling this function directly from a
loop and use @ref spinLock instead.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
@return true if the lock was acquired, false if it was already held.
/** @{ */
/**
* Classes to handle arrays of spinlocks packed into a single atomic integer:
*
* Packed spinlocks allow for tremendously space-efficient lock-sharding
* but they come at a cost.
*
* First, the implementation is necessarily low-level and uses advanced
* features like memory ordering and highly platform-specific tricks to
* maximize performance. This imposes a significant and ongoing cost to
* developers.
*
* Second, and perhaps most important, is that the packing of multiple
* locks into a single integer which, albeit space-efficient, also has
* performance implications stemming from data dependencies, increased
* cache-coherency traffic between processors and heavier loads on the
* processor's load/store units.
*
* To be sure, these locks can have advantages but they are definitely
* not general purpose locks and should not be thought of or used that
* way. The use cases for them are likely few and far between; without
* a compelling reason to use them, backed by profiling data, it might
* be best to use one of the standard locking primitives instead. Note
* that in most common platforms, `std::mutex` is so heavily optimized
* that it can, usually, outperform spinlocks.
*
* @tparam T An unsigned integral type (e.g. std::uint16_t)
*/
template <SpinlockValueType T>
[[nodiscard]] bool
spinTryLock(std::atomic<T>& lock) noexcept
{
// A compare-exchange is required here, not an unconditional exchange:
// a failed attempt must not modify the lock word, in case the atomic
// is shared with PackedSpinlock).
T expected = 0;
return lock.compare_exchange_strong(
expected,
std::numeric_limits<T>::max(),
std::memory_order::acquire,
std::memory_order::relaxed);
;
}
/** Acquire a spinlock, blocking until available.
Uses a TTAS (test-and-test-and-set) pattern, so waiters share the cache
line read-only, helping to avoid unnecessary coherency traffic.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
/**
* A class that grabs a single packed spinlock from an atomic integer.
*
* This class meets the requirements of Lockable:
* https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <SpinlockValueType T>
void
spinLock(std::atomic<T>& lock) noexcept
{
do
{
// Relaxed ordering is sufficient for the spin: this load is only
// a filter. The acquire on the successful exchange in spinTryLock
// is what synchronizes the critical section.
while (lock.load(std::memory_order::relaxed) != 0)
detail::spinPause();
} while (!spinTryLock(lock));
}
/** Release a spinlock.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
*/
template <SpinlockValueType T>
void
spinUnlock(std::atomic<T>& lock) noexcept
{
lock.store(0, std::memory_order::release);
}
//------------------------------------------------------------------------------
/** A Lockable interface to a spinlock implemented on top of an atomic.
@tparam T An unsigned integral type.
@note Using `PackedSpinlock` and `Spinlock` against the same underlying
atomic integer is possible but can result in `Spinlock` not being
able to acquire the lock during periods of high contention due to
the way the two locks operate: `Spinlock` spins and tries to grab
all the bits at once, whereas any given `PackedSpinlock` instance
only tries to grab one bit at a time. Caveat emptor.
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <SpinlockValueType T>
class Spinlock
{
std::atomic<T>& lock_;
public:
Spinlock(Spinlock const&) = delete;
Spinlock&
operator=(Spinlock const&) = delete;
/** Construct a spinlock handle.
@param lock The atomic integer to spin against.
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
*/
explicit Spinlock(std::atomic<T>& lock) noexcept : lock_(lock)
{
}
[[nodiscard]] bool
try_lock() noexcept // NOLINT(readability-identifier-naming)
{
return spinTryLock(lock_);
}
void
lock() noexcept
{
spinLock(lock_);
}
void
unlock() noexcept
{
spinUnlock(lock_);
}
};
//------------------------------------------------------------------------------
/** A Lockable interface to a packed spinlock implemented on top of an atomic.
Packed spinlocks offer tremendous space-efficient lock-sharding but
they come at a cost.
First, the implementation is necessarily low-level and uses advanced
features like memory ordering and highly platform-specific tricks to
maximize performance. This imposes a significant and ongoing cost to
developers.
Second, and perhaps most important, is that the packing of multiple
locks into a single integer which, albeit space-efficient, also has
performance implications stemming from data dependencies, increased
cache-coherency traffic between processors and heavier loads on the
processor's load/store units.
To be sure, these locks can have advantages but they are definitely
not general purpose locks and should not be thought of or used that
way. The use cases for them are likely few and far between; without
a compelling reason to use them, backed by profiling data, it might
be best to use one of the standard locking primitives instead. Note
that in most common platforms, `std::mutex` is so heavily optimized
that it can, usually, outperform spinlocks.
@tparam T An unsigned integral type (e.g. std::uint16_t)
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <PackedSpinlockValueType T>
template <class T>
class PackedSpinlock
{
// clang-format off
static_assert(std::is_unsigned_v<T>);
static_assert(std::atomic<T>::is_always_lock_free);
static_assert(
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_or(0)), T> &&
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_and(0)), T>,
"std::atomic<T>::fetch_and(T) and std::atomic<T>::fetch_and(T) are required by packed_spinlock");
// clang-format on
private:
std::atomic<T>& bits_;
T const mask_;
@@ -221,49 +94,120 @@ public:
PackedSpinlock&
operator=(PackedSpinlock const&) = delete;
/** Construct a packed spinlock handle for a single bit.
@param lock The atomic integer inside which the spinlock is packed.
@param index The index of the spinlock this object acquires.
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
/**
* A single spinlock packed inside the specified atomic
*
* @param lock The atomic integer inside which the spinlock is packed.
* @param index The index of the spinlock this object acquires.
*
* @note For performance reasons, you should strive to have `lock` be
* on a cacheline by itself.
*/
PackedSpinlock(std::atomic<T>& lock, int index) noexcept
: bits_(lock), mask_([index]() {
XRPL_ASSERT(
index >= 0 && index < std::numeric_limits<T>::digits,
"xrpl::PackedSpinlock::PackedSpinlock : valid index");
return static_cast<T>(T{1} << index);
}())
PackedSpinlock(std::atomic<T>& lock, int index) : bits_(lock), mask_(static_cast<T>(1) << index)
{
XRPL_ASSERT(
index >= 0 && (mask_ != 0),
"xrpl::PackedSpinlock::PackedSpinlock : valid index and mask");
}
[[nodiscard]] bool
try_lock() // NOLINT(readability-identifier-naming)
{
return (bits_.fetch_or(mask_, std::memory_order_acquire) & mask_) == 0;
}
void
lock()
{
while (!try_lock())
{
// The use of relaxed memory ordering here is intentional and
// serves to help reduce cache coherency traffic during times
// of contention by avoiding writes that would definitely not
// result in the lock being acquired.
while ((bits_.load(std::memory_order_relaxed) & mask_) != 0)
detail::spinPause();
}
}
void
unlock()
{
bits_.fetch_and(~mask_, std::memory_order_release);
}
};
/**
* A spinlock implemented on top of an atomic integer.
*
* @note Using `packed_spinlock` and `spinlock` against the same underlying
* atomic integer can result in `spinlock` not being able to actually
* acquire the lock during periods of high contention, because of how
* the two locks operate: `spinlock` will spin trying to grab all the
* bits at once, whereas any given `packed_spinlock` will only try to
* grab one bit at a time. Caveat emptor.
*
* This class meets the requirements of Lockable:
* https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <class T>
class Spinlock
{
static_assert(std::is_unsigned_v<T>);
static_assert(std::atomic<T>::is_always_lock_free);
private:
std::atomic<T>& lock_;
public:
Spinlock(Spinlock const&) = delete;
Spinlock&
operator=(Spinlock const&) = delete;
/**
* Grabs the
*
* @param lock The atomic integer to spin against.
*
* @note For performance reasons, you should strive to have `lock` be
* on a cacheline by itself.
*/
Spinlock(std::atomic<T>& lock) : lock_(lock)
{
}
[[nodiscard]] bool
try_lock() noexcept // NOLINT(readability-identifier-naming)
try_lock() // NOLINT(readability-identifier-naming)
{
return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
T expected = 0;
return lock_.compare_exchange_weak(
expected,
std::numeric_limits<T>::max(),
std::memory_order_acquire,
std::memory_order_relaxed);
}
void
lock() noexcept
lock()
{
do
while (!try_lock())
{
// The use of relaxed memory ordering here is intentional and
// serves to help reduce cache coherency traffic during times
// of contention by avoiding writes that are unlikely to grab
// the requested lock.
while ((bits_.load(std::memory_order::relaxed) & mask_) != 0)
// of contention by avoiding writes that would definitely not
// result in the lock being acquired.
while (lock_.load(std::memory_order_relaxed) != 0)
detail::spinPause();
} while (!try_lock());
}
}
void
unlock() noexcept
unlock()
{
bits_.fetch_and(~mask_, std::memory_order::release);
lock_.store(0, std::memory_order_release);
}
};
/** @} */
} // namespace xrpl
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include <cstdlib>
#include <string>
#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#include <cxxabi.h>
#endif
namespace beast {
template <typename T>
std::string
typeName()
{
using TR = std::remove_reference_t<T>;
std::string name = typeid(TR).name();
#ifndef _MSC_VER
if (auto s = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, nullptr))
{
name = s;
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
std::free(s);
}
#endif
if (std::is_const_v<TR>)
name += " const";
if (std::is_volatile_v<TR>)
name += " volatile";
if (std::is_lvalue_reference_v<T>)
{
name += "&";
}
else if (std::is_rvalue_reference_v<T>)
{
name += "&&";
}
return name;
}
} // namespace beast
+109 -36
View File
@@ -2,24 +2,26 @@
#pragma once
#include <compare>
#include <concepts>
namespace beast {
/**
* Zero allows classes to offer efficient comparisons to zero.
*
* Zero is a struct to allow classes to efficiently compare with zero without
* requiring an rvalue construction.
*
* It's often the case that we have classes which combine a number and a unit.
* In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons
* like t > 1 or t != 1 do not. Comparing against kZero expresses exactly that,
* without constructing a T.
* like t > 1 or t != 1 do not.
*
* A type T participates if either `t.signum()` or an unqualified `signum(t)`
* found by argument-dependent lookup returns an integer that is negative,
* zero, or positive according to the sign of t. Both `t == kZero` and
* `kZero == t` work, as do all six relational operators in either order.
* The class Zero allows such comparisons to be easily made.
*
* The comparing class T either needs to have a method called signum() which
* returns a positive number, 0, or a negative; or there needs to be a signum
* function which resolves in the namespace which takes an instance of T and
* returns a positive, zero or negative number.
*/
struct Zero
{
explicit Zero() = default;
@@ -28,44 +30,115 @@ struct Zero
inline constexpr Zero kZero{};
/**
* Default implementation of signum: call the member function.
* Default implementation of signum calls the method on the class.
*/
template <class T>
requires requires(T const& t) {
{ t.signum() } -> std::integral;
}
[[nodiscard]] constexpr auto
signum(T const& t) noexcept(noexcept(t.signum()))
template <typename T>
auto
signum(T const& t)
{
return t.signum();
}
namespace detail {
namespace detail::zero_helper {
/**
* A type with a usable signum: either the member-based default above, or a
* `signum(t)` overload in T's own namespace, found by ADL. A user overload
* that is a better match than the template wins, as usual.
*/
// For argument dependent lookup to function properly, calls to signum must
// be made from a namespace that does not include overloads of the function..
template <class T>
concept HasSignum = requires(T const& t) {
{ signum(t) } -> std::integral;
};
} // namespace detail
template <detail::HasSignum T>
[[nodiscard]] constexpr bool
operator==(T const& t, Zero) noexcept(noexcept(signum(t)))
auto
callSignum(T const& t)
{
return signum(t) == 0;
return signum(t);
}
template <detail::HasSignum T>
[[nodiscard]] constexpr std::strong_ordering
operator<=>(T const& t, Zero) noexcept(noexcept(signum(t)))
} // namespace detail::zero_helper
// Handle operators where T is on the left side using signum.
template <typename T>
bool
operator==(T const& t, Zero)
{
return signum(t) <=> 0;
return detail::zero_helper::callSignum(t) == 0;
}
template <typename T>
bool
operator!=(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) != 0;
}
template <typename T>
bool
operator<(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) < 0;
}
template <typename T>
bool
operator>(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) > 0;
}
template <typename T>
bool
operator>=(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) >= 0;
}
template <typename T>
bool
operator<=(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) <= 0;
}
// Handle operators where T is on the right side by
// reversing the operation, so that T is on the left side.
template <typename T>
bool
operator==(Zero, T const& t)
{
return t == kZero;
}
template <typename T>
bool
operator!=(Zero, T const& t)
{
return t != kZero;
}
template <typename T>
bool
operator<(Zero, T const& t)
{
return t > kZero;
}
template <typename T>
bool
operator>(Zero, T const& t)
{
return t < kZero;
}
template <typename T>
bool
operator>=(Zero, T const& t)
{
return t <= kZero;
}
template <typename T>
bool
operator<=(Zero, T const& t)
{
return t >= kZero;
}
} // namespace beast
+53 -12
View File
@@ -1,7 +1,7 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/enum_bitops.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/ReadView.h>
@@ -22,39 +22,80 @@
namespace xrpl {
enum class ApplyFlags : std::uint32_t {
None = 0x00,
// Bitwise flag enum with existing operator overloads
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum ApplyFlags : std::uint32_t {
TapNone = 0x00,
// This is a local transaction with the
// fail_hard flag set.
FailHard = 0x10,
TapFailHard = 0x10,
// This is not the transaction's last pass
// Transaction can be retried, soft failures allowed
Retry = 0x20,
TapRetry = 0x20,
// Transaction came from a privileged source
Unlimited = 0x400,
TapUnlimited = 0x400,
// Transaction is executing as part of a batch
Batch = 0x800,
TapBatch = 0x800,
// Transaction shouldn't be applied
// Signatures shouldn't be checked
DryRun = 0x1000,
TapDryRun = 0x1000,
// Transaction is being preflighted as the payload of a
// TransactionProposalCreate. Its signatures are collected on-ledger
// afterward, so signature-presence checks (e.g. Batch signer matching)
// are skipped at proposal-creation time (On-Chain Cosigner spec
// §5.3.1.2).
Proposal = 0x2000
TapProposal = 0x2000
};
template <>
struct enum_bitops::OptIn<ApplyFlags> : std::true_type
constexpr ApplyFlags
operator|(ApplyFlags const& lhs, ApplyFlags const& rhs)
{
};
return safeCast<ApplyFlags>(
safeCast<std::underlying_type_t<ApplyFlags>>(lhs) |
safeCast<std::underlying_type_t<ApplyFlags>>(rhs));
}
static_assert((TapFailHard | TapRetry) == safeCast<ApplyFlags>(0x30u), "ApplyFlags operator |");
static_assert((TapRetry | TapFailHard) == safeCast<ApplyFlags>(0x30u), "ApplyFlags operator |");
constexpr ApplyFlags
operator&(ApplyFlags const& lhs, ApplyFlags const& rhs)
{
return safeCast<ApplyFlags>(
safeCast<std::underlying_type_t<ApplyFlags>>(lhs) &
safeCast<std::underlying_type_t<ApplyFlags>>(rhs));
}
static_assert((TapFailHard & TapRetry) == TapNone, "ApplyFlags operator &");
static_assert((TapRetry & TapFailHard) == TapNone, "ApplyFlags operator &");
constexpr ApplyFlags
operator~(ApplyFlags const& flags)
{
return safeCast<ApplyFlags>(~safeCast<std::underlying_type_t<ApplyFlags>>(flags));
}
static_assert(~TapRetry == safeCast<ApplyFlags>(0xFFFFFFDFu), "ApplyFlags operator ~");
inline ApplyFlags
operator|=(ApplyFlags& lhs, ApplyFlags const& rhs)
{
lhs = lhs | rhs;
return lhs;
}
inline ApplyFlags
operator&=(ApplyFlags& lhs, ApplyFlags const& rhs)
{
lhs = lhs & rhs;
return lhs;
}
//------------------------------------------------------------------------------
@@ -1,17 +1,11 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STVector256.h>
#include <cstddef>
#include <optional>
namespace xrpl {
@@ -31,31 +25,6 @@ public:
: Base(keylet::skip(), view, j)
{
}
/**
* Looks up a hash `diff` slots back from the most recent entry in the
* sfHashes vector (diff == 0 is the most recent entry).
*
* Callers decide which skip list entry to read and how to translate a
* target ledger sequence into `diff`; this only does the bounds check
* and vector indexing shared by both the recent (stride 1) and distant
* (stride 256) skip lists.
*
* @param diff how many slots back from the most recent hash to look up;
* 0 is the most recent hash.
* @return the hash at that slot, or std::nullopt if the entry does not
* exist or `diff` is out of range.
*/
[[nodiscard]] std::optional<UInt256>
hashAt(std::size_t diff) const
{
if (!this->exists())
return std::nullopt;
STVector256 const vec = (*this)->getFieldV256(sfHashes);
if (vec.size() > diff)
return vec[vec.size() - diff - 1];
return std::nullopt;
}
};
using LedgerHashesEntryR = LedgerHashesEntry<ReadView>;
+3 -5
View File
@@ -1,10 +1,8 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/nodestore/NodeObject.h>
#include <memory>
#include <optional>
namespace xrpl::node_store {
@@ -25,7 +23,7 @@ public:
/**
* Construct the decoded blob from raw data.
*/
DecodedBlob(std::optional<uint256> key, void const* value, int valueBytes);
DecodedBlob(void const* key, void const* value, int valueBytes);
/**
* Determine if the decoding was successful.
@@ -45,10 +43,10 @@ public:
private:
bool success_{false};
uint256 key_;
void const* key_;
NodeObjectType objectType_{NodeObjectType::Unknown};
unsigned char const* objectData_{nullptr};
int dataBytes_ = 0;
int dataBytes_;
};
} // namespace xrpl::node_store
+8 -10
View File
@@ -1,6 +1,5 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/nodestore/NodeObject.h>
@@ -38,6 +37,11 @@ namespace xrpl::node_store {
class EncodedBlob
{
/**
* The 32-byte key of the serialized object.
*/
std::array<std::uint8_t, 32> key_{};
/**
* A pre-allocated buffer for the serialized object.
*
@@ -61,11 +65,6 @@ class EncodedBlob
*/
std::uint8_t* const ptr_;
/**
* The 32-byte key of the serialized object.
*/
uint256 key_;
public:
explicit EncodedBlob(std::shared_ptr<NodeObject> const& obj)
: size_([&obj]() {
@@ -77,12 +76,11 @@ public:
return obj->getData().size() + 9;
}())
, ptr_((size_ <= payload_.size()) ? payload_.data() : new std::uint8_t[size_])
, key_(obj->getHash())
{
std::fill_n(ptr_, 8, std::uint8_t{0});
ptr_[8] = static_cast<std::uint8_t>(obj->getType());
std::copy_n(obj->getData().data(), obj->getData().size(), ptr_ + 9);
std::copy_n(obj->getHash().data(), obj->getHash().size(), key_.data());
}
~EncodedBlob()
@@ -97,10 +95,10 @@ public:
delete[] ptr_;
}
[[nodiscard]] uint256 const&
[[nodiscard]] void const*
getKey() const noexcept
{
return key_;
return static_cast<void const*>(key_.data());
}
[[nodiscard]] std::size_t
+23 -15
View File
@@ -61,22 +61,14 @@ parseBase58(std::string const& s);
/**
* A special account that's used as the "issuer" for XRP.
*/
constexpr inline AccountID const&
xrpAccount() noexcept
{
static constexpr AccountID kAccount(beast::kZero);
return kAccount;
}
AccountID const&
xrpAccount();
/**
* A placeholder for empty accounts.
*/
constexpr inline AccountID const&
noAccount() noexcept
{
static constexpr AccountID kAccount = xrpAccount().next();
return kAccount;
}
AccountID const&
noAccount();
/**
* Convert hex or base58 string to AccountID.
@@ -88,10 +80,10 @@ bool
toIssuer(AccountID&, std::string const&);
// DEPRECATED Should be checking the currency or native flag
constexpr inline bool
isXRP(AccountID const& c) noexcept
inline bool
isXRP(AccountID const& c)
{
return c == xrpAccount();
return c == beast::kZero;
}
// DEPRECATED
@@ -109,6 +101,22 @@ operator<<(std::ostream& os, AccountID const& x)
return os;
}
/**
* Initialize the global cache used to map AccountID to base58 conversions.
*
* The cache is optional and need not be initialized. But because conversion
* is expensive (it requires a SHA-256 operation) in most cases the overhead
* of the cache is worth the benefit.
*
* @param count The number of entries the cache should accommodate. Zero will
* disable the cache, releasing any memory associated with it.
*
* @note The function will only initialize the cache the first time it is
* invoked. Subsequent invocations do nothing.
*/
void
initAccountIdCache(std::size_t count);
} // namespace xrpl
//------------------------------------------------------------------------------
+2 -2
View File
@@ -1,10 +1,10 @@
#pragma once
#include <xrpl/basics/contract.h>
#include <xrpl/beast/type_name.h>
#include <xrpl/protocol/SOTemplate.h>
#include <boost/container/flat_map.hpp>
#include <boost/core/type_name.hpp>
#include <algorithm>
#include <cstddef>
@@ -84,7 +84,7 @@ public:
* Derived classes will load the object with all the known formats.
*/
private:
KnownFormats() : name_(boost::core::type_name<Derived>())
KnownFormats() : name_(beast::typeName<Derived>())
{
}
+6
View File
@@ -75,6 +75,12 @@ addRaw(LedgerHeader const&, Serializer&, bool includeHash = false);
LedgerHeader
deserializeHeader(Slice data, bool hasHash = false);
/**
* Deserialize a ledger header (prefixed with 4 bytes) from a byte array.
*/
LedgerHeader
deserializePrefixedHeader(Slice data, bool hasHash = false);
/**
* Calculate the hash of a ledger header.
*/
+3 -2
View File
@@ -19,8 +19,8 @@ private:
// But an STAccount is always 160 bits, so we can store it with less
// overhead in an xrpl::UInt160. However, so the serialized format of the
// STAccount stays unchanged, we serialize and deserialize like an STBlob.
AccountID value_{};
bool default_{true};
AccountID value_;
bool default_;
public:
using value_type = AccountID;
@@ -28,6 +28,7 @@ public:
STAccount();
STAccount(SField const& n);
STAccount(SField const& n, Buffer const& v);
STAccount(SerialIter& sit, SField const& name);
STAccount(SField const& n, AccountID const& v);
+3 -16
View File
@@ -33,20 +33,7 @@ public:
STBitString(SField const& n);
STBitString(value_type const& v);
template <typename Tag>
requires(!std::is_void_v<Tag>)
STBitString(BaseUInt<Bits, Tag> const& v) : value_(v)
{
}
STBitString(SField const& n, value_type const& v);
template <typename Tag>
requires(!std::is_void_v<Tag>)
STBitString(SField const& n, BaseUInt<Bits, Tag> const& v) : STBase(n), value_(v)
{
}
STBitString(SerialIter& sit, SField const& name);
[[nodiscard]] SerializedTypeID
@@ -104,7 +91,7 @@ inline STBitString<Bits>::STBitString(SField const& n, value_type const& v) : ST
template <int Bits>
inline STBitString<Bits>::STBitString(SerialIter& sit, SField const& name)
: STBitString(name, sit.getBitString<value_type>())
: STBitString(name, sit.getBitString<Bits>())
{
}
@@ -171,7 +158,7 @@ STBitString<Bits>::add(Serializer& s) const
{
XRPL_ASSERT(getFName().isBinary(), "xrpl::STBitString::add : field is binary");
XRPL_ASSERT(getFName().fieldType == getSType(), "xrpl::STBitString::add : field type match");
s.addBitString(value_);
s.addBitString<Bits>(value_);
}
template <int Bits>
@@ -179,7 +166,7 @@ template <typename Tag>
void
STBitString<Bits>::setValue(BaseUInt<Bits, Tag> const& v)
{
value_ = value_type{v};
value_ = v;
}
template <int Bits>
+4 -15
View File
@@ -16,13 +16,12 @@ namespace xrpl {
template <typename Integer>
class STInteger : public STBase, public CountedObject<STInteger<Integer>>
{
static_assert(
SerializableInteger<Integer>,
"STInteger requires an integer type that Serializer can append");
public:
using value_type = Integer;
private:
Integer value_;
public:
explicit STInteger(Integer v);
STInteger(SField const& n, Integer v = 0);
@@ -63,8 +62,6 @@ private:
move(std::size_t n, void* buf) override;
friend class xrpl::detail::STVar;
Integer value_;
};
using STUInt8 = STInteger<unsigned char>;
@@ -104,15 +101,7 @@ STInteger<Integer>::add(Serializer& s) const
{
XRPL_ASSERT(getFName().isBinary(), "xrpl::STInteger::add : field is binary");
XRPL_ASSERT(getFName().fieldType == getSType(), "xrpl::STInteger::add : field type match");
if constexpr (sizeof(Integer) == 1)
s.add8(value_);
else if constexpr (sizeof(Integer) == 2)
s.add16(value_);
else if constexpr (sizeof(Integer) == 4)
s.add32(value_);
else
s.add64(value_);
s.addInteger(value_);
}
template <typename Integer>
+51 -56
View File
@@ -2,7 +2,6 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/enum_bitops.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
@@ -16,7 +15,6 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>
@@ -24,8 +22,18 @@ namespace xrpl {
class STPathElement final : public CountedObject<STPathElement>
{
unsigned int type_;
AccountID accountID_;
PathAsset assetID_;
AccountID issuerID_;
bool isOffer_;
std::size_t hashValue_;
public:
enum class Type : std::uint8_t {
// Bitwise values (typeCurrency | typeMPT)
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum Type {
TypeNone = 0x00,
TypeAccount = 0x01, // Rippling through an account (vs taking an offer).
TypeCurrency = 0x10, // Currency follows.
@@ -37,8 +45,6 @@ public:
// Combination of all types.
};
using enum Type;
STPathElement();
STPathElement(STPathElement const&) = default;
STPathElement&
@@ -56,42 +62,42 @@ public:
bool forceAsset = false);
STPathElement(
Type type,
unsigned int uType,
AccountID const& account,
PathAsset const& asset,
AccountID const& issuer);
[[nodiscard]] Type
getNodeType() const noexcept;
[[nodiscard]] std::uint32_t
getNodeType() const;
[[nodiscard]] bool
isOffer() const noexcept;
isOffer() const;
[[nodiscard]] bool
isAccount() const noexcept;
isAccount() const;
[[nodiscard]] bool
hasIssuer() const noexcept;
hasIssuer() const;
[[nodiscard]] bool
hasCurrency() const noexcept;
hasCurrency() const;
[[nodiscard]] bool
hasMPT() const noexcept;
hasMPT() const;
[[nodiscard]] bool
hasAsset() const noexcept;
hasAsset() const;
[[nodiscard]] bool
isNone() const noexcept;
isNone() const;
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a
// class of offers.
[[nodiscard]] AccountID const&
getAccountID() const noexcept;
getAccountID() const;
[[nodiscard]] PathAsset const&
getPathAsset() const noexcept;
getPathAsset() const;
[[nodiscard]] Currency const&
getCurrency() const;
@@ -100,35 +106,17 @@ public:
getMPTID() const;
[[nodiscard]] AccountID const&
getIssuerID() const noexcept;
getIssuerID() const;
[[nodiscard]] bool
isType(Type pe) const noexcept;
isType(Type const& pe) const;
friend bool
operator==(STPathElement const& lhs, STPathElement const& rhs) noexcept
{
return lhs.isType(TypeAccount) == rhs.isType(TypeAccount) &&
lhs.hashValue_ == rhs.hashValue_ && lhs.accountID_ == rhs.accountID_ &&
lhs.assetID_ == rhs.assetID_ && lhs.issuerID_ == rhs.issuerID_;
}
bool
operator==(STPathElement const& t) const;
private:
static std::size_t
getHash(STPathElement const& element);
Type type_;
AccountID accountID_;
PathAsset assetID_;
AccountID issuerID_;
bool isOffer_;
std::size_t hashValue_;
};
template <>
struct enum_bitops::OptIn<STPathElement::Type> : std::true_type
{
};
template <class Hasher>
@@ -136,7 +124,7 @@ void
hash_append(Hasher& h, STPathElement const& e) noexcept
{
using beast::hash_append;
hash_append(h, e.isType(STPathElement::TypeAccount));
hash_append(h, (e.getNodeType() & STPathElement::TypeAccount) != 0u);
hash_append(h, e.getAccountID());
hash_append(h, e.getPathAsset());
hash_append(h, e.getIssuerID());
@@ -416,11 +404,11 @@ inline STPathElement::STPathElement(
}
inline STPathElement::STPathElement(
Type type,
unsigned int uType,
AccountID const& account,
PathAsset const& asset,
AccountID const& issuer)
: type_(type)
: type_(uType)
, accountID_(account)
, assetID_(asset)
, issuerID_(issuer)
@@ -435,56 +423,56 @@ inline STPathElement::STPathElement(
hashValue_ = getHash(*this);
}
inline STPathElement::Type
STPathElement::getNodeType() const noexcept
inline std::uint32_t
STPathElement::getNodeType() const
{
return type_;
}
inline bool
STPathElement::isOffer() const noexcept
STPathElement::isOffer() const
{
return isOffer_;
}
inline bool
STPathElement::isAccount() const noexcept
STPathElement::isAccount() const
{
return !isOffer();
}
inline bool
STPathElement::isType(Type pe) const noexcept
STPathElement::isType(Type const& pe) const
{
return (type_ & pe) != STPathElement::TypeNone;
return (type_ & pe) != 0u;
}
inline bool
STPathElement::hasIssuer() const noexcept
STPathElement::hasIssuer() const
{
return isType(STPathElement::TypeIssuer);
}
inline bool
STPathElement::hasCurrency() const noexcept
STPathElement::hasCurrency() const
{
return isType(STPathElement::TypeCurrency);
}
inline bool
STPathElement::hasMPT() const noexcept
STPathElement::hasMPT() const
{
return isType(STPathElement::TypeMpt);
}
inline bool
STPathElement::hasAsset() const noexcept
STPathElement::hasAsset() const
{
return isType(STPathElement::TypeAsset);
}
inline bool
STPathElement::isNone() const noexcept
STPathElement::isNone() const
{
return getNodeType() == STPathElement::TypeNone;
}
@@ -492,13 +480,13 @@ STPathElement::isNone() const noexcept
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a
// class of offers.
inline AccountID const&
STPathElement::getAccountID() const noexcept
STPathElement::getAccountID() const
{
return accountID_;
}
inline PathAsset const&
STPathElement::getPathAsset() const noexcept
STPathElement::getPathAsset() const
{
return assetID_;
}
@@ -516,11 +504,18 @@ STPathElement::getMPTID() const
}
inline AccountID const&
STPathElement::getIssuerID() const noexcept
STPathElement::getIssuerID() const
{
return issuerID_;
}
inline bool
STPathElement::operator==(STPathElement const& t) const
{
return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hashValue_ == t.hashValue_ &&
accountID_ == t.accountID_ && assetID_ == t.assetID_ && issuerID_ == t.issuerID_;
}
// ------------ STPath ------------
inline STPath::STPath(std::vector<STPathElement> p) : path_(std::move(p))
+7 -1
View File
@@ -2,7 +2,6 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
@@ -154,6 +153,13 @@ public:
[[nodiscard]] std::string
getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData) const;
[[nodiscard]] std::string
getMetaSQL(
Serializer rawTxn,
std::uint32_t inLedger,
TxnSql status,
std::string const& escapedMetaData) const;
/**
* The IDs of the inner transactions of a Batch.
*/
File diff suppressed because it is too large. Load diff
+4 -3
View File
@@ -14,7 +14,7 @@ namespace xrpl {
// Various protocol and system specific constant globals.
/* The name of the system. */
inline std::string const&
static inline std::string const&
systemName()
{
static std::string const kName = "xrpld";
@@ -52,10 +52,11 @@ isLegalAmountSigned(XRPAmount const& amount)
}
/* The currency code for the native currency. */
inline std::string
static inline std::string const&
systemCurrencyCode()
{
return "XRP";
static std::string const kCode = "XRP";
return kCode;
}
/**
-6
View File
@@ -238,12 +238,6 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfSponsorshipEnd, 0x00010000) \
TF_FLAG(tfSponsorshipCreate, 0x00020000) \
TF_FLAG(tfSponsorshipReassign, 0x00040000), \
MASK_ADJ(0)) \
\
TRANSACTION(ConfidentialMPTHolderKeyUpdate, \
TF_FLAG(tfHolderKeyRotation, 0x00010000) \
TF_FLAG(tfHolderKeyRecovery, 0x00020000) \
TF_FLAG(tfCancelRecovery, 0x00040000), \
MASK_ADJ(0))
// clang-format on
+12 -25
View File
@@ -61,40 +61,26 @@ using Domain = BaseUInt<256>;
/**
* XRP currency.
*/
constexpr inline Currency const&
xrpCurrency() noexcept
{
static constexpr Currency const kCurrency(beast::kZero);
return kCurrency;
}
Currency const&
xrpCurrency();
/**
* A placeholder for empty currencies.
*/
constexpr inline Currency const&
noCurrency() noexcept
{
static constexpr Currency const kCurrency(xrpCurrency().next());
return kCurrency;
}
Currency const&
noCurrency();
/**
* We deliberately disallow the currency that looks like "XRP" because too
* many people were using it instead of the correct XRP currency.
*
* Note that this doesn't catch "xRP" or "xrp" or other case variations.
*/
constexpr inline Currency const&
badCurrency() noexcept
{
static constexpr Currency kCurrency{"0000000000000000000000005852500000000000"};
return kCurrency;
}
Currency const&
badCurrency();
constexpr inline bool
isXRP(Currency const& c) noexcept
inline bool
isXRP(Currency const& c)
{
return c == xrpCurrency();
return c == beast::kZero;
}
/**
@@ -112,7 +98,7 @@ to_string(Currency const& c);
* to rewrite some unit test code.
*/
bool
toCurrency(Currency&, std::string_view);
toCurrency(Currency&, std::string const&);
/**
* Tries to convert a string to a Currency, returns noCurrency() on failure.
@@ -121,7 +107,8 @@ toCurrency(Currency&, std::string_view);
* unfortunate; changing this will require very careful checking
* everywhere and may mean having to rewrite some unit test code.
*/
Currency toCurrency(std::string_view);
Currency
toCurrency(std::string const&);
inline std::ostream&
operator<<(std::ostream& os, Currency const& x)
@@ -439,7 +439,6 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
{sfIssuerKeyMirrorEpoch, SoeOptional},
{sfAuditorKeyMirrorEpoch, SoeOptional},
{sfHolderEncryptionKey, SoeOptional},
{sfRecoveryKey, SoeOptional},
}))
/** A ledger object which tracks Oracle
@@ -330,7 +330,6 @@ TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
TYPED_SFIELD(sfAmountCommitment, VL, 45)
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
TYPED_SFIELD(sfBytecode, VL, 47)
TYPED_SFIELD(sfRecoveryKey, VL, 48)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
@@ -1147,27 +1147,12 @@ TRANSACTION(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, 92, ConfidentialMPTMirrorUpdate,
{sfZKProof, SoeRequired},
}))
/** This transaction rotates or recovers a confidential MPT holder's ElGamal encryption key,
or cancels a recovery. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTHolderKeyUpdate.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE, 93, ConfidentialMPTHolderKeyUpdate,
({.amendment = featureConfidentialMPTKeyRotation}),
({
{sfMPTokenIssuanceID, SoeRequired},
{sfHolderEncryptionKey, SoeOptional},
{sfConfidentialBalanceSpending, SoeOptional},
{sfConfidentialBalanceInbox, SoeOptional},
{sfZKProof, SoeOptional},
}))
/** This transaction posts an unsigned transaction on-ledger as a
TransactionProposal, pending multi-signature collection. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/proposal/TransactionProposalCreate.h>
#endif
TRANSACTION(ttTRANSACTION_PROPOSAL_CREATE, 95, TransactionProposalCreate,
TRANSACTION(ttTRANSACTION_PROPOSAL_CREATE, 93, TransactionProposalCreate,
({.amendment = featureCosign}),
({
{sfProposedTransaction, SoeRequired},
+1 -2
View File
@@ -8,7 +8,6 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
#include <type_traits>
namespace xrpl {
@@ -183,7 +182,7 @@ public:
operator result_type() noexcept
{
auto const digest = Sha512Hasher::result_type(h_);
return result_type{std::span{digest}.first<result_type::size()>()};
return result_type::fromVoid(digest.data());
}
private:
-1
View File
@@ -392,7 +392,6 @@ JSS(max_ledger); // in/out: LedgerCleaner
JSS(max_queue_size); // out: TxQ
JSS(max_spend_drops); // out: AccountInfo
JSS(max_spend_drops_total); // out: AccountInfo
JSS(maximum);
JSS(mean); // out: get_aggregate_price
JSS(median); // out: get_aggregate_price
JSS(median_fee); // out: TxQ
+1 -3
View File
@@ -8,7 +8,6 @@
#include <cstdint>
#include <cstring>
#include <span>
namespace xrpl::nft {
@@ -99,8 +98,7 @@ getTaxon(UInt256 const& id)
inline AccountID
getIssuer(UInt256 const& id)
{
return AccountID{
std::span<unsigned char const, uint256::size()>{id}.subspan<4, AccountID::size()>()};
return AccountID::fromVoid(id.data() + 4);
}
} // namespace xrpl::nft
+4 -1
View File
@@ -2,10 +2,13 @@
#include <xrpl/basics/base_uint.h>
#include <string_view>
namespace xrpl::nft {
// NFT directory pages order their contents based only on the low 96 bits of
// the NFToken value. This mask provides easy access to the necessary mask.
constexpr UInt256 kPageMask{"0000000000000000000000000000000000000000ffffffffffffffffffffffff"};
constexpr UInt256 kPageMask(
std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"));
} // namespace xrpl::nft
+1 -1
View File
@@ -18,7 +18,7 @@ serializeBlob(Object const& o)
{
Serializer s;
o.add(s);
return s.takeData();
return s.peekData();
}
/**
@@ -339,30 +339,6 @@ public:
{
return this->sle_->isFieldPresent(sfHolderEncryptionKey);
}
/**
* @brief Get sfRecoveryKey (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getRecoveryKey() const
{
if (hasRecoveryKey())
return this->sle_->at(sfRecoveryKey);
return std::nullopt;
}
/**
* @brief Check if sfRecoveryKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasRecoveryKey() const
{
return this->sle_->isFieldPresent(sfRecoveryKey);
}
};
/**
@@ -576,17 +552,6 @@ public:
return *this;
}
/**
* @brief Set sfRecoveryKey (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setRecoveryKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfRecoveryKey] = value;
return *this;
}
/**
* @brief Build and return the completed MPToken wrapper.
* @param index The ledger entry index.
@@ -1,279 +0,0 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ConfidentialMPTHolderKeyUpdateBuilder;
/**
* @brief Transaction: ConfidentialMPTHolderKeyUpdate
*
* Type: ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE (93)
* Delegable: Delegation::NotDelegable
* Amendment: featureConfidentialMPTKeyRotation
* Privileges: Privilege::NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTHolderKeyUpdateBuilder to construct new transactions.
*/
class ConfidentialMPTHolderKeyUpdate : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE;
/**
* @brief Construct a ConfidentialMPTHolderKeyUpdate transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTHolderKeyUpdate(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTHolderKeyUpdate");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfHolderEncryptionKey (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getHolderEncryptionKey() const
{
if (hasHolderEncryptionKey())
{
return this->tx_->at(sfHolderEncryptionKey);
}
return std::nullopt;
}
/**
* @brief Check if sfHolderEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHolderEncryptionKey() const
{
return this->tx_->isFieldPresent(sfHolderEncryptionKey);
}
/**
* @brief Get sfConfidentialBalanceSpending (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getConfidentialBalanceSpending() const
{
if (hasConfidentialBalanceSpending())
{
return this->tx_->at(sfConfidentialBalanceSpending);
}
return std::nullopt;
}
/**
* @brief Check if sfConfidentialBalanceSpending is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialBalanceSpending() const
{
return this->tx_->isFieldPresent(sfConfidentialBalanceSpending);
}
/**
* @brief Get sfConfidentialBalanceInbox (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getConfidentialBalanceInbox() const
{
if (hasConfidentialBalanceInbox())
{
return this->tx_->at(sfConfidentialBalanceInbox);
}
return std::nullopt;
}
/**
* @brief Check if sfConfidentialBalanceInbox is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialBalanceInbox() const
{
return this->tx_->isFieldPresent(sfConfidentialBalanceInbox);
}
/**
* @brief Get sfZKProof (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getZKProof() const
{
if (hasZKProof())
{
return this->tx_->at(sfZKProof);
}
return std::nullopt;
}
/**
* @brief Check if sfZKProof is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasZKProof() const
{
return this->tx_->isFieldPresent(sfZKProof);
}
};
/**
* @brief Builder for ConfidentialMPTHolderKeyUpdate transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ConfidentialMPTHolderKeyUpdateBuilder : public TransactionBuilderBase<ConfidentialMPTHolderKeyUpdateBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTHolderKeyUpdateBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTHolderKeyUpdateBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTHolderKeyUpdateBuilder>(ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
}
/**
* @brief Construct a ConfidentialMPTHolderKeyUpdateBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTHolderKeyUpdateBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTHolderKeyUpdateBuilder");
}
object_ = *tx;
}
/**
* @brief Transaction-specific field setters
*/
/**
* @brief Set sfMPTokenIssuanceID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTHolderKeyUpdateBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptionKey (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTHolderKeyUpdateBuilder&
setHolderEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfHolderEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfConfidentialBalanceSpending (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTHolderKeyUpdateBuilder&
setConfidentialBalanceSpending(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfConfidentialBalanceSpending] = value;
return *this;
}
/**
* @brief Set sfConfidentialBalanceInbox (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTHolderKeyUpdateBuilder&
setConfidentialBalanceInbox(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfConfidentialBalanceInbox] = value;
return *this;
}
/**
* @brief Set sfZKProof (SoeOptional)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTHolderKeyUpdateBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTHolderKeyUpdate wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTHolderKeyUpdate
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTHolderKeyUpdate{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions
@@ -18,7 +18,7 @@ class TransactionProposalCreateBuilder;
/**
* @brief Transaction: TransactionProposalCreate
*
* Type: ttTRANSACTION_PROPOSAL_CREATE (95)
* Type: ttTRANSACTION_PROPOSAL_CREATE (93)
* Delegable: Delegation::NotDelegable
* Amendment: featureCosign
* Privileges: Privilege::NoPriv
+26 -16
View File
@@ -1,5 +1,6 @@
#pragma once
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/SlabAllocator.h>
#include <xrpl/basics/Slice.h>
@@ -14,13 +15,12 @@
#include <cstdint>
#include <cstring>
#include <memory>
#include <new>
#include <type_traits>
namespace xrpl {
// an item stored in a SHAMap
class alignas(8) SHAMapItem : public CountedObject<SHAMapItem>
class SHAMapItem : public CountedObject<SHAMapItem>
{
// These are used to support boost::intrusive_ptr reference counting
// These functions are used internally by boost::intrusive_ptr to handle
@@ -101,15 +101,15 @@ namespace detail {
// The slab cutoffs and the number of megabytes per allocation are customized
// based on the number of objects of each size we expect to need at any point
// in time and with an eye to minimize the number of slack bytes in a block.
inline constinit slab::Allocator<SHAMapItem, slab::HeapFallback,
slab::Config<1000000, 128>,
slab::Config<1000000, 296>,
slab::Config<125000, 392>,
slab::Config<125000, 520>,
slab::Config<62500, 760>,
slab::Config<62500, 856>,
slab::Config<31250, 1048>
> gSlabber;
inline SlabAllocatorSet<SHAMapItem> gSlabber({
{ 128, megabytes(std::size_t(60)) },
{ 192, megabytes(std::size_t(46)) },
{ 272, megabytes(std::size_t(60)) },
{ 384, megabytes(std::size_t(56)) },
{ 564, megabytes(std::size_t(40)) },
{ 772, megabytes(std::size_t(46)) },
{ 1052, megabytes(std::size_t(60)) },
});
// clang-format on
} // namespace detail
@@ -150,14 +150,24 @@ makeShamapitem(UInt256 const& tag, Slice data)
XRPL_ASSERT(
data.size() <= megabytes<std::size_t>(16), "xrpl::makeShamapitem : maximum input size");
// We do not increment the reference count here on purpose: the
// constructor of SHAMapItem explicitly sets it to 1.
if (auto raw = detail::gSlabber.allocate(data.size())) [[likely]]
return {new (raw) SHAMapItem{tag, data}, false};
// NOLINTNEXTLINE(misc-const-correctness)
std::uint8_t* raw = detail::gSlabber.allocate(data.size());
Throw<std::bad_alloc>();
// If we can't grab memory from the slab allocators, we fall back to
// the standard library and try to grab a precisely-sized memory block:
if (raw == nullptr)
raw = new std::uint8_t[sizeof(SHAMapItem) + data.size()];
// We do not increment the reference count here on purpose: the
// constructor of SHAMapItem explicitly sets it to 1. We use the fact
// that the refcount can never be zero before incrementing as an
// invariant.
return {new (raw) SHAMapItem{tag, data}, false};
}
static_assert(alignof(SHAMapItem) != 40);
static_assert(alignof(SHAMapItem) == 8 || alignof(SHAMapItem) == 4);
inline boost::intrusive_ptr<SHAMapItem>
makeShamapitem(SHAMapItem const& other)
{
+1 -2
View File
@@ -51,8 +51,7 @@ public:
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
: ApplyContext(registry, base, std::nullopt, tx, preclaimResult, baseFee, flags, journal)
{
XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
}
std::reference_wrapper<ServiceRegistry> registry;
+8 -11
View File
@@ -59,11 +59,10 @@ public:
, parentBatchId(parentBatchId)
, j(j)
{
XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::Batch, "Batch apply flag should be set");
XRPL_ASSERT((flags & TapBatch) == TapBatch, "Batch apply flag should be set");
XRPL_ASSERT_IF(
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
(flags & TapProposal) != TapNone,
(flags & TapDryRun) != TapNone,
"xrpl::PreflightContext : proposal preflight implies dry run");
}
@@ -75,11 +74,10 @@ public:
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: registry(registry), tx(tx), rules(std::move(rules)), flags(flags), j(j)
{
XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
XRPL_ASSERT_IF(
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
(flags & TapProposal) != TapNone,
(flags & TapDryRun) != TapNone,
"xrpl::PreflightContext : proposal preflight implies dry run");
}
@@ -118,7 +116,7 @@ public:
, j(j)
{
XRPL_ASSERT(
parentBatchId.has_value() == ((flags & ApplyFlags::Batch) == ApplyFlags::Batch),
parentBatchId.has_value() == ((flags & TapBatch) == TapBatch),
"Parent Batch ID should be set if batch apply flag is set");
}
@@ -131,8 +129,7 @@ public:
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: PreclaimContext(registry, view, preflightResult, tx, flags, std::nullopt, j)
{
XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
}
PreclaimContext&
+1 -1
View File
@@ -41,7 +41,7 @@ struct ApplyResult
inline bool
isTecClaimHardFail(TER ter, ApplyFlags flags)
{
return isTecClaim(ter) && ((flags & ApplyFlags::Retry) == ApplyFlags::None);
return isTecClaim(ter) && ((flags & TapRetry) == 0u);
}
/**
+2 -8
View File
@@ -46,9 +46,6 @@ class ValidMPTIssuance
* MPTokens and RippleStates deleted during apply. finalize() checks each
* holder's AccountRoot to detect vault pseudo-account holdings deleted
* outside VaultDelete. All these checks are gated on fixCleanup3_2_0.
*
* Under fixCleanup3_5_0, finalize() also rejects any MPToken erased with
* a non-zero sfMPTAmount.
*/
std::vector<std::shared_ptr<SLE const>> deletedHoldings_;
@@ -141,9 +138,8 @@ public:
* - Convert/ConvertBack symmetry:
* Regular MPToken balance change (±X) == COA (Confidential Outstanding Amount) change (∓X)
* - Cannot delete MPToken with non-zero confidential state:
* Cannot delete if any of sfConfidentialBalanceSpending, sfConfidentialBalanceInbox,
* sfIssuerEncryptedBalance or sfAuditorEncryptedBalance is present, and the issuance's
* sfConfidentialOutstandingAmount is non-zero. Mirrors MPTokenAuthorize::preclaim.
* Cannot delete if sfIssuerEncryptedBalance exists
* Cannot delete if sfConfidentialBalanceInbox and sfConfidentialBalanceSpending exist
* - Privacy flag consistency:
* MPToken confidential balance fields can only be created or changed if
* lsfMPTCanHoldConfidentialBalance is set on the issuance.
@@ -166,8 +162,6 @@ class ValidConfidentialMPToken
std::int64_t outstandingDelta = 0;
SLE::const_pointer issuance;
bool deletedWithEncrypted = false;
// True when an erased MPToken had a non-zero pre-tx public balance.
bool deletedWithBalanceBefore = false;
bool badConsistency = false;
bool badCOA = false;
bool changesConfidentialFields = false;
@@ -1,84 +0,0 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
namespace xrpl {
/**
* @brief Allows a confidential MPT holder to rotate or recover their ElGamal
* encryption key, or to cancel a pending recovery.
*
* Submitted by the holder. Exactly one of three modes must be selected via
* the transaction flags:
*
* - Rotation (tfHolderKeyRotation): the holder still has their current
* ElGamal private key. They provide a new public key along with their
* current spending/inbox balances re-encrypted under that new key. The
* holder's encryption key and confidential balances are updated
* immediately.
*
* - Recovery (tfHolderKeyRecovery): the holder has lost their current
* private key. They provide a new public key but cannot provide
* re-encrypted balances. The new key is recorded as a pending
* sfRecoveryKey; the confidential balances are left untouched. Completing
* the recovery (rewriting the balances) is done separately by the issuer
* via ConfidentialMPTRecoverBalance.
*
* - Cancel (tfCancelRecovery): the holder revokes a pending recovery
* authorization created by a prior Recovery-mode transaction. No key,
* balances, or proof are carried; authorization is via the holder's
* ordinary XRPL signature. sfRecoveryKey is removed and everything else
* is left untouched. Fails if no recovery is pending.
*
* @note Zero-knowledge proof verification for Rotation and Recovery is
* deferred to a follow-up once the mpt-crypto constructions are finalized.
*/
class ConfidentialMPTHolderKeyUpdate : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit ConfidentialMPTHolderKeyUpdate(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl
+73 -73
View File
@@ -2,144 +2,144 @@ Detected OS: macos (Darwin arm64)
Core build tools:
✅ cmake
cmake version 4.4.3
/nix/store/q85csxf4s4shx89zif097h1ql4ax1rrz-cmake-4.4.3/bin/cmake
cmake version 4.1.2
/nix/store/gvabsb4yqb5xsqzqph54rijnn4zpihnp-cmake-4.1.2/bin/cmake
✅ conan
Conan version 2.32.0
/nix/store/921jqsgbilixmr3xchj95si0jl8pi13z-conan-2.32.0/bin/conan
Conan version 2.28.1
/nix/store/9jiyxmkpwmn6dcqs0765s83riw3l5ail-conan-2.28.1/bin/conan
✅ git
git version 2.55.0
/nix/store/gw7c7m0dwca5lg4152x9lpmxpj171bzw-git-2.55.0/bin/git
git version 2.54.0
/nix/store/a14yxcqvv9x2l9mllgpirzhvz93pgprg-git-2.54.0/bin/git
✅ python3
Python 3.14.7
/nix/store/0kcws5blwbdyxqxjd1w8hjipz61xny3k-python3-3.14.7/bin/python3.14
Python 3.13.13
/nix/store/ygxqin6ydzjfawywqpp5pal8wv6sf5bh-python3-3.13.13/bin/python3.13
Development tooling:
✅ ccache
ccache version 4.13.6
/nix/store/ydr5nlzxp1djb5256y0zzqa87vz55gsk-ccache-4.13.6/bin/ccache
/nix/store/57davyvs6p6dkrl3svzwg1ph18wsy4cz-ccache-4.13.6/bin/ccache
✅ clang
clang version 22.1.8
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang
clang version 22.1.7
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang
✅ clang-22
clang version 22.1.8
/nix/store/w826sqb98nnym6kzackg829664iqdlxj-clang-22/bin/clang-22
clang version 22.1.7
/nix/store/rbap7zqq7mw00fyqa02p5rj7gqjp4w5i-clang-22/bin/clang-22
✅ clang++
clang version 22.1.8
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang++
clang version 22.1.7
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang++
✅ clang++-22
clang version 22.1.8
/nix/store/wnwyw3yfqrhpxij65bl3pa1x65q2zg3m-clang++-22/bin/clang++-22
clang version 22.1.7
/nix/store/v9haf787f7bcz0mq1sad4bpyx21pj6li-clang++-22/bin/clang++-22
✅ ClangBuildAnalyzer
ClangBuildAnalyzer 1.6.0
/nix/store/c7jjnw29ra78jdlzjww2m374izhy05n8-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
/nix/store/4l50ds9fa2mkvh7wg8qzrlbmjs12sb8l-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
✅ curl
curl 8.22.0 (aarch64-apple-darwin25.6.0) libcurl/8.22.0 OpenSSL/3.5.8 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.70.0 mit-krb5/1.22.2
/nix/store/pq1h17abqy6q5y6k5dn9qj0fwb89flba-curl-8.22.0-bin/bin/curl
curl 8.20.0 (aarch64-apple-darwin25.3.0) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
/nix/store/kclq0czaxvsgh4ym9ld7b6iwy50l1snk-curl-8.20.0-bin/bin/curl
✅ file
file-5.48
/nix/store/y44vpziwbzfm8020dzlqqbybyyy7lb6g-file-5.48/bin/file
file-5.47
/nix/store/dax63li7wwcbqxxkkgzc4g2rx7d4w86x-file-5.47/bin/file
✅ less
less 710 (PCRE2 regular expressions)
/nix/store/3yn3dx5fwg5i0inqqzwnxq7f6lwhpjqa-less-710/bin/less
less 692 (PCRE2 regular expressions)
/nix/store/lvr16y75r1pdxpdv0aph5ak2yd0hkvqm-less-692/bin/less
✅ make
GNU Make 4.4.1
/nix/store/8l4wgxmhdxfvc9g18y76bnxl1ghgcp7n-gnumake-4.4.1/bin/make
/nix/store/8wwiw8pwyhrkzyq28hqzxfl4z84lks81-gnumake-4.4.1/bin/make
✅ netstat
present
/nix/store/57fqvgdav8lrmzndkwm7qrgksl01yj23-network_cmds-741.100.2/bin/netstat
/nix/store/qsd1kzqb0ahrk433vmyl245gp623j19s-network_cmds-730.80.3/bin/netstat
✅ ninja
1.13.2
/nix/store/infm4vlx71q83bqz0k559w1gn4b0rq4i-ninja-1.13.2/bin/ninja
/nix/store/bqykhrblarkj4fl0hz2mf8ngwfv6x6bz-ninja-1.13.2/bin/ninja
✅ perl
v5.42.3
/nix/store/7rvr6qjfzk2ybzn1imjw6c6qq1n1a9cr-perl-5.42.3/bin/perl
v5.42.0
/nix/store/js13ri9fvm0ajk1fpd3acigys2a9whdv-perl-5.42.0/bin/perl
✅ pkg-config
0.29.2
/nix/store/nhgylvj98klqggy54h7gbc3b2ia4a00y-pkg-config-wrapper-0.29.2/bin/pkg-config
/nix/store/lzrwr375jqhhbca116kja96xf1md83l8-pkg-config-wrapper-0.29.2/bin/pkg-config
✅ vim
VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
/nix/store/akaa9kqm3b8rlkv61kvsqr2l2j70rvxb-vim-9.2.1001/bin/vim
/nix/store/6vbkykg92w603c0sw3mkk7p7mfaawbns-vim-9.2.0389/bin/vim
✅ zip
Zip 3.0
/nix/store/dvawr36npnad4xa6dxi2ss0fw44aa82r-zip-3.0/bin/zip
/nix/store/z6ph729vcakbvz3wh8ln1wk6mi06w487-zip-3.0/bin/zip
✅ clang-apply-replacements
clang-apply-replacements version 22.1.8
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-apply-replacements
clang-apply-replacements version 22.1.7
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-apply-replacements
✅ clang-apply-replacements-22
clang-apply-replacements version 22.1.8
/nix/store/w4bz034k4l0w719rrmbwnqcgyjbswy81-clang-apply-replacements-22/bin/clang-apply-replacements-22
clang-apply-replacements version 22.1.7
/nix/store/m3ii69rca4077lf4wlk7m3jcag1fs577-clang-apply-replacements-22/bin/clang-apply-replacements-22
✅ clang-format
clang-format version 22.1.8
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-format
clang-format version 22.1.7
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-format
✅ clang-format-22
clang-format version 22.1.8
/nix/store/w7z346l0r8y36b6lc5i7jzj3w0581l2y-clang-format-22/bin/clang-format-22
clang-format version 22.1.7
/nix/store/4fawqy6ngqcsqd2ygyyzm93q0xy3f5gs-clang-format-22/bin/clang-format-22
✅ clang-tidy
LLVM version 22.1.8
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-tidy
LLVM version 22.1.7
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-tidy
✅ clang-tidy-22
LLVM version 22.1.8
/nix/store/0lv25qd3ddgjm3ndn70q61jfr6krlkl7-clang-tidy-22/bin/clang-tidy-22
LLVM version 22.1.7
/nix/store/jqw4280saixaxxihwdba9ldm2fsm6dr3-clang-tidy-22/bin/clang-tidy-22
✅ dot
dot - graphviz version 15.1.1 (0)
/nix/store/mc99a6bpbk2waym2cnw6ifmxli4ndb4x-graphviz-15.1.1/bin/dot
dot - graphviz version 12.2.1 (0)
/nix/store/ijb4fbnqa6wzlpqnhb6q9knqpf7qqn5z-graphviz-12.2.1/bin/dot
✅ doxygen
1.17.0
/nix/store/kz43yaj0wg6fx6cbrcmi2442z1ramy0c-doxygen-1.17.0/bin/doxygen
1.16.1
/nix/store/kbryjdpq9jizjb0ws0nzbf2h2ymbdiwm-doxygen-1.16.1/bin/doxygen
✅ gcovr
gcovr 8.4
/nix/store/czj1vxsizrbba280gg22cys40qxkqjgr-python3.14-gcovr-8.4/bin/gcovr
/nix/store/wn8jiyh9p0bybs96s4163qp3k8vfmczx-python3.13-gcovr-8.4/bin/gcovr
✅ gh
gh version 2.102.0 (2026-09-30)
/nix/store/s8bahl6zssvry93ihivfmk1l6c58byrr-gh-2.102.0/bin/gh
gh version 2.94.0 (nixpkgs)
/nix/store/fhnpw0hs0gjms1ha6ap02jq7rx13gkbp-gh-2.94.0/bin/gh
✅ git-cliff
git-cliff 2.14.2
/nix/store/k4m5yk0qb7hfslwsq1zjajz1d94j12bj-git-cliff-2.14.2/bin/git-cliff
git-cliff 2.13.1
/nix/store/cy0wwhgxa7yvrz97zydbq6sqmixc90fq-git-cliff-2.13.1/bin/git-cliff
✅ git-lfs
git-lfs/3.8.0 (3.8.0; darwin arm64; go 1.26.8)
/nix/store/wzixjr3yb40a13m2q654hm3bdjn6sal3-git-lfs-3.8.0/bin/git-lfs
git-lfs/3.7.1 (3.7.1; darwin arm64; go 1.26.3)
/nix/store/k9r7zjfjplqa4d5s71cqvf2iv73jd9mc-git-lfs-3.7.1/bin/git-lfs
✅ gpg
gpg (GnuPG) 2.4.9
/nix/store/0ldfwnwx385i5kh7cyvxx4mkvgjhs5rh-gnupg-2.4.9/bin/gpg
/nix/store/cgh6iwzz5jgx9z5whka4vgj210i6npc6-gnupg-2.4.9/bin/gpg
✅ pre-commit
pre-commit 4.6.2
/nix/store/ivh54xypg9qvd3ha9lis0i8p4nkf8n7a-pre-commit-4.6.2/bin/pre-commit
pre-commit 4.5.1
/nix/store/z3cca68620w0w10f090szgzdnmh1waf2-pre-commit-4.5.1/bin/pre-commit
✅ run-clang-tidy
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/3xss1mm3mh1hzcmg182na6ki1p5rnxf1-run-clang-tidy/bin/run-clang-tidy
/nix/store/4x28x911z2f9y7adqlh3qspp4a16dig7-run-clang-tidy/bin/run-clang-tidy
✅ run-clang-tidy-22
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/w68r9z07hcq4fwbfyq3yvc7qxm8aqfbl-run-clang-tidy-22/bin/run-clang-tidy-22
/nix/store/x8iymrh76sk5q91ryg5pa7i32s6gfh34-run-clang-tidy-22/bin/run-clang-tidy-22
Rust toolchain:
✅ cargo
cargo 1.97.1 (c980f4866 2026-06-30)
/nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/cargo
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/cargo
✅ cargo-audit
cargo-audit-audit 0.22.2
/nix/store/7jgrwj557l91940aj0ag4i924ima1cqn-cargo-audit-0.22.2/bin/cargo-audit
cargo-audit-audit 0.22.1
/nix/store/snwkga2f5gyf404h7mmp9wriwxb8v65f-cargo-audit-0.22.1/bin/cargo-audit
✅ cargo-llvm-cov
cargo-llvm-cov 0.9.0
/nix/store/5q1h285z8zzkw2qxig9a0b1avcsk1cf0-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
cargo-llvm-cov 0.8.5
/nix/store/fpiqdh91gwyxalqp409ynm0s0g086w7w-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
✅ cargo-nextest
cargo-nextest 0.9.146
/nix/store/69v3hx3k597hcxd2zy7yad7fg0aw5yxd-cargo-nextest-0.9.146/bin/cargo-nextest
cargo-nextest 0.9.137
/nix/store/ylz7m947mhkgsp6i7611id3s3gcd58nq-cargo-nextest-0.9.137/bin/cargo-nextest
✅ clippy-driver
clippy 0.1.97 (8bab26f4f6 2026-07-14)
/nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/clippy-driver
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/clippy-driver
✅ rust-analyzer
rust-analyzer 1.97.1 (8bab26f4 2026-07-14)
/nix/store/30rbyk8gx7v53fxqhs4pp0fvxfjycspr-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer
/nix/store/j6apc5pmd0giy15da9p650r8zklslmvi-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer
✅ rust-nightly
rustc 1.101.0-nightly (282215592 2026-10-04)
/nix/store/a9552rf3pylcnkijc1z0hf1lcnv50djq-rust-nightly/bin/rust-nightly
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
/nix/store/fqpjz4l0nsnji8b2pz57mnj0akbp6hcl-rust-nightly/bin/rust-nightly
✅ rustc
rustc 1.97.1 (8bab26f4f 2026-07-14)
/nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/rustc
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/rustc
✅ rustfmt
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
/nix/store/0jmdipyn8266j9kqx2xxm2qkxlrlrjfc-rustfmt-preview-1.97.1-aarch64-apple-darwin/bin/rustfmt
/nix/store/5ymwgr9jqjz7zzbmj0j5vqbwcd3kp0vm-rustfmt-preview-1.97.1-aarch64-apple-darwin/bin/rustfmt
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
+1 -2
View File
@@ -21,8 +21,7 @@ let
)
);
toolchain =
if pkgs.stdenv.hostPlatform.isLinux then linux.toolchain else (darwin.toolchain ++ [ darwinEnv ]);
toolchain = if pkgs.stdenv.isLinux then linux.toolchain else (darwin.toolchain ++ [ darwinEnv ]);
in
{
default = pkgs.buildEnv {
+6 -7
View File
@@ -20,16 +20,15 @@ let
# Custom-glibc stdenvs, matching the CI environment. darwin has no custom
# glibc, so there they fall back to the plain nixpkgs stdenvs.
customGccStdenv = if pkgs.stdenv.hostPlatform.isLinux then linux.gccStdenv else plainGccStdenv;
customClangStdenv =
if pkgs.stdenv.hostPlatform.isLinux then linux.clangStdenv else plainClangStdenv;
customGccStdenv = if pkgs.stdenv.isLinux then linux.gccStdenv else plainGccStdenv;
customClangStdenv = if pkgs.stdenv.isLinux then linux.clangStdenv else plainClangStdenv;
# gcov matching each gcc shell, so `-Dcoverage=ON` builds work in the shell.
plainGcov = mkGcov {
name = "plain";
cc = gccPackage.cc;
};
customGccGcov = if pkgs.stdenv.hostPlatform.isLinux then linux.gcov else plainGcov;
customGccGcov = if pkgs.stdenv.isLinux then linux.gcov else plainGcov;
# Whole directory: init.sh locates the profiles relative to itself.
conanDir = ../conan;
@@ -52,7 +51,7 @@ let
# Not sdkEnv: a shell's stdenv already sets that up. Prepended so the stub
# beats the nixpkgs libresolv this shell's tooling drags in.
darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isDarwin (
darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.isDarwin (
pkgs.lib.concatLines (
pkgs.lib.mapAttrsToList (
name: value: ''export ${name}="${value} ''${${name}:-}"''
@@ -127,7 +126,7 @@ let
in
rec {
# macOS: Nix Clang. Linux: Nix GCC.
default = if pkgs.stdenv.hostPlatform.isDarwin then clang else gcc;
default = if pkgs.stdenv.isDarwin then clang else gcc;
# gcc/clang use the custom-glibc toolchain, matching CI. On darwin there is no
# custom glibc, so they fall back to the plain nixpkgs toolchain.
@@ -172,7 +171,7 @@ rec {
# The *-plain shells (stock nixpkgs toolchain) exist only on Linux: on darwin
# gcc/clang are already plain, so these would be redundant and are omitted, which
# makes `nix develop .#gcc-plain` fail there rather than silently aliasing gcc.
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
gcc-plain = makeShell {
shellName = "gcc-plain";
stdenv = plainGccStdenv;
+39
View File
@@ -0,0 +1,39 @@
#include <xrpl/basics/CountedObject.h>
#include <algorithm>
namespace xrpl {
CountedObjects&
CountedObjects::getInstance() noexcept
{
static CountedObjects kInstance;
return kInstance;
}
CountedObjects::CountedObjects() noexcept : count_(0), head_(nullptr)
{
}
CountedObjects::List
CountedObjects::getCounts(int minimumThreshold) const
{
List counts;
// When other operations are concurrent, the count
// might be temporarily less than the actual count.
counts.reserve(count_.load());
for (auto* ctr = head_.load(); ctr != nullptr; ctr = ctr->getNext())
{
if (ctr->getCount() >= minimumThreshold)
counts.emplace_back(ctr->getName(), ctr->getCount());
}
std::ranges::sort(counts);
return counts;
}
} // namespace xrpl
+2 -4
View File
@@ -5,7 +5,6 @@
#include <cerrno>
#include <cstddef>
#include <filesystem>
#include <format>
#include <fstream>
#include <iomanip>
#include <ios>
@@ -100,13 +99,12 @@ uniqueRandomPath(
if (ec)
{
Throw<std::runtime_error>(
std::format("Unable to check path '{}': {}", candidate.string(), ec.message()));
"Unable to check path '" + candidate.string() + "': " + ec.message());
}
if (!exists)
return candidate;
}
Throw<std::runtime_error>(
std::format("Unable to generate a unique path under '{}'", base.string()));
Throw<std::runtime_error>("Unable to generate a unique path under '" + base.string() + "'");
}
TempDir::TempDir() : path_(uniqueRandomPath(std::filesystem::temp_directory_path()))
+2 -1
View File
@@ -1,5 +1,6 @@
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/beast/net/IPEndpoint.h>
@@ -19,7 +20,7 @@
namespace xrpl {
std::string
sqlBlobLiteral(Slice blob)
sqlBlobLiteral(Blob const& blob)
{
std::string j;
+3 -3
View File
@@ -33,13 +33,13 @@ AcceptedLedgerTx::AcceptedLedgerTx(
Serializer s;
met->add(s);
rawMeta_ = s.takeData();
rawMeta_ = std::move(s.modData());
json_ = json::ValueType::Object;
json_[jss::transaction] = txn_->getJson(JsonOptions::Values::None);
json_[jss::meta] = meta_.getJson(JsonOptions::Values::None);
json_[jss::raw_meta] = strHex(makeSlice(rawMeta_));
json_[jss::raw_meta] = strHex(rawMeta_);
json_[jss::result] = transHuman(meta_.getResultTER());
@@ -74,7 +74,7 @@ std::string
AcceptedLedgerTx::getEscMeta() const
{
XRPL_ASSERT(!rawMeta_.empty(), "xrpl::AcceptedLedgerTx::getEscMeta : metadata is set");
return sqlBlobLiteral(makeSlice(rawMeta_));
return sqlBlobLiteral(rawMeta_);
}
} // namespace xrpl
+2 -2
View File
@@ -80,10 +80,10 @@ BookDirs::const_iterator::operator++()
XRPL_ASSERT(index_ != kZero, "xrpl::BookDirs::const_iterator::operator++ : nonzero index");
if (!cdirNext(*view_, curKey_, sle_, entry_, index_))
{
if (index_ == kZero)
if (index_ == 0)
curKey_ = view_->succ(++curKey_, nextQuality_).value_or(kZero);
if (index_ != kZero || curKey_ == kZero)
if (index_ != 0 || curKey_ == kZero)
{
curKey_ = key_;
entry_ = 0;
+15 -12
View File
@@ -11,7 +11,6 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/LedgerTiming.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
@@ -262,7 +261,7 @@ Ledger::Ledger(Ledger const& prevLedger, NetClock::time_point closeTime)
{
header_.seq = prevLedger.header_.seq + 1;
header_.parentCloseTime = prevLedger.header_.closeTime;
header_.hash = prevLedger.header().hash.next();
header_.hash = prevLedger.header().hash + UInt256(1);
header_.drops = prevLedger.header().drops;
header_.closeTimeResolution = prevLedger.header_.closeTimeResolution;
header_.parentHash = prevLedger.header().hash;
@@ -363,13 +362,17 @@ Ledger::deserializeTx(SHAMapItem const& item)
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>>
Ledger::deserializeTxPlusMeta(SHAMapItem const& item)
{
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>> result;
SerialIter sit(item.slice());
SerialIter txSit(sit.getVL());
SerialIter metaSit(sit.getVL());
return {
std::make_shared<STTx const>(txSit), std::make_shared<STObject const>(metaSit, sfMetadata)};
{
SerialIter s(sit.getSlice(sit.getVLDataLength()));
result.first = std::make_shared<STTx const>(s);
}
{
SerialIter s(sit.getSlice(sit.getVLDataLength()));
result.second = std::make_shared<STObject const>(s, sfMetadata);
}
return result;
}
//------------------------------------------------------------------------------
@@ -529,9 +532,9 @@ Ledger::rawTxInsert(
XRPL_ASSERT(metaData, "xrpl::Ledger::rawTxInsert : non-null metadata input");
// low-level - just add to table
Serializer s(txn->size() + metaData->size() + 16);
s.addVL(txn->slice());
s.addVL(metaData->slice());
Serializer s(txn->getDataLength() + metaData->getDataLength() + 16);
s.addVL(txn->peekData());
s.addVL(metaData->peekData());
if (!txMap_.addGiveItem(SHAMapNodeType::TnTransactionMd, makeShamapitem(key, s.slice())))
logicError("duplicate_tx: " + to_string(key));
}
@@ -557,7 +560,7 @@ Ledger::setup()
try
{
if (auto const sle = FeeSettingsEntryR(*this))
if (auto const sle = read(keylet::feeSettings()))
{
bool oldFees = false;
bool newFees = false;
+1 -2
View File
@@ -16,7 +16,6 @@
#include <boost/container/pmr/monotonic_buffer_resource.hpp>
#include <cstddef>
#include <format>
#include <memory>
#include <optional>
#include <stdexcept>
@@ -265,7 +264,7 @@ OpenView::rawTxInsert(
auto const result = txs_.emplace(
std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(txn, metaData));
if (!result.second)
Throw<std::logic_error>(std::format("rawTxInsert: duplicate TX id: {}", to_string(key)));
Throw<std::logic_error>("rawTxInsert: duplicate TX id: " + to_string(key));
}
} // namespace xrpl
+10 -8
View File
@@ -9,7 +9,6 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/LedgerHashesEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
@@ -362,16 +361,17 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
if (int const diff = ledger.seq() - seq; diff <= 256)
{
// Within 256...
LedgerHashesEntryR const hashIndex(ledger, journal);
auto const hashIndex = ledger.read(keylet::skip());
if (hashIndex)
{
XRPL_ASSERT(
hashIndex->getFieldU32(sfLastLedgerSequence) == (ledger.seq() - 1),
"xrpl::hashOfSeq : matching ledger sequence");
if (auto const hash = hashIndex.hashAt(diff - 1))
return hash;
STVector256 vec = hashIndex->getFieldV256(sfHashes);
if (vec.size() >= diff)
return vec[vec.size() - diff];
JLOG(journal.warn()) << "Ledger " << ledger.seq() << " missing hash for " << seq << " ("
<< hashIndex->getFieldV256(sfHashes).size() << "," << diff << ")";
<< vec.size() << "," << diff << ")";
}
else
{
@@ -387,14 +387,16 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
}
// in skiplist
LedgerHashesEntryR const hashIndex(keylet::skip(seq), ledger, journal);
auto const hashIndex = ledger.read(keylet::skip(seq));
if (hashIndex)
{
auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence);
XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger");
XRPL_ASSERT((lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger");
if (auto const hash = hashIndex.hashAt((lastSeq - seq) >> 8))
return hash;
auto const diff = (lastSeq - seq) >> 8;
STVector256 vec = hashIndex->getFieldV256(sfHashes);
if (vec.size() > diff)
return vec[vec.size() - diff - 1];
}
JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq() << " error";
return std::nullopt;
@@ -506,7 +506,7 @@ pseudoAccountAddress(ReadView const& view, UInt256 const& pseudoOwnerKey)
RipeshaHasher rsh;
auto const hash = sha512Half(i, view.header().parentHash, pseudoOwnerKey);
rsh(hash.data(), hash.size());
AccountID const ret{static_cast<RipeshaHasher::result_type>(rsh)};
AccountID const ret = AccountID::fromRaw(static_cast<RipeshaHasher::result_type>(rsh));
if (!view.read(keylet::account(ret)))
return ret;
}
+7 -10
View File
@@ -33,7 +33,6 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <format>
#include <functional>
#include <iterator>
#include <memory>
@@ -395,11 +394,9 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
if (!page2)
{
Throw<std::runtime_error>(std::format(
"page {} has a broken {} field pointing to {}",
to_string(page1->key()),
field.getName(),
to_string(*id)));
Throw<std::runtime_error>(
"page " + to_string(page1->key()) + " has a broken " + field.getName() +
" field pointing to " + to_string(*id));
}
}
@@ -600,7 +597,7 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t
else
{
Throw<std::runtime_error>(
std::format("Offer {} cannot be deleted!", to_string(offerIndexes[i])));
"Offer " + to_string(offerIndexes[i]) + " cannot be deleted!");
}
}
@@ -737,9 +734,9 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
if (!newPrev)
{
// LCOV_EXCL_START
Throw<std::runtime_error>(std::format(
"NFTokenPage directory for {} cannot be repaired. Unexpected link problem.",
to_string(owner)));
Throw<std::runtime_error>(
"NFTokenPage directory for " + to_string(owner) +
" cannot be repaired. Unexpected link problem.");
// LCOV_EXCL_STOP
}
newPrev->at(sfNextPageMin) = nextPage->key();
+3 -7
View File
@@ -1,6 +1,7 @@
#include <xrpl/nodestore/detail/DecodedBlob.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/nodestore/NodeObject.h>
@@ -11,13 +12,8 @@
namespace xrpl::node_store {
DecodedBlob::DecodedBlob(std::optional<UInt256> key, void const* value, int valueBytes)
DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) : key_(key)
{
if (!key)
return;
key_ = key.value();
/* Data format:
Bytes
@@ -67,7 +63,7 @@ DecodedBlob::createObject()
{
Blob data(objectData_, objectData_ + dataBytes_);
object = NodeObject::createObject(objectType_, std::move(data), key_);
object = NodeObject::createObject(objectType_, std::move(data), UInt256::fromVoid(key_));
}
return object;
+3 -15
View File
@@ -38,7 +38,6 @@
#include <functional>
#include <memory>
#include <optional>
#include <span>
#include <sstream>
#include <stdexcept>
#include <string>
@@ -217,7 +216,7 @@ public:
[&hash, pno, &status](void const* data, std::size_t size) {
nudb::detail::buffer bf;
auto const result = nodeobjectDecompress(data, size, bf);
DecodedBlob decoded(hash, result.first, result.second);
DecodedBlob decoded(hash.data(), result.first, result.second);
if (!decoded.wasOk())
{
status = Status::DataCorrupt;
@@ -241,7 +240,7 @@ public:
nudb::error_code ec;
nudb::detail::buffer bf;
auto const result = nodeobjectCompress(e.getData(), e.getSize(), bf);
db.insert(e.getKey().data(), result.first, result.second, ec);
db.insert(e.getKey(), result.first, result.second, ec);
if (ec && ec != nudb::error::key_exists)
Throw<nudb::system_error>(ec);
}
@@ -289,25 +288,14 @@ public:
Throw<nudb::system_error>(ec);
nudb::visit(
dp,
[&](void const* keyData,
[&](void const* key,
std::size_t keyBytes,
void const* data,
std::size_t size,
nudb::error_code&) {
auto key = uint256::fromRaw(
std::span{static_cast<unsigned char const*>(keyData), keyBytes});
if (!key) [[unlikely]]
{
ec = make_error_code(nudb::error::invalid_key_size);
return;
}
nudb::detail::buffer bf;
auto const result = nodeobjectDecompress(data, size, bf);
DecodedBlob decoded(key, result.first, result.second);
if (!decoded.wasOk())
{
ec = make_error_code(nudb::error::missing_value);
@@ -35,10 +35,8 @@
#include <atomic>
#include <cstddef>
#include <filesystem>
#include <format>
#include <functional>
#include <memory>
#include <span>
#include <stdexcept>
#include <string>
@@ -112,9 +110,6 @@ public:
if (!getIfExists(keyValues, Keys::kPath, name))
Throw<std::runtime_error>("Missing path in RocksDBFactory backend");
if (keyBytes != uint256::size())
Throw<std::runtime_error>("Incorrect key size: expected 32 bytes");
rocksdb::BlockBasedTableOptions tableOptions;
options.env = env;
@@ -198,7 +193,7 @@ public:
if (!s.ok())
{
Throw<std::runtime_error>(
std::format("Unable to set RocksDB bbt_options: {}", s.ToString()));
std::string("Unable to set RocksDB bbt_options: ") + s.ToString());
}
}
@@ -211,7 +206,7 @@ public:
if (!s.ok())
{
Throw<std::runtime_error>(
std::format("Unable to set RocksDB options: {}", s.ToString()));
std::string("Unable to set RocksDB options: ") + s.ToString());
}
}
@@ -246,7 +241,7 @@ public:
if (!status.ok() || (localDb == nullptr))
{
Throw<std::runtime_error>(
std::format("Unable to open/create RocksDB: {}", status.ToString()));
std::string("Unable to open/create RocksDB: ") + status.ToString());
}
db.reset(localDb);
}
@@ -296,7 +291,7 @@ public:
if (getStatus.ok())
{
DecodedBlob decoded(hash, string.data(), string.size());
DecodedBlob decoded(hash.data(), string.data(), string.size());
if (decoded.wasOk())
{
@@ -321,8 +316,8 @@ public:
}
else
{
status = checkedCast<Status>(
safeCast<int>(Status::CustomCode) + safeCast<int>(getStatus.code()));
status = static_cast<Status>(
static_cast<int>(Status::CustomCode) + unsafeCast<int>(getStatus.code()));
JLOG(journal.error()) << getStatus.ToString();
}
@@ -351,7 +346,7 @@ public:
EncodedBlob const encoded(e);
wb.Put(
rocksdb::Slice(reinterpret_cast<char const*>(encoded.getKey().data()), keyBytes),
rocksdb::Slice(reinterpret_cast<char const*>(encoded.getKey()), keyBytes),
rocksdb::Slice(
reinterpret_cast<char const*>(encoded.getData()), encoded.getSize()));
}
@@ -361,7 +356,7 @@ public:
auto ret = db->Write(options, &wb);
if (!ret.ok())
Throw<std::runtime_error>(std::format("storeBatch failed: {}", ret.ToString()));
Throw<std::runtime_error>("storeBatch failed: " + ret.ToString());
}
void
@@ -379,12 +374,9 @@ public:
for (it->SeekToFirst(); it->Valid(); it->Next())
{
if (it->key().size() == keyBytes && it->key().size() == uint256::size())
if (it->key().size() == keyBytes)
{
DecodedBlob decoded(
uint256::fromRaw(std::span{it->key().data(), it->key().size()}),
it->value().data(),
it->value().size());
DecodedBlob decoded(it->key().data(), it->value().data(), it->value().size());
if (decoded.wasOk())
{
+82 -116
View File
@@ -8,8 +8,6 @@
#include <xrpl/protocol/digest.h>
#include <xrpl/protocol/tokens.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <cstring>
@@ -21,143 +19,95 @@
namespace xrpl {
namespace {
namespace detail {
/** If true, we implement a cache for account ID lookups.
This is generally a good idea and can improve performance, especially
when serving client requests.
The cache may generate warnings under some sanitizers; if you want to
disable caching, set this variable to false.
/**
* Caches the base58 representations of AccountIDs
*/
constexpr bool cacheAccountIDs = true;
/** Represents a cached entry for the given AccountID.
We keep the size and alignment of the structure at 64 bytes to
make sure it fits within a cache line and avoid false sharing.
*/
struct alignas(64) CachedAccountID
class AccountIdCache
{
/** The sequence lock for the entry.
private:
struct CachedAccountID
{
AccountID id;
char encoding[40] = {0};
};
An even value means that the data is stable, whereas an odd
value indicates the data is being actively modified.
// The actual cache
std::vector<CachedAccountID> cache_;
We use a 32-bit value to ensure that the counter will never
return to an observed value during a read window.
*/
std::atomic<std::uint32_t> seq{0};
// We use a hash function designed to resist algorithmic complexity attacks
HardenedHash<> hasher_;
/** The account id. */
AccountID id{};
// 64 spinlocks, packed into a single 64-bit value
std::atomic<std::uint64_t> locks_ = 0;
/** The length of the cached encoding.
public:
AccountIdCache(std::size_t count) : cache_(count)
{
// This is non-binding, but we try to avoid wasting memory that
// is caused by overallocation.
cache_.shrink_to_fit();
}
This field disambiguates collisions between ACCOUNT_ZERO and
never-written-to slots, since they would otherwise share the
same encoded bits.
*/
std::uint8_t len = 0;
std::string
toBase58(AccountID const& id)
{
auto const index = hasher_(id) % cache_.size();
/** The actual string encoding. */
char encoding[39] = {0};
PackedSpinlock sl(locks_, index % 64);
{
std::scoped_lock const lock(sl);
// The check against the first character of the encoding ensures
// that we don't mishandle the case of the all-zero account:
if (cache_[index].encoding[0] != 0 && cache_[index].id == id)
return cache_[index].encoding;
}
auto ret = encodeBase58Token(TokenType::AccountID, id.data(), id.size());
XRPL_ASSERT(ret.size() <= 38, "xrpl::detail::AccountIdCache : maximum result size");
{
std::scoped_lock const lock(sl);
cache_[index].id = id;
std::strcpy(cache_[index].encoding, ret.c_str());
}
return ret;
}
};
static_assert(sizeof(CachedAccountID) == 64 && alignof(CachedAccountID) == 64);
} // namespace detail
// The actual cache to use: a simple, direct-mapped, best-effort construct.
//
// We do not use a hardened hash for this because an AccountID is already
// just cryptographically generated (i.e. uniformly distributed) bits and
// there is no amplification, since collisions do not carry an additional
// cost.
//
// The size is a "nice" power of 2 because it makes the calculation of an
// index easier.
constinit std::array<CachedAccountID, 65536> gAccountIdCache{};
static std::unique_ptr<detail::AccountIdCache> gAccountIdCache;
} // namespace
void
initAccountIdCache(std::size_t count)
{
if (!gAccountIdCache && count != 0)
gAccountIdCache = std::make_unique<detail::AccountIdCache>(count);
}
std::string
toBase58(AccountID const& v)
{
// Since the input bits are generated by a cryptographically secure
// hash construct, we can just pick any subset of them, no muss, no
// fuss.
auto const index = [&v]() -> std::uint16_t {
auto const data = v.data();
if (gAccountIdCache)
return gAccountIdCache->toBase58(v);
return static_cast<std::uint16_t>(data[0]) | (static_cast<std::uint16_t>(data[1]) << 8);
}();
if constexpr (cacheAccountIDs)
{
auto& slot = gAccountIdCache[index];
// This is the optimistic, store-free read path: a collision with a
// concurrent writer will be detected because the seq recheck fails
// and we fall through to the slow path.
//
// These unsynchronized reads of the slot fields are, formally, data
// races and, technically speaking, result in undefined behavior and
// a tool like TSAN might flag them. These should not have an impact
// on the correctness of the returned data: the sequence lock checks
// ensure we discard any data that was in flux.
if (auto const s0 = slot.seq.load(std::memory_order::acquire);
(s0 & 1) == 0 && slot.len != 0 && slot.id == v)
{
char buf[38];
auto const len = std::min<std::size_t>(slot.len, sizeof(buf));
std::memcpy(buf, slot.encoding, len);
// Prevents the loads above from sinking below the recheck; the
// acquire on s0 constrains later operations, not earlier ones.
std::atomic_thread_fence(std::memory_order::acquire);
// Everything read from the slot — the gates above and the bytes in
// buf — precedes this recheck and is validated by it. No slot access
// may follow it.
if (slot.seq.load(std::memory_order::relaxed) == s0)
return std::string(buf, len);
}
}
auto ret = encodeBase58Token(TokenType::AccountID, v.data(), v.size());
XRPL_ASSERT(!ret.empty() && ret.size() <= 38, "xrpl::toBase58 : maximum result size");
if constexpr (cacheAccountIDs)
{
auto& slot = gAccountIdCache[index];
// The CAS to an odd value excludes other writers; if it fails, another
// thread wrote or is writing into this slot. That is not a problem: we
// just don't store our result into the cache this time.
if (auto s0 = slot.seq.load(std::memory_order::relaxed); (s0 & 1) == 0 &&
slot.seq.compare_exchange_strong(s0, s0 + 1, std::memory_order::acquire))
{
slot.id = v;
slot.len = static_cast<std::uint8_t>(ret.size());
std::copy_n(ret.data(), ret.size(), slot.encoding);
// The acquire on the CAS above keeps the writes from becoming visible
// before the seqlock transitions to an odd value, which means readers
// can detect an in-progress write. This release publishes them before
// the seqlock transitions to an even value.
slot.seq.store(s0 + 2, std::memory_order::release);
}
}
return ret;
return encodeBase58Token(TokenType::AccountID, v.data(), v.size());
}
template <>
std::optional<AccountID>
parseBase58(std::string const& s)
{
return AccountID::fromRaw(decodeBase58Token(s, TokenType::AccountID));
auto const result = decodeBase58Token(s, TokenType::AccountID);
if (result.size() != AccountID::kBytes)
return std::nullopt;
return AccountID::fromRaw(result);
}
//------------------------------------------------------------------------------
@@ -198,9 +148,25 @@ parseBase58(std::string const& s)
AccountID
calcAccountID(PublicKey const& pk)
{
static_assert(AccountID::kBytes == sizeof(RipeshaHasher::result_type));
RipeshaHasher rsh;
rsh(pk.data(), pk.size());
return AccountID{static_cast<RipeshaHasher::result_type>(rsh)};
return AccountID::fromRaw(static_cast<RipeshaHasher::result_type>(rsh));
}
AccountID const&
xrpAccount()
{
static AccountID const kAccount(beast::kZero);
return kAccount;
}
AccountID const&
noAccount()
{
static AccountID const kAccount(1);
return kAccount;
}
bool
+5 -6
View File
@@ -185,9 +185,8 @@ getBookBase(Book const& book)
UInt256
getQualityNext(UInt256 const& uBase)
{
static constexpr UInt256 kNextQuality{
"0000000000000000000000000000000000000000000000010000000000000000"};
static constexpr UInt256 kNextQuality(
"0000000000000000000000000000000000000000000000010000000000000000");
return uBase + kNextQuality;
}
@@ -430,9 +429,9 @@ payChannel(AccountID const& src, AccountID const& dst, SeqProxy const& seq) noex
Keylet
nftokenPageMin(AccountID const& owner)
{
std::array<std::uint8_t, UInt256::size()> buf{};
std::ranges::copy(owner, buf.begin());
return {ltNFTOKEN_PAGE, UInt256{buf}};
std::array<std::uint8_t, 32> buf{};
std::memcpy(buf.data(), owner.data(), owner.size());
return {ltNFTOKEN_PAGE, UInt256::fromRaw(buf)};
}
Keylet
+9 -4
View File
@@ -2,7 +2,6 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/digest.h>
@@ -21,8 +20,8 @@ addRaw(LedgerHeader const& info, Serializer& s, bool includeHash)
s.addBitString(info.accountHash);
s.add32(info.parentCloseTime.time_since_epoch().count());
s.add32(info.closeTime.time_since_epoch().count());
s.add8(unsafeCast<std::uint8_t>(info.closeTimeResolution.count()));
s.add8(unsafeCast<std::uint8_t>(info.closeFlags));
s.add8(info.closeTimeResolution.count());
s.add8(info.closeFlags);
if (includeHash)
s.addBitString(info.hash);
@@ -31,7 +30,7 @@ addRaw(LedgerHeader const& info, Serializer& s, bool includeHash)
LedgerHeader
deserializeHeader(Slice data, bool hasHash)
{
SerialIter sit{data};
SerialIter sit(data.data(), data.size());
LedgerHeader header;
@@ -51,6 +50,12 @@ deserializeHeader(Slice data, bool hasHash)
return header;
}
LedgerHeader
deserializePrefixedHeader(Slice data, bool hasHash)
{
return deserializeHeader(data + 4, hasHash);
}
UInt256
calculateLedgerHash(LedgerHeader const& info)
{
+2 -3
View File
@@ -14,7 +14,6 @@
#include <algorithm>
#include <cstdint>
#include <format>
#include <functional>
#include <limits>
#include <optional>
@@ -106,8 +105,8 @@ Permission::Permission()
if (type <= UINT16_MAX)
{
// LCOV_EXCL_START
Throw<std::logic_error>(std::format(
"Granular permission value must exceed the maximum uint16_t value: {}", name));
Throw<std::logic_error>(
"Granular permission value must exceed the maximum uint16_t value: " + name);
// LCOV_EXCL_STOP
}
}
+3 -1
View File
@@ -295,9 +295,11 @@ verify(PublicKey const& publicKey, Slice const& m, Slice const& sig) noexcept
NodeID
calcNodeID(PublicKey const& pk)
{
static_assert(NodeID::kBytes == sizeof(RipeshaHasher::result_type));
RipeshaHasher h;
h(pk.data(), pk.size());
return NodeID{static_cast<RipeshaHasher::result_type>(h)};
return NodeID::fromRaw(static_cast<RipeshaHasher::result_type>(h));
}
} // namespace xrpl
+3 -5
View File
@@ -43,9 +43,8 @@ setCurrentTransactionRules(std::optional<Rules> r)
auto const range = [&r]() {
// If any new conditions with new amendments are added to "enableLargeNumbers", those
// amendments must also be added to useRulesGuards.
bool const enableLargeNumbers = !r ||
(r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol) ||
r->enabled(featureMPTokensV2));
bool const enableLargeNumbers =
!r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol));
// If enableLargeNumbers is true, then useRulesGuards must also return true.
// However, the reverse is not true. Other amendments can cause the rules guard to be used,
// even though large numbers are _not_ used.
@@ -85,8 +84,7 @@ useRulesGuards(Rules const& rules)
// with createGuards, and any other callers, and the first set of guards can be created directly
// at the call site, without using optional.
return rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol) ||
rules.enabled(fixCleanup3_2_0) || rules.enabled(fixCleanup3_3_0) ||
rules.enabled(featureMPTokensV2);
rules.enabled(fixCleanup3_2_0) || rules.enabled(fixCleanup3_3_0);
}
void
+20 -21
View File
@@ -17,29 +17,33 @@
namespace xrpl {
STAccount::STAccount()
STAccount::STAccount() : value_(beast::kZero), default_(true)
{
}
STAccount::STAccount(SField const& n) : STBase(n)
STAccount::STAccount(SField const& n) : STBase(n), value_(beast::kZero), default_(true)
{
}
STAccount::STAccount(SerialIter& sit, SField const& name) : STAccount(name)
STAccount::STAccount(SField const& n, Buffer const& v) : STAccount(n)
{
if (auto const s = sit.getVL(); !s.empty())
{
// Throwing from constructors can be awkward, but this is only
// called from STVar::STVar (SerialIter&, SField const&) which
// also throws.
auto const id = AccountID::fromRaw(s);
if (v.empty())
return; // Zero is a valid size for a defaulted STAccount.
if (!id) [[unlikely]]
Throw<std::runtime_error>("Invalid STAccount size");
// Is it safe to throw from this constructor? Today (November 2015)
// the only place that calls this constructor is
// STVar::STVar (SerialIter&, SField const&)
// which throws. If STVar can throw in its constructor, then so can
// STAccount.
if (v.size() != UInt160::kBytes)
Throw<std::runtime_error>("Invalid STAccount size");
value_ = *id;
default_ = false;
}
default_ = false;
memcpy(value_.begin(), v.data(), UInt160::kBytes);
}
STAccount::STAccount(SerialIter& sit, SField const& name) : STAccount(name, sit.getVLBuffer())
{
}
STAccount::STAccount(SField const& n, AccountID const& v) : STBase(n), value_(v), default_(false)
@@ -73,13 +77,8 @@ STAccount::add(Serializer& s) const
// Preserve the serialization behavior of an STBlob:
// o If we are default (all zeros) serialize as an empty blob.
// o Otherwise serialize 160 bits.
if (isDefault())
{
s.addVL(Slice{});
return;
}
s.addVL(Slice{value_.data(), value_.size()});
int const size = isDefault() ? 0 : UInt160::kBytes;
s.addVL(value_.data(), size);
}
bool
+7 -6
View File
@@ -37,7 +37,6 @@
#include <cstddef>
#include <cstdint>
#include <exception>
#include <format>
#include <iterator>
#include <limits>
#include <memory>
@@ -148,12 +147,12 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name)
}
Issue issue;
issue.currency = Currency{sit.get160()};
issue.currency = sit.get160();
if (isXRP(issue.currency))
Throw<std::runtime_error>("invalid native currency");
issue.account = AccountID{sit.get160()};
issue.account = sit.get160();
if (isXRP(issue.account))
Throw<std::runtime_error>("invalid native account");
@@ -1194,7 +1193,8 @@ muldiv(std::uint64_t multiplier, std::uint64_t multiplicand, std::uint64_t divis
if (ret > std::numeric_limits<std::uint64_t>::max())
{
Throw<std::overflow_error>(
std::format("overflow: ({} * {}) / {}", multiplier, multiplicand, divisor));
"overflow: (" + std::to_string(multiplier) + " * " + std::to_string(multiplicand) +
") / " + std::to_string(divisor));
}
return static_cast<uint64_t>(ret);
@@ -1215,8 +1215,9 @@ muldivRound(
if (ret > std::numeric_limits<std::uint64_t>::max())
{
Throw<std::overflow_error>(std::format(
"overflow: (({} * {}) + {}) / {}", multiplier, multiplicand, rounding, divisor));
Throw<std::overflow_error>(
"overflow: ((" + std::to_string(multiplier) + " * " + std::to_string(multiplicand) +
") + " + std::to_string(rounding) + ") / " + std::to_string(divisor));
}
return static_cast<uint64_t>(ret);
+2 -2
View File
@@ -12,7 +12,7 @@
namespace xrpl {
STBlob::STBlob(SerialIter& st, SField const& name) : STBase(name), value_(st.getVL())
STBlob::STBlob(SerialIter& st, SField const& name) : STBase(name), value_(st.getVLBuffer())
{
}
@@ -47,7 +47,7 @@ STBlob::add(Serializer& s) const
XRPL_ASSERT(
(getFName().fieldType == STI_VL) || (getFName().fieldType == STI_ACCOUNT),
"xrpl::STBlob::add : valid field type");
s.addVL(Slice{value_.data(), value_.size()});
s.addVL(value_.data(), value_.size());
}
bool
+4 -4
View File
@@ -19,13 +19,13 @@ STCurrency::STCurrency(SField const& name) : STBase{name}
{
}
STCurrency::STCurrency(SField const& name, Currency const& currency)
: STBase{name}, currency_{currency}
STCurrency::STCurrency(SerialIter& sit, SField const& name) : STBase{name}
{
currency_ = sit.get160();
}
STCurrency::STCurrency(SerialIter& sit, SField const& name)
: STCurrency{name, Currency{sit.get160()}}
STCurrency::STCurrency(SField const& name, Currency const& currency)
: STBase{name}, currency_{currency}
{
}
+2 -3
View File
@@ -64,8 +64,7 @@ STUInt8::getJson(JsonOptions) const
return token;
// LCOV_EXCL_START
JLOG(debugLog().error()) << "Unknown result code in metadata: "
<< safeCast<std::uint32_t>(value_);
JLOG(debugLog().error()) << "Unknown result code in metadata: " << value_;
// LCOV_EXCL_STOP
}
@@ -222,7 +221,7 @@ STUInt64::getJson(JsonOptions) const
template <>
STInteger<std::int32_t>::STInteger(SerialIter& sit, SField const& name)
: STInteger(name, sit.geti32())
: STInteger(name, sit.get32())
{
}
+35 -25
View File
@@ -28,38 +28,48 @@ STIssue::STIssue(SField const& name) : STBase{name}
STIssue::STIssue(SerialIter& sit, SField const& name) : STBase{name}
{
// Either the currency of an Issue or the issuer of an MPT; the
// following 160 bits disambiguate.
auto const currencyOrAccount = sit.get160();
if (isXRP(Currency{currencyOrAccount}))
if (isXRP(Currency::fromRaw(currencyOrAccount)))
{
asset_ = xrpIssue();
return;
}
// MPT is serialized as:
// - 160 bits MPT issuer account
// - 160 bits black hole account
// - 32 bits sequence
AccountID const account{sit.get160()};
// MPT
if (noAccount() == account)
// Check if MPT
else
{
// MPT: issuer (160), noAccount() (160), sequence (32). The sequence
// is stored in little-endian and get32() reads big-endian, so
// reverse it before building the canonical ID.
asset_ = MPTIssue{std::byteswap(sit.get32()), AccountID{currencyOrAccount}};
return;
// MPT is serialized as:
// - 160 bits MPT issuer account
// - 160 bits black hole account
// - 32 bits sequence
AccountID const account = AccountID::fromRaw(sit.get160());
// MPT
if (noAccount() == account)
{
MPTID mptID;
std::uint32_t sequence = sit.get32();
// MPTID stores the sequence in canonical big-endian bytes. STIssue
// ledger bytes are the legacy LE-host encoding, so convert the
// native get32() value to LE bytes before copying into the MPTID.
sequence = boost::endian::native_to_little(sequence);
static_assert(MPTID::size() == sizeof(sequence) + sizeof(currencyOrAccount));
memcpy(mptID.data(), &sequence, sizeof(sequence));
memcpy(
mptID.data() + sizeof(sequence),
currencyOrAccount.data(),
sizeof(currencyOrAccount));
MPTIssue const issue{mptID};
asset_ = issue;
}
else
{
Issue issue;
issue.currency = currencyOrAccount;
issue.account = account;
if (!isConsistent(issue))
Throw<std::runtime_error>("invalid issue: currency and account native mismatch");
asset_ = issue;
}
}
Issue const issue{Currency{currencyOrAccount}, account};
if (!isConsistent(issue))
Throw<std::runtime_error>("invalid issue: currency and account native mismatch");
asset_ = issue;
}
SerializedTypeID
+3 -2
View File
@@ -35,8 +35,9 @@ STLedgerEntry::STLedgerEntry(Keylet const& k) : STObject(sfLedgerEntry), key_(k.
if (format == nullptr)
{
Throw<std::runtime_error>(std::format(
"Attempt to create a SLE of unknown type {}", safeCast<std::uint16_t>(k.type)));
Throw<std::runtime_error>(
"Attempt to create a SLE of unknown type " +
std::to_string(safeCast<std::uint16_t>(k.type)));
}
set(format->getSOTemplate());
+1 -2
View File
@@ -19,7 +19,6 @@
#include <cstddef>
#include <cstdint>
#include <format>
#include <limits>
#include <ostream>
#include <stdexcept>
@@ -171,7 +170,7 @@ partsFromString(std::string const& number)
boost::smatch match;
if (!boost::regex_match(number, match, kReNumber))
Throw<std::runtime_error>(std::format("'{}' is not a number", number));
Throw<std::runtime_error>("'" + number + "' is not a number");
// Match fields:
// 0 = whole input
+4 -3
View File
@@ -1,11 +1,13 @@
#include <xrpl/protocol/STObject.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/InnerObjectFormats.h>
@@ -26,7 +28,6 @@
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/detail/STVar.h>
#include <xrpl/protocol/digest.h>
#include <algorithm>
#include <cstddef>
@@ -376,7 +377,7 @@ STObject::getHash(HashPrefix prefix) const
Serializer s;
s.add32(prefix);
add(s, WhichFields::WithAllFields);
return sha512Half(s);
return s.getSHA512Half();
}
UInt256
@@ -385,7 +386,7 @@ STObject::getSigningHash(HashPrefix prefix) const
Serializer s;
s.add32(prefix);
add(s, WhichFields::OmitSigningFields);
return sha512Half(s);
return s.getSHA512Half();
}
int
+48 -76
View File
@@ -5,7 +5,6 @@
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
@@ -18,13 +17,9 @@
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>
@@ -41,10 +36,10 @@ STPathElement::getHash(STPathElement const& element)
// important. We don't even really need to fully hash the whole
// base_uint here, as a few bytes would do for our use.
//
// The note above is only true because the result of this function is only
// used by the STPathElement equality operator as a fast-reject prefilter,
// ahead of the field comparisons that then decide the answer. Do not use
// it to key a container.
// The note above is only true because the result of this function reaches
// nothing but STPathElement::operator==, where it is a fast-reject
// prefilter ahead of the field comparisons that decide the answer. Do not
// use it to key a container.
for (auto const x : element.getAccountID())
hashAccount += (hashAccount * 257) ^ x;
@@ -111,38 +106,12 @@ STPathSet::operator=(STPathSet const& other)
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
{
using enum STPathElement::Type;
auto parsePathElementType = [](std::underlying_type_t<STPathElement::Type> byte) noexcept
-> std::optional<STPathElement::Type> {
if (byte == std::to_underlying(TypeBoundary))
return TypeBoundary;
constexpr auto knownBits = static_cast<std::underlying_type_t<STPathElement::Type>>(
std::to_underlying(TypeAccount) | std::to_underlying(TypeCurrency) |
std::to_underlying(TypeIssuer) | std::to_underlying(TypeMpt));
if ((byte & ~knownBits) != 0)
return std::nullopt;
return static_cast<STPathElement::Type>(byte);
};
std::vector<STPathElement> path;
std::optional<STPathElement::Type> type;
do
for (;;)
{
type = parsePathElementType(sit.get8());
int const iType = sit.get8();
if (!type)
{
JLOG(debugLog().error()) << "Bad path element in pathset";
Throw<std::runtime_error>("bad path element");
}
if (type == TypeNone || type == TypeBoundary)
if (iType == STPathElement::TypeNone || iType == STPathElement::TypeBoundary)
{
if (path.empty())
{
@@ -153,11 +122,28 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
// Move rather than converting the vector to an STPath by copy.
value_.emplace_back(std::move(path));
path.clear();
if (iType == STPathElement::TypeNone)
return;
}
else if ((iType & ~STPathElement::TypeAll) != 0)
{
JLOG(debugLog().error()) << "Bad path element " << iType << " in pathset";
Throw<std::runtime_error>("bad path element");
}
else
{
bool const hasCurrency = ((*type & TypeCurrency) == TypeCurrency);
bool const hasMPT = ((*type & TypeMpt) == TypeMpt);
auto const hasAccount = (iType & STPathElement::TypeAccount) != 0u;
auto const hasCurrency = (iType & STPathElement::TypeCurrency) != 0u;
auto const hasIssuer = (iType & STPathElement::TypeIssuer) != 0u;
auto const hasMPT = (iType & STPathElement::TypeMpt) != 0u;
AccountID account;
PathAsset asset;
AccountID issuer;
if (hasAccount)
account = sit.get160();
if (hasCurrency && hasMPT)
{
@@ -165,26 +151,18 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
Throw<std::runtime_error>("bad path element: MPT and Currency");
}
AccountID account;
AccountID issuer;
if ((*type & TypeAccount) == TypeAccount)
account = sit.getBitString<AccountID>();
PathAsset asset;
if (hasCurrency)
asset = sit.getBitString<Currency>();
asset = Currency::fromRaw(sit.get160());
if (hasMPT)
asset = sit.getBitString<MPTID>();
asset = sit.get192();
if ((*type & TypeIssuer) == TypeIssuer)
issuer = sit.getBitString<AccountID>();
if (hasIssuer)
issuer = sit.get160();
path.emplace_back(account, asset, issuer, hasCurrency || hasMPT);
}
} while (type != TypeNone);
}
}
STBase*
@@ -238,22 +216,24 @@ STPath::getJson(JsonOptions) const
for (auto const& it : path_)
{
json::Value elem(json::ValueType::Object);
auto const iType = it.getNodeType();
elem[jss::type] = safeCast<std::uint32_t>(it.getNodeType());
elem[jss::type] = iType;
if (it.isType(STPathElement::TypeAccount))
if ((iType & STPathElement::TypeAccount) != 0u)
elem[jss::account] = to_string(it.getAccountID());
XRPL_ASSERT(
!(it.hasCurrency() && it.hasMPT()),
((iType & STPathElement::TypeCurrency) == 0u) ||
((iType & STPathElement::TypeMpt) == 0u),
"xrpl::STPath::getJson : not type Currency and MPT");
if (it.hasCurrency())
if ((iType & STPathElement::TypeCurrency) != 0u)
elem[jss::currency] = to_string(it.getCurrency());
if (it.hasMPT())
if ((iType & STPathElement::TypeMpt) != 0u)
elem[jss::mpt_issuance_id] = to_string(it.getMPTID());
if (it.hasIssuer())
if ((iType & STPathElement::TypeIssuer) != 0u)
elem[jss::issuer] = to_string(it.getIssuerID());
ret.append(elem);
@@ -285,42 +265,34 @@ STPathSet::add(Serializer& s) const
XRPL_ASSERT(getFName().fieldType == STI_PATHSET, "xrpl::STPathSet::add : valid field type");
bool first = true;
// Workaround, since `Serializer` does not accept strongly-typed enums
// yet. When support is added, the static_assert will trigger, and the
// lambda can be removed entirely.
auto const toByte = [](std::same_as<STPathElement::Type> auto t) noexcept {
static_assert(
!requires(Serializer& ser) { ser.add8(t); },
"Serializer::add8 now accepts STPathElement::Type directly; remove toByte.");
return std::to_underlying(t);
};
for (auto const& spPath : value_)
{
if (!first)
s.add8(toByte(STPathElement::TypeBoundary));
s.add8(STPathElement::TypeBoundary);
for (auto const& speElement : spPath)
{
s.add8(toByte(speElement.getNodeType()));
int const iType = speElement.getNodeType();
if (speElement.isType(STPathElement::TypeAccount))
s.add8(iType);
if ((iType & STPathElement::TypeAccount) != 0u)
s.addBitString(speElement.getAccountID());
if (speElement.hasMPT())
if ((iType & STPathElement::TypeMpt) != 0u)
s.addBitString(speElement.getMPTID());
if (speElement.hasCurrency())
if ((iType & STPathElement::TypeCurrency) != 0u)
s.addBitString(speElement.getCurrency());
if (speElement.hasIssuer())
if ((iType & STPathElement::TypeIssuer) != 0u)
s.addBitString(speElement.getIssuerID());
}
first = false;
}
s.add8(toByte(STPathElement::TypeNone));
s.add8(STPathElement::TypeNone);
}
} // namespace xrpl
+28 -9
View File
@@ -59,8 +59,9 @@ getTxFormat(TxType type)
if (format == nullptr)
{
Throw<std::runtime_error>(std::format(
"Invalid transaction type {}", safeCast<std::underlying_type_t<TxType>>(type)));
Throw<std::runtime_error>(
"Invalid transaction type " +
std::to_string(safeCast<std::underlying_type_t<TxType>>(type)));
}
return format;
@@ -76,7 +77,9 @@ STTx::STTx(STObject&& object)
STTx::STTx(SerialIter& sit) : STObject(sfTransaction)
{
if (auto const sz = sit.size(); (sz < kTxMinSizeBytes) || (sz > kTxMaxSizeBytes))
int const length = sit.getBytesLeft();
if ((length < kTxMinSizeBytes) || (length > kTxMaxSizeBytes))
Throw<std::runtime_error>("Transaction length invalid");
if (set(sit))
@@ -170,7 +173,7 @@ getSigningData(STTx const& that, HashPrefix prefix)
Serializer s;
s.add32(prefix);
that.addWithoutSigningFields(s);
return s.takeData();
return s.getData();
}
UInt256
@@ -361,7 +364,8 @@ STTx::getJson(JsonOptions options, bool binary) const
if (binary)
{
std::string const dataBin = strHex(getSerializer().slice());
Serializer const s = STObject::getSerializer();
std::string const dataBin = strHex(s.peekData());
if (v1)
{
@@ -396,7 +400,22 @@ STTx::getMetaSQLInsertReplaceHeader()
std::string
STTx::getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData) const
{
auto const format = TxFormats::getInstance().findByType(txType_);
Serializer s;
add(s);
return getMetaSQL(s, inLedger, TxnSql::Validated, escapedMetaData);
}
// VFALCO This could be a free function elsewhere
std::string
STTx::getMetaSQL(
Serializer rawTxn,
std::uint32_t inLedger,
TxnSql status,
std::string const& escapedMetaData) const
{
std::string rTxn = sqlBlobLiteral(rawTxn.peekData());
auto format = TxFormats::getInstance().findByType(txType_);
XRPL_ASSERT(format, "xrpl::STTx::getMetaSQL : non-null type format");
return std::format(
@@ -406,8 +425,8 @@ STTx::getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData) con
toBase58(getAccountID(sfAccount)),
getFieldU32(sfSequence),
inLedger,
safeCast<char>(TxnSql::Validated),
sqlBlobLiteral(getSerializer().slice()),
safeCast<char>(status),
rTxn,
escapedMetaData);
}
@@ -676,7 +695,7 @@ isMemoOkay(STObject const& st, std::string& reason)
memos.add(s);
// FIXME move the memo limit into a config tunable
if (s.size() > 1024)
if (s.getDataLength() > 1024)
{
reason = "The memo exceeds the maximum allowed size.";
return false;
+1 -1
View File
@@ -168,7 +168,7 @@ STValidation::getSerialized() const
{
Serializer s;
add(s);
return s.takeData();
return s.peekData();
}
} // namespace xrpl
+8 -14
View File
@@ -9,30 +9,28 @@
#include <xrpl/protocol/Serializer.h>
#include <cstddef>
#include <cstdint>
#include <format>
#include <span>
#include <stdexcept>
#include <string>
#include <utility>
namespace xrpl {
STVector256::STVector256(SerialIter& sit, SField const& name) : STBase(name)
{
auto const slice = sit.getVL();
auto const slice = sit.getSlice(sit.getVLDataLength());
if (slice.size() % UInt256::size() != 0)
{
Throw<std::runtime_error>(
std::format("Bad serialization for STVector256: {}", slice.size()));
"Bad serialization for STVector256: " + std::to_string(slice.size()));
}
value_.reserve(slice.size() / UInt256::kBytes);
auto const cnt = slice.size() / UInt256::size();
SerialIter inner{slice};
value_.reserve(cnt);
while (!inner.empty())
value_.push_back(inner.get256());
for (std::size_t i = 0; i != cnt; ++i)
value_.push_back(UInt256::fromRaw(slice.substr(i * UInt256::size(), UInt256::size())));
}
STBase*
@@ -64,11 +62,7 @@ STVector256::add(Serializer& s) const
{
XRPL_ASSERT(getFName().isBinary(), "xrpl::STVector256::add : field is binary");
XRPL_ASSERT(getFName().fieldType == STI_VECTOR256, "xrpl::STVector256::add : valid field type");
// Because uint256 has no padding and the container stores the values
// contiguously, they are already in wire layout, so we can serialize
// them in one go:
s.addVL(Slice{value_.data(), value_.size() * UInt256::size()});
s.addVL(value_.begin(), value_.end(), value_.size() * (256 / 8));
}
bool
+1 -2
View File
@@ -70,8 +70,7 @@ STXChainBridge::STXChainBridge(SField const& name, json::Value const& v) : STBas
std::string const name = it.memberName();
if (!kBridgeJson.isMember(name))
{
Throw<std::runtime_error>(
std::format("STXChainBridge extra field detected: {}", name));
Throw<std::runtime_error>("STXChainBridge extra field detected: " + name);
}
}
return true;
+2 -3
View File
@@ -104,9 +104,8 @@ parseGenericSeed(std::string const& str, bool rfc1751)
std::string key;
if (RFC1751::getKeyFromEnglish(key, str) == 1)
{
std::array<std::uint8_t, UInt128::size()> bytes{};
std::ranges::reverse_copy(key, bytes.begin());
return Seed{UInt128{bytes}};
Blob const blob(key.rbegin(), key.rend());
return Seed{UInt128::fromRaw(blob)};
}
}
+550
View File
@@ -0,0 +1,550 @@
#include <xrpl/protocol/Serializer.h>
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/digest.h>
#include <boost/endian/conversion.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <string>
#include <type_traits>
namespace xrpl {
int
Serializer::add16(std::uint16_t i)
{
int const ret = data_.size();
data_.push_back(static_cast<unsigned char>(i >> 8));
data_.push_back(static_cast<unsigned char>(i & 0xff));
return ret;
}
int
Serializer::add32(HashPrefix p)
{
// This should never trigger; the size & type of a hash prefix are
// integral parts of the protocol and unlikely to ever change.
static_assert(std::is_same_v<std::uint32_t, std::underlying_type_t<decltype(p)>>);
return add32(safeCast<std::uint32_t>(p));
}
template <>
int
Serializer::addInteger(unsigned char i)
{
return add8(i);
}
template <>
int
Serializer::addInteger(std::uint16_t i)
{
return add16(i);
}
template <>
int
Serializer::addInteger(std::uint32_t i)
{
return add32(i);
}
template <>
int
Serializer::addInteger(std::uint64_t i)
{
return add64(i);
}
template <>
int
Serializer::addInteger(std::int32_t i)
{
return add32(i);
}
int
Serializer::addRaw(Blob const& vector)
{
int const ret = data_.size();
data_.insert(data_.end(), vector.begin(), vector.end());
return ret;
}
int
Serializer::addRaw(Slice slice)
{
int const ret = data_.size();
data_.insert(data_.end(), slice.begin(), slice.end());
return ret;
}
int
Serializer::addRaw(Serializer const& s)
{
int const ret = data_.size();
data_.insert(data_.end(), s.begin(), s.end());
return ret;
}
int
Serializer::addRaw(void const* ptr, int len)
{
int const ret = data_.size();
data_.insert(data_.end(), static_cast<char const*>(ptr), static_cast<char const*>(ptr) + len);
return ret;
}
int
Serializer::addFieldID(int type, int name)
{
int const ret = data_.size();
XRPL_ASSERT(
(type > 0) && (type < 256) && (name > 0) && (name < 256),
"xrpl::Serializer::addFieldID : inputs inside range");
if (type < 16)
{
if (name < 16)
{
// common type, common name
data_.push_back(static_cast<unsigned char>((type << 4) | name));
}
else
{
// common type, uncommon name
data_.push_back(static_cast<unsigned char>(type << 4));
data_.push_back(static_cast<unsigned char>(name));
}
}
else if (name < 16)
{
// uncommon type, common name
data_.push_back(static_cast<unsigned char>(name));
data_.push_back(static_cast<unsigned char>(type));
}
else
{
// uncommon type, uncommon name
data_.push_back(static_cast<unsigned char>(0));
data_.push_back(static_cast<unsigned char>(type));
data_.push_back(static_cast<unsigned char>(name));
}
return ret;
}
int
Serializer::add8(unsigned char byteValue)
{
int const ret = data_.size();
data_.push_back(byteValue);
return ret;
}
bool
Serializer::get8(int& byte, int offset) const
{
if (offset >= data_.size())
return false;
byte = data_[offset];
return true;
}
bool
Serializer::chop(int bytes)
{
if (bytes > data_.size())
return false;
data_.resize(data_.size() - bytes);
return true;
}
UInt256
Serializer::getSHA512Half() const
{
return sha512Half(makeSlice(data_));
}
int
Serializer::addVL(Blob const& vector)
{
int const ret = addEncoded(vector.size());
addRaw(vector);
XRPL_ASSERT(
data_.size() == (ret + vector.size() + encodeLengthLength(vector.size())),
"xrpl::Serializer::addVL : size matches expected");
return ret;
}
int
Serializer::addVL(Slice const& slice)
{
int const ret = addEncoded(slice.size());
if (!slice.empty())
addRaw(slice.data(), slice.size());
return ret;
}
int
Serializer::addVL(void const* ptr, int len)
{
int const ret = addEncoded(len);
if (len != 0)
addRaw(ptr, len);
return ret;
}
int
Serializer::addEncoded(int length)
{
// Without this, a negative length would fall into the 1 byte case below and
// be cast to a first byte no header uses. A size too big for int arrives
// here negative as well, since callers pass sizes through this parameter.
if (length < kMinValueOfLengthFor1ByteHeader)
Throw<std::overflow_error>("addEncoded: length is negative or did not fit in an int");
std::array<std::byte, kMaxNumberOfBytesInHeader> bytes{};
int numBytes = 0;
if (length <= kMaxValueOfLengthFor1ByteHeader)
{
bytes[0] = static_cast<std::byte>(length);
numBytes = 1;
}
else if (length <= kMaxValueOfLengthFor2ByteHeader)
{
// Count from the smallest length a 2 byte header covers.
int const offset = length - kMinValueOfLengthFor2ByteHeader;
bytes[0] = static_cast<std::byte>(
kMinValueOfFirstByteFor2ByteHeader + (offset / kNumberOfValuesInOneByte));
bytes[1] = static_cast<std::byte>(offset % kNumberOfValuesInOneByte);
numBytes = 2;
}
else if (length <= kMaxValueOfLengthFor3ByteHeader)
{
int const offset = length - kMinValueOfLengthFor3ByteHeader;
bytes[0] = static_cast<std::byte>(
kMinValueOfFirstByteFor3ByteHeader + (offset / kNumberOfValuesInTwoBytes));
bytes[1] =
static_cast<std::byte>((offset / kNumberOfValuesInOneByte) % kNumberOfValuesInOneByte);
bytes[2] = static_cast<std::byte>(offset % kNumberOfValuesInOneByte);
numBytes = 3;
}
else
{
Throw<std::overflow_error>("addEncoded: length is too large to encode");
}
return addRaw(bytes.data(), numBytes);
}
int
Serializer::encodeLengthLength(int length)
{
if (length < kMinValueOfLengthFor1ByteHeader)
{
Throw<std::overflow_error>(
"encodeLengthLength: length is negative or did not fit in an int");
}
if (length <= kMaxValueOfLengthFor1ByteHeader)
return 1;
if (length <= kMaxValueOfLengthFor2ByteHeader)
return 2;
if (length <= kMaxValueOfLengthFor3ByteHeader)
return 3;
Throw<std::overflow_error>("encodeLengthLength: length is too large to encode");
}
int
Serializer::decodeLengthLength(std::byte firstByte)
{
int const firstByteValue = std::to_integer<int>(firstByte);
if (firstByteValue <= kMaxValueOfFirstByteFor1ByteHeader)
return 1;
if (firstByteValue <= kMaxValueOfFirstByteFor2ByteHeader)
return 2;
if (firstByteValue <= kMaxValueOfFirstByteFor3ByteHeader)
return 3;
Throw<std::overflow_error>("decodeLengthLength: first byte does not start any header");
}
int
Serializer::decodeVLLength(std::byte firstByte)
{
int const length = std::to_integer<int>(firstByte);
// A bigger value means a longer header, so it is not a length by itself.
if (length > kMaxValueOfLengthFor1ByteHeader)
Throw<std::overflow_error>("decodeVLLength 1 byte: first byte is not a length");
return length;
}
int
Serializer::decodeVLLength(std::byte firstByte, std::byte secondByte)
{
int const firstByteValue = std::to_integer<int>(firstByte);
if (firstByteValue < kMinValueOfFirstByteFor2ByteHeader)
Throw<std::overflow_error>("decodeVLLength 2 byte: first byte is below the range");
if (firstByteValue > kMaxValueOfFirstByteFor2ByteHeader)
Throw<std::overflow_error>("decodeVLLength 2 byte: first byte is above the range");
// Both bytes are bounded by their own type, and the first one is bounded to
// the 2 byte range above, so this cannot leave the range the header covers.
return kMinValueOfLengthFor2ByteHeader +
((firstByteValue - kMinValueOfFirstByteFor2ByteHeader) * kNumberOfValuesInOneByte) +
std::to_integer<int>(secondByte);
}
int
Serializer::decodeVLLength(std::byte firstByte, std::byte secondByte, std::byte thirdByte)
{
int const firstByteValue = std::to_integer<int>(firstByte);
if (firstByteValue < kMinValueOfFirstByteFor3ByteHeader)
Throw<std::overflow_error>("decodeVLLength 3 byte: first byte is below the range");
if (firstByteValue > kMaxValueOfFirstByteFor3ByteHeader)
Throw<std::overflow_error>("decodeVLLength 3 byte: first byte is above the range");
int const length = kMinValueOfLengthFor3ByteHeader +
((firstByteValue - kMinValueOfFirstByteFor3ByteHeader) * kNumberOfValuesInTwoBytes) +
(std::to_integer<int>(secondByte) * kNumberOfValuesInOneByte) +
std::to_integer<int>(thirdByte);
// A 3 byte header reaches further than kMaxValueOfLengthFor3ByteHeader, which
// is as far as the encoder goes. Refuse the rest, so every length accepted
// here is one that can be written back.
if (length > kMaxValueOfLengthFor3ByteHeader)
Throw<std::overflow_error>("decodeVLLength 3 byte: length is too large to re-encode");
return length;
}
//------------------------------------------------------------------------------
SerialIter::SerialIter(void const* data, std::size_t size) noexcept
: p_(reinterpret_cast<std::uint8_t const*>(data)), remain_(size)
{
}
void
SerialIter::reset() noexcept
{
p_ -= used_;
remain_ += used_;
used_ = 0;
}
void
SerialIter::skip(int length)
{
if (remain_ < length)
Throw<std::runtime_error>("invalid SerialIter skip");
p_ += length;
used_ += length;
remain_ -= length;
}
unsigned char
SerialIter::get8()
{
if (remain_ < 1)
Throw<std::runtime_error>("invalid SerialIter get8");
unsigned char const t = *p_;
++p_;
++used_;
--remain_;
return t;
}
std::uint16_t
SerialIter::get16()
{
if (remain_ < 2)
Throw<std::runtime_error>("invalid SerialIter get16");
auto t = p_;
p_ += 2;
used_ += 2;
remain_ -= 2;
return (std::uint64_t(t[0]) << 8) + std::uint64_t(t[1]);
}
std::uint32_t
SerialIter::get32()
{
if (remain_ < 4)
Throw<std::runtime_error>("invalid SerialIter get32");
auto t = p_;
p_ += 4;
used_ += 4;
remain_ -= 4;
return (std::uint64_t(t[0]) << 24) + (std::uint64_t(t[1]) << 16) + (std::uint64_t(t[2]) << 8) +
std::uint64_t(t[3]);
}
std::uint64_t
SerialIter::get64()
{
if (remain_ < 8)
Throw<std::runtime_error>("invalid SerialIter get64");
auto t = p_;
p_ += 8;
used_ += 8;
remain_ -= 8;
return (std::uint64_t(t[0]) << 56) + (std::uint64_t(t[1]) << 48) + (std::uint64_t(t[2]) << 40) +
(std::uint64_t(t[3]) << 32) + (std::uint64_t(t[4]) << 24) + (std::uint64_t(t[5]) << 16) +
(std::uint64_t(t[6]) << 8) + std::uint64_t(t[7]);
}
std::int32_t
SerialIter::geti32()
{
if (remain_ < 4)
Throw<std::runtime_error>("invalid SerialIter geti32");
auto t = p_;
p_ += 4;
used_ += 4;
remain_ -= 4;
return boost::endian::load_big_s32(t);
}
std::int64_t
SerialIter::geti64()
{
if (remain_ < 8)
Throw<std::runtime_error>("invalid SerialIter geti64");
auto t = p_;
p_ += 8;
used_ += 8;
remain_ -= 8;
return boost::endian::load_big_s64(t);
}
void
SerialIter::getFieldID(int& type, int& name)
{
type = get8();
name = type & 15;
type >>= 4;
if (type == 0)
{
// uncommon type
type = get8();
if (type < 16)
Throw<std::runtime_error>("gFID: uncommon type out of range " + std::to_string(type));
}
if (name == 0)
{
// uncommon name
name = get8();
if (name < 16)
Throw<std::runtime_error>("gFID: uncommon name out of range " + std::to_string(name));
}
}
// getRaw for blob or buffer
template <class T>
T
SerialIter::getRawHelper(int size)
{
static_assert(std::is_same_v<T, Blob> || std::is_same_v<T, Buffer>);
if (remain_ < size)
Throw<std::runtime_error>("invalid SerialIter getRaw");
T result(size);
if (size != 0)
{
// It's normally safe to call memcpy with size set to 0 (see the
// C99 standard 7.21.1/2). However, here this could mean that
// result.data would be null, which would trigger undefined behavior.
std::memcpy(result.data(), p_, size);
p_ += size;
used_ += size;
remain_ -= size;
}
return result;
}
// VFALCO DEPRECATED Returns a copy
Blob
SerialIter::getRaw(int size)
{
return getRawHelper<Blob>(size);
}
int
SerialIter::getVLDataLength()
{
std::byte const firstByte{get8()};
int datLen = 0;
int const lenLen = Serializer::decodeLengthLength(firstByte);
if (lenLen == 1)
{
datLen = Serializer::decodeVLLength(firstByte);
}
else if (lenLen == 2)
{
std::byte const secondByte{get8()};
datLen = Serializer::decodeVLLength(firstByte, secondByte);
}
else
{
XRPL_ASSERT(lenLen == 3, "xrpl::SerialIter::getVLDataLength : lenLen is 3");
std::byte const secondByte{get8()};
std::byte const thirdByte{get8()};
datLen = Serializer::decodeVLLength(firstByte, secondByte, thirdByte);
}
return datLen;
}
Slice
SerialIter::getSlice(std::size_t bytes)
{
if (bytes > remain_)
Throw<std::runtime_error>("invalid SerialIter getSlice");
Slice const s(p_, bytes);
p_ += bytes;
used_ += bytes;
remain_ -= bytes;
return s;
}
// VFALCO DEPRECATED Returns a copy
Blob
SerialIter::getVL()
{
return getRaw(getVLDataLength());
}
Buffer
SerialIter::getVLBuffer()
{
return getRawHelper<Buffer>(getVLDataLength());
}
} // namespace xrpl
+1 -1
View File
@@ -95,7 +95,7 @@ verify(STObject const& st, HashPrefix const& prefix, PublicKey const& pk, SF_VL
Serializer ss;
ss.add32(prefix);
st.addWithoutSigningFields(ss);
return verify(pk, ss.slice(), *sig);
return verify(pk, Slice(ss.data(), ss.size()), Slice(sig->data(), sig->size()));
}
// Questions regarding buildMultiSigningData:
+24 -3
View File
@@ -35,7 +35,7 @@ constexpr std::size_t kIsoCodeLength = 3;
std::string
to_string(Currency const& currency)
{
if (currency == xrpCurrency())
if (currency == beast::kZero)
return systemCurrencyCode();
if (currency == noCurrency())
@@ -62,7 +62,7 @@ to_string(Currency const& currency)
}
bool
toCurrency(Currency& currency, std::string_view code)
toCurrency(Currency& currency, std::string const& code)
{
if (code.empty() || code == systemCurrencyCode())
{
@@ -87,7 +87,7 @@ toCurrency(Currency& currency, std::string_view code)
}
Currency
toCurrency(std::string_view code)
toCurrency(std::string const& code)
{
Currency currency;
if (!toCurrency(currency, code))
@@ -95,4 +95,25 @@ toCurrency(std::string_view code)
return currency;
}
Currency const&
xrpCurrency()
{
static Currency const kCurrency(beast::kZero);
return kCurrency;
}
Currency const&
noCurrency()
{
static Currency const kCurrency(1);
return kCurrency;
}
Currency const&
badCurrency()
{
static Currency const kCurrency(0x5852500000000000);
return kCurrency;
}
} // namespace xrpl
+2 -2
View File
@@ -209,7 +209,7 @@ AttestationClaim::message(
Serializer s;
o.add(s);
return s.takeData();
return std::move(s.modData());
}
std::vector<std::uint8_t>
@@ -346,7 +346,7 @@ AttestationCreateAccount::message(
Serializer s;
o.add(s);
return s.takeData();
return std::move(s.modData());
}
std::vector<std::uint8_t>
+4 -5
View File
@@ -27,7 +27,6 @@
#include <soci/sqlite3/soci-sqlite3.h> // IWYU pragma: keep
#include <format>
#include <memory>
namespace xrpl {
@@ -41,8 +40,8 @@ getSociSqliteInit(std::string const& name, std::string const& dir, std::string c
{
if (name.empty())
{
Throw<std::runtime_error>(std::format(
"Sqlite databases must specify a dir and a name. Name: {} Dir: {}", name, dir));
Throw<std::runtime_error>(
"Sqlite databases must specify a dir and a name. Name: " + name + " Dir: " + dir);
}
std::filesystem::path file(dir);
if (std::filesystem::is_directory(file))
@@ -57,7 +56,7 @@ getSociInit(BasicConfig const& config, std::string const& dbName)
auto const backendName = get(section, Keys::kBackend, "sqlite");
if (backendName != "sqlite")
Throw<std::runtime_error>(std::format("Unsupported soci backend: {}", backendName));
Throw<std::runtime_error>("Unsupported soci backend: " + backendName);
auto const path = config.legacy(Sections::kDatabasePath);
auto const ext = dbName == "validators" || dbName == "peerfinder" ? ".sqlite" : ".db";
@@ -102,7 +101,7 @@ open(soci::session& s, std::string const& beName, std::string const& connectionS
}
else
{
Throw<std::runtime_error>(std::format("Unsupported soci backend: {}", beName));
Throw<std::runtime_error>("Unsupported soci backend: " + beName);
}
}
+9 -6
View File
@@ -26,7 +26,6 @@
#include <cstddef>
#include <cstdint>
#include <exception>
#include <format>
#include <functional>
#include <limits>
#include <mutex>
@@ -49,7 +48,7 @@ to_string(Manifest const& m)
return "Revocation Manifest " + mk;
if (!m.signingKey)
Throw<std::runtime_error>(std::format("No SigningKey in manifest {}", mk));
Throw<std::runtime_error>("No SigningKey in manifest " + mk);
return "Manifest " + mk + " (" + std::to_string(m.sequence) + ": " +
toBase58(TokenType::NodePublic, *m.signingKey) + ")";
@@ -197,7 +196,9 @@ logMftAct(
bool
Manifest::verify() const
{
STObject const st(SerialIter{makeSlice(serialized)}, sfGeneric);
STObject st(sfGeneric);
SerialIter sit(serialized.data(), serialized.size());
st.set(sit);
// The manifest must either have a signing key or be revoked. This check
// prevents us from accessing an unseated signingKey in the next check.
@@ -215,7 +216,9 @@ Manifest::verify() const
UInt256
Manifest::hash() const
{
STObject const st(SerialIter{makeSlice(serialized)}, sfGeneric);
STObject st(sfGeneric);
SerialIter sit(serialized.data(), serialized.size());
st.set(sit);
return st.getHash(HashPrefix::Manifest);
}
@@ -241,7 +244,7 @@ std::optional<Blob>
Manifest::getSignature() const
{
STObject st(sfGeneric);
SerialIter sit(makeSlice(serialized));
SerialIter sit(serialized.data(), serialized.size());
st.set(sit);
if (!get(st, sfSignature))
return std::nullopt;
@@ -252,7 +255,7 @@ Blob
Manifest::getMasterSignature() const
{
STObject st(sfGeneric);
SerialIter sit(makeSlice(serialized));
SerialIter sit(serialized.data(), serialized.size());
st.set(sit);
return st.getFieldVL(sfMasterSignature);
}
+1 -1
View File
@@ -901,7 +901,7 @@ SHAMap::writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const
Serializer s;
node->serializeWithPrefix(s);
f_.db().store(t, s.takeData(), node->getHash().asUInt256(), ledgerSeq_);
f_.db().store(t, std::move(s.modData()), node->getHash().asUInt256(), ledgerSeq_);
return node;
}
+3 -3
View File
@@ -135,7 +135,7 @@ SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashVali
for (auto i = 0u; i < kBranchFactor; ++i)
{
hashes[i].asUInt256() = si.get256();
hashes[i].asUInt256() = si.getBitString<256>();
if (hashes[i].isNonZero())
ret->isBranch_ |= (1u << i);
@@ -173,7 +173,7 @@ SHAMapInnerNode::makeCompressedInner(Slice data)
while (!si.empty())
{
auto const hash = si.get256();
auto const hash = si.getBitString<256>();
auto const pos = si.get8();
if (pos >= kBranchFactor)
@@ -231,7 +231,7 @@ SHAMapInnerNode::serializeForWire(Serializer& s) const
auto hashes = hashesAndChildren_.getHashes();
iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
s.addBitString(hashes[indexNum].asUInt256());
s.add8(checkedCast<std::uint8_t>(branchNum));
s.add8(branchNum);
});
s.add8(kWireTypeCompressedInner);
}
+9 -13
View File
@@ -2,16 +2,13 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/shamap/SHAMap.h>
#include <algorithm>
#include <cstddef>
#include <format>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
@@ -88,8 +85,8 @@ SHAMapNodeID::getRawString() const
{
Serializer s(33);
s.addBitString(id_);
s.add8(checkedCast<std::uint8_t>(depth_));
return std::string(static_cast<char const*>(s.data()), s.size());
s.add8(depth_);
return s.getString();
}
SHAMapNodeID
@@ -110,10 +107,10 @@ SHAMapNodeID::getChildNodeID(unsigned int branch) const
depth_ <= SHAMap::kLeafDepth, "xrpl::SHAMapNodeID::getChildNodeID : maximum leaf depth");
if (depth_ >= SHAMap::kLeafDepth)
Throw<std::logic_error>(std::format("Request for child node ID of {}", to_string(*this)));
Throw<std::logic_error>("Request for child node ID of " + to_string(*this));
if (!isPrefixOf(id_))
Throw<std::logic_error>(std::format("Incorrect mask for {}", to_string(*this)));
Throw<std::logic_error>("Incorrect mask for " + to_string(*this));
SHAMapNodeID node{depth_ + 1, id_};
node.id_.begin()[depth_ / 2] |= ((depth_ & 1) != 0u) ? branch : (branch << 4);
@@ -131,15 +128,14 @@ deserializeSHAMapNodeID(void const* data, std::size_t size)
{
std::optional<SHAMapNodeID> ret;
if (size == UInt256::size() + 1)
if (size == 33)
{
std::span const bytes{static_cast<unsigned char const*>(data), size};
if (unsigned int const depth = bytes[UInt256::size()]; depth <= SHAMap::kLeafDepth)
unsigned int const depth = *(static_cast<unsigned char const*>(data) + 32);
if (depth <= SHAMap::kLeafDepth)
{
// Reject a serialized ID carrying bits below its own depth. Checked before
// constructing the node, since the constructor asserts that same property.
if (UInt256 const id{bytes.first<UInt256::size()>()}; isPrefixOfAtDepth(id, depth, id))
// constructing, since the constructor asserts that same property.
if (auto const id = UInt256::fromVoid(data); isPrefixOfAtDepth(id, depth, id))
ret.emplace(depth, id);
}
}
+12 -11
View File
@@ -461,18 +461,17 @@ SHAMap::getNodeFat(
std::stack<std::tuple<SHAMapTreeNode*, SHAMapNodeID, std::uint32_t>> stack;
stack.emplace(node, nodeID, depth);
Serializer s(8192);
while (!stack.empty())
{
std::tie(node, nodeID, depth) = stack.top();
stack.pop();
// Add this node to the reply
data.emplace_back(nodeID, node->isLeaf(), [&node]() {
Serializer s(1024);
node->serializeForWire(s);
return s.takeData();
}());
s.erase();
node->serializeForWire(s);
data.emplace_back(nodeID, node->isLeaf(), s.getData());
if (node->isInner())
{
@@ -500,9 +499,9 @@ SHAMap::getNodeFat(
else if (childNode->isInner() || fatLeaves)
{
// Just include this node
Serializer s(1024);
s.erase();
childNode->serializeForWire(s);
data.emplace_back(childID, childNode->isLeaf(), s.takeData());
data.emplace_back(childID, childNode->isLeaf(), s.getData());
}
}
}
@@ -555,7 +554,8 @@ SHAMap::addRootNode(
{
Serializer s;
root_->serializeWithPrefix(s);
filter->gotNode(false, root_->getHash(), ledgerSeq_, s.takeData(), root_->getType());
filter->gotNode(
false, root_->getHash(), ledgerSeq_, std::move(s.modData()), root_->getType());
}
return SHAMapAddNode::useful();
@@ -646,7 +646,8 @@ SHAMap::addKnownNode(
{
Serializer s;
treeNode->serializeWithPrefix(s);
filter->gotNode(false, childHash, ledgerSeq_, s.takeData(), treeNode->getType());
filter->gotNode(
false, childHash, ledgerSeq_, std::move(s.modData()), treeNode->getType());
}
return SHAMapAddNode::useful();
@@ -814,7 +815,7 @@ SHAMap::getProofPath(UInt256 const& key) const
{
Serializer s;
stack.top().first->serializeForWire(s);
path.emplace_back(s.takeData());
path.emplace_back(std::move(s.modData()));
stack.pop();
}
+75 -37
View File
@@ -18,7 +18,6 @@
#include <xrpl/shamap/SHAMapTxPlusMetaLeafNode.h>
#include <cstdint>
#include <format>
#include <stdexcept>
#include <string>
#include <type_traits>
@@ -26,36 +25,14 @@
namespace xrpl {
namespace {
// A leaf node is serialized as the item data followed by a 32-byte tag.
// Returns {item, tag}. Throws if there isn't room for the tag.
std::pair<Slice, UInt256>
splitTag(Slice data, char const* nodeType)
{
if (data.size() < kMinShaMapItemBytes + UInt256::kBytes)
{
Throw<std::runtime_error>(std::format(
"Short {} node: {} bytes (minimum {} required)",
nodeType,
data.size(),
(kMinShaMapItemBytes + UInt256::kBytes)));
}
auto const itemSize = data.size() - UInt256::kBytes;
return {data.substr(0, itemSize), SerialIter{data + itemSize}.get256()};
}
} // namespace
SHAMapTreeNodePtr
SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid)
{
if (data.size() < kMinShaMapItemBytes)
{
Throw<std::runtime_error>(std::format(
"Short TXN node: {} bytes (minimum {} required)", data.size(), kMinShaMapItemBytes));
Throw<std::runtime_error>(
"Short TXN node: " + std::to_string(data.size()) + " bytes (minimum " +
std::to_string(kMinShaMapItemBytes) + " required)");
}
auto item = makeShamapitem(sha512Half(HashPrefix::TransactionId, data), data);
@@ -69,26 +46,82 @@ SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashVal
SHAMapTreeNodePtr
SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid)
{
auto const [item, tag] = splitTag(data, "TXN+MD");
Serializer s(data.data(), data.size());
UInt256 tag;
if (s.size() < tag.kBytes)
{
Throw<std::runtime_error>(
"Short TXN+MD node: " + std::to_string(s.size()) + " bytes (minimum " +
std::to_string(tag.kBytes) + " required for tag)");
}
// FIXME: improve this interface so that the above check isn't needed
if (!s.getBitString(tag, s.size() - tag.kBytes))
{
Throw<std::out_of_range>(
"Short TXN+MD node: failed to read tag at offset " +
std::to_string(s.size() - tag.kBytes));
}
s.chop(tag.kBytes);
if (s.size() < kMinShaMapItemBytes)
{
Throw<std::runtime_error>(
"Short TXN+MD node: " + std::to_string(s.size()) +
" bytes after tag removal (minimum " + std::to_string(kMinShaMapItemBytes) +
" required)");
}
auto item = makeShamapitem(tag, s.slice());
if (hashValid)
return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(makeShamapitem(tag, item), 0, hash);
return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(std::move(item), 0, hash);
return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(makeShamapitem(tag, item), 0);
return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(std::move(item), 0);
}
SHAMapTreeNodePtr
SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid)
{
auto const [item, tag] = splitTag(data, "AS");
Serializer s(data.data(), data.size());
UInt256 tag;
if (s.size() < tag.kBytes)
{
Throw<std::runtime_error>(
"Short AS node: " + std::to_string(s.size()) + " bytes (minimum " +
std::to_string(tag.kBytes) + " required for tag)");
}
// FIXME: improve this interface so that the above check isn't needed
if (!s.getBitString(tag, s.size() - tag.kBytes))
{
Throw<std::out_of_range>(
"Short AS node: failed to read tag at offset " + std::to_string(s.size() - tag.kBytes));
}
s.chop(tag.kBytes);
if (tag.isZero())
Throw<std::runtime_error>("Invalid AS node");
if (hashValid)
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(makeShamapitem(tag, item), 0, hash);
if (s.size() < kMinShaMapItemBytes)
{
Throw<std::runtime_error>(
"Short AS node: " + std::to_string(s.size()) + " bytes after tag removal (minimum " +
std::to_string(kMinShaMapItemBytes) + " required)");
}
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(makeShamapitem(tag, item), 0);
auto item = makeShamapitem(tag, s.slice());
if (hashValid)
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(std::move(item), 0, hash);
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(std::move(item), 0);
}
SHAMapTreeNodePtr
@@ -119,7 +152,7 @@ SHAMapTreeNode::makeFromWire(Slice rawNode)
if (type == kWireTypeTransactionWithMeta)
return makeTransactionWithMeta(rawNode, hash, hashValid);
Throw<std::runtime_error>(std::format("wire: Unknown type ({})", type));
Throw<std::runtime_error>("wire: Unknown type (" + std::to_string(type) + ")");
}
SHAMapTreeNodePtr
@@ -128,7 +161,11 @@ SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash)
if (rawNode.size() < 4)
Throw<std::runtime_error>("prefix: short node");
auto const type = safeCast<HashPrefix>(SerialIter{rawNode}.get32());
// FIXME: Use SerialIter::get32?
// Extract the prefix
auto const type = safeCast<HashPrefix>(
(safeCast<std::uint32_t>(rawNode[0]) << 24) + (safeCast<std::uint32_t>(rawNode[1]) << 16) +
(safeCast<std::uint32_t>(rawNode[2]) << 8) + (safeCast<std::uint32_t>(rawNode[3])));
rawNode.removePrefix(4);
@@ -146,8 +183,9 @@ SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash)
if (type == HashPrefix::TxNode)
return makeTransactionWithMeta(rawNode, hash, hashValid);
Throw<std::runtime_error>(std::format(
"prefix: unknown type ({})", safeCast<std::underlying_type_t<HashPrefix>>(type)));
Throw<std::runtime_error>(
"prefix: unknown type (" +
std::to_string(safeCast<std::underlying_type_t<HashPrefix>>(type)) + ")");
}
std::string
+3 -4
View File
@@ -38,7 +38,7 @@ ApplyContext::ApplyContext(
, parentBatchId_(parentBatchId)
{
XRPL_ASSERT(
parentBatchId.has_value() == ((flags_ & ApplyFlags::Batch) == ApplyFlags::Batch),
parentBatchId.has_value() == ((flags_ & TapBatch) == TapBatch),
"Parent Batch ID should be set if batch apply flag is set");
view_.emplace(&base_, flags_);
}
@@ -53,14 +53,13 @@ std::optional<TxMeta>
ApplyContext::apply(TER ter)
{
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
return view_->apply(
base_, tx, ter, parentBatchId_, (flags_ & ApplyFlags::DryRun) != ApplyFlags::None, journal);
return view_->apply(base_, tx, ter, parentBatchId_, (flags_ & TapDryRun) != 0u, journal);
}
void
ApplyContext::addOrderBook(Book const& book)
{
if ((flags_ & ApplyFlags::DryRun) == ApplyFlags::None)
if ((flags_ & TapDryRun) == TapNone)
registry.get().getOrderBookDB().addOrderBook(book);
}
Loaded 100 of 307 files, more files were not shown because too many files have changed in this diff. Show more