Compare commits

...
Author SHA1 Message Date
Nik Bougalis ef0635d26c Simplify and harden Serializer and SerialIter:
Reduce both classes to a small, header-only interface built on Slice.

Serializer becomes a strictly append-only buffer and SerialIter a
strictly forward-only reader; all deprecated and duplicated paths
are removed and data copying is entirely eliminated. But the wire
format, you'll be happy to know, remains unchanged.

Serializer:
- Make the buffer append-only. Remove modData, resize, chop, mutable
  begin/end and getDataPtr, the (void const*, size_t) constructor, and
  the read-side helpers get8, getInteger and getBitString.
- Collapse the accessors (peekData, getData, getDataPtr, getDataLength,
  getLength, getString, begin/end, operator==) into slice(), data()
  and size().
- Add takeData(), which moves the buffer out. Use it wherever callers
  copied via getData/peekData or reached in with std::move(modData()).
- addRaw and addVL take a Slice only; the Blob, (void const*, int),
  Serializer and iterator-range overloads are removed. Both throw if
  the input aliases the serializer's own storage.
- Funnel every append through one private write(), which reserves for
  the combined size of its parts before inserting. An add that throws
  leaves the serializer unchanged.
- Constrain add8/add16/add32/add64 on the exact argument type: no
  implicit conversions, no bool or character types, and the width must
  match the function name. Call sites that relied on silent narrowing
  now cast explicitly.
- The add functions return void rather than the offset written to.
- Replace getSHA512Half() with a hash_append hidden friend; callers
  use sha512Half(s).
- Move the VL length-prefix constants into xrpl::detail as std::size_t
  and fold header encoding into addVL. Remove addEncoded,
  encodeLengthLength, decodeLengthLength and decodeVLLength; decoding
  now lives in SerialIter::getVL.

SerialIter:
- Construct only, and explicitly, from a Slice. Remove the
  (void const*, size_t) and array-reference constructors. A null
  pointer with a non-zero size throws.
- Remove reset() and skip(), and with them the used_ counter. A reader
  cannot be rewound; copy it to retain a position.
- getVL() returns a Slice into the underlying buffer instead of a Blob
  copy. Remove getVLDataLength, getSlice, getRaw, getVLBuffer and
  getRawHelper.
- Route every read through a single bounds-checked consume(). Integers
  are decoded with std::bit_cast and std::byteswap, which drops the
  boost::endian dependency.
- Replace getBytesLeft() with size(), returning std::size_t, and add
  slice() for the unread remainder.
- getBitString<T> takes the BaseUInt type rather than <Bits, Tag>, so
  tagged types (AccountID, Currency, MPTID) are read directly.
- Add geti16() and mark the accessors [[nodiscard]].

Error reporting:
- VL length errors, on both the encode and the decode side, now throw
  std::runtime_error; they previously threw std::overflow_error.
- Short reads all throw std::runtime_error("short buffer") in place of
  the per-function messages.

Other changes:
- Slice: add overlap() global utility function, used for the aliasing
  checks above.
- sqlBlobLiteral takes a Slice.
- LedgerHeader: remove deserializePrefixedHeader. Its caller in
  InboundLedger skips the prefix locally; the prefix is still not
  validated, which is now marked with a FIXME.
- STAccount: deserialize from the getVL slice via AccountID::fromRaw
  and remove the (SField, Buffer) constructor.
- STVector256: serialize the whole vector with a single addVL, relying
  on contiguous storage and the absence of padding in UInt256.
- STInteger: static_assert that the type is serializable and dispatch
  add() on sizeof(Integer). Read STInteger<std::int32_t> with geti32().
  Log an unknown transaction result code as a number instead of a raw
  character.
- STTx: fold the four-argument getMetaSQL overload into its only
  caller.
- SHAMapTreeNode: the two leaf deserializers share a splitTag helper
  that slices off the trailing tag, replacing the copy into a
  Serializer followed by getBitString-at-offset and chop. The size
  check is done once, up front.
- SHAMap::getNodeFat: serialize each node into its own Serializer and
  move the buffer into the reply instead of copying out of a shared
  one.
- LedgerMaster::getCloseTimeByHash: parse the stored header with
  deserializeHeader instead of skipping hard-coded offsets.
- ConsensusTransSetSF::gotNode and LedgerReplayMsgHandler parse
  directly from the incoming bytes without an intermediate Serializer
  copy.
- TransactionSign: use sterilize() for the serialization round trip.

Tests:
- Port to the new interfaces; byte payloads become std::array so they
  can go through makeSlice.
- STAccount: the bad-size case now covers both a short and a long
  field, and fails if no exception is thrown. Previously it passed
  silently in that case.
2026-10-05 19:50:58 -07:00
Nik Bougalis 3c6279b735 Restructure the slab allocator
- Define size classes as template parameters, validated at compile
  time, allowing allocators to be constinit-constructed
- Move slab metadata out of the data buffer into a cache-line-aligned
  global pool
- Replace per-block mutexes with spinlocks, and add lock-free
  "parking" slots so most alloc/dealloc pairs avoid the lock entirely
- Release a slab's buffer when it fully drains; cache one spare
  buffer to avoid thrashing around slab boundaries
- Make the fallback path a compile-time policy (heap or none)
- Add sized deallocate() overloads that route directly to the
  owning size class
2026-10-05 19:42:53 -07:00
Nik Bougalis 55b265a8c2 Improve BaseUInt:
This commit reworks the BaseUInt class, which was originally taken from
Bitcoin and has since been heavily modified.

The internal limb type now widens from 32 to 64 bits when the requested
width is a multiple of 64. This halves the iteration count when working
on limbs; coupled with the use of add-with-carry intrinsics, the result
is better code generation and optimized use of processor resources.

The constructors have been reshuffled, constraining how an instance can
be initialized. As a result, many common initialization errors will now
fail at compile time instead of run time.

A `BaseUInt` can now be initialized with:

 - A byte sequence of fixed size, i.e an `std::array`, a C array, or a
   fixed-extent `std::span` of `unsigned char` or `std::byte`.
 - A string literal, formatted as a hexadecimal string with no leading
   0x. Malformed or improperly sized inputs are a compile-time error.
 - An unsigned integer. This constructor is mostly used for tests, and
   must execute at compile time.
 - A byte sequence whose length is not known at compile time, via the
   fromRaw() function which returns a seated `std::optional` only if
   the input is byte sequence is properly sized.

The runtime raw-pointer and `std::uint64_t` constructors are gone, as
are the container assignment operator and the the `fromVoid` and
`fromVoidChecked` functions.

Other changes:

 - The SFINAE-based container trait is replaced by the ByteCopySource
   and FixedByteRange concepts.
 - operator== and operator<=> are now hidden friends. The workaround
   in the old operator<=> is gone.
 - Efficient tag conversion is now possible.
 - `BaseUInt` is now `noexcept` and (almost) fully `constexpr`: every
   operation except byte access, hashing and text output is usable in
   constant evaluation.

Cleanups:

 - Most invocations of `stringIsUInt256Sized` in PeerImp are now gone
   and leverage `fromRaw`.
2026-10-05 19:42:52 -07:00
Nik Bougalis 02e40e7a00 Restructure the spinlock code:
- Add function-based spinlock API
- Improve comments
- Reduce bouncing with contested locks
2026-10-05 19:42:50 -07:00
Nik Bougalis f14395427e Modernize CountedObject infrastructure:
- Replace the lazy singleton with a constinit registry of per-type
  static counters. The `getInstance` method is removed.
- A concurrency bug that would corrupt the list of counters during
  insertion has been fixed.
- Debug asserts can detect incorrect accounting that can result in
  counter under- or overflow.
- `CountedObject` can only be used as a CRTP base for its own type
  parameter.
- Track the current and maximum values of counter. This results in
  a change in the output of `get_counts`, with individual counters
  now reported as sub-objects of a `counters` object.
2026-10-05 19:42:50 -07:00
Nik Bougalis b707bac74f Improve AccountID base58 conversion cache:
The previous cache guarded a configurably-sized vector with 64
packed spinlocks. Two aspects of that setup were not worth the
cost:

- Callers were forced to perform atomic RMW operations on a
  single word which was shared by all 64 locks; this caused
  the lock line to bounce between cores on every lookup.

- The configurable size and runtime initialization resulted
  in complexity that did not yield a meaningful improvement
  in performance.

This commit ditches the 64 packed spinlocks, replacing them
with per-entry sequence locks, allowing readers on the fast
path to avoid performing any stores at all.

Cache entries are now carefully sized and aligned to fit into
a typical cache line, which helps avoid false sharing and any
unnecessary coherence traffic.

The cache is now a fixed array of 65,536 entries. The size was
chosen to balance capacity against memory overhead and to keep
the indexing operation fast and simple: since AccountID values
are uniformly distributed, two bytes can serve directly as the
index without requiring additional hashing.

Note: The reader path will perform unsynchronized reads against
      concurrent writes; the sequence lock protocol detects and
      discards any data observed mid-update, so these races are
      benign. A sanitizer like TSAN can still flag them and the
      warnings are expected. A compile-time knob to disable the
      cache is available, if necessary.
2026-10-05 19:42:50 -07:00
Nik Bougalis da2b5d5eaf Remove beast::typeName
Replace Beast's demangling wrapper (originally by @HowardHinnant)
with calls to `boost::core::type_name`, eliminating the low-level
dependency and manual memory management from the codebase.
2026-10-05 19:42:50 -07:00
Nik Bougalis 4de4902200 Rework and clean up the safe_cast framework:
The SafeToCast concept transposed its signedness clause: it was written in
<Dest, Src> order but it declared <Src, Dest>. This introduced what can be
best described as a polarity bug. As a result:

* Signed-to-wider-unsigned casts were wrongly deemed safe.
* Unsigned-to-wider-signed casts were wrongly deemed unsafe.

The root cause was drift caused by the safety condition being repeated in
the concept and again as a static_assert in safeCast. The checking is now
done only in the concept and is expressed in terms of range coverage, and
not via a sizeof/signedness proxy. This change also improves the handling
of bool (which previously compiled despite truncating) and eliminates the
platform-dependent accept/reject behavior for same-size types.

Additional fixes:

* The pointer form of safeDowncast now performs the same static_cast
  in all builds. The dynamic_cast check is now entirely contained in
  the XRPL_ASSERT.
* Extended integer types wider than intmax_t (e.g. __int128) are now
  excluded from SafeToCast instead of silently misevaluating through
  wrapped bounds; they remain expressible via unsafeCast.

Cleanups:

* Remove the single-parameter enum overload of safeCast: it had an
  explicitly-specified template argument binding to Src, where all
  sibling overload bound to Dest, silently yielding the underlying
  type from expressions that read as casts *to* an enum.
* Add checkedCast for conversions guarded by runtime bounds checks
  where safety varies per instantiation.
* Constrain safeDowncast to polymorphic sources and genuine public
  unambiguous downcasts, rejecting upcasts and unrelated types.
* Route enum conversions through std::to_underlying; clarify the
  unsafeCast/checkedCast documentation split.
2026-10-05 19:42:49 -07:00
Nik Bougalis 2ea1f09dcb Modernize beast::Zero for C++20
- Replace the twelve comparison operators with two
  `constexpr`-capable replacements: `==` and `<=>`
  and allow the compiler to synthesize the rest.
- Constrain `==` and `<=>` using concepts and mark
  them as conditionally noexcept).
- Remove the detail::zero_helper indirection which
  did not do what its comment claimed.
2026-10-05 19:30:38 -07:00
Nik Bougalis 68b4b894de Make ApplyFlags a scoped enumeration:
Convert ApplyFlags to a scoped enum, drop the now-redundant `Tap`
prefix from its enumerators and replace the hand-written bitwise
operators by opting in to enum_bitops.

No functional change.
2026-10-05 19:30:38 -07:00
Nik Bougalis defd83e5a6 Make STPathElement::Type a scoped enumeration:
`STPathElement` stored its type as an `unsigned int`, returned it
as a `std::uint32_t`, while the flags themselves were an unscoped
`enum` (i.e. an `int`). The type field has always been serialized
as a single byte.

This commit turns `STPathElement::Type` into a scoped `enum` with
a fixed sized that matches the serialized width, and opts it into
the bitwise operators from `enum_bitops.h`:

- Store the type as `Type`, and use `Type` for the constructor
  parameter and the return type of `getNodeType()`.
- Keep the existing spellings such as `STPathElement::TypeAccount`
  valid through `using enum`.
- Replace raw mask tests in the serializer, JSON output, strand
  construction and pathfinder with the existing predicates.
- Validate the type byte once when deserializing, before any field
  is read, and restructure the loop around that.
- Make `operator==` a hidden friend.
- Mark the trivial accessors `noexcept`.

No functional changes are intended. The one observable difference
is the rejection error generated for a malformed path element: if
the currency and MPT bits are both set and the input is truncated
within the account field, the invalid bit combination is detected
first; previously the short read would be detected. Such elements
are rejected either way.
2026-10-05 19:30:38 -07:00
Nik Bougalis 452e31a05f Introduce opt-in bitwise operators for scoped enumerations:
Scoped enumerations do not support bitwise operators, so using an
`enum class` as a set of flags requires extensive casting at call
sites or a hand-written set of operators for each type.

Add `enum_bitops.h`, which provides `&`, `|`, `^`, `~` along with
the compound assignment forms for any scoped enumeration that has
an unsigned underlying type and is opted in:

    enum class Flags : std::uint8_t { a = 1, b = 2 };

    // Opt into bitwise operations
    template <>
    struct enum_bitops::OptIn<Flags> : std::true_type {};

All synthesized operators are constexpr and noexcept, and preserve
the enumeration type.
2026-10-05 19:30:37 -07:00
Mayukha Vadari 1798262d7a refactor: Use LedgerHashesEntry everywhere (#8369) 2026-10-05 18:15:02 +00:00
Mayukha Vadari cd633b9ac9 refactor: Use EscrowEntry everywhere (#8354) 2026-10-05 17:51:18 +00:00
Ayaz Salikhov 5c5f7d315a chore: Update nix flake file (#8479) 2026-10-05 16:58:41 +00:00
Mayukha Vadari 0f7493ce50 refactor: Use FeeSettingsEntry everywhere (#8370) 2026-10-05 16:56:41 +00:00
Harshit Gupta 40f61f828a fix: Add string type validation for channel_id and signature (#7582) 2026-10-05 16:55:14 +00:00
Mayukha Vadari 108277f4b7 test: Migrate three beast suites from beast::unit_test to gtest (#7993) 2026-10-05 15:28:05 +00:00
265 changed files with 5456 additions and 5171 deletions

No files matched your search

+5
View File
@@ -30,6 +30,11 @@ 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)) - `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 ## 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. 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. // builder's STObject and the wrapper's SLE.
TEST(${name}Tests, BuilderSettersRoundTrip) TEST(${name}Tests, BuilderSettersRoundTrip)
{ {
UInt256 const index{1u}; constexpr UInt256 index{1};
% for field in fields: % for field in fields:
auto const ${field["paramName"]}Value = ${canonical_expr(field)}; 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()). // from that SLE, build a new wrapper, and verify all fields (and validate()).
TEST(${name}Tests, BuilderFromSleRoundTrip) TEST(${name}Tests, BuilderFromSleRoundTrip)
{ {
UInt256 const index{2u}; constexpr UInt256 index{2};
% for field in fields: % for field in fields:
auto const ${field["paramName"]}Value = ${canonical_expr(field)}; 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. // 3) Verify wrapper throws when constructed from wrong ledger entry type.
TEST(${name}Tests, WrapperThrowsOnWrongEntryType) TEST(${name}Tests, WrapperThrowsOnWrongEntryType)
{ {
UInt256 const index{3u}; constexpr UInt256 index{3};
// Build a valid ledger entry of a different type // Build a valid ledger entry of a different type
// Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq // 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. // 4) Verify builder throws when constructed from wrong ledger entry type.
TEST(${name}Tests, BuilderThrowsOnWrongEntryType) TEST(${name}Tests, BuilderThrowsOnWrongEntryType)
{ {
UInt256 const index{4u}; constexpr UInt256 index{4};
// Build a valid ledger entry of a different type // Build a valid ledger entry of a different type
% if wrong_le_include == "Ticket": % 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. // 5) Build with only required fields and verify optional fields return nullopt.
TEST(${name}Tests, OptionalFieldsReturnNullopt) TEST(${name}Tests, OptionalFieldsReturnNullopt)
{ {
UInt256 const index{3u}; constexpr UInt256 index{3};
% for field in required_fields: % for field in required_fields:
auto const ${field["paramName"]}Value = ${canonical_expr(field)}; auto const ${field["paramName"]}Value = ${canonical_expr(field)};
Generated
+6 -6
View File
@@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1781173989, "lastModified": 1791130267,
"narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=", "narHash": "sha256-1sjwcQMcgAbmiip/VivBSiK8p2bTkQGOvCuO7vnwfx4=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "8c91a71d13451abc40eb9dae8910f972f979852f", "rev": "9013764fcc0ea99fa16cf7aa7decf8a5c3889dd2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -47,11 +47,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1784611586, "lastModified": 1791189933,
"narHash": "sha256-OfqgY+0hp/zseZB7uyH0U8kIDPS4scZZCyAurEplvG0=", "narHash": "sha256-Y8eN7ki0Urx4Ojf4w1xSpWg8uk/3lJqK+E0Oth77rH0=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "14f58845249f3552a89b07772626b8d3c632fa86", "rev": "e60029353d0c48d216bc4b065168ccd4079c166f",
"type": "github" "type": "github"
}, },
"original": { "original": {
+146 -55
View File
@@ -1,29 +1,31 @@
#pragma once #pragma once
#include <xrpl/beast/type_name.h> #include <xrpl/beast/utility/instrumentation.h>
#include <boost/core/type_name.hpp>
#include <algorithm>
#include <atomic> #include <atomic>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <string> #include <string>
#include <type_traits>
#include <utility> #include <utility>
#include <vector>
namespace xrpl { namespace xrpl {
/** /**
* Manages all counted object types. * 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 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: public:
/** /**
* Implementation for @ref CountedObject. * Implementation for @ref CountedObject.
@@ -33,68 +35,147 @@ public:
class Counter class Counter
{ {
public: public:
Counter(std::string name) noexcept : name_(std::move(name)), count_(0) Counter(std::string name) noexcept;
{
// Insert ourselves at the front of the lock-free linked list
CountedObjects& instance = CountedObjects::getInstance();
Counter* head = nullptr;
do // Counters are intrusive list nodes whose addresses are published
{ // in the registry; they must never be copied or moved. The atomic
head = instance.head_.load(); // members already force this, but we make it explicit.
next_ = head; Counter(Counter const&) = delete;
} while (instance.head_.exchange(this) != head); Counter&
operator=(Counter const&) = delete;
Counter(Counter&&) = delete;
Counter&
operator=(Counter&&) = delete;
++instance.count_; std::uint32_t
}
~Counter() noexcept = default;
int
increment() noexcept increment() noexcept
{ {
return ++count_; 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))
{
} }
int return newCount;
}
std::uint32_t
decrement() noexcept decrement() noexcept
{ {
return --count_; auto const prev = count_.fetch_sub(1, std::memory_order::relaxed);
XRPL_ASSERT(prev != 0, "xrpl::CountedObjects::Counter::decrement : no underflow");
return prev - 1;
} }
[[nodiscard]] int [[nodiscard]] std::uint32_t
getCount() const noexcept count() const noexcept
{ {
return count_.load(); return count_.load(std::memory_order::relaxed);
} }
[[nodiscard]] Counter* [[nodiscard]] std::uint32_t
getNext() const noexcept max() const noexcept
{ {
return next_; return std::max(
count_.load(std::memory_order::relaxed),
maxCount_.load(std::memory_order::relaxed));
} }
[[nodiscard]] std::string const& [[nodiscard]] std::string const&
getName() const noexcept name() const noexcept
{ {
return name_; return name_;
} }
private: 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::string const name_;
std::atomic<int> count_;
Counter* next_;
}; };
private: class Iterator
CountedObjects() noexcept; {
~CountedObjects() noexcept = default; 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: private:
std::atomic<int> count_; Counter* current_;
std::atomic<Counter*> head_;
}; };
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;
};
/** 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))
;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** /**
@@ -103,27 +184,37 @@ private:
* Derived classes have their instances counted automatically. This is used * Derived classes have their instances counted automatically. This is used
* for reporting purposes. * 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 * @ingroup basics
*/ */
template <class Object> template <class Object>
requires std::is_class_v<Object>
class CountedObject class CountedObject
{ {
private: static inline CountedObjects::Counter counter{boost::core::type_name<Object>()};
static auto&
getCounter() noexcept
{
static CountedObjects::Counter kC{beast::typeName<Object>()};
return kC;
}
CountedObject() noexcept CountedObject() noexcept
{ {
getCounter().increment(); counter.increment();
} }
CountedObject(CountedObject const&) noexcept CountedObject(CountedObject const&) noexcept
{ {
getCounter().increment(); counter.increment();
} }
CountedObject& CountedObject&
@@ -132,7 +223,7 @@ private:
public: public:
~CountedObject() noexcept ~CountedObject() noexcept
{ {
getCounter().decrement(); counter.decrement();
} }
friend Object; friend Object;
File diff suppressed because it is too large. Load diff
+23
View File
@@ -253,4 +253,27 @@ makeSlice(std::basic_string_view<char, Traits> s)
return Slice(s.data(), s.size()); 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 } // namespace xrpl
+2 -1
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <xrpl/basics/Blob.h> #include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <boost/utility/string_view.hpp> #include <boost/utility/string_view.hpp>
@@ -28,7 +29,7 @@ namespace xrpl {
* @return The input, encoded as a blob literal. * @return The input, encoded as a blob literal.
*/ */
std::string std::string
sqlBlobLiteral(Blob const& blob); sqlBlobLiteral(Slice blob);
namespace detail { namespace detail {
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -1,7 +1,8 @@
#pragma once #pragma once
#include <xrpl/basics/sanitizers.h> #include <xrpl/basics/sanitizers.h>
#include <xrpl/beast/type_name.h>
#include <boost/core/type_name.hpp>
#include <exception> #include <exception>
#include <string> #include <string>
@@ -55,7 +56,7 @@ Throw(Args&&... args)
std::is_convertible_v<E*, std::exception*>, "Exception must derive from std::exception."); std::is_convertible_v<E*, std::exception*>, "Exception must derive from std::exception.");
E e(std::forward<Args>(args)...); E e(std::forward<Args>(args)...);
logThrow(std::string("Throwing exception of type " + beast::typeName<E>() + ": ") + e.what()); logThrow("Throwing exception of type " + boost::core::type_name<E>() + ": " + e.what());
throw std::move(e); throw std::move(e);
} }
+200
View File
@@ -0,0 +1,200 @@
#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
+152 -47
View File
@@ -2,107 +2,212 @@
#include <xrpl/beast/utility/instrumentation.h> // IWYU pragma: keep #include <xrpl/beast/utility/instrumentation.h> // IWYU pragma: keep
#include <concepts>
#include <cstdint>
#include <limits>
#include <memory>
#include <type_traits> #include <type_traits>
#include <utility>
namespace xrpl { namespace xrpl {
// safe_cast adds compile-time checks to a static_cast to ensure that /** Every value of @p Src can be represented by @p Dest.
// the destination can hold all values of the source. This is particularly
// handy when the source or destination is an enumeration type.
template <class Src, class Dest> Given two integral types, the cast is safe when the destination can
concept SafeToCast = (std::is_integral_v<Src> && std::is_integral_v<Dest>) && hold every possible value of the source.
(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));
template <class Dest, class 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>)
constexpr Dest constexpr Dest
safeCast(Src s) noexcept safeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
{ {
static_assert( static_assert(
std::is_signed_v<Dest> || std::is_unsigned_v<Src>, "Cannot cast signed to unsigned"); SafeToCast<Src, Dest>, "This cast is not value-preserving. Please use unsafeCast instead.");
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); return static_cast<Dest>(s);
} }
template <class Dest, class Src> template <typename Dest, typename Src>
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
constexpr Dest constexpr Dest
safeCast(Src s) noexcept 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)); return static_cast<Dest>(safeCast<std::underlying_type_t<Dest>>(s));
} }
template <class Dest, class Src> template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
constexpr Dest constexpr Dest
safeCast(Src s) noexcept safeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
{ {
return safeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s)); return safeCast<Dest>(std::to_underlying(s));
} }
/** @} */
// unsafe_cast explicitly flags a static_cast as not necessarily able to hold /** Integral-to-integral cast that is known to be narrowing or sign-erasing.
// 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.
template <class Dest, class Src> 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>)
constexpr Dest constexpr Dest
unsafeCast(Src s) noexcept unsafeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
{ {
static_assert( static_assert(
!SafeToCast<Src, Dest>, !SafeToCast<Src, Dest>, "This cast is value-preserving. Please use safeCast instead.");
"Only unsafe if casting signed to unsigned or "
"destination is too small");
return static_cast<Dest>(s); return static_cast<Dest>(s);
} }
template <class Dest, class Src> template <typename Dest, typename Src>
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
constexpr Dest constexpr Dest
unsafeCast(Src s) noexcept 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)); return static_cast<Dest>(unsafeCast<std::underlying_type_t<Dest>>(s));
} }
template <class Dest, class Src> template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
constexpr Dest constexpr Dest
unsafeCast(Src s) noexcept unsafeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
{ {
return unsafeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s)); 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));
}
/** @} */
/** 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> template <class Dest, class Src>
requires std::is_pointer_v<Dest> 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>))
inline Dest inline Dest
safeDowncast(Src* s) noexcept safeDowncast(Src* s) noexcept
{ {
#ifdef NDEBUG XRPL_ASSERT(
s == nullptr || dynamic_cast<Dest>(s) != nullptr, "xrpl::safeDowncast : valid downcast");
return static_cast<Dest>(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) 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> template <class Dest, class Src>
requires std::is_lvalue_reference_v<Dest> requires(
std::is_lvalue_reference_v<Dest> && std::is_polymorphic_v<Src> &&
std::derived_from<std::remove_reference_t<Dest>, Src>)
inline Dest inline Dest
safeDowncast(Src& s) noexcept safeDowncast(Src& s) noexcept
{ {
#ifndef NDEBUG return *safeDowncast<std::add_pointer_t<std::remove_reference_t<Dest>>>(std::addressof(s));
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 } // namespace xrpl
+208 -152
View File
@@ -4,88 +4,215 @@
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <boost/predef/architecture.h>
#include <atomic> #include <atomic>
#include <concepts>
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
#ifndef __aarch64__ #if BOOST_ARCH_X86
#include <immintrin.h> #include <immintrin.h>
#endif #endif
namespace xrpl { 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 { namespace detail {
/**
* Inform the processor that we are in a tight spin-wait loop. /** Inform the processor that we are in a tight spin-wait loop.
*
* Spinlocks caught in tight loops can result in the processor's pipeline Spinlocks caught in tight loops can result in the processor's pipeline
* filling up with comparison operations, resulting in a misprediction at filling up with comparison operations, resulting in a misprediction at
* the time the lock is finally acquired, necessitating pipeline flushing the time the lock is finally acquired, necessitating pipeline flushing
* which is ridiculously expensive and results in very high latency. which is ridiculously expensive and results in very high latency.
*
* This function instructs the processor to "pause" for some architecture This function instructs the processor to "pause" for some architecture
* specific amount of time, to prevent this. specific amount of time, to prevent this.
*/ */
inline void inline void
spinPause() noexcept spinPause() noexcept
{ {
#ifdef __aarch64__ #if BOOST_ARCH_X86
asm volatile("yield");
#else
_mm_pause(); _mm_pause();
#elif BOOST_ARCH_ARM
asm volatile("yield" ::: "memory");
#else
#error No implementation available for spinPause to use
#endif #endif
} }
} // namespace detail } // namespace detail
/** @{ */ //------------------------------------------------------------------------------
/**
* 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)
*/
/** /** Attempt to acquire a spinlock without blocking.
* A class that grabs a single packed spinlock from an atomic integer.
* @note This interface is primarily intended for one-shot attempts to
* This class meets the requirements of Lockable: acquire the lock. Avoid calling this function directly from a
* https://en.cppreference.com/w/cpp/named_req/Lockable 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.
*/ */
template <class 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.
*/
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>
class PackedSpinlock 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_; std::atomic<T>& bits_;
T const mask_; T const mask_;
@@ -94,120 +221,49 @@ public:
PackedSpinlock& PackedSpinlock&
operator=(PackedSpinlock const&) = delete; operator=(PackedSpinlock const&) = delete;
/** /** Construct a packed spinlock handle for a single bit.
* A single spinlock packed inside the specified atomic
* @param lock The atomic integer inside which the spinlock is packed.
* @param lock The atomic integer inside which the spinlock is packed. @param index The index of the spinlock this object acquires.
* @param index The index of the spinlock this object acquires.
* @note For performance reasons, you should strive to have `lock` be
* @note For performance reasons, you should strive to have `lock` be on a cacheline by itself.
* on a cacheline by itself.
*/ */
PackedSpinlock(std::atomic<T>& lock, int index) : bits_(lock), mask_(static_cast<T>(1) << index) PackedSpinlock(std::atomic<T>& lock, int index) noexcept
{ : bits_(lock), mask_([index]() {
XRPL_ASSERT( XRPL_ASSERT(
index >= 0 && (mask_ != 0), index >= 0 && index < std::numeric_limits<T>::digits,
"xrpl::PackedSpinlock::PackedSpinlock : valid index and mask"); "xrpl::PackedSpinlock::PackedSpinlock : valid index");
} return static_cast<T>(T{1} << index);
}())
[[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 [[nodiscard]] bool
try_lock() // NOLINT(readability-identifier-naming) try_lock() noexcept // NOLINT(readability-identifier-naming)
{ {
T expected = 0; return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
return lock_.compare_exchange_weak(
expected,
std::numeric_limits<T>::max(),
std::memory_order_acquire,
std::memory_order_relaxed);
} }
void void
lock() lock() noexcept
{ {
while (!try_lock()) do
{ {
// The use of relaxed memory ordering here is intentional and // The use of relaxed memory ordering here is intentional and
// serves to help reduce cache coherency traffic during times // serves to help reduce cache coherency traffic during times
// of contention by avoiding writes that would definitely not // of contention by avoiding writes that are unlikely to grab
// result in the lock being acquired. // the requested lock.
while (lock_.load(std::memory_order_relaxed) != 0) while ((bits_.load(std::memory_order::relaxed) & mask_) != 0)
detail::spinPause(); detail::spinPause();
} } while (!try_lock());
} }
void void
unlock() unlock() noexcept
{ {
lock_.store(0, std::memory_order_release); bits_.fetch_and(~mask_, std::memory_order::release);
} }
}; };
/** @} */
} // namespace xrpl } // namespace xrpl
-47
View File
@@ -1,47 +0,0 @@
#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
+36 -109
View File
@@ -2,26 +2,24 @@
#pragma once #pragma once
#include <compare>
#include <concepts>
namespace beast { namespace beast {
/** /**
* Zero allows classes to offer efficient comparisons to zero. * 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. * 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 * In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons
* like t > 1 or t != 1 do not. * like t > 1 or t != 1 do not. Comparing against kZero expresses exactly that,
* without constructing a T.
* *
* The class Zero allows such comparisons to be easily made. * A type T participates if either `t.signum()` or an unqualified `signum(t)`
* * found by argument-dependent lookup returns an integer that is negative,
* The comparing class T either needs to have a method called signum() which * zero, or positive according to the sign of t. Both `t == kZero` and
* returns a positive number, 0, or a negative; or there needs to be a signum * `kZero == t` work, as do all six relational operators in either order.
* function which resolves in the namespace which takes an instance of T and
* returns a positive, zero or negative number.
*/ */
struct Zero struct Zero
{ {
explicit Zero() = default; explicit Zero() = default;
@@ -30,115 +28,44 @@ struct Zero
inline constexpr Zero kZero{}; inline constexpr Zero kZero{};
/** /**
* Default implementation of signum calls the method on the class. * Default implementation of signum: call the member function.
*/ */
template <typename T> template <class T>
auto requires requires(T const& t) {
signum(T const& t) { t.signum() } -> std::integral;
}
[[nodiscard]] constexpr auto
signum(T const& t) noexcept(noexcept(t.signum()))
{ {
return t.signum(); return t.signum();
} }
namespace detail::zero_helper { namespace detail {
// For argument dependent lookup to function properly, calls to signum must /**
// be made from a namespace that does not include overloads of the function.. * 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.
*/
template <class T> template <class T>
auto concept HasSignum = requires(T const& t) {
callSignum(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)))
{ {
return signum(t); return signum(t) == 0;
} }
} // namespace detail::zero_helper template <detail::HasSignum T>
[[nodiscard]] constexpr std::strong_ordering
// Handle operators where T is on the left side using signum. operator<=>(T const& t, Zero) noexcept(noexcept(signum(t)))
template <typename T>
bool
operator==(T const& t, Zero)
{ {
return detail::zero_helper::callSignum(t) == 0; return signum(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 } // namespace beast
+12 -53
View File
@@ -1,7 +1,7 @@
#pragma once #pragma once
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/safe_cast.h> #include <xrpl/basics/enum_bitops.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/OwnerCounts.h> #include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/ledger/ReadView.h>
@@ -22,80 +22,39 @@
namespace xrpl { namespace xrpl {
// Bitwise flag enum with existing operator overloads enum class ApplyFlags : std::uint32_t {
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) None = 0x00,
enum ApplyFlags : std::uint32_t {
TapNone = 0x00,
// This is a local transaction with the // This is a local transaction with the
// fail_hard flag set. // fail_hard flag set.
TapFailHard = 0x10, FailHard = 0x10,
// This is not the transaction's last pass // This is not the transaction's last pass
// Transaction can be retried, soft failures allowed // Transaction can be retried, soft failures allowed
TapRetry = 0x20, Retry = 0x20,
// Transaction came from a privileged source // Transaction came from a privileged source
TapUnlimited = 0x400, Unlimited = 0x400,
// Transaction is executing as part of a batch // Transaction is executing as part of a batch
TapBatch = 0x800, Batch = 0x800,
// Transaction shouldn't be applied // Transaction shouldn't be applied
// Signatures shouldn't be checked // Signatures shouldn't be checked
TapDryRun = 0x1000, DryRun = 0x1000,
// Transaction is being preflighted as the payload of a // Transaction is being preflighted as the payload of a
// TransactionProposalCreate. Its signatures are collected on-ledger // TransactionProposalCreate. Its signatures are collected on-ledger
// afterward, so signature-presence checks (e.g. Batch signer matching) // afterward, so signature-presence checks (e.g. Batch signer matching)
// are skipped at proposal-creation time (On-Chain Cosigner spec // are skipped at proposal-creation time (On-Chain Cosigner spec
// §5.3.1.2). // §5.3.1.2).
TapProposal = 0x2000 Proposal = 0x2000
}; };
constexpr ApplyFlags template <>
operator|(ApplyFlags const& lhs, ApplyFlags const& rhs) struct enum_bitops::OptIn<ApplyFlags> : std::true_type
{ {
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,11 +1,17 @@
#pragma once #pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h> #include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h> #include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h> #include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h> #include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STVector256.h>
#include <cstddef>
#include <optional>
namespace xrpl { namespace xrpl {
@@ -25,6 +31,31 @@ public:
: Base(keylet::skip(), view, j) : 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>; using LedgerHashesEntryR = LedgerHashesEntry<ReadView>;
+5 -3
View File
@@ -1,8 +1,10 @@
#pragma once #pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/nodestore/NodeObject.h> #include <xrpl/nodestore/NodeObject.h>
#include <memory> #include <memory>
#include <optional>
namespace xrpl::node_store { namespace xrpl::node_store {
@@ -23,7 +25,7 @@ public:
/** /**
* Construct the decoded blob from raw data. * Construct the decoded blob from raw data.
*/ */
DecodedBlob(void const* key, void const* value, int valueBytes); DecodedBlob(std::optional<uint256> key, void const* value, int valueBytes);
/** /**
* Determine if the decoding was successful. * Determine if the decoding was successful.
@@ -43,10 +45,10 @@ public:
private: private:
bool success_{false}; bool success_{false};
void const* key_; uint256 key_;
NodeObjectType objectType_{NodeObjectType::Unknown}; NodeObjectType objectType_{NodeObjectType::Unknown};
unsigned char const* objectData_{nullptr}; unsigned char const* objectData_{nullptr};
int dataBytes_; int dataBytes_ = 0;
}; };
} // namespace xrpl::node_store } // namespace xrpl::node_store
+10 -8
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/nodestore/NodeObject.h> #include <xrpl/nodestore/NodeObject.h>
@@ -37,11 +38,6 @@ namespace xrpl::node_store {
class EncodedBlob 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. * A pre-allocated buffer for the serialized object.
* *
@@ -65,6 +61,11 @@ class EncodedBlob
*/ */
std::uint8_t* const ptr_; std::uint8_t* const ptr_;
/**
* The 32-byte key of the serialized object.
*/
uint256 key_;
public: public:
explicit EncodedBlob(std::shared_ptr<NodeObject> const& obj) explicit EncodedBlob(std::shared_ptr<NodeObject> const& obj)
: size_([&obj]() { : size_([&obj]() {
@@ -76,11 +77,12 @@ public:
return obj->getData().size() + 9; return obj->getData().size() + 9;
}()) }())
, ptr_((size_ <= payload_.size()) ? payload_.data() : new std::uint8_t[size_]) , ptr_((size_ <= payload_.size()) ? payload_.data() : new std::uint8_t[size_])
, key_(obj->getHash())
{ {
std::fill_n(ptr_, 8, std::uint8_t{0}); std::fill_n(ptr_, 8, std::uint8_t{0});
ptr_[8] = static_cast<std::uint8_t>(obj->getType()); ptr_[8] = static_cast<std::uint8_t>(obj->getType());
std::copy_n(obj->getData().data(), obj->getData().size(), ptr_ + 9); std::copy_n(obj->getData().data(), obj->getData().size(), ptr_ + 9);
std::copy_n(obj->getHash().data(), obj->getHash().size(), key_.data());
} }
~EncodedBlob() ~EncodedBlob()
@@ -95,10 +97,10 @@ public:
delete[] ptr_; delete[] ptr_;
} }
[[nodiscard]] void const* [[nodiscard]] uint256 const&
getKey() const noexcept getKey() const noexcept
{ {
return static_cast<void const*>(key_.data()); return key_;
} }
[[nodiscard]] std::size_t [[nodiscard]] std::size_t
+15 -23
View File
@@ -61,14 +61,22 @@ parseBase58(std::string const& s);
/** /**
* A special account that's used as the "issuer" for XRP. * A special account that's used as the "issuer" for XRP.
*/ */
AccountID const& constexpr inline AccountID const&
xrpAccount(); xrpAccount() noexcept
{
static constexpr AccountID kAccount(beast::kZero);
return kAccount;
}
/** /**
* A placeholder for empty accounts. * A placeholder for empty accounts.
*/ */
AccountID const& constexpr inline AccountID const&
noAccount(); noAccount() noexcept
{
static constexpr AccountID kAccount = xrpAccount().next();
return kAccount;
}
/** /**
* Convert hex or base58 string to AccountID. * Convert hex or base58 string to AccountID.
@@ -80,10 +88,10 @@ bool
toIssuer(AccountID&, std::string const&); toIssuer(AccountID&, std::string const&);
// DEPRECATED Should be checking the currency or native flag // DEPRECATED Should be checking the currency or native flag
inline bool constexpr inline bool
isXRP(AccountID const& c) isXRP(AccountID const& c) noexcept
{ {
return c == beast::kZero; return c == xrpAccount();
} }
// DEPRECATED // DEPRECATED
@@ -101,22 +109,6 @@ operator<<(std::ostream& os, AccountID const& x)
return os; 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 } // namespace xrpl
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
+2 -2
View File
@@ -1,10 +1,10 @@
#pragma once #pragma once
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/beast/type_name.h>
#include <xrpl/protocol/SOTemplate.h> #include <xrpl/protocol/SOTemplate.h>
#include <boost/container/flat_map.hpp> #include <boost/container/flat_map.hpp>
#include <boost/core/type_name.hpp>
#include <algorithm> #include <algorithm>
#include <cstddef> #include <cstddef>
@@ -84,7 +84,7 @@ public:
* Derived classes will load the object with all the known formats. * Derived classes will load the object with all the known formats.
*/ */
private: private:
KnownFormats() : name_(beast::typeName<Derived>()) KnownFormats() : name_(boost::core::type_name<Derived>())
{ {
} }
-6
View File
@@ -75,12 +75,6 @@ addRaw(LedgerHeader const&, Serializer&, bool includeHash = false);
LedgerHeader LedgerHeader
deserializeHeader(Slice data, bool hasHash = false); 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. * Calculate the hash of a ledger header.
*/ */
+2 -3
View File
@@ -19,8 +19,8 @@ private:
// But an STAccount is always 160 bits, so we can store it with less // 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 // overhead in an xrpl::UInt160. However, so the serialized format of the
// STAccount stays unchanged, we serialize and deserialize like an STBlob. // STAccount stays unchanged, we serialize and deserialize like an STBlob.
AccountID value_; AccountID value_{};
bool default_; bool default_{true};
public: public:
using value_type = AccountID; using value_type = AccountID;
@@ -28,7 +28,6 @@ public:
STAccount(); STAccount();
STAccount(SField const& n); STAccount(SField const& n);
STAccount(SField const& n, Buffer const& v);
STAccount(SerialIter& sit, SField const& name); STAccount(SerialIter& sit, SField const& name);
STAccount(SField const& n, AccountID const& v); STAccount(SField const& n, AccountID const& v);
+16 -3
View File
@@ -33,7 +33,20 @@ public:
STBitString(SField const& n); STBitString(SField const& n);
STBitString(value_type const& v); 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); 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); STBitString(SerialIter& sit, SField const& name);
[[nodiscard]] SerializedTypeID [[nodiscard]] SerializedTypeID
@@ -91,7 +104,7 @@ inline STBitString<Bits>::STBitString(SField const& n, value_type const& v) : ST
template <int Bits> template <int Bits>
inline STBitString<Bits>::STBitString(SerialIter& sit, SField const& name) inline STBitString<Bits>::STBitString(SerialIter& sit, SField const& name)
: STBitString(name, sit.getBitString<Bits>()) : STBitString(name, sit.getBitString<value_type>())
{ {
} }
@@ -158,7 +171,7 @@ STBitString<Bits>::add(Serializer& s) const
{ {
XRPL_ASSERT(getFName().isBinary(), "xrpl::STBitString::add : field is binary"); XRPL_ASSERT(getFName().isBinary(), "xrpl::STBitString::add : field is binary");
XRPL_ASSERT(getFName().fieldType == getSType(), "xrpl::STBitString::add : field type match"); XRPL_ASSERT(getFName().fieldType == getSType(), "xrpl::STBitString::add : field type match");
s.addBitString<Bits>(value_); s.addBitString(value_);
} }
template <int Bits> template <int Bits>
@@ -166,7 +179,7 @@ template <typename Tag>
void void
STBitString<Bits>::setValue(BaseUInt<Bits, Tag> const& v) STBitString<Bits>::setValue(BaseUInt<Bits, Tag> const& v)
{ {
value_ = v; value_ = value_type{v};
} }
template <int Bits> template <int Bits>
+15 -4
View File
@@ -16,12 +16,13 @@ namespace xrpl {
template <typename Integer> template <typename Integer>
class STInteger : public STBase, public CountedObject<STInteger<Integer>> class STInteger : public STBase, public CountedObject<STInteger<Integer>>
{ {
static_assert(
SerializableInteger<Integer>,
"STInteger requires an integer type that Serializer can append");
public: public:
using value_type = Integer; using value_type = Integer;
private:
Integer value_;
public: public:
explicit STInteger(Integer v); explicit STInteger(Integer v);
STInteger(SField const& n, Integer v = 0); STInteger(SField const& n, Integer v = 0);
@@ -62,6 +63,8 @@ private:
move(std::size_t n, void* buf) override; move(std::size_t n, void* buf) override;
friend class xrpl::detail::STVar; friend class xrpl::detail::STVar;
Integer value_;
}; };
using STUInt8 = STInteger<unsigned char>; using STUInt8 = STInteger<unsigned char>;
@@ -101,7 +104,15 @@ STInteger<Integer>::add(Serializer& s) const
{ {
XRPL_ASSERT(getFName().isBinary(), "xrpl::STInteger::add : field is binary"); XRPL_ASSERT(getFName().isBinary(), "xrpl::STInteger::add : field is binary");
XRPL_ASSERT(getFName().fieldType == getSType(), "xrpl::STInteger::add : field type match"); XRPL_ASSERT(getFName().fieldType == getSType(), "xrpl::STInteger::add : field type match");
s.addInteger(value_);
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_);
} }
template <typename Integer> template <typename Integer>
+56 -51
View File
@@ -2,6 +2,7 @@
#include <xrpl/basics/CountedObject.h> #include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/UnorderedContainers.h> #include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/enum_bitops.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h> #include <xrpl/protocol/AccountID.h>
@@ -15,6 +16,7 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -22,18 +24,8 @@ namespace xrpl {
class STPathElement final : public CountedObject<STPathElement> class STPathElement final : public CountedObject<STPathElement>
{ {
unsigned int type_;
AccountID accountID_;
PathAsset assetID_;
AccountID issuerID_;
bool isOffer_;
std::size_t hashValue_;
public: public:
// Bitwise values (typeCurrency | typeMPT) enum class Type : std::uint8_t {
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum Type {
TypeNone = 0x00, TypeNone = 0x00,
TypeAccount = 0x01, // Rippling through an account (vs taking an offer). TypeAccount = 0x01, // Rippling through an account (vs taking an offer).
TypeCurrency = 0x10, // Currency follows. TypeCurrency = 0x10, // Currency follows.
@@ -45,6 +37,8 @@ public:
// Combination of all types. // Combination of all types.
}; };
using enum Type;
STPathElement(); STPathElement();
STPathElement(STPathElement const&) = default; STPathElement(STPathElement const&) = default;
STPathElement& STPathElement&
@@ -62,42 +56,42 @@ public:
bool forceAsset = false); bool forceAsset = false);
STPathElement( STPathElement(
unsigned int uType, Type type,
AccountID const& account, AccountID const& account,
PathAsset const& asset, PathAsset const& asset,
AccountID const& issuer); AccountID const& issuer);
[[nodiscard]] std::uint32_t [[nodiscard]] Type
getNodeType() const; getNodeType() const noexcept;
[[nodiscard]] bool [[nodiscard]] bool
isOffer() const; isOffer() const noexcept;
[[nodiscard]] bool [[nodiscard]] bool
isAccount() const; isAccount() const noexcept;
[[nodiscard]] bool [[nodiscard]] bool
hasIssuer() const; hasIssuer() const noexcept;
[[nodiscard]] bool [[nodiscard]] bool
hasCurrency() const; hasCurrency() const noexcept;
[[nodiscard]] bool [[nodiscard]] bool
hasMPT() const; hasMPT() const noexcept;
[[nodiscard]] bool [[nodiscard]] bool
hasAsset() const; hasAsset() const noexcept;
[[nodiscard]] bool [[nodiscard]] bool
isNone() const; isNone() const noexcept;
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a // Nodes are either an account ID or a offer prefix. Offer prefixs denote a
// class of offers. // class of offers.
[[nodiscard]] AccountID const& [[nodiscard]] AccountID const&
getAccountID() const; getAccountID() const noexcept;
[[nodiscard]] PathAsset const& [[nodiscard]] PathAsset const&
getPathAsset() const; getPathAsset() const noexcept;
[[nodiscard]] Currency const& [[nodiscard]] Currency const&
getCurrency() const; getCurrency() const;
@@ -106,17 +100,35 @@ public:
getMPTID() const; getMPTID() const;
[[nodiscard]] AccountID const& [[nodiscard]] AccountID const&
getIssuerID() const; getIssuerID() const noexcept;
[[nodiscard]] bool [[nodiscard]] bool
isType(Type const& pe) const; isType(Type pe) const noexcept;
bool friend bool
operator==(STPathElement const& t) const; 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_;
}
private: private:
static std::size_t static std::size_t
getHash(STPathElement const& element); 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> template <class Hasher>
@@ -124,7 +136,7 @@ void
hash_append(Hasher& h, STPathElement const& e) noexcept hash_append(Hasher& h, STPathElement const& e) noexcept
{ {
using beast::hash_append; using beast::hash_append;
hash_append(h, (e.getNodeType() & STPathElement::TypeAccount) != 0u); hash_append(h, e.isType(STPathElement::TypeAccount));
hash_append(h, e.getAccountID()); hash_append(h, e.getAccountID());
hash_append(h, e.getPathAsset()); hash_append(h, e.getPathAsset());
hash_append(h, e.getIssuerID()); hash_append(h, e.getIssuerID());
@@ -404,11 +416,11 @@ inline STPathElement::STPathElement(
} }
inline STPathElement::STPathElement( inline STPathElement::STPathElement(
unsigned int uType, Type type,
AccountID const& account, AccountID const& account,
PathAsset const& asset, PathAsset const& asset,
AccountID const& issuer) AccountID const& issuer)
: type_(uType) : type_(type)
, accountID_(account) , accountID_(account)
, assetID_(asset) , assetID_(asset)
, issuerID_(issuer) , issuerID_(issuer)
@@ -423,56 +435,56 @@ inline STPathElement::STPathElement(
hashValue_ = getHash(*this); hashValue_ = getHash(*this);
} }
inline std::uint32_t inline STPathElement::Type
STPathElement::getNodeType() const STPathElement::getNodeType() const noexcept
{ {
return type_; return type_;
} }
inline bool inline bool
STPathElement::isOffer() const STPathElement::isOffer() const noexcept
{ {
return isOffer_; return isOffer_;
} }
inline bool inline bool
STPathElement::isAccount() const STPathElement::isAccount() const noexcept
{ {
return !isOffer(); return !isOffer();
} }
inline bool inline bool
STPathElement::isType(Type const& pe) const STPathElement::isType(Type pe) const noexcept
{ {
return (type_ & pe) != 0u; return (type_ & pe) != STPathElement::TypeNone;
} }
inline bool inline bool
STPathElement::hasIssuer() const STPathElement::hasIssuer() const noexcept
{ {
return isType(STPathElement::TypeIssuer); return isType(STPathElement::TypeIssuer);
} }
inline bool inline bool
STPathElement::hasCurrency() const STPathElement::hasCurrency() const noexcept
{ {
return isType(STPathElement::TypeCurrency); return isType(STPathElement::TypeCurrency);
} }
inline bool inline bool
STPathElement::hasMPT() const STPathElement::hasMPT() const noexcept
{ {
return isType(STPathElement::TypeMpt); return isType(STPathElement::TypeMpt);
} }
inline bool inline bool
STPathElement::hasAsset() const STPathElement::hasAsset() const noexcept
{ {
return isType(STPathElement::TypeAsset); return isType(STPathElement::TypeAsset);
} }
inline bool inline bool
STPathElement::isNone() const STPathElement::isNone() const noexcept
{ {
return getNodeType() == STPathElement::TypeNone; return getNodeType() == STPathElement::TypeNone;
} }
@@ -480,13 +492,13 @@ STPathElement::isNone() const
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a // Nodes are either an account ID or a offer prefix. Offer prefixs denote a
// class of offers. // class of offers.
inline AccountID const& inline AccountID const&
STPathElement::getAccountID() const STPathElement::getAccountID() const noexcept
{ {
return accountID_; return accountID_;
} }
inline PathAsset const& inline PathAsset const&
STPathElement::getPathAsset() const STPathElement::getPathAsset() const noexcept
{ {
return assetID_; return assetID_;
} }
@@ -504,18 +516,11 @@ STPathElement::getMPTID() const
} }
inline AccountID const& inline AccountID const&
STPathElement::getIssuerID() const STPathElement::getIssuerID() const noexcept
{ {
return issuerID_; 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 ------------ // ------------ STPath ------------
inline STPath::STPath(std::vector<STPathElement> p) : path_(std::move(p)) inline STPath::STPath(std::vector<STPathElement> p) : path_(std::move(p))
+1 -7
View File
@@ -2,6 +2,7 @@
#include <xrpl/basics/Blob.h> #include <xrpl/basics/Blob.h>
#include <xrpl/basics/CountedObject.h> #include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h> #include <xrpl/protocol/AccountID.h>
@@ -153,13 +154,6 @@ public:
[[nodiscard]] std::string [[nodiscard]] std::string
getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData) const; 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. * The IDs of the inner transactions of a Batch.
*/ */
File diff suppressed because it is too large. Load diff
+3 -4
View File
@@ -14,7 +14,7 @@ namespace xrpl {
// Various protocol and system specific constant globals. // Various protocol and system specific constant globals.
/* The name of the system. */ /* The name of the system. */
static inline std::string const& inline std::string const&
systemName() systemName()
{ {
static std::string const kName = "xrpld"; static std::string const kName = "xrpld";
@@ -52,11 +52,10 @@ isLegalAmountSigned(XRPAmount const& amount)
} }
/* The currency code for the native currency. */ /* The currency code for the native currency. */
static inline std::string const& inline std::string
systemCurrencyCode() systemCurrencyCode()
{ {
static std::string const kCode = "XRP"; return "XRP";
return kCode;
} }
/** /**
+26 -13
View File
@@ -61,26 +61,40 @@ using Domain = BaseUInt<256>;
/** /**
* XRP currency. * XRP currency.
*/ */
Currency const& constexpr inline Currency const&
xrpCurrency(); xrpCurrency() noexcept
{
static constexpr Currency const kCurrency(beast::kZero);
return kCurrency;
}
/** /**
* A placeholder for empty currencies. * A placeholder for empty currencies.
*/ */
Currency const& constexpr inline Currency const&
noCurrency(); noCurrency() noexcept
{
static constexpr Currency const kCurrency(xrpCurrency().next());
return kCurrency;
}
/** /**
* We deliberately disallow the currency that looks like "XRP" because too * We deliberately disallow the currency that looks like "XRP" because too
* many people were using it instead of the correct XRP currency. * many people were using it instead of the correct XRP currency.
*
* Note that this doesn't catch "xRP" or "xrp" or other case variations.
*/ */
Currency const& constexpr inline Currency const&
badCurrency(); badCurrency() noexcept
inline bool
isXRP(Currency const& c)
{ {
return c == beast::kZero; static constexpr Currency kCurrency{"0000000000000000000000005852500000000000"};
return kCurrency;
}
constexpr inline bool
isXRP(Currency const& c) noexcept
{
return c == xrpCurrency();
} }
/** /**
@@ -98,7 +112,7 @@ to_string(Currency const& c);
* to rewrite some unit test code. * to rewrite some unit test code.
*/ */
bool bool
toCurrency(Currency&, std::string const&); toCurrency(Currency&, std::string_view);
/** /**
* Tries to convert a string to a Currency, returns noCurrency() on failure. * Tries to convert a string to a Currency, returns noCurrency() on failure.
@@ -107,8 +121,7 @@ toCurrency(Currency&, std::string const&);
* unfortunate; changing this will require very careful checking * unfortunate; changing this will require very careful checking
* everywhere and may mean having to rewrite some unit test code. * everywhere and may mean having to rewrite some unit test code.
*/ */
Currency Currency toCurrency(std::string_view);
toCurrency(std::string const&);
inline std::ostream& inline std::ostream&
operator<<(std::ostream& os, Currency const& x) operator<<(std::ostream& os, Currency const& x)
+2 -1
View File
@@ -8,6 +8,7 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <span>
#include <type_traits> #include <type_traits>
namespace xrpl { namespace xrpl {
@@ -182,7 +183,7 @@ public:
operator result_type() noexcept operator result_type() noexcept
{ {
auto const digest = Sha512Hasher::result_type(h_); auto const digest = Sha512Hasher::result_type(h_);
return result_type::fromVoid(digest.data()); return result_type{std::span{digest}.first<result_type::size()>()};
} }
private: private:
+1
View File
@@ -392,6 +392,7 @@ JSS(max_ledger); // in/out: LedgerCleaner
JSS(max_queue_size); // out: TxQ JSS(max_queue_size); // out: TxQ
JSS(max_spend_drops); // out: AccountInfo JSS(max_spend_drops); // out: AccountInfo
JSS(max_spend_drops_total); // out: AccountInfo JSS(max_spend_drops_total); // out: AccountInfo
JSS(maximum);
JSS(mean); // out: get_aggregate_price JSS(mean); // out: get_aggregate_price
JSS(median); // out: get_aggregate_price JSS(median); // out: get_aggregate_price
JSS(median_fee); // out: TxQ JSS(median_fee); // out: TxQ
+3 -1
View File
@@ -8,6 +8,7 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <span>
namespace xrpl::nft { namespace xrpl::nft {
@@ -98,7 +99,8 @@ getTaxon(UInt256 const& id)
inline AccountID inline AccountID
getIssuer(UInt256 const& id) getIssuer(UInt256 const& id)
{ {
return AccountID::fromVoid(id.data() + 4); return AccountID{
std::span<unsigned char const, uint256::size()>{id}.subspan<4, AccountID::size()>()};
} }
} // namespace xrpl::nft } // namespace xrpl::nft
+1 -4
View File
@@ -2,13 +2,10 @@
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <string_view>
namespace xrpl::nft { namespace xrpl::nft {
// NFT directory pages order their contents based only on the low 96 bits of // 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. // the NFToken value. This mask provides easy access to the necessary mask.
constexpr UInt256 kPageMask( constexpr UInt256 kPageMask{"0000000000000000000000000000000000000000ffffffffffffffffffffffff"};
std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"));
} // namespace xrpl::nft } // namespace xrpl::nft
+1 -1
View File
@@ -18,7 +18,7 @@ serializeBlob(Object const& o)
{ {
Serializer s; Serializer s;
o.add(s); o.add(s);
return s.peekData(); return s.takeData();
} }
/** /**
+15 -25
View File
@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/CountedObject.h> #include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/SlabAllocator.h> #include <xrpl/basics/SlabAllocator.h>
#include <xrpl/basics/Slice.h> #include <xrpl/basics/Slice.h>
@@ -15,12 +14,13 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <new>
#include <type_traits> #include <type_traits>
namespace xrpl { namespace xrpl {
// an item stored in a SHAMap // an item stored in a SHAMap
class SHAMapItem : public CountedObject<SHAMapItem> class alignas(8) SHAMapItem : public CountedObject<SHAMapItem>
{ {
// These are used to support boost::intrusive_ptr reference counting // These are used to support boost::intrusive_ptr reference counting
// These functions are used internally by boost::intrusive_ptr to handle // 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 // 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 // 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. // in time and with an eye to minimize the number of slack bytes in a block.
inline SlabAllocatorSet<SHAMapItem> gSlabber({ inline constinit slab::Allocator<SHAMapItem, slab::HeapFallback,
{ 128, megabytes(std::size_t(60)) }, slab::Config<1000000, 128>,
{ 192, megabytes(std::size_t(46)) }, slab::Config<1000000, 296>,
{ 272, megabytes(std::size_t(60)) }, slab::Config<125000, 392>,
{ 384, megabytes(std::size_t(56)) }, slab::Config<125000, 520>,
{ 564, megabytes(std::size_t(40)) }, slab::Config<62500, 760>,
{ 772, megabytes(std::size_t(46)) }, slab::Config<62500, 856>,
{ 1052, megabytes(std::size_t(60)) }, slab::Config<31250, 1048>
}); > gSlabber;
// clang-format on // clang-format on
} // namespace detail } // namespace detail
@@ -150,23 +150,13 @@ makeShamapitem(UInt256 const& tag, Slice data)
XRPL_ASSERT( XRPL_ASSERT(
data.size() <= megabytes<std::size_t>(16), "xrpl::makeShamapitem : maximum input size"); data.size() <= megabytes<std::size_t>(16), "xrpl::makeShamapitem : maximum input size");
// NOLINTNEXTLINE(misc-const-correctness)
std::uint8_t* raw = detail::gSlabber.allocate(data.size());
// 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 // We do not increment the reference count here on purpose: the
// constructor of SHAMapItem explicitly sets it to 1. We use the fact // constructor of SHAMapItem explicitly sets it to 1.
// that the refcount can never be zero before incrementing as an if (auto raw = detail::gSlabber.allocate(data.size())) [[likely]]
// invariant.
return {new (raw) SHAMapItem{tag, data}, false}; return {new (raw) SHAMapItem{tag, data}, false};
}
static_assert(alignof(SHAMapItem) != 40); Throw<std::bad_alloc>();
static_assert(alignof(SHAMapItem) == 8 || alignof(SHAMapItem) == 4); }
inline boost::intrusive_ptr<SHAMapItem> inline boost::intrusive_ptr<SHAMapItem>
makeShamapitem(SHAMapItem const& other) makeShamapitem(SHAMapItem const& other)
+2 -1
View File
@@ -51,7 +51,8 @@ public:
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()}) beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
: ApplyContext(registry, base, std::nullopt, tx, preclaimResult, baseFee, flags, journal) : ApplyContext(registry, base, std::nullopt, tx, preclaimResult, baseFee, flags, journal)
{ {
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set"); XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
} }
std::reference_wrapper<ServiceRegistry> registry; std::reference_wrapper<ServiceRegistry> registry;
+11 -8
View File
@@ -59,10 +59,11 @@ public:
, parentBatchId(parentBatchId) , parentBatchId(parentBatchId)
, j(j) , j(j)
{ {
XRPL_ASSERT((flags & TapBatch) == TapBatch, "Batch apply flag should be set"); XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::Batch, "Batch apply flag should be set");
XRPL_ASSERT_IF( XRPL_ASSERT_IF(
(flags & TapProposal) != TapNone, (flags & ApplyFlags::Proposal) != ApplyFlags::None,
(flags & TapDryRun) != TapNone, (flags & ApplyFlags::DryRun) != ApplyFlags::None,
"xrpl::PreflightContext : proposal preflight implies dry run"); "xrpl::PreflightContext : proposal preflight implies dry run");
} }
@@ -74,10 +75,11 @@ public:
beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: registry(registry), tx(tx), rules(std::move(rules)), flags(flags), j(j) : registry(registry), tx(tx), rules(std::move(rules)), flags(flags), j(j)
{ {
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set"); XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
XRPL_ASSERT_IF( XRPL_ASSERT_IF(
(flags & TapProposal) != TapNone, (flags & ApplyFlags::Proposal) != ApplyFlags::None,
(flags & TapDryRun) != TapNone, (flags & ApplyFlags::DryRun) != ApplyFlags::None,
"xrpl::PreflightContext : proposal preflight implies dry run"); "xrpl::PreflightContext : proposal preflight implies dry run");
} }
@@ -116,7 +118,7 @@ public:
, j(j) , j(j)
{ {
XRPL_ASSERT( XRPL_ASSERT(
parentBatchId.has_value() == ((flags & TapBatch) == TapBatch), parentBatchId.has_value() == ((flags & ApplyFlags::Batch) == ApplyFlags::Batch),
"Parent Batch ID should be set if batch apply flag is set"); "Parent Batch ID should be set if batch apply flag is set");
} }
@@ -129,7 +131,8 @@ public:
beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: PreclaimContext(registry, view, preflightResult, tx, flags, std::nullopt, j) : PreclaimContext(registry, view, preflightResult, tx, flags, std::nullopt, j)
{ {
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set"); XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
} }
PreclaimContext& PreclaimContext&
+1 -1
View File
@@ -41,7 +41,7 @@ struct ApplyResult
inline bool inline bool
isTecClaimHardFail(TER ter, ApplyFlags flags) isTecClaimHardFail(TER ter, ApplyFlags flags)
{ {
return isTecClaim(ter) && ((flags & TapRetry) == 0u); return isTecClaim(ter) && ((flags & ApplyFlags::Retry) == ApplyFlags::None);
} }
/** /**
+73 -73
View File
@@ -2,144 +2,144 @@ Detected OS: macos (Darwin arm64)
Core build tools: Core build tools:
✅ cmake ✅ cmake
cmake version 4.1.2 cmake version 4.4.3
/nix/store/gvabsb4yqb5xsqzqph54rijnn4zpihnp-cmake-4.1.2/bin/cmake /nix/store/q85csxf4s4shx89zif097h1ql4ax1rrz-cmake-4.4.3/bin/cmake
✅ conan ✅ conan
Conan version 2.28.1 Conan version 2.32.0
/nix/store/9jiyxmkpwmn6dcqs0765s83riw3l5ail-conan-2.28.1/bin/conan /nix/store/921jqsgbilixmr3xchj95si0jl8pi13z-conan-2.32.0/bin/conan
✅ git ✅ git
git version 2.54.0 git version 2.55.0
/nix/store/a14yxcqvv9x2l9mllgpirzhvz93pgprg-git-2.54.0/bin/git /nix/store/gw7c7m0dwca5lg4152x9lpmxpj171bzw-git-2.55.0/bin/git
✅ python3 ✅ python3
Python 3.13.13 Python 3.14.7
/nix/store/ygxqin6ydzjfawywqpp5pal8wv6sf5bh-python3-3.13.13/bin/python3.13 /nix/store/0kcws5blwbdyxqxjd1w8hjipz61xny3k-python3-3.14.7/bin/python3.14
Development tooling: Development tooling:
✅ ccache ✅ ccache
ccache version 4.13.6 ccache version 4.13.6
/nix/store/57davyvs6p6dkrl3svzwg1ph18wsy4cz-ccache-4.13.6/bin/ccache /nix/store/ydr5nlzxp1djb5256y0zzqa87vz55gsk-ccache-4.13.6/bin/ccache
✅ clang ✅ clang
clang version 22.1.7 clang version 22.1.8
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang /nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang
✅ clang-22 ✅ clang-22
clang version 22.1.7 clang version 22.1.8
/nix/store/rbap7zqq7mw00fyqa02p5rj7gqjp4w5i-clang-22/bin/clang-22 /nix/store/w826sqb98nnym6kzackg829664iqdlxj-clang-22/bin/clang-22
✅ clang++ ✅ clang++
clang version 22.1.7 clang version 22.1.8
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang++ /nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang++
✅ clang++-22 ✅ clang++-22
clang version 22.1.7 clang version 22.1.8
/nix/store/v9haf787f7bcz0mq1sad4bpyx21pj6li-clang++-22/bin/clang++-22 /nix/store/wnwyw3yfqrhpxij65bl3pa1x65q2zg3m-clang++-22/bin/clang++-22
✅ ClangBuildAnalyzer ✅ ClangBuildAnalyzer
ClangBuildAnalyzer 1.6.0 ClangBuildAnalyzer 1.6.0
/nix/store/4l50ds9fa2mkvh7wg8qzrlbmjs12sb8l-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer /nix/store/c7jjnw29ra78jdlzjww2m374izhy05n8-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
✅ curl ✅ 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 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/kclq0czaxvsgh4ym9ld7b6iwy50l1snk-curl-8.20.0-bin/bin/curl /nix/store/pq1h17abqy6q5y6k5dn9qj0fwb89flba-curl-8.22.0-bin/bin/curl
✅ file ✅ file
file-5.47 file-5.48
/nix/store/dax63li7wwcbqxxkkgzc4g2rx7d4w86x-file-5.47/bin/file /nix/store/y44vpziwbzfm8020dzlqqbybyyy7lb6g-file-5.48/bin/file
✅ less ✅ less
less 692 (PCRE2 regular expressions) less 710 (PCRE2 regular expressions)
/nix/store/lvr16y75r1pdxpdv0aph5ak2yd0hkvqm-less-692/bin/less /nix/store/3yn3dx5fwg5i0inqqzwnxq7f6lwhpjqa-less-710/bin/less
✅ make ✅ make
GNU Make 4.4.1 GNU Make 4.4.1
/nix/store/8wwiw8pwyhrkzyq28hqzxfl4z84lks81-gnumake-4.4.1/bin/make /nix/store/8l4wgxmhdxfvc9g18y76bnxl1ghgcp7n-gnumake-4.4.1/bin/make
✅ netstat ✅ netstat
present present
/nix/store/qsd1kzqb0ahrk433vmyl245gp623j19s-network_cmds-730.80.3/bin/netstat /nix/store/57fqvgdav8lrmzndkwm7qrgksl01yj23-network_cmds-741.100.2/bin/netstat
✅ ninja ✅ ninja
1.13.2 1.13.2
/nix/store/bqykhrblarkj4fl0hz2mf8ngwfv6x6bz-ninja-1.13.2/bin/ninja /nix/store/infm4vlx71q83bqz0k559w1gn4b0rq4i-ninja-1.13.2/bin/ninja
✅ perl ✅ perl
v5.42.0 v5.42.3
/nix/store/js13ri9fvm0ajk1fpd3acigys2a9whdv-perl-5.42.0/bin/perl /nix/store/7rvr6qjfzk2ybzn1imjw6c6qq1n1a9cr-perl-5.42.3/bin/perl
✅ pkg-config ✅ pkg-config
0.29.2 0.29.2
/nix/store/lzrwr375jqhhbca116kja96xf1md83l8-pkg-config-wrapper-0.29.2/bin/pkg-config /nix/store/nhgylvj98klqggy54h7gbc3b2ia4a00y-pkg-config-wrapper-0.29.2/bin/pkg-config
✅ vim ✅ vim
VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00) VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
/nix/store/6vbkykg92w603c0sw3mkk7p7mfaawbns-vim-9.2.0389/bin/vim /nix/store/akaa9kqm3b8rlkv61kvsqr2l2j70rvxb-vim-9.2.1001/bin/vim
✅ zip ✅ zip
Zip 3.0 Zip 3.0
/nix/store/z6ph729vcakbvz3wh8ln1wk6mi06w487-zip-3.0/bin/zip /nix/store/dvawr36npnad4xa6dxi2ss0fw44aa82r-zip-3.0/bin/zip
✅ clang-apply-replacements ✅ clang-apply-replacements
clang-apply-replacements version 22.1.7 clang-apply-replacements version 22.1.8
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-apply-replacements /nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-apply-replacements
✅ clang-apply-replacements-22 ✅ clang-apply-replacements-22
clang-apply-replacements version 22.1.7 clang-apply-replacements version 22.1.8
/nix/store/m3ii69rca4077lf4wlk7m3jcag1fs577-clang-apply-replacements-22/bin/clang-apply-replacements-22 /nix/store/w4bz034k4l0w719rrmbwnqcgyjbswy81-clang-apply-replacements-22/bin/clang-apply-replacements-22
✅ clang-format ✅ clang-format
clang-format version 22.1.7 clang-format version 22.1.8
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-format /nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-format
✅ clang-format-22 ✅ clang-format-22
clang-format version 22.1.7 clang-format version 22.1.8
/nix/store/4fawqy6ngqcsqd2ygyyzm93q0xy3f5gs-clang-format-22/bin/clang-format-22 /nix/store/w7z346l0r8y36b6lc5i7jzj3w0581l2y-clang-format-22/bin/clang-format-22
✅ clang-tidy ✅ clang-tidy
LLVM version 22.1.7 LLVM version 22.1.8
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-tidy /nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-tidy
✅ clang-tidy-22 ✅ clang-tidy-22
LLVM version 22.1.7 LLVM version 22.1.8
/nix/store/jqw4280saixaxxihwdba9ldm2fsm6dr3-clang-tidy-22/bin/clang-tidy-22 /nix/store/0lv25qd3ddgjm3ndn70q61jfr6krlkl7-clang-tidy-22/bin/clang-tidy-22
✅ dot ✅ dot
dot - graphviz version 12.2.1 (0) dot - graphviz version 15.1.1 (0)
/nix/store/ijb4fbnqa6wzlpqnhb6q9knqpf7qqn5z-graphviz-12.2.1/bin/dot /nix/store/mc99a6bpbk2waym2cnw6ifmxli4ndb4x-graphviz-15.1.1/bin/dot
✅ doxygen ✅ doxygen
1.16.1 1.17.0
/nix/store/kbryjdpq9jizjb0ws0nzbf2h2ymbdiwm-doxygen-1.16.1/bin/doxygen /nix/store/kz43yaj0wg6fx6cbrcmi2442z1ramy0c-doxygen-1.17.0/bin/doxygen
✅ gcovr ✅ gcovr
gcovr 8.4 gcovr 8.4
/nix/store/wn8jiyh9p0bybs96s4163qp3k8vfmczx-python3.13-gcovr-8.4/bin/gcovr /nix/store/czj1vxsizrbba280gg22cys40qxkqjgr-python3.14-gcovr-8.4/bin/gcovr
✅ gh ✅ gh
gh version 2.94.0 (nixpkgs) gh version 2.102.0 (2026-09-30)
/nix/store/fhnpw0hs0gjms1ha6ap02jq7rx13gkbp-gh-2.94.0/bin/gh /nix/store/s8bahl6zssvry93ihivfmk1l6c58byrr-gh-2.102.0/bin/gh
✅ git-cliff ✅ git-cliff
git-cliff 2.13.1 git-cliff 2.14.2
/nix/store/cy0wwhgxa7yvrz97zydbq6sqmixc90fq-git-cliff-2.13.1/bin/git-cliff /nix/store/k4m5yk0qb7hfslwsq1zjajz1d94j12bj-git-cliff-2.14.2/bin/git-cliff
✅ git-lfs ✅ git-lfs
git-lfs/3.7.1 (3.7.1; darwin arm64; go 1.26.3) git-lfs/3.8.0 (3.8.0; darwin arm64; go 1.26.8)
/nix/store/k9r7zjfjplqa4d5s71cqvf2iv73jd9mc-git-lfs-3.7.1/bin/git-lfs /nix/store/wzixjr3yb40a13m2q654hm3bdjn6sal3-git-lfs-3.8.0/bin/git-lfs
✅ gpg ✅ gpg
gpg (GnuPG) 2.4.9 gpg (GnuPG) 2.4.9
/nix/store/cgh6iwzz5jgx9z5whka4vgj210i6npc6-gnupg-2.4.9/bin/gpg /nix/store/0ldfwnwx385i5kh7cyvxx4mkvgjhs5rh-gnupg-2.4.9/bin/gpg
✅ pre-commit ✅ pre-commit
pre-commit 4.5.1 pre-commit 4.6.2
/nix/store/z3cca68620w0w10f090szgzdnmh1waf2-pre-commit-4.5.1/bin/pre-commit /nix/store/ivh54xypg9qvd3ha9lis0i8p4nkf8n7a-pre-commit-4.6.2/bin/pre-commit
✅ run-clang-tidy ✅ run-clang-tidy
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers] usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/4x28x911z2f9y7adqlh3qspp4a16dig7-run-clang-tidy/bin/run-clang-tidy /nix/store/3xss1mm3mh1hzcmg182na6ki1p5rnxf1-run-clang-tidy/bin/run-clang-tidy
✅ run-clang-tidy-22 ✅ run-clang-tidy-22
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers] usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/x8iymrh76sk5q91ryg5pa7i32s6gfh34-run-clang-tidy-22/bin/run-clang-tidy-22 /nix/store/w68r9z07hcq4fwbfyq3yvc7qxm8aqfbl-run-clang-tidy-22/bin/run-clang-tidy-22
Rust toolchain: Rust toolchain:
✅ cargo ✅ cargo
cargo 1.97.1 (c980f4866 2026-06-30) cargo 1.97.1 (c980f4866 2026-06-30)
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/cargo /nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/cargo
✅ cargo-audit ✅ cargo-audit
cargo-audit-audit 0.22.1 cargo-audit-audit 0.22.2
/nix/store/snwkga2f5gyf404h7mmp9wriwxb8v65f-cargo-audit-0.22.1/bin/cargo-audit /nix/store/7jgrwj557l91940aj0ag4i924ima1cqn-cargo-audit-0.22.2/bin/cargo-audit
✅ cargo-llvm-cov ✅ cargo-llvm-cov
cargo-llvm-cov 0.8.5 cargo-llvm-cov 0.9.0
/nix/store/fpiqdh91gwyxalqp409ynm0s0g086w7w-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov /nix/store/5q1h285z8zzkw2qxig9a0b1avcsk1cf0-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
✅ cargo-nextest ✅ cargo-nextest
cargo-nextest 0.9.137 cargo-nextest 0.9.146
/nix/store/ylz7m947mhkgsp6i7611id3s3gcd58nq-cargo-nextest-0.9.137/bin/cargo-nextest /nix/store/69v3hx3k597hcxd2zy7yad7fg0aw5yxd-cargo-nextest-0.9.146/bin/cargo-nextest
✅ clippy-driver ✅ clippy-driver
clippy 0.1.97 (8bab26f4f6 2026-07-14) clippy 0.1.97 (8bab26f4f6 2026-07-14)
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/clippy-driver /nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/clippy-driver
✅ rust-analyzer ✅ rust-analyzer
rust-analyzer 1.97.1 (8bab26f4 2026-07-14) rust-analyzer 1.97.1 (8bab26f4 2026-07-14)
/nix/store/j6apc5pmd0giy15da9p650r8zklslmvi-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer /nix/store/30rbyk8gx7v53fxqhs4pp0fvxfjycspr-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer
✅ rust-nightly ✅ rust-nightly
rustc 1.99.0-nightly (87e5904f5 2026-07-20) rustc 1.101.0-nightly (282215592 2026-10-04)
/nix/store/fqpjz4l0nsnji8b2pz57mnj0akbp6hcl-rust-nightly/bin/rust-nightly /nix/store/a9552rf3pylcnkijc1z0hf1lcnv50djq-rust-nightly/bin/rust-nightly
✅ rustc ✅ rustc
rustc 1.97.1 (8bab26f4f 2026-07-14) rustc 1.97.1 (8bab26f4f 2026-07-14)
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/rustc /nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/rustc
✅ rustfmt ✅ rustfmt
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14) rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
/nix/store/5ymwgr9jqjz7zzbmj0j5vqbwcd3kp0vm-rustfmt-preview-1.97.1-aarch64-apple-darwin/bin/rustfmt /nix/store/0jmdipyn8266j9kqx2xxm2qkxlrlrjfc-rustfmt-preview-1.97.1-aarch64-apple-darwin/bin/rustfmt
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set). Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
+2 -1
View File
@@ -21,7 +21,8 @@ let
) )
); );
toolchain = if pkgs.stdenv.isLinux then linux.toolchain else (darwin.toolchain ++ [ darwinEnv ]); toolchain =
if pkgs.stdenv.hostPlatform.isLinux then linux.toolchain else (darwin.toolchain ++ [ darwinEnv ]);
in in
{ {
default = pkgs.buildEnv { default = pkgs.buildEnv {
+7 -6
View File
@@ -20,15 +20,16 @@ let
# Custom-glibc stdenvs, matching the CI environment. darwin has no custom # Custom-glibc stdenvs, matching the CI environment. darwin has no custom
# glibc, so there they fall back to the plain nixpkgs stdenvs. # glibc, so there they fall back to the plain nixpkgs stdenvs.
customGccStdenv = if pkgs.stdenv.isLinux then linux.gccStdenv else plainGccStdenv; customGccStdenv = if pkgs.stdenv.hostPlatform.isLinux then linux.gccStdenv else plainGccStdenv;
customClangStdenv = if pkgs.stdenv.isLinux then linux.clangStdenv else plainClangStdenv; customClangStdenv =
if pkgs.stdenv.hostPlatform.isLinux then linux.clangStdenv else plainClangStdenv;
# gcov matching each gcc shell, so `-Dcoverage=ON` builds work in the shell. # gcov matching each gcc shell, so `-Dcoverage=ON` builds work in the shell.
plainGcov = mkGcov { plainGcov = mkGcov {
name = "plain"; name = "plain";
cc = gccPackage.cc; cc = gccPackage.cc;
}; };
customGccGcov = if pkgs.stdenv.isLinux then linux.gcov else plainGcov; customGccGcov = if pkgs.stdenv.hostPlatform.isLinux then linux.gcov else plainGcov;
# Whole directory: init.sh locates the profiles relative to itself. # Whole directory: init.sh locates the profiles relative to itself.
conanDir = ../conan; conanDir = ../conan;
@@ -51,7 +52,7 @@ let
# Not sdkEnv: a shell's stdenv already sets that up. Prepended so the stub # Not sdkEnv: a shell's stdenv already sets that up. Prepended so the stub
# beats the nixpkgs libresolv this shell's tooling drags in. # beats the nixpkgs libresolv this shell's tooling drags in.
darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.isDarwin ( darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isDarwin (
pkgs.lib.concatLines ( pkgs.lib.concatLines (
pkgs.lib.mapAttrsToList ( pkgs.lib.mapAttrsToList (
name: value: ''export ${name}="${value} ''${${name}:-}"'' name: value: ''export ${name}="${value} ''${${name}:-}"''
@@ -126,7 +127,7 @@ let
in in
rec { rec {
# macOS: Nix Clang. Linux: Nix GCC. # macOS: Nix Clang. Linux: Nix GCC.
default = if pkgs.stdenv.isDarwin then clang else gcc; default = if pkgs.stdenv.hostPlatform.isDarwin then clang else gcc;
# gcc/clang use the custom-glibc toolchain, matching CI. On darwin there is no # 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. # custom glibc, so they fall back to the plain nixpkgs toolchain.
@@ -171,7 +172,7 @@ rec {
# The *-plain shells (stock nixpkgs toolchain) exist only on Linux: on darwin # 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 # 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. # makes `nix develop .#gcc-plain` fail there rather than silently aliasing gcc.
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux { // pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
gcc-plain = makeShell { gcc-plain = makeShell {
shellName = "gcc-plain"; shellName = "gcc-plain";
stdenv = plainGccStdenv; stdenv = plainGccStdenv;
-39
View File
@@ -1,39 +0,0 @@
#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
+1 -2
View File
@@ -1,6 +1,5 @@
#include <xrpl/basics/StringUtilities.h> #include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/beast/core/LexicalCast.h> #include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/beast/net/IPEndpoint.h> #include <xrpl/beast/net/IPEndpoint.h>
@@ -20,7 +19,7 @@
namespace xrpl { namespace xrpl {
std::string std::string
sqlBlobLiteral(Blob const& blob) sqlBlobLiteral(Slice blob)
{ {
std::string j; std::string j;
+3 -3
View File
@@ -33,13 +33,13 @@ AcceptedLedgerTx::AcceptedLedgerTx(
Serializer s; Serializer s;
met->add(s); met->add(s);
rawMeta_ = std::move(s.modData()); rawMeta_ = s.takeData();
json_ = json::ValueType::Object; json_ = json::ValueType::Object;
json_[jss::transaction] = txn_->getJson(JsonOptions::Values::None); json_[jss::transaction] = txn_->getJson(JsonOptions::Values::None);
json_[jss::meta] = meta_.getJson(JsonOptions::Values::None); json_[jss::meta] = meta_.getJson(JsonOptions::Values::None);
json_[jss::raw_meta] = strHex(rawMeta_); json_[jss::raw_meta] = strHex(makeSlice(rawMeta_));
json_[jss::result] = transHuman(meta_.getResultTER()); json_[jss::result] = transHuman(meta_.getResultTER());
@@ -74,7 +74,7 @@ std::string
AcceptedLedgerTx::getEscMeta() const AcceptedLedgerTx::getEscMeta() const
{ {
XRPL_ASSERT(!rawMeta_.empty(), "xrpl::AcceptedLedgerTx::getEscMeta : metadata is set"); XRPL_ASSERT(!rawMeta_.empty(), "xrpl::AcceptedLedgerTx::getEscMeta : metadata is set");
return sqlBlobLiteral(rawMeta_); return sqlBlobLiteral(makeSlice(rawMeta_));
} }
} // namespace xrpl } // 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"); XRPL_ASSERT(index_ != kZero, "xrpl::BookDirs::const_iterator::operator++ : nonzero index");
if (!cdirNext(*view_, curKey_, sle_, entry_, index_)) if (!cdirNext(*view_, curKey_, sle_, entry_, index_))
{ {
if (index_ == 0) if (index_ == kZero)
curKey_ = view_->succ(++curKey_, nextQuality_).value_or(kZero); curKey_ = view_->succ(++curKey_, nextQuality_).value_or(kZero);
if (index_ != 0 || curKey_ == kZero) if (index_ != kZero || curKey_ == kZero)
{ {
curKey_ = key_; curKey_ = key_;
entry_ = 0; entry_ = 0;
+12 -15
View File
@@ -11,6 +11,7 @@
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/LedgerTiming.h> #include <xrpl/ledger/LedgerTiming.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
#include <xrpl/nodestore/NodeObject.h> #include <xrpl/nodestore/NodeObject.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h> #include <xrpl/protocol/Fees.h>
@@ -261,7 +262,7 @@ Ledger::Ledger(Ledger const& prevLedger, NetClock::time_point closeTime)
{ {
header_.seq = prevLedger.header_.seq + 1; header_.seq = prevLedger.header_.seq + 1;
header_.parentCloseTime = prevLedger.header_.closeTime; header_.parentCloseTime = prevLedger.header_.closeTime;
header_.hash = prevLedger.header().hash + UInt256(1); header_.hash = prevLedger.header().hash.next();
header_.drops = prevLedger.header().drops; header_.drops = prevLedger.header().drops;
header_.closeTimeResolution = prevLedger.header_.closeTimeResolution; header_.closeTimeResolution = prevLedger.header_.closeTimeResolution;
header_.parentHash = prevLedger.header().hash; header_.parentHash = prevLedger.header().hash;
@@ -362,17 +363,13 @@ Ledger::deserializeTx(SHAMapItem const& item)
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>> std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>>
Ledger::deserializeTxPlusMeta(SHAMapItem const& item) Ledger::deserializeTxPlusMeta(SHAMapItem const& item)
{ {
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>> result;
SerialIter sit(item.slice()); SerialIter sit(item.slice());
{
SerialIter s(sit.getSlice(sit.getVLDataLength())); SerialIter txSit(sit.getVL());
result.first = std::make_shared<STTx const>(s); SerialIter metaSit(sit.getVL());
}
{ return {
SerialIter s(sit.getSlice(sit.getVLDataLength())); std::make_shared<STTx const>(txSit), std::make_shared<STObject const>(metaSit, sfMetadata)};
result.second = std::make_shared<STObject const>(s, sfMetadata);
}
return result;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -532,9 +529,9 @@ Ledger::rawTxInsert(
XRPL_ASSERT(metaData, "xrpl::Ledger::rawTxInsert : non-null metadata input"); XRPL_ASSERT(metaData, "xrpl::Ledger::rawTxInsert : non-null metadata input");
// low-level - just add to table // low-level - just add to table
Serializer s(txn->getDataLength() + metaData->getDataLength() + 16); Serializer s(txn->size() + metaData->size() + 16);
s.addVL(txn->peekData()); s.addVL(txn->slice());
s.addVL(metaData->peekData()); s.addVL(metaData->slice());
if (!txMap_.addGiveItem(SHAMapNodeType::TnTransactionMd, makeShamapitem(key, s.slice()))) if (!txMap_.addGiveItem(SHAMapNodeType::TnTransactionMd, makeShamapitem(key, s.slice())))
logicError("duplicate_tx: " + to_string(key)); logicError("duplicate_tx: " + to_string(key));
} }
@@ -560,7 +557,7 @@ Ledger::setup()
try try
{ {
if (auto const sle = read(keylet::feeSettings())) if (auto const sle = FeeSettingsEntryR(*this))
{ {
bool oldFees = false; bool oldFees = false;
bool newFees = false; bool newFees = false;
+8 -10
View File
@@ -9,6 +9,7 @@
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h> #include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/LedgerHashesEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h> #include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h> #include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h> #include <xrpl/ledger/helpers/DirectoryHelpers.h>
@@ -361,17 +362,16 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
if (int const diff = ledger.seq() - seq; diff <= 256) if (int const diff = ledger.seq() - seq; diff <= 256)
{ {
// Within 256... // Within 256...
auto const hashIndex = ledger.read(keylet::skip()); LedgerHashesEntryR const hashIndex(ledger, journal);
if (hashIndex) if (hashIndex)
{ {
XRPL_ASSERT( XRPL_ASSERT(
hashIndex->getFieldU32(sfLastLedgerSequence) == (ledger.seq() - 1), hashIndex->getFieldU32(sfLastLedgerSequence) == (ledger.seq() - 1),
"xrpl::hashOfSeq : matching ledger sequence"); "xrpl::hashOfSeq : matching ledger sequence");
STVector256 vec = hashIndex->getFieldV256(sfHashes); if (auto const hash = hashIndex.hashAt(diff - 1))
if (vec.size() >= diff) return hash;
return vec[vec.size() - diff];
JLOG(journal.warn()) << "Ledger " << ledger.seq() << " missing hash for " << seq << " (" JLOG(journal.warn()) << "Ledger " << ledger.seq() << " missing hash for " << seq << " ("
<< vec.size() << "," << diff << ")"; << hashIndex->getFieldV256(sfHashes).size() << "," << diff << ")";
} }
else else
{ {
@@ -387,16 +387,14 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
} }
// in skiplist // in skiplist
auto const hashIndex = ledger.read(keylet::skip(seq)); LedgerHashesEntryR const hashIndex(keylet::skip(seq), ledger, journal);
if (hashIndex) if (hashIndex)
{ {
auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence); auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence);
XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger"); XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger");
XRPL_ASSERT((lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger"); XRPL_ASSERT((lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger");
auto const diff = (lastSeq - seq) >> 8; if (auto const hash = hashIndex.hashAt((lastSeq - seq) >> 8))
STVector256 vec = hashIndex->getFieldV256(sfHashes); return hash;
if (vec.size() > diff)
return vec[vec.size() - diff - 1];
} }
JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq() << " error"; JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq() << " error";
return std::nullopt; return std::nullopt;
@@ -506,7 +506,7 @@ pseudoAccountAddress(ReadView const& view, UInt256 const& pseudoOwnerKey)
RipeshaHasher rsh; RipeshaHasher rsh;
auto const hash = sha512Half(i, view.header().parentHash, pseudoOwnerKey); auto const hash = sha512Half(i, view.header().parentHash, pseudoOwnerKey);
rsh(hash.data(), hash.size()); rsh(hash.data(), hash.size());
AccountID const ret = AccountID::fromRaw(static_cast<RipeshaHasher::result_type>(rsh)); AccountID const ret{static_cast<RipeshaHasher::result_type>(rsh)};
if (!view.read(keylet::account(ret))) if (!view.read(keylet::account(ret)))
return ret; return ret;
} }
+7 -3
View File
@@ -1,7 +1,6 @@
#include <xrpl/nodestore/detail/DecodedBlob.h> #include <xrpl/nodestore/detail/DecodedBlob.h>
#include <xrpl/basics/Blob.h> #include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/safe_cast.h> #include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/nodestore/NodeObject.h> #include <xrpl/nodestore/NodeObject.h>
@@ -12,8 +11,13 @@
namespace xrpl::node_store { namespace xrpl::node_store {
DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) : key_(key) DecodedBlob::DecodedBlob(std::optional<UInt256> key, void const* value, int valueBytes)
{ {
if (!key)
return;
key_ = key.value();
/* Data format: /* Data format:
Bytes Bytes
@@ -63,7 +67,7 @@ DecodedBlob::createObject()
{ {
Blob data(objectData_, objectData_ + dataBytes_); Blob data(objectData_, objectData_ + dataBytes_);
object = NodeObject::createObject(objectType_, std::move(data), UInt256::fromVoid(key_)); object = NodeObject::createObject(objectType_, std::move(data), key_);
} }
return object; return object;
+15 -3
View File
@@ -38,6 +38,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@@ -216,7 +217,7 @@ public:
[&hash, pno, &status](void const* data, std::size_t size) { [&hash, pno, &status](void const* data, std::size_t size) {
nudb::detail::buffer bf; nudb::detail::buffer bf;
auto const result = nodeobjectDecompress(data, size, bf); auto const result = nodeobjectDecompress(data, size, bf);
DecodedBlob decoded(hash.data(), result.first, result.second); DecodedBlob decoded(hash, result.first, result.second);
if (!decoded.wasOk()) if (!decoded.wasOk())
{ {
status = Status::DataCorrupt; status = Status::DataCorrupt;
@@ -240,7 +241,7 @@ public:
nudb::error_code ec; nudb::error_code ec;
nudb::detail::buffer bf; nudb::detail::buffer bf;
auto const result = nodeobjectCompress(e.getData(), e.getSize(), bf); auto const result = nodeobjectCompress(e.getData(), e.getSize(), bf);
db.insert(e.getKey(), result.first, result.second, ec); db.insert(e.getKey().data(), result.first, result.second, ec);
if (ec && ec != nudb::error::key_exists) if (ec && ec != nudb::error::key_exists)
Throw<nudb::system_error>(ec); Throw<nudb::system_error>(ec);
} }
@@ -288,14 +289,25 @@ public:
Throw<nudb::system_error>(ec); Throw<nudb::system_error>(ec);
nudb::visit( nudb::visit(
dp, dp,
[&](void const* key, [&](void const* keyData,
std::size_t keyBytes, std::size_t keyBytes,
void const* data, void const* data,
std::size_t size, std::size_t size,
nudb::error_code&) { 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; nudb::detail::buffer bf;
auto const result = nodeobjectDecompress(data, size, bf); auto const result = nodeobjectDecompress(data, size, bf);
DecodedBlob decoded(key, result.first, result.second); DecodedBlob decoded(key, result.first, result.second);
if (!decoded.wasOk()) if (!decoded.wasOk())
{ {
ec = make_error_code(nudb::error::missing_value); ec = make_error_code(nudb::error::missing_value);
@@ -38,6 +38,7 @@
#include <format> #include <format>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <span>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@@ -111,6 +112,9 @@ public:
if (!getIfExists(keyValues, Keys::kPath, name)) if (!getIfExists(keyValues, Keys::kPath, name))
Throw<std::runtime_error>("Missing path in RocksDBFactory backend"); 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; rocksdb::BlockBasedTableOptions tableOptions;
options.env = env; options.env = env;
@@ -292,7 +296,7 @@ public:
if (getStatus.ok()) if (getStatus.ok())
{ {
DecodedBlob decoded(hash.data(), string.data(), string.size()); DecodedBlob decoded(hash, string.data(), string.size());
if (decoded.wasOk()) if (decoded.wasOk())
{ {
@@ -317,8 +321,8 @@ public:
} }
else else
{ {
status = static_cast<Status>( status = checkedCast<Status>(
static_cast<int>(Status::CustomCode) + unsafeCast<int>(getStatus.code())); safeCast<int>(Status::CustomCode) + safeCast<int>(getStatus.code()));
JLOG(journal.error()) << getStatus.ToString(); JLOG(journal.error()) << getStatus.ToString();
} }
@@ -347,7 +351,7 @@ public:
EncodedBlob const encoded(e); EncodedBlob const encoded(e);
wb.Put( wb.Put(
rocksdb::Slice(reinterpret_cast<char const*>(encoded.getKey()), keyBytes), rocksdb::Slice(reinterpret_cast<char const*>(encoded.getKey().data()), keyBytes),
rocksdb::Slice( rocksdb::Slice(
reinterpret_cast<char const*>(encoded.getData()), encoded.getSize())); reinterpret_cast<char const*>(encoded.getData()), encoded.getSize()));
} }
@@ -375,9 +379,12 @@ public:
for (it->SeekToFirst(); it->Valid(); it->Next()) for (it->SeekToFirst(); it->Valid(); it->Next())
{ {
if (it->key().size() == keyBytes) if (it->key().size() == keyBytes && it->key().size() == uint256::size())
{ {
DecodedBlob decoded(it->key().data(), it->value().data(), it->value().size()); DecodedBlob decoded(
uint256::fromRaw(std::span{it->key().data(), it->key().size()}),
it->value().data(),
it->value().size());
if (decoded.wasOk()) if (decoded.wasOk())
{ {
+122 -88
View File
@@ -8,6 +8,8 @@
#include <xrpl/protocol/digest.h> #include <xrpl/protocol/digest.h>
#include <xrpl/protocol/tokens.h> #include <xrpl/protocol/tokens.h>
#include <algorithm>
#include <array>
#include <atomic> #include <atomic>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
@@ -19,95 +21,143 @@
namespace xrpl { namespace xrpl {
namespace detail { namespace {
/** /** If true, we implement a cache for account ID lookups.
* Caches the base58 representations of AccountIDs
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.
*/ */
class AccountIdCache 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
{ {
private: /** The sequence lock for the entry.
struct CachedAccountID
{ An even value means that the data is stable, whereas an odd
AccountID id; value indicates the data is being actively modified.
char encoding[40] = {0};
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};
/** The account id. */
AccountID id{};
/** The length of the cached encoding.
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;
/** The actual string encoding. */
char encoding[39] = {0};
}; };
// The actual cache static_assert(sizeof(CachedAccountID) == 64 && alignof(CachedAccountID) == 64);
std::vector<CachedAccountID> cache_;
// We use a hash function designed to resist algorithmic complexity attacks // The actual cache to use: a simple, direct-mapped, best-effort construct.
HardenedHash<> hasher_; //
// 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{};
// 64 spinlocks, packed into a single 64-bit value } // namespace
std::atomic<std::uint64_t> locks_ = 0;
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();
}
std::string
toBase58(AccountID const& id)
{
auto const index = hasher_(id) % cache_.size();
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;
}
};
} // namespace detail
static std::unique_ptr<detail::AccountIdCache> gAccountIdCache;
void
initAccountIdCache(std::size_t count)
{
if (!gAccountIdCache && count != 0)
gAccountIdCache = std::make_unique<detail::AccountIdCache>(count);
}
std::string std::string
toBase58(AccountID const& v) toBase58(AccountID const& v)
{ {
if (gAccountIdCache) // Since the input bits are generated by a cryptographically secure
return gAccountIdCache->toBase58(v); // 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();
return encodeBase58Token(TokenType::AccountID, v.data(), v.size()); 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;
} }
template <> template <>
std::optional<AccountID> std::optional<AccountID>
parseBase58(std::string const& s) parseBase58(std::string const& s)
{ {
auto const result = decodeBase58Token(s, TokenType::AccountID); return AccountID::fromRaw(decodeBase58Token(s, TokenType::AccountID));
if (result.size() != AccountID::kBytes)
return std::nullopt;
return AccountID::fromRaw(result);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -148,25 +198,9 @@ parseBase58(std::string const& s)
AccountID AccountID
calcAccountID(PublicKey const& pk) calcAccountID(PublicKey const& pk)
{ {
static_assert(AccountID::kBytes == sizeof(RipeshaHasher::result_type));
RipeshaHasher rsh; RipeshaHasher rsh;
rsh(pk.data(), pk.size()); rsh(pk.data(), pk.size());
return AccountID::fromRaw(static_cast<RipeshaHasher::result_type>(rsh)); return AccountID{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 bool
+6 -5
View File
@@ -185,8 +185,9 @@ getBookBase(Book const& book)
UInt256 UInt256
getQualityNext(UInt256 const& uBase) getQualityNext(UInt256 const& uBase)
{ {
static constexpr UInt256 kNextQuality( static constexpr UInt256 kNextQuality{
"0000000000000000000000000000000000000000000000010000000000000000"); "0000000000000000000000000000000000000000000000010000000000000000"};
return uBase + kNextQuality; return uBase + kNextQuality;
} }
@@ -429,9 +430,9 @@ payChannel(AccountID const& src, AccountID const& dst, SeqProxy const& seq) noex
Keylet Keylet
nftokenPageMin(AccountID const& owner) nftokenPageMin(AccountID const& owner)
{ {
std::array<std::uint8_t, 32> buf{}; std::array<std::uint8_t, UInt256::size()> buf{};
std::memcpy(buf.data(), owner.data(), owner.size()); std::ranges::copy(owner, buf.begin());
return {ltNFTOKEN_PAGE, UInt256::fromRaw(buf)}; return {ltNFTOKEN_PAGE, UInt256{buf}};
} }
Keylet Keylet
+4 -9
View File
@@ -2,6 +2,7 @@
#include <xrpl/basics/Slice.h> #include <xrpl/basics/Slice.h>
#include <xrpl/basics/chrono.h> #include <xrpl/basics/chrono.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/protocol/HashPrefix.h> #include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/Serializer.h> #include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/digest.h> #include <xrpl/protocol/digest.h>
@@ -20,8 +21,8 @@ addRaw(LedgerHeader const& info, Serializer& s, bool includeHash)
s.addBitString(info.accountHash); s.addBitString(info.accountHash);
s.add32(info.parentCloseTime.time_since_epoch().count()); s.add32(info.parentCloseTime.time_since_epoch().count());
s.add32(info.closeTime.time_since_epoch().count()); s.add32(info.closeTime.time_since_epoch().count());
s.add8(info.closeTimeResolution.count()); s.add8(unsafeCast<std::uint8_t>(info.closeTimeResolution.count()));
s.add8(info.closeFlags); s.add8(unsafeCast<std::uint8_t>(info.closeFlags));
if (includeHash) if (includeHash)
s.addBitString(info.hash); s.addBitString(info.hash);
@@ -30,7 +31,7 @@ addRaw(LedgerHeader const& info, Serializer& s, bool includeHash)
LedgerHeader LedgerHeader
deserializeHeader(Slice data, bool hasHash) deserializeHeader(Slice data, bool hasHash)
{ {
SerialIter sit(data.data(), data.size()); SerialIter sit{data};
LedgerHeader header; LedgerHeader header;
@@ -50,12 +51,6 @@ deserializeHeader(Slice data, bool hasHash)
return header; return header;
} }
LedgerHeader
deserializePrefixedHeader(Slice data, bool hasHash)
{
return deserializeHeader(data + 4, hasHash);
}
UInt256 UInt256
calculateLedgerHash(LedgerHeader const& info) calculateLedgerHash(LedgerHeader const& info)
{ {
+1 -3
View File
@@ -295,11 +295,9 @@ verify(PublicKey const& publicKey, Slice const& m, Slice const& sig) noexcept
NodeID NodeID
calcNodeID(PublicKey const& pk) calcNodeID(PublicKey const& pk)
{ {
static_assert(NodeID::kBytes == sizeof(RipeshaHasher::result_type));
RipeshaHasher h; RipeshaHasher h;
h(pk.data(), pk.size()); h(pk.data(), pk.size());
return NodeID::fromRaw(static_cast<RipeshaHasher::result_type>(h)); return NodeID{static_cast<RipeshaHasher::result_type>(h)};
} }
} // namespace xrpl } // namespace xrpl
+18 -17
View File
@@ -17,33 +17,29 @@
namespace xrpl { namespace xrpl {
STAccount::STAccount() : value_(beast::kZero), default_(true) STAccount::STAccount()
{ {
} }
STAccount::STAccount(SField const& n) : STBase(n), value_(beast::kZero), default_(true) STAccount::STAccount(SField const& n) : STBase(n)
{ {
} }
STAccount::STAccount(SField const& n, Buffer const& v) : STAccount(n) STAccount::STAccount(SerialIter& sit, SField const& name) : STAccount(name)
{ {
if (v.empty()) if (auto const s = sit.getVL(); !s.empty())
return; // Zero is a valid size for a defaulted STAccount. {
// 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);
// Is it safe to throw from this constructor? Today (November 2015) if (!id) [[unlikely]]
// 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"); 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) STAccount::STAccount(SField const& n, AccountID const& v) : STBase(n), value_(v), default_(false)
@@ -77,8 +73,13 @@ STAccount::add(Serializer& s) const
// Preserve the serialization behavior of an STBlob: // Preserve the serialization behavior of an STBlob:
// o If we are default (all zeros) serialize as an empty blob. // o If we are default (all zeros) serialize as an empty blob.
// o Otherwise serialize 160 bits. // o Otherwise serialize 160 bits.
int const size = isDefault() ? 0 : UInt160::kBytes; if (isDefault())
s.addVL(value_.data(), size); {
s.addVL(Slice{});
return;
}
s.addVL(Slice{value_.data(), value_.size()});
} }
bool bool
+2 -2
View File
@@ -148,12 +148,12 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name)
} }
Issue issue; Issue issue;
issue.currency = sit.get160(); issue.currency = Currency{sit.get160()};
if (isXRP(issue.currency)) if (isXRP(issue.currency))
Throw<std::runtime_error>("invalid native currency"); Throw<std::runtime_error>("invalid native currency");
issue.account = sit.get160(); issue.account = AccountID{sit.get160()};
if (isXRP(issue.account)) if (isXRP(issue.account))
Throw<std::runtime_error>("invalid native account"); Throw<std::runtime_error>("invalid native account");
+2 -2
View File
@@ -12,7 +12,7 @@
namespace xrpl { namespace xrpl {
STBlob::STBlob(SerialIter& st, SField const& name) : STBase(name), value_(st.getVLBuffer()) STBlob::STBlob(SerialIter& st, SField const& name) : STBase(name), value_(st.getVL())
{ {
} }
@@ -47,7 +47,7 @@ STBlob::add(Serializer& s) const
XRPL_ASSERT( XRPL_ASSERT(
(getFName().fieldType == STI_VL) || (getFName().fieldType == STI_ACCOUNT), (getFName().fieldType == STI_VL) || (getFName().fieldType == STI_ACCOUNT),
"xrpl::STBlob::add : valid field type"); "xrpl::STBlob::add : valid field type");
s.addVL(value_.data(), value_.size()); s.addVL(Slice{value_.data(), value_.size()});
} }
bool bool
+5 -5
View File
@@ -19,16 +19,16 @@ STCurrency::STCurrency(SField const& name) : STBase{name}
{ {
} }
STCurrency::STCurrency(SerialIter& sit, SField const& name) : STBase{name}
{
currency_ = sit.get160();
}
STCurrency::STCurrency(SField const& name, Currency const& currency) STCurrency::STCurrency(SField const& name, Currency const& currency)
: STBase{name}, currency_{currency} : STBase{name}, currency_{currency}
{ {
} }
STCurrency::STCurrency(SerialIter& sit, SField const& name)
: STCurrency{name, Currency{sit.get160()}}
{
}
SerializedTypeID SerializedTypeID
STCurrency::getSType() const STCurrency::getSType() const
{ {
+3 -2
View File
@@ -64,7 +64,8 @@ STUInt8::getJson(JsonOptions) const
return token; return token;
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(debugLog().error()) << "Unknown result code in metadata: " << value_; JLOG(debugLog().error()) << "Unknown result code in metadata: "
<< safeCast<std::uint32_t>(value_);
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
@@ -221,7 +222,7 @@ STUInt64::getJson(JsonOptions) const
template <> template <>
STInteger<std::int32_t>::STInteger(SerialIter& sit, SField const& name) STInteger<std::int32_t>::STInteger(SerialIter& sit, SField const& name)
: STInteger(name, sit.get32()) : STInteger(name, sit.geti32())
{ {
} }
+16 -26
View File
@@ -28,49 +28,39 @@ STIssue::STIssue(SField const& name) : STBase{name}
STIssue::STIssue(SerialIter& sit, 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(); auto const currencyOrAccount = sit.get160();
if (isXRP(Currency::fromRaw(currencyOrAccount))) if (isXRP(Currency{currencyOrAccount}))
{ {
asset_ = xrpIssue(); asset_ = xrpIssue();
return;
} }
// Check if MPT
else
{
// MPT is serialized as: // MPT is serialized as:
// - 160 bits MPT issuer account // - 160 bits MPT issuer account
// - 160 bits black hole account // - 160 bits black hole account
// - 32 bits sequence // - 32 bits sequence
AccountID const account = AccountID::fromRaw(sit.get160()); AccountID const account{sit.get160()};
// MPT // MPT
if (noAccount() == account) if (noAccount() == account)
{ {
MPTID mptID; // MPT: issuer (160), noAccount() (160), sequence (32). The sequence
std::uint32_t sequence = sit.get32(); // is stored in little-endian and get32() reads big-endian, so
// MPTID stores the sequence in canonical big-endian bytes. STIssue // reverse it before building the canonical ID.
// ledger bytes are the legacy LE-host encoding, so convert the asset_ = MPTIssue{std::byteswap(sit.get32()), AccountID{currencyOrAccount}};
// native get32() value to LE bytes before copying into the MPTID. return;
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 const issue{Currency{currencyOrAccount}, account};
Issue issue;
issue.currency = currencyOrAccount;
issue.account = account;
if (!isConsistent(issue)) if (!isConsistent(issue))
Throw<std::runtime_error>("invalid issue: currency and account native mismatch"); Throw<std::runtime_error>("invalid issue: currency and account native mismatch");
asset_ = issue; asset_ = issue;
} }
}
}
SerializedTypeID SerializedTypeID
STIssue::getSType() const STIssue::getSType() const
+3 -4
View File
@@ -1,13 +1,11 @@
#include <xrpl/protocol/STObject.h> #include <xrpl/protocol/STObject.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Log.h> #include <xrpl/basics/Log.h>
#include <xrpl/basics/Slice.h> #include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/HashPrefix.h> #include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/InnerObjectFormats.h> #include <xrpl/protocol/InnerObjectFormats.h>
@@ -28,6 +26,7 @@
#include <xrpl/protocol/STVector256.h> #include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/Serializer.h> #include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/detail/STVar.h> #include <xrpl/protocol/detail/STVar.h>
#include <xrpl/protocol/digest.h>
#include <algorithm> #include <algorithm>
#include <cstddef> #include <cstddef>
@@ -377,7 +376,7 @@ STObject::getHash(HashPrefix prefix) const
Serializer s; Serializer s;
s.add32(prefix); s.add32(prefix);
add(s, WhichFields::WithAllFields); add(s, WhichFields::WithAllFields);
return s.getSHA512Half(); return sha512Half(s);
} }
UInt256 UInt256
@@ -386,7 +385,7 @@ STObject::getSigningHash(HashPrefix prefix) const
Serializer s; Serializer s;
s.add32(prefix); s.add32(prefix);
add(s, WhichFields::OmitSigningFields); add(s, WhichFields::OmitSigningFields);
return s.getSHA512Half(); return sha512Half(s);
} }
int int
+78 -50
View File
@@ -5,6 +5,7 @@
#include <xrpl/basics/UnorderedContainers.h> #include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/hash/uhash.h> #include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h> #include <xrpl/json/json_value.h>
@@ -17,9 +18,13 @@
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
#include <algorithm> #include <algorithm>
#include <concepts>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <memory> #include <memory>
#include <optional>
#include <stdexcept> #include <stdexcept>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -36,10 +41,10 @@ STPathElement::getHash(STPathElement const& element)
// important. We don't even really need to fully hash the whole // important. We don't even really need to fully hash the whole
// base_uint here, as a few bytes would do for our use. // base_uint here, as a few bytes would do for our use.
// //
// The note above is only true because the result of this function reaches // The note above is only true because the result of this function is only
// nothing but STPathElement::operator==, where it is a fast-reject // used by the STPathElement equality operator as a fast-reject prefilter,
// prefilter ahead of the field comparisons that decide the answer. Do not // ahead of the field comparisons that then decide the answer. Do not use
// use it to key a container. // it to key a container.
for (auto const x : element.getAccountID()) for (auto const x : element.getAccountID())
hashAccount += (hashAccount * 257) ^ x; hashAccount += (hashAccount * 257) ^ x;
@@ -106,12 +111,38 @@ STPathSet::operator=(STPathSet const& other)
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name) STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
{ {
std::vector<STPathElement> path; using enum STPathElement::Type;
for (;;)
{
int const iType = sit.get8();
if (iType == STPathElement::TypeNone || iType == STPathElement::TypeBoundary) 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
{
type = parsePathElementType(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 (path.empty()) if (path.empty())
{ {
@@ -122,28 +153,11 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
// Move rather than converting the vector to an STPath by copy. // Move rather than converting the vector to an STPath by copy.
value_.emplace_back(std::move(path)); value_.emplace_back(std::move(path));
path.clear(); 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 else
{ {
auto const hasAccount = (iType & STPathElement::TypeAccount) != 0u; bool const hasCurrency = ((*type & TypeCurrency) == TypeCurrency);
auto const hasCurrency = (iType & STPathElement::TypeCurrency) != 0u; bool const hasMPT = ((*type & TypeMpt) == TypeMpt);
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) if (hasCurrency && hasMPT)
{ {
@@ -151,18 +165,26 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
Throw<std::runtime_error>("bad path element: MPT and Currency"); 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) if (hasCurrency)
asset = Currency::fromRaw(sit.get160()); asset = sit.getBitString<Currency>();
if (hasMPT) if (hasMPT)
asset = sit.get192(); asset = sit.getBitString<MPTID>();
if (hasIssuer) if ((*type & TypeIssuer) == TypeIssuer)
issuer = sit.get160(); issuer = sit.getBitString<AccountID>();
path.emplace_back(account, asset, issuer, hasCurrency || hasMPT); path.emplace_back(account, asset, issuer, hasCurrency || hasMPT);
} }
} } while (type != TypeNone);
} }
STBase* STBase*
@@ -216,24 +238,22 @@ STPath::getJson(JsonOptions) const
for (auto const& it : path_) for (auto const& it : path_)
{ {
json::Value elem(json::ValueType::Object); json::Value elem(json::ValueType::Object);
auto const iType = it.getNodeType();
elem[jss::type] = iType; elem[jss::type] = safeCast<std::uint32_t>(it.getNodeType());
if ((iType & STPathElement::TypeAccount) != 0u) if (it.isType(STPathElement::TypeAccount))
elem[jss::account] = to_string(it.getAccountID()); elem[jss::account] = to_string(it.getAccountID());
XRPL_ASSERT( XRPL_ASSERT(
((iType & STPathElement::TypeCurrency) == 0u) || !(it.hasCurrency() && it.hasMPT()),
((iType & STPathElement::TypeMpt) == 0u),
"xrpl::STPath::getJson : not type Currency and MPT"); "xrpl::STPath::getJson : not type Currency and MPT");
if ((iType & STPathElement::TypeCurrency) != 0u) if (it.hasCurrency())
elem[jss::currency] = to_string(it.getCurrency()); elem[jss::currency] = to_string(it.getCurrency());
if ((iType & STPathElement::TypeMpt) != 0u) if (it.hasMPT())
elem[jss::mpt_issuance_id] = to_string(it.getMPTID()); elem[jss::mpt_issuance_id] = to_string(it.getMPTID());
if ((iType & STPathElement::TypeIssuer) != 0u) if (it.hasIssuer())
elem[jss::issuer] = to_string(it.getIssuerID()); elem[jss::issuer] = to_string(it.getIssuerID());
ret.append(elem); ret.append(elem);
@@ -265,34 +285,42 @@ STPathSet::add(Serializer& s) const
XRPL_ASSERT(getFName().fieldType == STI_PATHSET, "xrpl::STPathSet::add : valid field type"); XRPL_ASSERT(getFName().fieldType == STI_PATHSET, "xrpl::STPathSet::add : valid field type");
bool first = true; 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_) for (auto const& spPath : value_)
{ {
if (!first) if (!first)
s.add8(STPathElement::TypeBoundary); s.add8(toByte(STPathElement::TypeBoundary));
for (auto const& speElement : spPath) for (auto const& speElement : spPath)
{ {
int const iType = speElement.getNodeType(); s.add8(toByte(speElement.getNodeType()));
s.add8(iType); if (speElement.isType(STPathElement::TypeAccount))
if ((iType & STPathElement::TypeAccount) != 0u)
s.addBitString(speElement.getAccountID()); s.addBitString(speElement.getAccountID());
if ((iType & STPathElement::TypeMpt) != 0u) if (speElement.hasMPT())
s.addBitString(speElement.getMPTID()); s.addBitString(speElement.getMPTID());
if ((iType & STPathElement::TypeCurrency) != 0u) if (speElement.hasCurrency())
s.addBitString(speElement.getCurrency()); s.addBitString(speElement.getCurrency());
if ((iType & STPathElement::TypeIssuer) != 0u) if (speElement.hasIssuer())
s.addBitString(speElement.getIssuerID()); s.addBitString(speElement.getIssuerID());
} }
first = false; first = false;
} }
s.add8(STPathElement::TypeNone); s.add8(toByte(STPathElement::TypeNone));
} }
} // namespace xrpl } // namespace xrpl
+7 -25
View File
@@ -76,9 +76,7 @@ STTx::STTx(STObject&& object)
STTx::STTx(SerialIter& sit) : STObject(sfTransaction) STTx::STTx(SerialIter& sit) : STObject(sfTransaction)
{ {
int const length = sit.getBytesLeft(); if (auto const sz = sit.size(); (sz < kTxMinSizeBytes) || (sz > kTxMaxSizeBytes))
if ((length < kTxMinSizeBytes) || (length > kTxMaxSizeBytes))
Throw<std::runtime_error>("Transaction length invalid"); Throw<std::runtime_error>("Transaction length invalid");
if (set(sit)) if (set(sit))
@@ -172,7 +170,7 @@ getSigningData(STTx const& that, HashPrefix prefix)
Serializer s; Serializer s;
s.add32(prefix); s.add32(prefix);
that.addWithoutSigningFields(s); that.addWithoutSigningFields(s);
return s.getData(); return s.takeData();
} }
UInt256 UInt256
@@ -363,8 +361,7 @@ STTx::getJson(JsonOptions options, bool binary) const
if (binary) if (binary)
{ {
Serializer const s = STObject::getSerializer(); std::string const dataBin = strHex(getSerializer().slice());
std::string const dataBin = strHex(s.peekData());
if (v1) if (v1)
{ {
@@ -399,22 +396,7 @@ STTx::getMetaSQLInsertReplaceHeader()
std::string std::string
STTx::getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData) const STTx::getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData) const
{ {
Serializer s; auto const format = TxFormats::getInstance().findByType(txType_);
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"); XRPL_ASSERT(format, "xrpl::STTx::getMetaSQL : non-null type format");
return std::format( return std::format(
@@ -424,8 +406,8 @@ STTx::getMetaSQL(
toBase58(getAccountID(sfAccount)), toBase58(getAccountID(sfAccount)),
getFieldU32(sfSequence), getFieldU32(sfSequence),
inLedger, inLedger,
safeCast<char>(status), safeCast<char>(TxnSql::Validated),
rTxn, sqlBlobLiteral(getSerializer().slice()),
escapedMetaData); escapedMetaData);
} }
@@ -694,7 +676,7 @@ isMemoOkay(STObject const& st, std::string& reason)
memos.add(s); memos.add(s);
// FIXME move the memo limit into a config tunable // FIXME move the memo limit into a config tunable
if (s.getDataLength() > 1024) if (s.size() > 1024)
{ {
reason = "The memo exceeds the maximum allowed size."; reason = "The memo exceeds the maximum allowed size.";
return false; return false;
+1 -1
View File
@@ -168,7 +168,7 @@ STValidation::getSerialized() const
{ {
Serializer s; Serializer s;
add(s); add(s);
return s.peekData(); return s.takeData();
} }
} // namespace xrpl } // namespace xrpl
+12 -6
View File
@@ -9,7 +9,9 @@
#include <xrpl/protocol/Serializer.h> #include <xrpl/protocol/Serializer.h>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <format> #include <format>
#include <span>
#include <stdexcept> #include <stdexcept>
#include <utility> #include <utility>
@@ -17,7 +19,7 @@ namespace xrpl {
STVector256::STVector256(SerialIter& sit, SField const& name) : STBase(name) STVector256::STVector256(SerialIter& sit, SField const& name) : STBase(name)
{ {
auto const slice = sit.getSlice(sit.getVLDataLength()); auto const slice = sit.getVL();
if (slice.size() % UInt256::size() != 0) if (slice.size() % UInt256::size() != 0)
{ {
@@ -25,12 +27,12 @@ STVector256::STVector256(SerialIter& sit, SField const& name) : STBase(name)
std::format("Bad serialization for STVector256: {}", slice.size())); std::format("Bad serialization for STVector256: {}", slice.size()));
} }
auto const cnt = slice.size() / UInt256::size(); value_.reserve(slice.size() / UInt256::kBytes);
value_.reserve(cnt); SerialIter inner{slice};
for (std::size_t i = 0; i != cnt; ++i) while (!inner.empty())
value_.push_back(UInt256::fromRaw(slice.substr(i * UInt256::size(), UInt256::size()))); value_.push_back(inner.get256());
} }
STBase* STBase*
@@ -62,7 +64,11 @@ STVector256::add(Serializer& s) const
{ {
XRPL_ASSERT(getFName().isBinary(), "xrpl::STVector256::add : field is binary"); XRPL_ASSERT(getFName().isBinary(), "xrpl::STVector256::add : field is binary");
XRPL_ASSERT(getFName().fieldType == STI_VECTOR256, "xrpl::STVector256::add : valid field type"); XRPL_ASSERT(getFName().fieldType == STI_VECTOR256, "xrpl::STVector256::add : valid field type");
s.addVL(value_.begin(), value_.end(), value_.size() * (256 / 8));
// 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()});
} }
bool bool
+3 -2
View File
@@ -104,8 +104,9 @@ parseGenericSeed(std::string const& str, bool rfc1751)
std::string key; std::string key;
if (RFC1751::getKeyFromEnglish(key, str) == 1) if (RFC1751::getKeyFromEnglish(key, str) == 1)
{ {
Blob const blob(key.rbegin(), key.rend()); std::array<std::uint8_t, UInt128::size()> bytes{};
return Seed{UInt128::fromRaw(blob)}; std::ranges::reverse_copy(key, bytes.begin());
return Seed{UInt128{bytes}};
} }
} }
-550
View File
@@ -1,550 +0,0 @@
#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 <format>
#include <stdexcept>
#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>(std::format("gFID: uncommon type out of range {}", type));
}
if (name == 0)
{
// uncommon name
name = get8();
if (name < 16)
Throw<std::runtime_error>(std::format("gFID: uncommon name out of range {}", 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; Serializer ss;
ss.add32(prefix); ss.add32(prefix);
st.addWithoutSigningFields(ss); st.addWithoutSigningFields(ss);
return verify(pk, Slice(ss.data(), ss.size()), Slice(sig->data(), sig->size())); return verify(pk, ss.slice(), *sig);
} }
// Questions regarding buildMultiSigningData: // Questions regarding buildMultiSigningData:
+3 -24
View File
@@ -35,7 +35,7 @@ constexpr std::size_t kIsoCodeLength = 3;
std::string std::string
to_string(Currency const& currency) to_string(Currency const& currency)
{ {
if (currency == beast::kZero) if (currency == xrpCurrency())
return systemCurrencyCode(); return systemCurrencyCode();
if (currency == noCurrency()) if (currency == noCurrency())
@@ -62,7 +62,7 @@ to_string(Currency const& currency)
} }
bool bool
toCurrency(Currency& currency, std::string const& code) toCurrency(Currency& currency, std::string_view code)
{ {
if (code.empty() || code == systemCurrencyCode()) if (code.empty() || code == systemCurrencyCode())
{ {
@@ -87,7 +87,7 @@ toCurrency(Currency& currency, std::string const& code)
} }
Currency Currency
toCurrency(std::string const& code) toCurrency(std::string_view code)
{ {
Currency currency; Currency currency;
if (!toCurrency(currency, code)) if (!toCurrency(currency, code))
@@ -95,25 +95,4 @@ toCurrency(std::string const& code)
return currency; 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 } // namespace xrpl
+2 -2
View File
@@ -209,7 +209,7 @@ AttestationClaim::message(
Serializer s; Serializer s;
o.add(s); o.add(s);
return std::move(s.modData()); return s.takeData();
} }
std::vector<std::uint8_t> std::vector<std::uint8_t>
@@ -346,7 +346,7 @@ AttestationCreateAccount::message(
Serializer s; Serializer s;
o.add(s); o.add(s);
return std::move(s.modData()); return s.takeData();
} }
std::vector<std::uint8_t> std::vector<std::uint8_t>
+4 -8
View File
@@ -197,9 +197,7 @@ logMftAct(
bool bool
Manifest::verify() const Manifest::verify() const
{ {
STObject st(sfGeneric); STObject const st(SerialIter{makeSlice(serialized)}, sfGeneric);
SerialIter sit(serialized.data(), serialized.size());
st.set(sit);
// The manifest must either have a signing key or be revoked. This check // The manifest must either have a signing key or be revoked. This check
// prevents us from accessing an unseated signingKey in the next check. // prevents us from accessing an unseated signingKey in the next check.
@@ -217,9 +215,7 @@ Manifest::verify() const
UInt256 UInt256
Manifest::hash() const Manifest::hash() const
{ {
STObject st(sfGeneric); STObject const st(SerialIter{makeSlice(serialized)}, sfGeneric);
SerialIter sit(serialized.data(), serialized.size());
st.set(sit);
return st.getHash(HashPrefix::Manifest); return st.getHash(HashPrefix::Manifest);
} }
@@ -245,7 +241,7 @@ std::optional<Blob>
Manifest::getSignature() const Manifest::getSignature() const
{ {
STObject st(sfGeneric); STObject st(sfGeneric);
SerialIter sit(serialized.data(), serialized.size()); SerialIter sit(makeSlice(serialized));
st.set(sit); st.set(sit);
if (!get(st, sfSignature)) if (!get(st, sfSignature))
return std::nullopt; return std::nullopt;
@@ -256,7 +252,7 @@ Blob
Manifest::getMasterSignature() const Manifest::getMasterSignature() const
{ {
STObject st(sfGeneric); STObject st(sfGeneric);
SerialIter sit(serialized.data(), serialized.size()); SerialIter sit(makeSlice(serialized));
st.set(sit); st.set(sit);
return st.getFieldVL(sfMasterSignature); return st.getFieldVL(sfMasterSignature);
} }
+1 -1
View File
@@ -901,7 +901,7 @@ SHAMap::writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const
Serializer s; Serializer s;
node->serializeWithPrefix(s); node->serializeWithPrefix(s);
f_.db().store(t, std::move(s.modData()), node->getHash().asUInt256(), ledgerSeq_); f_.db().store(t, s.takeData(), node->getHash().asUInt256(), ledgerSeq_);
return node; 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) for (auto i = 0u; i < kBranchFactor; ++i)
{ {
hashes[i].asUInt256() = si.getBitString<256>(); hashes[i].asUInt256() = si.get256();
if (hashes[i].isNonZero()) if (hashes[i].isNonZero())
ret->isBranch_ |= (1u << i); ret->isBranch_ |= (1u << i);
@@ -173,7 +173,7 @@ SHAMapInnerNode::makeCompressedInner(Slice data)
while (!si.empty()) while (!si.empty())
{ {
auto const hash = si.getBitString<256>(); auto const hash = si.get256();
auto const pos = si.get8(); auto const pos = si.get8();
if (pos >= kBranchFactor) if (pos >= kBranchFactor)
@@ -231,7 +231,7 @@ SHAMapInnerNode::serializeForWire(Serializer& s) const
auto hashes = hashesAndChildren_.getHashes(); auto hashes = hashesAndChildren_.getHashes();
iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) { iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
s.addBitString(hashes[indexNum].asUInt256()); s.addBitString(hashes[indexNum].asUInt256());
s.add8(branchNum); s.add8(checkedCast<std::uint8_t>(branchNum));
}); });
s.add8(kWireTypeCompressedInner); s.add8(kWireTypeCompressedInner);
} }
+10 -7
View File
@@ -2,6 +2,7 @@
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h> #include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Serializer.h> #include <xrpl/protocol/Serializer.h>
#include <xrpl/shamap/SHAMap.h> #include <xrpl/shamap/SHAMap.h>
@@ -10,6 +11,7 @@
#include <cstddef> #include <cstddef>
#include <format> #include <format>
#include <optional> #include <optional>
#include <span>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@@ -86,8 +88,8 @@ SHAMapNodeID::getRawString() const
{ {
Serializer s(33); Serializer s(33);
s.addBitString(id_); s.addBitString(id_);
s.add8(depth_); s.add8(checkedCast<std::uint8_t>(depth_));
return s.getString(); return std::string(static_cast<char const*>(s.data()), s.size());
} }
SHAMapNodeID SHAMapNodeID
@@ -129,14 +131,15 @@ deserializeSHAMapNodeID(void const* data, std::size_t size)
{ {
std::optional<SHAMapNodeID> ret; std::optional<SHAMapNodeID> ret;
if (size == 33) if (size == UInt256::size() + 1)
{ {
unsigned int const depth = *(static_cast<unsigned char const*>(data) + 32); std::span const bytes{static_cast<unsigned char const*>(data), size};
if (depth <= SHAMap::kLeafDepth)
if (unsigned int const depth = bytes[UInt256::size()]; depth <= SHAMap::kLeafDepth)
{ {
// Reject a serialized ID carrying bits below its own depth. Checked before // Reject a serialized ID carrying bits below its own depth. Checked before
// constructing, since the constructor asserts that same property. // constructing the node, since the constructor asserts that same property.
if (auto const id = UInt256::fromVoid(data); isPrefixOfAtDepth(id, depth, id)) if (UInt256 const id{bytes.first<UInt256::size()>()}; isPrefixOfAtDepth(id, depth, id))
ret.emplace(depth, id); ret.emplace(depth, id);
} }
} }
+10 -11
View File
@@ -461,17 +461,18 @@ SHAMap::getNodeFat(
std::stack<std::tuple<SHAMapTreeNode*, SHAMapNodeID, std::uint32_t>> stack; std::stack<std::tuple<SHAMapTreeNode*, SHAMapNodeID, std::uint32_t>> stack;
stack.emplace(node, nodeID, depth); stack.emplace(node, nodeID, depth);
Serializer s(8192);
while (!stack.empty()) while (!stack.empty())
{ {
std::tie(node, nodeID, depth) = stack.top(); std::tie(node, nodeID, depth) = stack.top();
stack.pop(); stack.pop();
// Add this node to the reply // Add this node to the reply
s.erase();
data.emplace_back(nodeID, node->isLeaf(), [&node]() {
Serializer s(1024);
node->serializeForWire(s); node->serializeForWire(s);
data.emplace_back(nodeID, node->isLeaf(), s.getData()); return s.takeData();
}());
if (node->isInner()) if (node->isInner())
{ {
@@ -499,9 +500,9 @@ SHAMap::getNodeFat(
else if (childNode->isInner() || fatLeaves) else if (childNode->isInner() || fatLeaves)
{ {
// Just include this node // Just include this node
s.erase(); Serializer s(1024);
childNode->serializeForWire(s); childNode->serializeForWire(s);
data.emplace_back(childID, childNode->isLeaf(), s.getData()); data.emplace_back(childID, childNode->isLeaf(), s.takeData());
} }
} }
} }
@@ -554,8 +555,7 @@ SHAMap::addRootNode(
{ {
Serializer s; Serializer s;
root_->serializeWithPrefix(s); root_->serializeWithPrefix(s);
filter->gotNode( filter->gotNode(false, root_->getHash(), ledgerSeq_, s.takeData(), root_->getType());
false, root_->getHash(), ledgerSeq_, std::move(s.modData()), root_->getType());
} }
return SHAMapAddNode::useful(); return SHAMapAddNode::useful();
@@ -646,8 +646,7 @@ SHAMap::addKnownNode(
{ {
Serializer s; Serializer s;
treeNode->serializeWithPrefix(s); treeNode->serializeWithPrefix(s);
filter->gotNode( filter->gotNode(false, childHash, ledgerSeq_, s.takeData(), treeNode->getType());
false, childHash, ledgerSeq_, std::move(s.modData()), treeNode->getType());
} }
return SHAMapAddNode::useful(); return SHAMapAddNode::useful();
@@ -815,7 +814,7 @@ SHAMap::getProofPath(UInt256 const& key) const
{ {
Serializer s; Serializer s;
stack.top().first->serializeForWire(s); stack.top().first->serializeForWire(s);
path.emplace_back(std::move(s.modData())); path.emplace_back(s.takeData());
stack.pop(); stack.pop();
} }
+30 -65
View File
@@ -26,6 +26,29 @@
namespace xrpl { 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 SHAMapTreeNodePtr
SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid) SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid)
{ {
@@ -46,80 +69,26 @@ SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashVal
SHAMapTreeNodePtr SHAMapTreeNodePtr
SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid) SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid)
{ {
Serializer s(data.data(), data.size()); auto const [item, tag] = splitTag(data, "TXN+MD");
UInt256 tag;
if (s.size() < tag.kBytes)
{
Throw<std::runtime_error>(std::format(
"Short TXN+MD node: {} bytes (minimum {} required for tag)", s.size(), tag.kBytes));
}
// 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>(std::format(
"Short TXN+MD node: failed to read tag at offset {}", s.size() - tag.kBytes));
}
s.chop(tag.kBytes);
if (s.size() < kMinShaMapItemBytes)
{
Throw<std::runtime_error>(std::format(
"Short TXN+MD node: {} bytes after tag removal (minimum {} required)",
s.size(),
kMinShaMapItemBytes));
}
auto item = makeShamapitem(tag, s.slice());
if (hashValid) if (hashValid)
return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(std::move(item), 0, hash); return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(makeShamapitem(tag, item), 0, hash);
return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(std::move(item), 0); return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(makeShamapitem(tag, item), 0);
} }
SHAMapTreeNodePtr SHAMapTreeNodePtr
SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid) SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid)
{ {
Serializer s(data.data(), data.size()); auto const [item, tag] = splitTag(data, "AS");
UInt256 tag;
if (s.size() < tag.kBytes)
{
Throw<std::runtime_error>(std::format(
"Short AS node: {} bytes (minimum {} required for tag)", s.size(), tag.kBytes));
}
// 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>(
std::format("Short AS node: failed to read tag at offset {}", s.size() - tag.kBytes));
}
s.chop(tag.kBytes);
if (tag.isZero()) if (tag.isZero())
Throw<std::runtime_error>("Invalid AS node"); Throw<std::runtime_error>("Invalid AS node");
if (s.size() < kMinShaMapItemBytes)
{
Throw<std::runtime_error>(std::format(
"Short AS node: {} bytes after tag removal (minimum {} required)",
s.size(),
kMinShaMapItemBytes));
}
auto item = makeShamapitem(tag, s.slice());
if (hashValid) if (hashValid)
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(std::move(item), 0, hash); return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(makeShamapitem(tag, item), 0, hash);
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(std::move(item), 0); return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(makeShamapitem(tag, item), 0);
} }
SHAMapTreeNodePtr SHAMapTreeNodePtr
@@ -159,11 +128,7 @@ SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash)
if (rawNode.size() < 4) if (rawNode.size() < 4)
Throw<std::runtime_error>("prefix: short node"); Throw<std::runtime_error>("prefix: short node");
// FIXME: Use SerialIter::get32? auto const type = safeCast<HashPrefix>(SerialIter{rawNode}.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); rawNode.removePrefix(4);
+4 -3
View File
@@ -38,7 +38,7 @@ ApplyContext::ApplyContext(
, parentBatchId_(parentBatchId) , parentBatchId_(parentBatchId)
{ {
XRPL_ASSERT( XRPL_ASSERT(
parentBatchId.has_value() == ((flags_ & TapBatch) == TapBatch), parentBatchId.has_value() == ((flags_ & ApplyFlags::Batch) == ApplyFlags::Batch),
"Parent Batch ID should be set if batch apply flag is set"); "Parent Batch ID should be set if batch apply flag is set");
view_.emplace(&base_, flags_); view_.emplace(&base_, flags_);
} }
@@ -53,13 +53,14 @@ std::optional<TxMeta>
ApplyContext::apply(TER ter) ApplyContext::apply(TER ter)
{ {
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor // NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
return view_->apply(base_, tx, ter, parentBatchId_, (flags_ & TapDryRun) != 0u, journal); return view_->apply(
base_, tx, ter, parentBatchId_, (flags_ & ApplyFlags::DryRun) != ApplyFlags::None, journal);
} }
void void
ApplyContext::addOrderBook(Book const& book) ApplyContext::addOrderBook(Book const& book)
{ {
if ((flags_ & TapDryRun) == TapNone) if ((flags_ & ApplyFlags::DryRun) == ApplyFlags::None)
registry.get().getOrderBookDB().addOrderBook(book); registry.get().getOrderBookDB().addOrderBook(book);
} }
+9 -7
View File
@@ -134,7 +134,7 @@ preflightCheckSigningKey(STObject const& sigObject, beast::Journal j)
std::optional<NotTEC> std::optional<NotTEC>
preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::Journal j) preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::Journal j)
{ {
if ((flags & TapDryRun) != 0u) // simulation if ((flags & ApplyFlags::DryRun) != ApplyFlags::None) // simulation
{ {
std::optional<Slice> const signature = sigObject[~sfTxnSignature]; std::optional<Slice> const signature = sigObject[~sfTxnSignature];
if (signature && !signature->empty()) if (signature && !signature->empty())
@@ -510,7 +510,8 @@ Transactor::minimumFee(
Fees const& fees, Fees const& fees,
ApplyFlags flags) ApplyFlags flags)
{ {
return scaleFeeLoad(baseFee, registry.getFeeTrack(), fees, (flags & TapUnlimited) != 0u); return scaleFeeLoad(
baseFee, registry.getFeeTrack(), fees, (flags & ApplyFlags::Unlimited) != ApplyFlags::None);
} }
TER TER
@@ -521,7 +522,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
auto const feePaid = ctx.tx[sfFee].xrp(); auto const feePaid = ctx.tx[sfFee].xrp();
if ((ctx.flags & TapBatch) != 0u) if ((ctx.flags & ApplyFlags::Batch) != ApplyFlags::None)
{ {
if (feePaid == beast::kZero) if (feePaid == beast::kZero)
return tesSUCCESS; return tesSUCCESS;
@@ -951,7 +952,8 @@ Transactor::checkSign(
return tesSUCCESS; return tesSUCCESS;
} }
if (((flags & TapDryRun) != 0u) && pkSigner.empty() && !sigObject.isFieldPresent(sfSigners)) if (((flags & ApplyFlags::DryRun) != ApplyFlags::None) && pkSigner.empty() &&
!sigObject.isFieldPresent(sfSigners))
{ {
// simulate: skip signature validation when neither SigningPubKey nor // simulate: skip signature validation when neither SigningPubKey nor
// Signers are provided // Signers are provided
@@ -1122,7 +1124,7 @@ Transactor::checkMultiSign(
} }
XRPL_ASSERT( XRPL_ASSERT(
(flags & TapDryRun) || !spk.empty(), (flags & ApplyFlags::DryRun) != ApplyFlags::None || !spk.empty(),
"xrpl::Transactor::checkMultiSign : non-empty signer or " "xrpl::Transactor::checkMultiSign : non-empty signer or "
"simulation"); "simulation");
AccountID const signingAcctIDFromPubKey = AccountID const signingAcctIDFromPubKey =
@@ -1604,7 +1606,7 @@ Transactor::operator()()
if (ctx_.size() > kOversizeMetaDataCap) if (ctx_.size() > kOversizeMetaDataCap)
result = tecOVERSIZE; result = tecOVERSIZE;
if (isTecClaim(result) && ((view().flags() & TapFailHard) != 0u)) if (isTecClaim(result) && ((view().flags() & ApplyFlags::FailHard) != ApplyFlags::None))
{ {
// If the TapFailHard flag is set, a tec result // If the TapFailHard flag is set, a tec result
// must not do anything // must not do anything
@@ -1684,7 +1686,7 @@ Transactor::operator()()
// Once we call apply, we will no longer be able to look at view() // Once we call apply, we will no longer be able to look at view()
metadata = ctx_.apply(result); metadata = ctx_.apply(result);
if ((ctx_.flags() & TapDryRun) != 0u) if ((ctx_.flags() & ApplyFlags::DryRun) != ApplyFlags::None)
return logger(result, false, std::move(metadata)); return logger(result, false, std::move(metadata));
return logger(result, canApply, std::move(metadata)); return logger(result, canApply, std::move(metadata));
+2 -2
View File
@@ -215,7 +215,7 @@ applyBatchTransactions(
auto applyOneTransaction = [&registry, &j, &parentBatchId, &batchView](STTx const& tx) { auto applyOneTransaction = [&registry, &j, &parentBatchId, &batchView](STTx const& tx) {
OpenView perTxBatchView(kBatchView, batchView); OpenView perTxBatchView(kBatchView, batchView);
auto const ret = apply(registry, perTxBatchView, parentBatchId, tx, TapBatch, j); auto const ret = apply(registry, perTxBatchView, parentBatchId, tx, ApplyFlags::Batch, j);
XRPL_ASSERT( XRPL_ASSERT(
ret.applied == (isTesSuccess(ret.ter) || isTecClaim(ret.ter)), ret.applied == (isTesSuccess(ret.ter) || isTecClaim(ret.ter)),
"Inner transaction should not be applied"); "Inner transaction should not be applied");
@@ -274,7 +274,7 @@ applyTransaction(
{ {
// Returns false if the transaction has need not be retried. // Returns false if the transaction has need not be retried.
if (retryAssured) if (retryAssured)
flags = flags | TapRetry; flags = flags | ApplyFlags::Retry;
JLOG(j.debug()) << "TXN " << txn.getTransactionID() << (retryAssured ? "/retry" : "/final"); JLOG(j.debug()) << "TXN " << txn.getTransactionID() << (retryAssured ? "/retry" : "/final");
+4 -2
View File
@@ -346,7 +346,8 @@ preflight(
// builds; re-check the same invariant here so a release build can't // builds; re-check the same invariant here so a release build can't
// silently skip a proposed transaction's signature-presence checks // silently skip a proposed transaction's signature-presence checks
// outside of a dry run. // outside of a dry run.
if ((flags & TapProposal) != TapNone && (flags & TapDryRun) == TapNone) if ((flags & ApplyFlags::Proposal) != ApplyFlags::None &&
(flags & ApplyFlags::DryRun) == ApplyFlags::None)
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun."; JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";
@@ -377,7 +378,8 @@ preflight(
PreflightContext const pfCtx(registry, tx, parentBatchId, rules, flags, j); PreflightContext const pfCtx(registry, tx, parentBatchId, rules, flags, j);
// See the comment in the other preflight() overload above. // See the comment in the other preflight() overload above.
if ((flags & TapProposal) != TapNone && (flags & TapDryRun) == TapNone) if ((flags & ApplyFlags::Proposal) != ApplyFlags::None &&
(flags & ApplyFlags::DryRun) == ApplyFlags::None)
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun."; JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";
+1 -1
View File
@@ -985,7 +985,7 @@ std::optional<std::variant<Quality, AMMOffer<TIn, TOut>>>
BookStep<TIn, TOut, TDerived>::tip(ReadView const& view) const BookStep<TIn, TOut, TDerived>::tip(ReadView const& view) const
{ {
// This can be simplified (and sped up) if directories are never empty. // This can be simplified (and sped up) if directories are never empty.
Sandbox sb(&view, TapNone); Sandbox sb(&view, ApplyFlags::None);
BookTip bt(sb, book_); BookTip bt(sb, book_);
auto const lobQuality = bt.step(j_) ? std::optional<Quality>(bt.quality()) : std::nullopt; auto const lobQuality = bt.step(j_) ? std::optional<Quality>(bt.quality()) : std::nullopt;
// Multi-path offer generates an offer with the quality // Multi-path offer generates an offer with the quality
+13 -27
View File
@@ -57,10 +57,8 @@ checkNear(IOUAmount const& expected, IOUAmount const& actual)
static bool static bool
isXRPAccount(STPathElement const& pe) isXRPAccount(STPathElement const& pe)
{ {
if (pe.getNodeType() != STPathElement::TypeAccount) return pe.getNodeType() == STPathElement::TypeAccount && isXRP(pe.getAccountID());
return false; }
return isXRP(pe.getAccountID());
};
static std::pair<TER, std::unique_ptr<Step>> static std::pair<TER, std::unique_ptr<Step>>
toStep( toStep(
@@ -71,11 +69,8 @@ toStep(
{ {
auto& j = ctx.j; auto& j = ctx.j;
if (ctx.isFirst && e1->isAccount() && if (ctx.isFirst && e1->isAccount() && e1->hasCurrency() && e1->getPathAsset().isXRP())
((e1->getNodeType() & STPathElement::TypeCurrency) != 0u) && e1->getPathAsset().isXRP())
{
return makeXrpEndpointStep(ctx, e1->getAccountID()); return makeXrpEndpointStep(ctx, e1->getAccountID());
}
if (ctx.isLast && isXRPAccount(*e1) && e2->isAccount()) if (ctx.isLast && isXRPAccount(*e1) && e2->isAccount())
return makeXrpEndpointStep(ctx, e2->getAccountID()); return makeXrpEndpointStep(ctx, e2->getAccountID());
@@ -117,15 +112,9 @@ toStep(
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
XRPL_ASSERT( XRPL_ASSERT(e2->hasAsset() || e2->hasIssuer(), "xrpl::toStep : currency or issuer");
(e2->getNodeType() & STPathElement::TypeAsset) || PathAsset const outAsset = e2->hasAsset() ? e2->getPathAsset() : curAsset;
(e2->getNodeType() & STPathElement::TypeIssuer), auto const outIssuer = e2->hasIssuer() ? e2->getIssuerID() : curAsset.getIssuer();
"xrpl::toStep : currency or issuer");
PathAsset const outAsset =
((e2->getNodeType() & STPathElement::TypeAsset) != 0u) ? e2->getPathAsset() : curAsset;
auto const outIssuer = ((e2->getNodeType() & STPathElement::TypeIssuer) != 0u)
? e2->getIssuerID()
: curAsset.getIssuer();
if (isXRP(curAsset) && outAsset.isXRP()) if (isXRP(curAsset) && outAsset.isXRP())
{ {
@@ -197,16 +186,15 @@ toStrand(
for (std::size_t i = 0; i < path.size(); ++i) for (std::size_t i = 0; i < path.size(); ++i)
{ {
auto const& pe = path[i]; auto const& pe = path[i];
auto const t = pe.getNodeType();
if (((t & ~STPathElement::TypeAll) != 0u) || (t == 0u)) if ((pe.getNodeType() & ~STPathElement::TypeAll) != STPathElement::TypeNone || pe.isNone())
return {temBAD_PATH, Strand{}}; return {temBAD_PATH, Strand{}};
bool const hasAccount = (t & STPathElement::TypeAccount) != 0u; bool const hasAccount = pe.isType(STPathElement::TypeAccount);
bool const hasIssuer = (t & STPathElement::TypeIssuer) != 0u; bool const hasIssuer = pe.hasIssuer();
bool const hasCurrency = (t & STPathElement::TypeCurrency) != 0u; bool const hasCurrency = pe.hasCurrency();
bool const hasMPT = (t & STPathElement::TypeMpt) != 0u; bool const hasMPT = pe.hasMPT();
bool const hasAsset = (t & STPathElement::TypeAsset) != 0u; bool const hasAsset = pe.hasAsset();
if (hasAccount && (hasIssuer || hasCurrency)) if (hasAccount && (hasIssuer || hasCurrency))
return {temBAD_PATH, Strand{}}; return {temBAD_PATH, Strand{}};
@@ -250,9 +238,7 @@ toStrand(
}(); }();
// Currency or MPT // Currency or MPT
auto hasAsset = [](STPathElement const pe) { auto hasAsset = [](STPathElement const& pe) { return pe.hasAsset(); };
return pe.getNodeType() & STPathElement::TypeAsset;
};
std::vector<STPathElement> normPath; std::vector<STPathElement> normPath;
// reserve enough for the path, the implied source, destination, // reserve enough for the path, the implied source, destination,
@@ -187,7 +187,8 @@ AccountSet::preclaim(PreclaimContext const& ctx)
if (!dirIsEmpty(ctx.view, keylet::ownerDir(id))) if (!dirIsEmpty(ctx.view, keylet::ownerDir(id)))
{ {
JLOG(ctx.j.trace()) << "Retry: Owner directory not empty."; JLOG(ctx.j.trace()) << "Retry: Owner directory not empty.";
return ((ctx.flags & TapRetry) != 0u) ? TER{terOWNERS} : TER{tecOWNERS}; return ((ctx.flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terOWNERS}
: TER{tecOWNERS};
} }
} }
+1 -1
View File
@@ -89,7 +89,7 @@ applyVote(ApplyContext& ctx, Sandbox& sb, AccountID const& accountID, beast::Jou
auto const lpTokensNew = ammLPHolds(sb, *ammSle, accountID, ctx.journal); auto const lpTokensNew = ammLPHolds(sb, *ammSle, accountID, ctx.journal);
std::optional<STAmount> minTokens; std::optional<STAmount> minTokens;
std::size_t minPos{0}; std::size_t minPos{0};
AccountID minAccount{0}; AccountID minAccount;
std::uint32_t minFee{0}; std::uint32_t minFee{0};
STArray updatedVoteSlots; STArray updatedVoteSlots;
Number num{0}; Number num{0};
@@ -295,7 +295,8 @@ OfferCreate::checkAcceptAsset(
JLOG(j.debug()) << "delay: can't receive IOUs from non-existent issuer: " JLOG(j.debug()) << "delay: can't receive IOUs from non-existent issuer: "
<< to_string(asset.getIssuer()); << to_string(asset.getIssuer());
return ((flags & TapRetry) != 0u) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER}; return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_ACCOUNT}
: TER{tecNO_ISSUER};
} }
// An account cannot create a trustline to itself, so no line can exist // An account cannot create a trustline to itself, so no line can exist
@@ -318,7 +319,8 @@ OfferCreate::checkAcceptAsset(
{ {
JLOG(j.debug()) << "delay: can't receive IOUs from issuer with " JLOG(j.debug()) << "delay: can't receive IOUs from issuer with "
"DisallowIncomingTrustline set"; "DisallowIncomingTrustline set";
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE}; return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_LINE}
: TER{tecNO_LINE};
} }
} }
@@ -326,7 +328,8 @@ OfferCreate::checkAcceptAsset(
{ {
if (!trustLine) if (!trustLine)
{ {
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE}; return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_LINE}
: TER{tecNO_LINE};
} }
// Entries have a canonical representation, determined by a // Entries have a canonical representation, determined by a
@@ -342,7 +345,8 @@ OfferCreate::checkAcceptAsset(
JLOG(j.debug()) << "delay: can't receive IOUs from " JLOG(j.debug()) << "delay: can't receive IOUs from "
"issuer without auth."; "issuer without auth.";
return ((flags & TapRetry) != 0u) ? TER{terNO_AUTH} : TER{tecNO_AUTH}; return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_AUTH}
: TER{tecNO_AUTH};
} }
} }
@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.h> #include <xrpl/basics/Log.h>
#include <xrpl/ledger/ApplyView.h> #include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h> #include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/EscrowEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h> #include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h> #include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h> #include <xrpl/ledger/helpers/MPTokenHelpers.h>
@@ -94,13 +95,12 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
if (ctx.view.rules().enabled(featureTokenEscrow)) if (ctx.view.rules().enabled(featureTokenEscrow))
{ {
auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]); auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy); EscrowEntryR const escrow(ctx.tx[sfOwner], seqProxy, ctx.view);
auto const slep = ctx.view.read(k); if (!escrow)
if (!slep)
return tecNO_TARGET; return tecNO_TARGET;
AccountID const account = (*slep)[sfAccount]; AccountID const account = (*escrow)[sfAccount];
STAmount const amount = (*slep)[sfAmount]; STAmount const amount = (*escrow)[sfAmount];
if (!isXRP(amount)) if (!isXRP(amount))
{ {
@@ -120,9 +120,8 @@ TER
EscrowCancel::doApply() EscrowCancel::doApply()
{ {
auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]); auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
auto const k = keylet::escrow(ctx_.tx[sfOwner], seqProxy); EscrowEntryW escrow(ctx_.tx[sfOwner], seqProxy, ctx_.view(), j_);
auto const slep = ctx_.view().peek(k); if (!escrow)
if (!slep)
{ {
if (ctx_.view().rules().enabled(featureTokenEscrow)) if (ctx_.view().rules().enabled(featureTokenEscrow))
return tecINTERNAL; // LCOV_EXCL_LINE return tecINTERNAL; // LCOV_EXCL_LINE
@@ -133,19 +132,19 @@ EscrowCancel::doApply()
auto const now = ctx_.view().header().parentCloseTime; auto const now = ctx_.view().header().parentCloseTime;
// No cancel time specified: can't execute at all. // No cancel time specified: can't execute at all.
if (!(*slep)[~sfCancelAfter]) if (!(*escrow)[~sfCancelAfter])
return tecNO_PERMISSION; return tecNO_PERMISSION;
// Too soon: can't execute before the cancel time. // Too soon: can't execute before the cancel time.
if (!after(now, (*slep)[sfCancelAfter])) if (!after(now, (*escrow)[sfCancelAfter]))
return tecNO_PERMISSION; return tecNO_PERMISSION;
AccountID const account = (*slep)[sfAccount]; AccountID const account = (*escrow)[sfAccount];
// Remove escrow from owner directory // Remove escrow from owner directory
{ {
auto const page = (*slep)[sfOwnerNode]; auto const page = (*escrow)[sfOwnerNode];
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true)) if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, escrow.key(), true))
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from owner."; JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
@@ -155,9 +154,10 @@ EscrowCancel::doApply()
} }
// Remove escrow from recipient's owner directory, if present. // Remove escrow from recipient's owner directory, if present.
if (auto const optPage = (*slep)[~sfDestinationNode]; optPage) if (auto const optPage = (*escrow)[~sfDestinationNode]; optPage)
{ {
if (!ctx_.view().dirRemove(keylet::ownerDir((*slep)[sfDestination]), *optPage, k.key, true)) if (!ctx_.view().dirRemove(
keylet::ownerDir((*escrow)[sfDestination]), *optPage, escrow.key(), true))
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient."; JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
@@ -167,13 +167,13 @@ EscrowCancel::doApply()
} }
auto const sle = ctx_.view().peek(keylet::account(account)); auto const sle = ctx_.view().peek(keylet::account(account));
STAmount const amount = slep->getFieldAmount(sfAmount); STAmount const amount = escrow->getFieldAmount(sfAmount);
// The return can re-create a holding the owner deleted while the escrow // The return can re-create a holding the owner deleted while the escrow
// was pending; the removed escrow must not be counted against its reserve. // was pending; the removed escrow must not be counted against its reserve.
bool const recycleReserve = ctx_.view().rules().enabled(fixCleanup3_4_0); bool const recycleReserve = ctx_.view().rules().enabled(fixCleanup3_4_0);
if (recycleReserve) if (recycleReserve)
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal); decreaseOwnerCountForObject(ctx_.view(), sle, escrow.mutableRawSle(), 1, ctx_.journal);
// Transfer amount back to the owner // Transfer amount back to the owner
if (isXRP(amount)) if (isXRP(amount))
@@ -192,7 +192,7 @@ EscrowCancel::doApply()
return escrowUnlockApplyHelper<T>( return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(), ctx_.getApplyViewContext(),
kParityRate, kParityRate,
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep, ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : escrow.mutableRawSle(),
preFeeBalance_, preFeeBalance_,
amount, amount,
issuer, issuer,
@@ -206,9 +206,9 @@ EscrowCancel::doApply()
return ret; // LCOV_EXCL_LINE return ret; // LCOV_EXCL_LINE
// Remove escrow from issuers owner directory, if present. // Remove escrow from issuers owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage) if (auto const optPage = (*escrow)[~sfIssuerNode]; optPage)
{ {
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true)) if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, escrow.key(), true))
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient."; JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
@@ -219,10 +219,10 @@ EscrowCancel::doApply()
} }
if (!recycleReserve) if (!recycleReserve)
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal); decreaseOwnerCountForObject(ctx_.view(), sle, escrow.mutableRawSle(), 1, ctx_.journal);
// Remove escrow from ledger // Remove escrow from ledger
ctx_.view().erase(slep); escrow.erase();
return tesSUCCESS; return tesSUCCESS;
} }
@@ -7,6 +7,7 @@
#include <xrpl/core/ServiceRegistry.h> #include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h> #include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h> #include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/EscrowEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h> #include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h> #include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h> #include <xrpl/ledger/helpers/MPTokenHelpers.h>
@@ -481,30 +482,31 @@ EscrowCreate::doApply()
// Create escrow in ledger. Note that we use the value from the // Create escrow in ledger. Note that we use the value from the
// sequence or ticket. For more explanation see comments in SeqProxy.h. // sequence or ticket. For more explanation see comments in SeqProxy.h.
Keylet const escrowKeylet = keylet::escrow(accountID_, ctx_.tx.getSeqProxy()); EscrowEntryW escrow(accountID_, ctx_.tx.getSeqProxy(), ctx_.view(), j_);
auto const slep = std::make_shared<SLE>(escrowKeylet); Keylet const escrowKeylet = escrow.keylet();
(*slep)[sfAmount] = amount; escrow.newSLE();
(*slep)[sfAccount] = accountID_; (*escrow)[sfAmount] = amount;
(*slep)[~sfCondition] = ctx_.tx[~sfCondition]; (*escrow)[sfAccount] = accountID_;
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag]; (*escrow)[~sfCondition] = ctx_.tx[~sfCondition];
(*slep)[sfDestination] = ctx_.tx[sfDestination]; (*escrow)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter]; (*escrow)[sfDestination] = ctx_.tx[sfDestination];
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter]; (*escrow)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag]; (*escrow)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
(*escrow)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
if (ctx_.view().rules().enabled(fixIncludeKeyletFields)) if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
{ {
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value(); (*escrow)[sfSequence] = ctx_.tx.getSeqProxy().value();
} }
if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount)) if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount))
{ {
auto const xferRate = transferRate(ctx_.view(), amount); auto const xferRate = transferRate(ctx_.view(), amount);
if (xferRate != kParityRate) if (xferRate != kParityRate)
(*slep)[sfTransferRate] = xferRate.value; (*escrow)[sfTransferRate] = xferRate.value;
} }
ctx_.view().insert(slep); escrow.insert();
// Add escrow to sender's owner directory // Add escrow to sender's owner directory
{ {
@@ -512,7 +514,7 @@ EscrowCreate::doApply()
keylet::ownerDir(accountID_), escrowKeylet, describeOwnerDir(accountID_)); keylet::ownerDir(accountID_), escrowKeylet, describeOwnerDir(accountID_));
if (!page) if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfOwnerNode] = *page; (*escrow)[sfOwnerNode] = *page;
} }
// If it's not a self-send, add escrow to recipient's owner directory. // If it's not a self-send, add escrow to recipient's owner directory.
@@ -523,7 +525,7 @@ EscrowCreate::doApply()
ctx_.view().dirInsert(keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest)); ctx_.view().dirInsert(keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
if (!page) if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfDestinationNode] = *page; (*escrow)[sfDestinationNode] = *page;
} }
// IOU escrow objects are added to the issuer's owner directory to help // IOU escrow objects are added to the issuer's owner directory to help
@@ -536,7 +538,7 @@ EscrowCreate::doApply()
ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer)); ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer));
if (!page) if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfIssuerNode] = *page; (*escrow)[sfIssuerNode] = *page;
} }
// Deduct owner's balance // Deduct owner's balance
@@ -559,7 +561,7 @@ EscrowCreate::doApply()
// increment owner count // increment owner count
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal); increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep); addSponsorToLedgerEntry(ctx_.getApplyViewContext(), escrow.mutableRawSle());
ctx_.view().update(sle); ctx_.view().update(sle);
return tesSUCCESS; return tesSUCCESS;
} }
@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h> #include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h> #include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/EscrowEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h> #include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h> #include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h> #include <xrpl/ledger/helpers/EscrowHelpers.h>
@@ -211,13 +212,12 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
if (ctx.view.rules().enabled(featureTokenEscrow)) if (ctx.view.rules().enabled(featureTokenEscrow))
{ {
auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]); auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy); EscrowEntryR const escrow(ctx.tx[sfOwner], seqProxy, ctx.view);
auto const slep = ctx.view.read(k); if (!escrow)
if (!slep)
return tecNO_TARGET; return tecNO_TARGET;
AccountID const dest = (*slep)[sfDestination]; AccountID const dest = (*escrow)[sfDestination];
STAmount const amount = (*slep)[sfAmount]; STAmount const amount = (*escrow)[sfAmount];
if (!isXRP(amount)) if (!isXRP(amount))
{ {
@@ -237,9 +237,8 @@ TER
EscrowFinish::doApply() EscrowFinish::doApply()
{ {
auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]); auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
auto const k = keylet::escrow(ctx_.tx[sfOwner], seqProxy); EscrowEntryW escrow(ctx_.tx[sfOwner], seqProxy, ctx_.view(), j_);
auto const slep = ctx_.view().peek(k); if (!escrow)
if (!slep)
{ {
if (ctx_.view().rules().enabled(featureTokenEscrow)) if (ctx_.view().rules().enabled(featureTokenEscrow))
return tecINTERNAL; // LCOV_EXCL_LINE return tecINTERNAL; // LCOV_EXCL_LINE
@@ -252,11 +251,11 @@ EscrowFinish::doApply()
auto const now = ctx_.view().header().parentCloseTime; auto const now = ctx_.view().header().parentCloseTime;
// Too soon: can't execute before the finish time // Too soon: can't execute before the finish time
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter])) if ((*escrow)[~sfFinishAfter] && !after(now, (*escrow)[sfFinishAfter]))
return tecNO_PERMISSION; return tecNO_PERMISSION;
// Too late: can't execute after the cancel time // Too late: can't execute after the cancel time
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter])) if ((*escrow)[~sfCancelAfter] && after(now, (*escrow)[sfCancelAfter]))
return tecNO_PERMISSION; return tecNO_PERMISSION;
// Check cryptocondition fulfillment // Check cryptocondition fulfillment
@@ -296,7 +295,7 @@ EscrowFinish::doApply()
return tecCRYPTOCONDITION_ERROR; return tecCRYPTOCONDITION_ERROR;
// Check against condition in the ledger entry: // Check against condition in the ledger entry:
auto const cond = (*slep)[~sfCondition]; auto const cond = (*escrow)[~sfCondition];
// If a condition wasn't specified during creation, // If a condition wasn't specified during creation,
// one shouldn't be included now. // one shouldn't be included now.
@@ -313,7 +312,7 @@ EscrowFinish::doApply()
} }
// NOTE: Escrow payments cannot be used to fund accounts. // NOTE: Escrow payments cannot be used to fund accounts.
AccountID const destID = (*slep)[sfDestination]; AccountID const destID = (*escrow)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID)); auto const sled = ctx_.view().peek(keylet::account(destID));
if (!sled) if (!sled)
return tecNO_DST; return tecNO_DST;
@@ -323,12 +322,12 @@ EscrowFinish::doApply()
!isTesSuccess(err)) !isTesSuccess(err))
return err; return err;
AccountID const account = (*slep)[sfAccount]; AccountID const account = (*escrow)[sfAccount];
// Remove escrow from owner directory // Remove escrow from owner directory
{ {
auto const page = (*slep)[sfOwnerNode]; auto const page = (*escrow)[sfOwnerNode];
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true)) if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, escrow.key(), true))
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from owner."; JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
@@ -338,9 +337,9 @@ EscrowFinish::doApply()
} }
// Remove escrow from recipient's owner directory, if present. // Remove escrow from recipient's owner directory, if present.
if (auto const optPage = (*slep)[~sfDestinationNode]) if (auto const optPage = (*escrow)[~sfDestinationNode])
{ {
if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, k.key, true)) if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, escrow.key(), true))
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient."; JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
@@ -355,9 +354,9 @@ EscrowFinish::doApply()
bool const recycleReserve = bool const recycleReserve =
ctx_.view().rules().enabled(featureSponsor) || ctx_.view().rules().enabled(fixCleanup3_4_0); ctx_.view().rules().enabled(featureSponsor) || ctx_.view().rules().enabled(fixCleanup3_4_0);
if (recycleReserve) if (recycleReserve)
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal); decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
STAmount const amount = slep->getFieldAmount(sfAmount); STAmount const amount = escrow->getFieldAmount(sfAmount);
// Transfer amount to destination // Transfer amount to destination
if (isXRP(amount)) if (isXRP(amount))
{ {
@@ -368,8 +367,8 @@ EscrowFinish::doApply()
if (!ctx_.view().rules().enabled(featureTokenEscrow)) if (!ctx_.view().rules().enabled(featureTokenEscrow))
return temDISABLED; // LCOV_EXCL_LINE return temDISABLED; // LCOV_EXCL_LINE
Rate lockedRate = slep->isFieldPresent(sfTransferRate) Rate lockedRate = escrow->isFieldPresent(sfTransferRate)
? xrpl::Rate(slep->getFieldU32(sfTransferRate)) ? xrpl::Rate(escrow->getFieldU32(sfTransferRate))
: kParityRate; : kParityRate;
auto const issuer = amount.getIssuer(); auto const issuer = amount.getIssuer();
bool const createAsset = destID == accountID_; bool const createAsset = destID == accountID_;
@@ -392,9 +391,9 @@ EscrowFinish::doApply()
return ret; return ret;
// Remove escrow from issuers owner directory, if present. // Remove escrow from issuers owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage) if (auto const optPage = (*escrow)[~sfIssuerNode]; optPage)
{ {
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true)) if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, escrow.key(), true))
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient."; JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
@@ -407,10 +406,10 @@ EscrowFinish::doApply()
ctx_.view().update(sled); ctx_.view().update(sled);
if (!recycleReserve) if (!recycleReserve)
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal); decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
// Remove escrow from ledger // Remove escrow from ledger
ctx_.view().erase(slep); escrow.erase();
return tesSUCCESS; return tesSUCCESS;
} }
@@ -76,7 +76,7 @@ LoanSet::preflight(PreflightContext const& ctx)
if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatchV1_1) && if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatchV1_1) &&
!tx.isFieldPresent(sfCounterparty)) !tx.isFieldPresent(sfCounterparty))
{ {
auto const parentBatchId = ctx.parentBatchId.value_or(UInt256{0}); auto const parentBatchId = ctx.parentBatchId.value_or(beast::kZero);
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: " JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "no Counterparty for inner LoanSet transaction."; << "no Counterparty for inner LoanSet transaction.";
return temBAD_SIGNER; return temBAD_SIGNER;
@@ -91,7 +91,8 @@ LoanSet::preflight(PreflightContext const& ctx)
// A proposed LoanSet is stored unsigned; its CounterpartySignature is // A proposed LoanSet is stored unsigned; its CounterpartySignature is
// collected on-ledger afterward, so its absence here is expected, not an // collected on-ledger afterward, so its absence here is expected, not an
// error (On-Chain Cosigner spec §5.3.1.2). // error (On-Chain Cosigner spec §5.3.1.2).
if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig && (ctx.flags & TapProposal) == 0) if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig &&
(ctx.flags & ApplyFlags::Proposal) == ApplyFlags::None)
{ {
JLOG(ctx.j.warn()) << "LoanSet transaction must have a CounterpartySignature."; JLOG(ctx.j.warn()) << "LoanSet transaction must have a CounterpartySignature.";
return temBAD_SIGNER; return temBAD_SIGNER;
@@ -176,7 +176,7 @@ NFTokenMint::createNFTokenID(
std::distance(buf.data(), ptr) == buf.size(), std::distance(buf.data(), ptr) == buf.size(),
"xrpl::NFTokenMint::createNFTokenID : data size matches the buffer"); "xrpl::NFTokenMint::createNFTokenID : data size matches the buffer");
return UInt256::fromVoid(buf.data()); return uint256{buf};
} }
TER TER
@@ -54,8 +54,8 @@ TransactionProposalCreate::preflight(PreflightContext const& ctx)
try try
{ {
STTx const stx{STObject{proposedTx}}; STTx const stx{STObject{proposedTx}};
auto const inner = auto const inner = xrpl::preflight(
xrpl::preflight(ctx.registry, ctx.rules, stx, TapDryRun | TapProposal, ctx.j); ctx.registry, ctx.rules, stx, ApplyFlags::DryRun | ApplyFlags::Proposal, ctx.j);
if (!isTesSuccess(inner.ter)) if (!isTesSuccess(inner.ter))
{ {
JLOG(ctx.j.debug()) << "TransactionProposalCreate: proposed txn " JLOG(ctx.j.debug()) << "TransactionProposalCreate: proposed txn "
@@ -205,7 +205,7 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx)
// is collected on-ledger afterward, so its absence here is expected, not // is collected on-ledger afterward, so its absence here is expected, not
// an error (On-Chain Cosigner spec §5.3.1.2). // an error (On-Chain Cosigner spec §5.3.1.2).
if (isAccountReserveSponsorship && !ctx.tx.isFieldPresent(sfSponsorSignature) && if (isAccountReserveSponsorship && !ctx.tx.isFieldPresent(sfSponsorSignature) &&
(ctx.flags & TapProposal) == 0) (ctx.flags & ApplyFlags::Proposal) == ApplyFlags::None)
{ {
JLOG(ctx.j.debug()) << "preflight: account sponsorship requires sfSponsorSignature"; JLOG(ctx.j.debug()) << "preflight: account sponsorship requires sfSponsorSignature";
return temMALFORMED; return temMALFORMED;
+3 -2
View File
@@ -359,7 +359,8 @@ Batch::preflight(PreflightContext const& ctx)
// TapBatch — PreflightContext with a parentBatchId requires it. // TapBatch — PreflightContext with a parentBatchId requires it.
// LoanSet already short-circuits on tfInnerBatchTxn; it is also in // LoanSet already short-circuits on tfInnerBatchTxn; it is also in
// kDisabledTxTypes, so it never reaches this call. // kDisabledTxTypes, so it never reaches this call.
ApplyFlags const innerFlags = TapBatch | (ctx.flags & (TapProposal | TapDryRun)); ApplyFlags const innerFlags =
ApplyFlags::Batch | (ctx.flags & (ApplyFlags::Proposal | ApplyFlags::DryRun));
if (auto const preflightResult = if (auto const preflightResult =
xrpl::preflight(ctx.registry, ctx.rules, parentBatchId, stx, innerFlags, ctx.j); xrpl::preflight(ctx.registry, ctx.rules, parentBatchId, stx, innerFlags, ctx.j);
!isTesSuccess(preflightResult.ter)) !isTesSuccess(preflightResult.ter))
@@ -431,7 +432,7 @@ Batch::preflightSigValidated(PreflightContext const& ctx)
// A proposed Batch is stored unsigned; its BatchSigners are collected // A proposed Batch is stored unsigned; its BatchSigners are collected
// on-ledger afterward, so the signer-presence match belongs to submission // on-ledger afterward, so the signer-presence match belongs to submission
// time, not proposal creation (On-Chain Cosigner spec §5.3.1.2). // time, not proposal creation (On-Chain Cosigner spec §5.3.1.2).
if ((ctx.flags & TapProposal) != 0) if ((ctx.flags & ApplyFlags::Proposal) != ApplyFlags::None)
return tesSUCCESS; return tesSUCCESS;
auto const parentBatchId = ctx.tx.getTransactionID(); auto const parentBatchId = ctx.tx.getTransactionID();
+6 -7
View File
@@ -8,6 +8,7 @@
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h> #include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/AmendmentTable.h> #include <xrpl/ledger/AmendmentTable.h>
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h> #include <xrpl/protocol/Protocol.h>
@@ -260,16 +261,14 @@ Change::applyAmendment()
TER TER
Change::applyFee() Change::applyFee()
{ {
auto const k = keylet::feeSettings(); FeeSettingsEntryW feeObject(view());
SLE::pointer feeObject = view().peek(k);
if (!feeObject) if (!feeObject)
{ {
feeObject = std::make_shared<SLE>(k); feeObject.newSLE();
view().insert(feeObject); feeObject.insert();
} }
auto set = [](SLE::pointer& feeObject, STTx const& tx, auto const& field) { auto set = [](FeeSettingsEntryW& feeObject, STTx const& tx, auto const& field) {
feeObject->at(field) = tx[field]; feeObject->at(field) = tx[field];
}; };
if (view().rules().enabled(featureXRPFees)) if (view().rules().enabled(featureXRPFees))
@@ -291,7 +290,7 @@ Change::applyFee()
set(feeObject, ctx_.tx, sfReserveIncrement); set(feeObject, ctx_.tx, sfReserveIncrement);
} }
view().update(feeObject); feeObject.update();
JLOG(j_.warn()) << "Fees have been changed"; JLOG(j_.warn()) << "Fees have been changed";
return tesSUCCESS; return tesSUCCESS;
+2 -2
View File
@@ -1890,7 +1890,7 @@ private:
auto const flowResult = [&] { auto const flowResult = [&] {
STAmount const deliver(eth(51'000'000)); STAmount const deliver(eth(51'000'000));
STAmount smax(btc(61'000'000)); STAmount smax(btc(61'000'000));
PaymentSandbox sb(env.current().get(), TapNone); PaymentSandbox sb(env.current().get(), ApplyFlags::None);
STPathSet paths; STPathSet paths;
auto ipe = [](MPTTester const& iss) { auto ipe = [](MPTTester const& iss) {
return STPathElement( return STPathElement(
@@ -1928,7 +1928,7 @@ private:
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) { env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
if (flowResult.removableOffers.empty()) if (flowResult.removableOffers.empty())
return false; return false;
Sandbox sb(&view, TapNone); Sandbox sb(&view, ApplyFlags::None);
for (auto const& o : flowResult.removableOffers) for (auto const& o : flowResult.removableOffers)
{ {
if (auto ok = sb.peek(keylet::offer(o))) if (auto ok = sb.peek(keylet::offer(o)))
+2 -2
View File
@@ -2049,7 +2049,7 @@ private:
auto const flowResult = [&] { auto const flowResult = [&] {
STAmount const deliver(USD(51)); STAmount const deliver(USD(51));
STAmount smax(BTC(61)); STAmount smax(BTC(61));
PaymentSandbox sb(env.current().get(), TapNone); PaymentSandbox sb(env.current().get(), ApplyFlags::None);
STPathSet paths; STPathSet paths;
auto ipe = [](Issue const& iss) { auto ipe = [](Issue const& iss) {
return STPathElement( return STPathElement(
@@ -2087,7 +2087,7 @@ private:
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) { env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
if (flowResult.removableOffers.empty()) if (flowResult.removableOffers.empty())
return false; return false;
Sandbox sb(&view, TapNone); Sandbox sb(&view, ApplyFlags::None);
for (auto const& o : flowResult.removableOffers) for (auto const& o : flowResult.removableOffers)
{ {
if (auto ok = sb.peek(keylet::offer(o))) if (auto ok = sb.peek(keylet::offer(o)))
+4 -4
View File
@@ -461,8 +461,8 @@ private:
// Invalid tokens // Invalid tokens
{ {
auto const mpt1 = MPTIssue{MPTID(0xabc)}; auto const mpt1 = MPTIssue{MPTID{0xabc}};
auto const mpt2 = MPTIssue{MPTID(0xdef)}; auto const mpt2 = MPTIssue{MPTID{0xdef}};
ammAlice.deposit( ammAlice.deposit(
alice_, alice_,
1'000, 1'000,
@@ -4158,7 +4158,7 @@ private:
auto jtx = env.jt(tx, Seq(1), Fee(10)); auto jtx = env.jt(tx, Seq(1), Fee(10));
env.app().config().features.erase(featureMPTokensV2); env.app().config().features.erase(featureMPTokensV2);
PreflightContext const ctx( PreflightContext const ctx(
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal); env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
auto pf = AMMBid::checkExtraFeatures(ctx); auto pf = AMMBid::checkExtraFeatures(ctx);
BEAST_EXPECT(pf == false); BEAST_EXPECT(pf == false);
env.app().config().features.insert(featureMPTokensV2); env.app().config().features.insert(featureMPTokensV2);
@@ -4170,7 +4170,7 @@ private:
jtx.jv["Asset2"].removeMember("mpt_issuance_id"); jtx.jv["Asset2"].removeMember("mpt_issuance_id");
jtx.stx = env.ust(jtx); jtx.stx = env.ust(jtx);
PreflightContext const ctx( PreflightContext const ctx(
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal); env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
auto pf = AMMBid::preflight(ctx); auto pf = AMMBid::preflight(ctx);
BEAST_EXPECT(pf == temBAD_AMM_TOKENS); BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
} }
+5 -5
View File
@@ -603,8 +603,8 @@ private:
// Invalid tokens - bogus currency // Invalid tokens - bogus currency
{ {
auto const iss1 = Issue{Currency(0xabc), gw_.id()}; auto const iss1 = Issue{Currency{0xabc}, gw_.id()};
auto const iss2 = Issue{Currency(0xdef), gw_.id()}; auto const iss2 = Issue{Currency{0xdef}, gw_.id()};
ammAlice.deposit( ammAlice.deposit(
alice_, alice_,
1'000, 1'000,
@@ -3263,7 +3263,7 @@ private:
auto jtx = env.jt(tx, Seq(1), Fee(baseFee)); auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
env.app().config().features.erase(featureAMM); env.app().config().features.erase(featureAMM);
PreflightContext const pfCtx( PreflightContext const pfCtx(
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal); env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
auto pf = Transactor::invokePreflight<AMMBid>(pfCtx); auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
BEAST_EXPECT(pf == temDISABLED); BEAST_EXPECT(pf == temDISABLED);
env.app().config().features.insert(featureAMM); env.app().config().features.insert(featureAMM);
@@ -3274,7 +3274,7 @@ private:
jtx.jv["TxnSignature"] = "deadbeef"; jtx.jv["TxnSignature"] = "deadbeef";
jtx.stx = env.ust(jtx); jtx.stx = env.ust(jtx);
PreflightContext const pfCtx( PreflightContext const pfCtx(
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal); env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
auto pf = Transactor::invokePreflight<AMMBid>(pfCtx); auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
BEAST_EXPECT(!isTesSuccess(pf)); BEAST_EXPECT(!isTesSuccess(pf));
} }
@@ -3285,7 +3285,7 @@ private:
jtx.jv["Asset2"].removeMember("issuer"); jtx.jv["Asset2"].removeMember("issuer");
jtx.stx = env.ust(jtx); jtx.stx = env.ust(jtx);
PreflightContext const pfCtx( PreflightContext const pfCtx(
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal); env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
auto pf = Transactor::invokePreflight<AMMBid>(pfCtx); auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
BEAST_EXPECT(pf == temBAD_AMM_TOKENS); BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
} }
+1 -1
View File
@@ -594,7 +594,7 @@ public:
stx->at(sfSigningPubKey) = makeSlice(std::string("badkey")); stx->at(sfSigningPubKey) = makeSlice(std::string("badkey"));
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) { env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
auto const result = xrpl::apply(env.app(), view, *stx, TapNone, j); auto const result = xrpl::apply(env.app(), view, *stx, ApplyFlags::None, j);
BEAST_EXPECT(result.ter == temBAD_SIGNATURE); BEAST_EXPECT(result.ter == temBAD_SIGNATURE);
BEAST_EXPECT(!result.applied); BEAST_EXPECT(!result.applied);
return result.applied; return result.applied;
Loaded 100 of 265 files, more files were not shown because too many files have changed in this diff. Show more