mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-12 22:58:11 +00:00
Compare commits
21 Commits
bthomee/co
...
nb-seriali
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef0635d26c | ||
|
|
3c6279b735 | ||
|
|
55b265a8c2 | ||
|
|
02e40e7a00 | ||
|
|
f14395427e | ||
|
|
b707bac74f | ||
|
|
da2b5d5eaf | ||
|
|
4de4902200 | ||
|
|
2ea1f09dcb | ||
|
|
68b4b894de | ||
|
|
defd83e5a6 | ||
|
|
452e31a05f | ||
|
|
1798262d7a | ||
|
|
cd633b9ac9 | ||
|
|
5c5f7d315a | ||
|
|
0f7493ce50 | ||
|
|
40f61f828a | ||
|
|
108277f4b7 | ||
|
|
651bb207b4 | ||
|
|
c45363fd8b | ||
|
|
f1744cb76e |
@@ -255,6 +255,7 @@ words:
|
||||
- queuable
|
||||
- Raphson
|
||||
- rcflags
|
||||
- reencrypted
|
||||
- replayer
|
||||
- repodata
|
||||
- repomd
|
||||
|
||||
@@ -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))
|
||||
|
||||
### Bugfixes in 3.5.0
|
||||
|
||||
- `channel_authorize`: The `channel_id` field now returns an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
|
||||
- `channel_verify`: The `channel_id` and `signature` fields now return an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
|
||||
|
||||
## XRP Ledger server version 3.4.0
|
||||
|
||||
Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta releases.
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace xrpl::ledger_entries {
|
||||
// builder's STObject and the wrapper's SLE.
|
||||
TEST(${name}Tests, BuilderSettersRoundTrip)
|
||||
{
|
||||
UInt256 const index{1u};
|
||||
constexpr UInt256 index{1};
|
||||
|
||||
% for field in fields:
|
||||
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
|
||||
@@ -85,7 +85,7 @@ TEST(${name}Tests, BuilderSettersRoundTrip)
|
||||
// from that SLE, build a new wrapper, and verify all fields (and validate()).
|
||||
TEST(${name}Tests, BuilderFromSleRoundTrip)
|
||||
{
|
||||
UInt256 const index{2u};
|
||||
constexpr UInt256 index{2};
|
||||
|
||||
% for field in fields:
|
||||
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
|
||||
@@ -146,7 +146,7 @@ TEST(${name}Tests, BuilderFromSleRoundTrip)
|
||||
// 3) Verify wrapper throws when constructed from wrong ledger entry type.
|
||||
TEST(${name}Tests, WrapperThrowsOnWrongEntryType)
|
||||
{
|
||||
UInt256 const index{3u};
|
||||
constexpr UInt256 index{3};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
// Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq
|
||||
@@ -177,7 +177,7 @@ TEST(${name}Tests, WrapperThrowsOnWrongEntryType)
|
||||
// 4) Verify builder throws when constructed from wrong ledger entry type.
|
||||
TEST(${name}Tests, BuilderThrowsOnWrongEntryType)
|
||||
{
|
||||
UInt256 const index{4u};
|
||||
constexpr UInt256 index{4};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
% if wrong_le_include == "Ticket":
|
||||
@@ -207,7 +207,7 @@ TEST(${name}Tests, BuilderThrowsOnWrongEntryType)
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(${name}Tests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
UInt256 const index{3u};
|
||||
constexpr UInt256 index{3};
|
||||
|
||||
% for field in required_fields:
|
||||
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
|
||||
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1781173989,
|
||||
"narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=",
|
||||
"lastModified": 1791130267,
|
||||
"narHash": "sha256-1sjwcQMcgAbmiip/VivBSiK8p2bTkQGOvCuO7vnwfx4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8c91a71d13451abc40eb9dae8910f972f979852f",
|
||||
"rev": "9013764fcc0ea99fa16cf7aa7decf8a5c3889dd2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -47,11 +47,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784611586,
|
||||
"narHash": "sha256-OfqgY+0hp/zseZB7uyH0U8kIDPS4scZZCyAurEplvG0=",
|
||||
"lastModified": 1791189933,
|
||||
"narHash": "sha256-Y8eN7ki0Urx4Ojf4w1xSpWg8uk/3lJqK+E0Oth77rH0=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "14f58845249f3552a89b07772626b8d3c632fa86",
|
||||
"rev": "e60029353d0c48d216bc4b065168ccd4079c166f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
#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 <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Manages all counted object types.
|
||||
*
|
||||
* Counters register themselves on a lock-free intrusive list maintained
|
||||
* by this object when constructed. Because counters are never destroyed
|
||||
* or removed, the ABA problem does not apply.
|
||||
*
|
||||
* The registry is iterable as a forward range.
|
||||
*/
|
||||
class CountedObjects
|
||||
{
|
||||
public:
|
||||
static CountedObjects&
|
||||
getInstance() noexcept;
|
||||
|
||||
using Entry = std::pair<std::string, int>;
|
||||
using List = std::vector<Entry>;
|
||||
|
||||
[[nodiscard]] List
|
||||
getCounts(int minimumThreshold) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Implementation for @ref CountedObject.
|
||||
@@ -33,68 +35,147 @@ public:
|
||||
class Counter
|
||||
{
|
||||
public:
|
||||
Counter(std::string name) noexcept : name_(std::move(name)), count_(0)
|
||||
{
|
||||
// Insert ourselves at the front of the lock-free linked list
|
||||
CountedObjects& instance = CountedObjects::getInstance();
|
||||
Counter* head = nullptr;
|
||||
Counter(std::string name) noexcept;
|
||||
|
||||
do
|
||||
{
|
||||
head = instance.head_.load();
|
||||
next_ = head;
|
||||
} while (instance.head_.exchange(this) != head);
|
||||
// Counters are intrusive list nodes whose addresses are published
|
||||
// in the registry; they must never be copied or moved. The atomic
|
||||
// members already force this, but we make it explicit.
|
||||
Counter(Counter const&) = delete;
|
||||
Counter&
|
||||
operator=(Counter const&) = delete;
|
||||
Counter(Counter&&) = delete;
|
||||
Counter&
|
||||
operator=(Counter&&) = delete;
|
||||
|
||||
++instance.count_;
|
||||
}
|
||||
|
||||
~Counter() noexcept = default;
|
||||
|
||||
int
|
||||
std::uint32_t
|
||||
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))
|
||||
{
|
||||
}
|
||||
|
||||
return newCount;
|
||||
}
|
||||
|
||||
int
|
||||
std::uint32_t
|
||||
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
|
||||
getCount() const noexcept
|
||||
[[nodiscard]] std::uint32_t
|
||||
count() const noexcept
|
||||
{
|
||||
return count_.load();
|
||||
return count_.load(std::memory_order::relaxed);
|
||||
}
|
||||
|
||||
[[nodiscard]] Counter*
|
||||
getNext() const noexcept
|
||||
[[nodiscard]] std::uint32_t
|
||||
max() const noexcept
|
||||
{
|
||||
return next_;
|
||||
return std::max(
|
||||
count_.load(std::memory_order::relaxed),
|
||||
maxCount_.load(std::memory_order::relaxed));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string const&
|
||||
getName() const noexcept
|
||||
name() const noexcept
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class CountedObjects;
|
||||
|
||||
Counter* next_ = nullptr;
|
||||
std::atomic<std::uint32_t> count_ = 0;
|
||||
std::atomic<std::uint32_t> maxCount_ = 0;
|
||||
std::string const name_;
|
||||
std::atomic<int> count_;
|
||||
Counter* next_;
|
||||
};
|
||||
|
||||
private:
|
||||
CountedObjects() noexcept;
|
||||
~CountedObjects() noexcept = default;
|
||||
class Iterator
|
||||
{
|
||||
public:
|
||||
using value_type = Counter const;
|
||||
using reference = value_type&;
|
||||
using pointer = value_type*;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
explicit Iterator(Counter* c = nullptr) noexcept : current_(c)
|
||||
{
|
||||
}
|
||||
|
||||
reference
|
||||
operator*() const noexcept
|
||||
{
|
||||
return *current_;
|
||||
}
|
||||
|
||||
pointer
|
||||
operator->() const noexcept
|
||||
{
|
||||
return current_;
|
||||
}
|
||||
|
||||
Iterator&
|
||||
operator++() noexcept
|
||||
{
|
||||
current_ = current_->next_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator
|
||||
operator++(int) noexcept
|
||||
{
|
||||
auto tmp = *this;
|
||||
++*this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(Iterator const&) const noexcept = default;
|
||||
|
||||
private:
|
||||
Counter* current_;
|
||||
};
|
||||
|
||||
constexpr CountedObjects() noexcept = default;
|
||||
|
||||
[[nodiscard]] auto
|
||||
begin() const noexcept
|
||||
{
|
||||
return Iterator{head_.load(std::memory_order::acquire)};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto
|
||||
end() const noexcept
|
||||
{
|
||||
return Iterator{};
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<int> count_;
|
||||
std::atomic<Counter*> head_;
|
||||
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
|
||||
* for reporting purposes.
|
||||
*
|
||||
* The constructors are private and `Object` is befriended so that the
|
||||
* CRTP parameter must be the deriving class itself: a copy-paste error
|
||||
* like `class B : public CountedObject<A>` fails to compile instead of
|
||||
* silently polluting A's count.
|
||||
*
|
||||
* @note This class has no move operations by design: a derived class's
|
||||
* move constructor falls back to the copy constructor for this
|
||||
* base, so the newly created instance is counted. This keeps the
|
||||
* invariant that count is the number of outstanding subobjects.
|
||||
*
|
||||
* @warning Counted objects constructed during dynamic initialization of
|
||||
* other translation units may have their increments discarded when
|
||||
* counter itself is dynamically initialized. Do not create counted
|
||||
* objects before main() begins.
|
||||
*
|
||||
* @ingroup basics
|
||||
*/
|
||||
template <class Object>
|
||||
requires std::is_class_v<Object>
|
||||
class CountedObject
|
||||
{
|
||||
private:
|
||||
static auto&
|
||||
getCounter() noexcept
|
||||
{
|
||||
static CountedObjects::Counter kC{beast::typeName<Object>()};
|
||||
return kC;
|
||||
}
|
||||
static inline CountedObjects::Counter counter{boost::core::type_name<Object>()};
|
||||
|
||||
CountedObject() noexcept
|
||||
{
|
||||
getCounter().increment();
|
||||
counter.increment();
|
||||
}
|
||||
|
||||
CountedObject(CountedObject const&) noexcept
|
||||
{
|
||||
getCounter().increment();
|
||||
counter.increment();
|
||||
}
|
||||
|
||||
CountedObject&
|
||||
@@ -132,7 +223,7 @@ private:
|
||||
public:
|
||||
~CountedObject() noexcept
|
||||
{
|
||||
getCounter().decrement();
|
||||
counter.decrement();
|
||||
}
|
||||
|
||||
friend Object;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -253,4 +253,27 @@ makeSlice(std::basic_string_view<char, Traits> s)
|
||||
return Slice(s.data(), s.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether two byte ranges share at least one byte of storage.
|
||||
*
|
||||
* Each slice is treated as the half-open range [data, data + size), so
|
||||
* ranges that merely touch do not overlap. An empty slice overlaps
|
||||
* nothing, wherever it points.
|
||||
*
|
||||
* @param a The first range.
|
||||
* @param b The second range.
|
||||
* @return true if the ranges overlap, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] inline bool
|
||||
overlap(Slice a, Slice b) noexcept
|
||||
{
|
||||
if (a.empty() || b.empty())
|
||||
return false;
|
||||
|
||||
auto const a0 = reinterpret_cast<std::uintptr_t>(a.data());
|
||||
auto const b0 = reinterpret_cast<std::uintptr_t>(b.data());
|
||||
|
||||
return a0 < b0 + b.size() && b0 < a0 + a.size();
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
@@ -28,7 +29,7 @@ namespace xrpl {
|
||||
* @return The input, encoded as a blob literal.
|
||||
*/
|
||||
std::string
|
||||
sqlBlobLiteral(Blob const& blob);
|
||||
sqlBlobLiteral(Slice blob);
|
||||
|
||||
namespace detail {
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/sanitizers.h>
|
||||
#include <xrpl/beast/type_name.h>
|
||||
|
||||
#include <boost/core/type_name.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
@@ -55,7 +56,7 @@ Throw(Args&&... args)
|
||||
std::is_convertible_v<E*, std::exception*>, "Exception must derive from std::exception.");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
200
include/xrpl/basics/enum_bitops.h
Normal file
200
include/xrpl/basics/enum_bitops.h
Normal 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
|
||||
@@ -2,107 +2,212 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h> // IWYU pragma: keep
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// safe_cast adds compile-time checks to a static_cast to ensure that
|
||||
// the destination can hold all values of the source. This is particularly
|
||||
// handy when the source or destination is an enumeration type.
|
||||
/** Every value of @p Src can be represented by @p Dest.
|
||||
|
||||
template <class Src, class Dest>
|
||||
concept SafeToCast = (std::is_integral_v<Src> && std::is_integral_v<Dest>) &&
|
||||
(std::is_signed_v<Src> || std::is_unsigned_v<Dest>) &&
|
||||
(std::is_signed_v<Src> != std::is_signed_v<Dest> ? sizeof(Dest) > sizeof(Src)
|
||||
: sizeof(Dest) >= sizeof(Src));
|
||||
Given two integral types, the cast is safe when the destination can
|
||||
hold every possible value of the source.
|
||||
|
||||
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
|
||||
safeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
static_assert(
|
||||
std::is_signed_v<Dest> || std::is_unsigned_v<Src>, "Cannot cast signed to unsigned");
|
||||
constexpr unsigned kNotSame = std::is_signed_v<Dest> != std::is_signed_v<Src>;
|
||||
static_assert(
|
||||
sizeof(Dest) >= sizeof(Src) + kNotSame,
|
||||
"Destination is too small to hold all values of source");
|
||||
SafeToCast<Src, Dest>, "This cast is not value-preserving. Please use unsafeCast instead.");
|
||||
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
|
||||
safeCast(Src s) noexcept
|
||||
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
return static_cast<Dest>(safeCast<std::underlying_type_t<Dest>>(s));
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
constexpr Dest
|
||||
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
|
||||
// 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.
|
||||
/** Integral-to-integral cast that is known to be narrowing or sign-erasing.
|
||||
|
||||
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
|
||||
unsafeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
static_assert(
|
||||
!SafeToCast<Src, Dest>,
|
||||
"Only unsafe if casting signed to unsigned or "
|
||||
"destination is too small");
|
||||
!SafeToCast<Src, Dest>, "This cast is value-preserving. Please use safeCast instead.");
|
||||
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
|
||||
unsafeCast(Src s) noexcept
|
||||
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
return static_cast<Dest>(unsafeCast<std::underlying_type_t<Dest>>(s));
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
constexpr Dest
|
||||
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>
|
||||
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
|
||||
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)
|
||||
#else
|
||||
auto* result = dynamic_cast<Dest>(s);
|
||||
XRPL_ASSERT(result != nullptr, "xrpl::safeDowncast : pointer downcast is valid");
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
requires std::is_lvalue_reference_v<Dest>
|
||||
requires(
|
||||
std::is_lvalue_reference_v<Dest> && std::is_polymorphic_v<Src> &&
|
||||
std::derived_from<std::remove_reference_t<Dest>, Src>)
|
||||
inline Dest
|
||||
safeDowncast(Src& s) noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
XRPL_ASSERT(
|
||||
dynamic_cast<std::add_pointer_t<std::remove_reference_t<Dest>>>(&s) != nullptr,
|
||||
"xrpl::safeDowncast : reference downcast is valid");
|
||||
#endif
|
||||
return static_cast<Dest>(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||
return *safeDowncast<std::add_pointer_t<std::remove_reference_t<Dest>>>(std::addressof(s));
|
||||
}
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -4,88 +4,215 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <boost/predef/architecture.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#ifndef __aarch64__
|
||||
#if BOOST_ARCH_X86
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** An unsigned integral type suitable for use as a spinlock.
|
||||
|
||||
The type must be always lock-free when wrapped in std::atomic, so
|
||||
that lock operations cannot themselves take a (library-level) lock.
|
||||
*/
|
||||
template <typename T>
|
||||
concept SpinlockValueType = std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free;
|
||||
|
||||
/** A spinlock value type that additionally supports the atomic bitwise
|
||||
operations required to pack multiple locks into a single integer.
|
||||
*/
|
||||
template <typename T>
|
||||
concept PackedSpinlockValueType = SpinlockValueType<T> && requires(std::atomic<T>& a, T v) {
|
||||
{ a.fetch_or(v) } -> std::same_as<T>;
|
||||
{ a.fetch_and(v) } -> std::same_as<T>;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
/**
|
||||
* Inform the processor that we are in a tight spin-wait loop.
|
||||
*
|
||||
* Spinlocks caught in tight loops can result in the processor's pipeline
|
||||
* filling up with comparison operations, resulting in a misprediction at
|
||||
* the time the lock is finally acquired, necessitating pipeline flushing
|
||||
* which is ridiculously expensive and results in very high latency.
|
||||
*
|
||||
* This function instructs the processor to "pause" for some architecture
|
||||
* specific amount of time, to prevent this.
|
||||
|
||||
/** Inform the processor that we are in a tight spin-wait loop.
|
||||
|
||||
Spinlocks caught in tight loops can result in the processor's pipeline
|
||||
filling up with comparison operations, resulting in a misprediction at
|
||||
the time the lock is finally acquired, necessitating pipeline flushing
|
||||
which is ridiculously expensive and results in very high latency.
|
||||
|
||||
This function instructs the processor to "pause" for some architecture
|
||||
specific amount of time, to prevent this.
|
||||
*/
|
||||
inline void
|
||||
spinPause() noexcept
|
||||
{
|
||||
#ifdef __aarch64__
|
||||
asm volatile("yield");
|
||||
#else
|
||||
#if BOOST_ARCH_X86
|
||||
_mm_pause();
|
||||
#elif BOOST_ARCH_ARM
|
||||
asm volatile("yield" ::: "memory");
|
||||
#else
|
||||
#error No implementation available for spinPause to use
|
||||
#endif
|
||||
}
|
||||
|
||||
} // 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)
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A class that grabs a single packed spinlock from an atomic integer.
|
||||
*
|
||||
* This class meets the requirements of Lockable:
|
||||
* https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||
/** Attempt to acquire a spinlock without blocking.
|
||||
|
||||
@note This interface is primarily intended for one-shot attempts to
|
||||
acquire the lock. Avoid calling this function directly from a
|
||||
loop and use @ref spinLock instead.
|
||||
|
||||
@tparam T An unsigned integral type.
|
||||
@param lock The atomic variable used as the lock.
|
||||
@return true if the lock was acquired, false if it was already held.
|
||||
*/
|
||||
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
|
||||
{
|
||||
// clang-format off
|
||||
static_assert(std::is_unsigned_v<T>);
|
||||
static_assert(std::atomic<T>::is_always_lock_free);
|
||||
static_assert(
|
||||
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_or(0)), T> &&
|
||||
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_and(0)), T>,
|
||||
"std::atomic<T>::fetch_and(T) and std::atomic<T>::fetch_and(T) are required by packed_spinlock");
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
std::atomic<T>& bits_;
|
||||
T const mask_;
|
||||
|
||||
@@ -94,120 +221,49 @@ public:
|
||||
PackedSpinlock&
|
||||
operator=(PackedSpinlock const&) = delete;
|
||||
|
||||
/**
|
||||
* A single spinlock packed inside the specified atomic
|
||||
*
|
||||
* @param lock The atomic integer inside which the spinlock is packed.
|
||||
* @param index The index of the spinlock this object acquires.
|
||||
*
|
||||
* @note For performance reasons, you should strive to have `lock` be
|
||||
* on a cacheline by itself.
|
||||
/** Construct a packed spinlock handle for a single bit.
|
||||
|
||||
@param lock The atomic integer inside which the spinlock is packed.
|
||||
@param index The index of the spinlock this object acquires.
|
||||
|
||||
@note For performance reasons, you should strive to have `lock` be
|
||||
on a cacheline by itself.
|
||||
*/
|
||||
PackedSpinlock(std::atomic<T>& lock, int index) : bits_(lock), mask_(static_cast<T>(1) << index)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
index >= 0 && (mask_ != 0),
|
||||
"xrpl::PackedSpinlock::PackedSpinlock : valid index and mask");
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
try_lock() // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
return (bits_.fetch_or(mask_, std::memory_order_acquire) & mask_) == 0;
|
||||
}
|
||||
|
||||
void
|
||||
lock()
|
||||
{
|
||||
while (!try_lock())
|
||||
{
|
||||
// The use of relaxed memory ordering here is intentional and
|
||||
// serves to help reduce cache coherency traffic during times
|
||||
// of contention by avoiding writes that would definitely not
|
||||
// result in the lock being acquired.
|
||||
while ((bits_.load(std::memory_order_relaxed) & mask_) != 0)
|
||||
detail::spinPause();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
unlock()
|
||||
{
|
||||
bits_.fetch_and(~mask_, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A spinlock implemented on top of an atomic integer.
|
||||
*
|
||||
* @note Using `packed_spinlock` and `spinlock` against the same underlying
|
||||
* atomic integer can result in `spinlock` not being able to actually
|
||||
* acquire the lock during periods of high contention, because of how
|
||||
* the two locks operate: `spinlock` will spin trying to grab all the
|
||||
* bits at once, whereas any given `packed_spinlock` will only try to
|
||||
* grab one bit at a time. Caveat emptor.
|
||||
*
|
||||
* This class meets the requirements of Lockable:
|
||||
* https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||
*/
|
||||
template <class T>
|
||||
class Spinlock
|
||||
{
|
||||
static_assert(std::is_unsigned_v<T>);
|
||||
static_assert(std::atomic<T>::is_always_lock_free);
|
||||
|
||||
private:
|
||||
std::atomic<T>& lock_;
|
||||
|
||||
public:
|
||||
Spinlock(Spinlock const&) = delete;
|
||||
Spinlock&
|
||||
operator=(Spinlock const&) = delete;
|
||||
|
||||
/**
|
||||
* Grabs the
|
||||
*
|
||||
* @param lock The atomic integer to spin against.
|
||||
*
|
||||
* @note For performance reasons, you should strive to have `lock` be
|
||||
* on a cacheline by itself.
|
||||
*/
|
||||
Spinlock(std::atomic<T>& lock) : lock_(lock)
|
||||
PackedSpinlock(std::atomic<T>& lock, int index) noexcept
|
||||
: bits_(lock), mask_([index]() {
|
||||
XRPL_ASSERT(
|
||||
index >= 0 && index < std::numeric_limits<T>::digits,
|
||||
"xrpl::PackedSpinlock::PackedSpinlock : valid index");
|
||||
return static_cast<T>(T{1} << index);
|
||||
}())
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
try_lock() // NOLINT(readability-identifier-naming)
|
||||
try_lock() noexcept // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
T expected = 0;
|
||||
|
||||
return lock_.compare_exchange_weak(
|
||||
expected,
|
||||
std::numeric_limits<T>::max(),
|
||||
std::memory_order_acquire,
|
||||
std::memory_order_relaxed);
|
||||
return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
|
||||
}
|
||||
|
||||
void
|
||||
lock()
|
||||
lock() noexcept
|
||||
{
|
||||
while (!try_lock())
|
||||
do
|
||||
{
|
||||
// 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 (lock_.load(std::memory_order_relaxed) != 0)
|
||||
// of contention by avoiding writes that are unlikely to grab
|
||||
// the requested lock.
|
||||
while ((bits_.load(std::memory_order::relaxed) & mask_) != 0)
|
||||
detail::spinPause();
|
||||
}
|
||||
} while (!try_lock());
|
||||
}
|
||||
|
||||
void
|
||||
unlock()
|
||||
unlock() noexcept
|
||||
{
|
||||
lock_.store(0, std::memory_order_release);
|
||||
bits_.fetch_and(~mask_, std::memory_order::release);
|
||||
}
|
||||
};
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -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
|
||||
@@ -2,26 +2,24 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <compare>
|
||||
#include <concepts>
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* Zero allows classes to offer efficient comparisons to zero.
|
||||
*
|
||||
* Zero is a struct to allow classes to efficiently compare with zero without
|
||||
* requiring an rvalue construction.
|
||||
*
|
||||
* It's often the case that we have classes which combine a number and a unit.
|
||||
* In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons
|
||||
* like t > 1 or t != 1 do not.
|
||||
* 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.
|
||||
*
|
||||
* The comparing class T either needs to have a method called signum() which
|
||||
* returns a positive number, 0, or a negative; or there needs to be a signum
|
||||
* function which resolves in the namespace which takes an instance of T and
|
||||
* returns a positive, zero or negative number.
|
||||
* A type T participates if either `t.signum()` or an unqualified `signum(t)`
|
||||
* found by argument-dependent lookup returns an integer that is negative,
|
||||
* zero, or positive according to the sign of t. Both `t == kZero` and
|
||||
* `kZero == t` work, as do all six relational operators in either order.
|
||||
*/
|
||||
|
||||
struct Zero
|
||||
{
|
||||
explicit Zero() = default;
|
||||
@@ -30,115 +28,44 @@ struct Zero
|
||||
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>
|
||||
auto
|
||||
signum(T const& t)
|
||||
template <class T>
|
||||
requires requires(T const& t) {
|
||||
{ t.signum() } -> std::integral;
|
||||
}
|
||||
[[nodiscard]] constexpr auto
|
||||
signum(T const& t) noexcept(noexcept(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>
|
||||
auto
|
||||
callSignum(T const& t)
|
||||
concept HasSignum = requires(T const& t) {
|
||||
{ signum(t) } -> std::integral;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <detail::HasSignum T>
|
||||
[[nodiscard]] constexpr bool
|
||||
operator==(T const& t, Zero) noexcept(noexcept(signum(t)))
|
||||
{
|
||||
return signum(t);
|
||||
return signum(t) == 0;
|
||||
}
|
||||
|
||||
} // namespace detail::zero_helper
|
||||
|
||||
// Handle operators where T is on the left side using signum.
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator==(T const& t, Zero)
|
||||
template <detail::HasSignum T>
|
||||
[[nodiscard]] constexpr std::strong_ordering
|
||||
operator<=>(T const& t, Zero) noexcept(noexcept(signum(t)))
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator!=(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) != 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator<(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) < 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator>(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) > 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator>=(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) >= 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator<=(T const& t, Zero)
|
||||
{
|
||||
return detail::zero_helper::callSignum(t) <= 0;
|
||||
}
|
||||
|
||||
// Handle operators where T is on the right side by
|
||||
// reversing the operation, so that T is on the left side.
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator==(Zero, T const& t)
|
||||
{
|
||||
return t == kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator!=(Zero, T const& t)
|
||||
{
|
||||
return t != kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator<(Zero, T const& t)
|
||||
{
|
||||
return t > kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator>(Zero, T const& t)
|
||||
{
|
||||
return t < kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator>=(Zero, T const& t)
|
||||
{
|
||||
return t <= kZero;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator<=(Zero, T const& t)
|
||||
{
|
||||
return t >= kZero;
|
||||
return signum(t) <=> 0;
|
||||
}
|
||||
|
||||
} // namespace beast
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#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/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
@@ -22,80 +22,39 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Bitwise flag enum with existing operator overloads
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
|
||||
enum ApplyFlags : std::uint32_t {
|
||||
TapNone = 0x00,
|
||||
enum class ApplyFlags : std::uint32_t {
|
||||
None = 0x00,
|
||||
|
||||
// This is a local transaction with the
|
||||
// fail_hard flag set.
|
||||
TapFailHard = 0x10,
|
||||
FailHard = 0x10,
|
||||
|
||||
// This is not the transaction's last pass
|
||||
// Transaction can be retried, soft failures allowed
|
||||
TapRetry = 0x20,
|
||||
Retry = 0x20,
|
||||
|
||||
// Transaction came from a privileged source
|
||||
TapUnlimited = 0x400,
|
||||
Unlimited = 0x400,
|
||||
|
||||
// Transaction is executing as part of a batch
|
||||
TapBatch = 0x800,
|
||||
Batch = 0x800,
|
||||
|
||||
// Transaction shouldn't be applied
|
||||
// Signatures shouldn't be checked
|
||||
TapDryRun = 0x1000,
|
||||
DryRun = 0x1000,
|
||||
|
||||
// Transaction is being preflighted as the payload of a
|
||||
// TransactionProposalCreate. Its signatures are collected on-ledger
|
||||
// afterward, so signature-presence checks (e.g. Batch signer matching)
|
||||
// are skipped at proposal-creation time (On-Chain Cosigner spec
|
||||
// §5.3.1.2).
|
||||
TapProposal = 0x2000
|
||||
Proposal = 0x2000
|
||||
};
|
||||
|
||||
constexpr ApplyFlags
|
||||
operator|(ApplyFlags const& lhs, ApplyFlags const& rhs)
|
||||
template <>
|
||||
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
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/SLEBase.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -25,6 +31,31 @@ public:
|
||||
: Base(keylet::skip(), view, j)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a hash `diff` slots back from the most recent entry in the
|
||||
* sfHashes vector (diff == 0 is the most recent entry).
|
||||
*
|
||||
* Callers decide which skip list entry to read and how to translate a
|
||||
* target ledger sequence into `diff`; this only does the bounds check
|
||||
* and vector indexing shared by both the recent (stride 1) and distant
|
||||
* (stride 256) skip lists.
|
||||
*
|
||||
* @param diff how many slots back from the most recent hash to look up;
|
||||
* 0 is the most recent hash.
|
||||
* @return the hash at that slot, or std::nullopt if the entry does not
|
||||
* exist or `diff` is out of range.
|
||||
*/
|
||||
[[nodiscard]] std::optional<UInt256>
|
||||
hashAt(std::size_t diff) const
|
||||
{
|
||||
if (!this->exists())
|
||||
return std::nullopt;
|
||||
STVector256 const vec = (*this)->getFieldV256(sfHashes);
|
||||
if (vec.size() > diff)
|
||||
return vec[vec.size() - diff - 1];
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
using LedgerHashesEntryR = LedgerHashesEntry<ReadView>;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::node_store {
|
||||
|
||||
@@ -23,7 +25,7 @@ public:
|
||||
/**
|
||||
* 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.
|
||||
@@ -43,10 +45,10 @@ public:
|
||||
private:
|
||||
bool success_{false};
|
||||
|
||||
void const* key_;
|
||||
uint256 key_;
|
||||
NodeObjectType objectType_{NodeObjectType::Unknown};
|
||||
unsigned char const* objectData_{nullptr};
|
||||
int dataBytes_;
|
||||
int dataBytes_ = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
|
||||
@@ -37,11 +38,6 @@ namespace xrpl::node_store {
|
||||
|
||||
class EncodedBlob
|
||||
{
|
||||
/**
|
||||
* The 32-byte key of the serialized object.
|
||||
*/
|
||||
std::array<std::uint8_t, 32> key_{};
|
||||
|
||||
/**
|
||||
* A pre-allocated buffer for the serialized object.
|
||||
*
|
||||
@@ -65,6 +61,11 @@ class EncodedBlob
|
||||
*/
|
||||
std::uint8_t* const ptr_;
|
||||
|
||||
/**
|
||||
* The 32-byte key of the serialized object.
|
||||
*/
|
||||
uint256 key_;
|
||||
|
||||
public:
|
||||
explicit EncodedBlob(std::shared_ptr<NodeObject> const& obj)
|
||||
: size_([&obj]() {
|
||||
@@ -76,11 +77,12 @@ public:
|
||||
return obj->getData().size() + 9;
|
||||
}())
|
||||
, ptr_((size_ <= payload_.size()) ? payload_.data() : new std::uint8_t[size_])
|
||||
, key_(obj->getHash())
|
||||
{
|
||||
std::fill_n(ptr_, 8, std::uint8_t{0});
|
||||
ptr_[8] = static_cast<std::uint8_t>(obj->getType());
|
||||
|
||||
std::copy_n(obj->getData().data(), obj->getData().size(), ptr_ + 9);
|
||||
std::copy_n(obj->getHash().data(), obj->getHash().size(), key_.data());
|
||||
}
|
||||
|
||||
~EncodedBlob()
|
||||
@@ -95,10 +97,10 @@ public:
|
||||
delete[] ptr_;
|
||||
}
|
||||
|
||||
[[nodiscard]] void const*
|
||||
[[nodiscard]] uint256 const&
|
||||
getKey() const noexcept
|
||||
{
|
||||
return static_cast<void const*>(key_.data());
|
||||
return key_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
|
||||
@@ -61,14 +61,22 @@ parseBase58(std::string const& s);
|
||||
/**
|
||||
* A special account that's used as the "issuer" for XRP.
|
||||
*/
|
||||
AccountID const&
|
||||
xrpAccount();
|
||||
constexpr inline AccountID const&
|
||||
xrpAccount() noexcept
|
||||
{
|
||||
static constexpr AccountID kAccount(beast::kZero);
|
||||
return kAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* A placeholder for empty accounts.
|
||||
*/
|
||||
AccountID const&
|
||||
noAccount();
|
||||
constexpr inline AccountID const&
|
||||
noAccount() noexcept
|
||||
{
|
||||
static constexpr AccountID kAccount = xrpAccount().next();
|
||||
return kAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hex or base58 string to AccountID.
|
||||
@@ -80,10 +88,10 @@ bool
|
||||
toIssuer(AccountID&, std::string const&);
|
||||
|
||||
// DEPRECATED Should be checking the currency or native flag
|
||||
inline bool
|
||||
isXRP(AccountID const& c)
|
||||
constexpr inline bool
|
||||
isXRP(AccountID const& c) noexcept
|
||||
{
|
||||
return c == beast::kZero;
|
||||
return c == xrpAccount();
|
||||
}
|
||||
|
||||
// DEPRECATED
|
||||
@@ -101,22 +109,6 @@ operator<<(std::ostream& os, AccountID const& x)
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the global cache used to map AccountID to base58 conversions.
|
||||
*
|
||||
* The cache is optional and need not be initialized. But because conversion
|
||||
* is expensive (it requires a SHA-256 operation) in most cases the overhead
|
||||
* of the cache is worth the benefit.
|
||||
*
|
||||
* @param count The number of entries the cache should accommodate. Zero will
|
||||
* disable the cache, releasing any memory associated with it.
|
||||
*
|
||||
* @note The function will only initialize the cache the first time it is
|
||||
* invoked. Subsequent invocations do nothing.
|
||||
*/
|
||||
void
|
||||
initAccountIdCache(std::size_t count);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <xrpl/protocol/SOTemplate.h>
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/core/type_name.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
* Derived classes will load the object with all the known formats.
|
||||
*/
|
||||
private:
|
||||
KnownFormats() : name_(beast::typeName<Derived>())
|
||||
KnownFormats() : name_(boost::core::type_name<Derived>())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -75,12 +75,6 @@ addRaw(LedgerHeader const&, Serializer&, bool includeHash = false);
|
||||
LedgerHeader
|
||||
deserializeHeader(Slice data, bool hasHash = false);
|
||||
|
||||
/**
|
||||
* Deserialize a ledger header (prefixed with 4 bytes) from a byte array.
|
||||
*/
|
||||
LedgerHeader
|
||||
deserializePrefixedHeader(Slice data, bool hasHash = false);
|
||||
|
||||
/**
|
||||
* Calculate the hash of a ledger header.
|
||||
*/
|
||||
|
||||
@@ -19,8 +19,8 @@ private:
|
||||
// But an STAccount is always 160 bits, so we can store it with less
|
||||
// overhead in an xrpl::UInt160. However, so the serialized format of the
|
||||
// STAccount stays unchanged, we serialize and deserialize like an STBlob.
|
||||
AccountID value_;
|
||||
bool default_;
|
||||
AccountID value_{};
|
||||
bool default_{true};
|
||||
|
||||
public:
|
||||
using value_type = AccountID;
|
||||
@@ -28,7 +28,6 @@ public:
|
||||
STAccount();
|
||||
|
||||
STAccount(SField const& n);
|
||||
STAccount(SField const& n, Buffer const& v);
|
||||
STAccount(SerialIter& sit, SField const& name);
|
||||
STAccount(SField const& n, AccountID const& v);
|
||||
|
||||
|
||||
@@ -33,7 +33,20 @@ public:
|
||||
|
||||
STBitString(SField const& n);
|
||||
STBitString(value_type const& v);
|
||||
|
||||
template <typename Tag>
|
||||
requires(!std::is_void_v<Tag>)
|
||||
STBitString(BaseUInt<Bits, Tag> const& v) : value_(v)
|
||||
{
|
||||
}
|
||||
|
||||
STBitString(SField const& n, value_type const& v);
|
||||
template <typename Tag>
|
||||
requires(!std::is_void_v<Tag>)
|
||||
STBitString(SField const& n, BaseUInt<Bits, Tag> const& v) : STBase(n), value_(v)
|
||||
{
|
||||
}
|
||||
|
||||
STBitString(SerialIter& sit, SField const& name);
|
||||
|
||||
[[nodiscard]] SerializedTypeID
|
||||
@@ -91,7 +104,7 @@ inline STBitString<Bits>::STBitString(SField const& n, value_type const& v) : ST
|
||||
|
||||
template <int Bits>
|
||||
inline STBitString<Bits>::STBitString(SerialIter& sit, SField const& name)
|
||||
: STBitString(name, sit.getBitString<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().fieldType == getSType(), "xrpl::STBitString::add : field type match");
|
||||
s.addBitString<Bits>(value_);
|
||||
s.addBitString(value_);
|
||||
}
|
||||
|
||||
template <int Bits>
|
||||
@@ -166,7 +179,7 @@ template <typename Tag>
|
||||
void
|
||||
STBitString<Bits>::setValue(BaseUInt<Bits, Tag> const& v)
|
||||
{
|
||||
value_ = v;
|
||||
value_ = value_type{v};
|
||||
}
|
||||
|
||||
template <int Bits>
|
||||
|
||||
@@ -16,12 +16,13 @@ namespace xrpl {
|
||||
template <typename Integer>
|
||||
class STInteger : public STBase, public CountedObject<STInteger<Integer>>
|
||||
{
|
||||
static_assert(
|
||||
SerializableInteger<Integer>,
|
||||
"STInteger requires an integer type that Serializer can append");
|
||||
|
||||
public:
|
||||
using value_type = Integer;
|
||||
|
||||
private:
|
||||
Integer value_;
|
||||
|
||||
public:
|
||||
explicit STInteger(Integer v);
|
||||
STInteger(SField const& n, Integer v = 0);
|
||||
@@ -62,6 +63,8 @@ private:
|
||||
move(std::size_t n, void* buf) override;
|
||||
|
||||
friend class xrpl::detail::STVar;
|
||||
|
||||
Integer value_;
|
||||
};
|
||||
|
||||
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().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>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/enum_bitops.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -22,18 +24,8 @@ namespace xrpl {
|
||||
|
||||
class STPathElement final : public CountedObject<STPathElement>
|
||||
{
|
||||
unsigned int type_;
|
||||
AccountID accountID_;
|
||||
PathAsset assetID_;
|
||||
AccountID issuerID_;
|
||||
|
||||
bool isOffer_;
|
||||
std::size_t hashValue_;
|
||||
|
||||
public:
|
||||
// Bitwise values (typeCurrency | typeMPT)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
|
||||
enum Type {
|
||||
enum class Type : std::uint8_t {
|
||||
TypeNone = 0x00,
|
||||
TypeAccount = 0x01, // Rippling through an account (vs taking an offer).
|
||||
TypeCurrency = 0x10, // Currency follows.
|
||||
@@ -45,6 +37,8 @@ public:
|
||||
// Combination of all types.
|
||||
};
|
||||
|
||||
using enum Type;
|
||||
|
||||
STPathElement();
|
||||
STPathElement(STPathElement const&) = default;
|
||||
STPathElement&
|
||||
@@ -62,42 +56,42 @@ public:
|
||||
bool forceAsset = false);
|
||||
|
||||
STPathElement(
|
||||
unsigned int uType,
|
||||
Type type,
|
||||
AccountID const& account,
|
||||
PathAsset const& asset,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] std::uint32_t
|
||||
getNodeType() const;
|
||||
[[nodiscard]] Type
|
||||
getNodeType() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isOffer() const;
|
||||
isOffer() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAccount() const;
|
||||
isAccount() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasIssuer() const;
|
||||
hasIssuer() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasCurrency() const;
|
||||
hasCurrency() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasMPT() const;
|
||||
hasMPT() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasAsset() const;
|
||||
hasAsset() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isNone() const;
|
||||
isNone() const noexcept;
|
||||
|
||||
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a
|
||||
// class of offers.
|
||||
[[nodiscard]] AccountID const&
|
||||
getAccountID() const;
|
||||
getAccountID() const noexcept;
|
||||
|
||||
[[nodiscard]] PathAsset const&
|
||||
getPathAsset() const;
|
||||
getPathAsset() const noexcept;
|
||||
|
||||
[[nodiscard]] Currency const&
|
||||
getCurrency() const;
|
||||
@@ -106,17 +100,35 @@ public:
|
||||
getMPTID() const;
|
||||
|
||||
[[nodiscard]] AccountID const&
|
||||
getIssuerID() const;
|
||||
getIssuerID() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isType(Type const& pe) const;
|
||||
isType(Type pe) const noexcept;
|
||||
|
||||
bool
|
||||
operator==(STPathElement const& t) const;
|
||||
friend bool
|
||||
operator==(STPathElement const& lhs, STPathElement const& rhs) noexcept
|
||||
{
|
||||
return lhs.isType(TypeAccount) == rhs.isType(TypeAccount) &&
|
||||
lhs.hashValue_ == rhs.hashValue_ && lhs.accountID_ == rhs.accountID_ &&
|
||||
lhs.assetID_ == rhs.assetID_ && lhs.issuerID_ == rhs.issuerID_;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::size_t
|
||||
getHash(STPathElement const& element);
|
||||
|
||||
Type type_;
|
||||
AccountID accountID_;
|
||||
PathAsset assetID_;
|
||||
AccountID issuerID_;
|
||||
|
||||
bool isOffer_;
|
||||
std::size_t hashValue_;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct enum_bitops::OptIn<STPathElement::Type> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <class Hasher>
|
||||
@@ -124,7 +136,7 @@ void
|
||||
hash_append(Hasher& h, STPathElement const& e) noexcept
|
||||
{
|
||||
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.getPathAsset());
|
||||
hash_append(h, e.getIssuerID());
|
||||
@@ -404,11 +416,11 @@ inline STPathElement::STPathElement(
|
||||
}
|
||||
|
||||
inline STPathElement::STPathElement(
|
||||
unsigned int uType,
|
||||
Type type,
|
||||
AccountID const& account,
|
||||
PathAsset const& asset,
|
||||
AccountID const& issuer)
|
||||
: type_(uType)
|
||||
: type_(type)
|
||||
, accountID_(account)
|
||||
, assetID_(asset)
|
||||
, issuerID_(issuer)
|
||||
@@ -423,56 +435,56 @@ inline STPathElement::STPathElement(
|
||||
hashValue_ = getHash(*this);
|
||||
}
|
||||
|
||||
inline std::uint32_t
|
||||
STPathElement::getNodeType() const
|
||||
inline STPathElement::Type
|
||||
STPathElement::getNodeType() const noexcept
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isOffer() const
|
||||
STPathElement::isOffer() const noexcept
|
||||
{
|
||||
return isOffer_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isAccount() const
|
||||
STPathElement::isAccount() const noexcept
|
||||
{
|
||||
return !isOffer();
|
||||
}
|
||||
|
||||
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
|
||||
STPathElement::hasIssuer() const
|
||||
STPathElement::hasIssuer() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeIssuer);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasCurrency() const
|
||||
STPathElement::hasCurrency() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeCurrency);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasMPT() const
|
||||
STPathElement::hasMPT() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeMpt);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasAsset() const
|
||||
STPathElement::hasAsset() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeAsset);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isNone() const
|
||||
STPathElement::isNone() const noexcept
|
||||
{
|
||||
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
|
||||
// class of offers.
|
||||
inline AccountID const&
|
||||
STPathElement::getAccountID() const
|
||||
STPathElement::getAccountID() const noexcept
|
||||
{
|
||||
return accountID_;
|
||||
}
|
||||
|
||||
inline PathAsset const&
|
||||
STPathElement::getPathAsset() const
|
||||
STPathElement::getPathAsset() const noexcept
|
||||
{
|
||||
return assetID_;
|
||||
}
|
||||
@@ -504,18 +516,11 @@ STPathElement::getMPTID() const
|
||||
}
|
||||
|
||||
inline AccountID const&
|
||||
STPathElement::getIssuerID() const
|
||||
STPathElement::getIssuerID() const noexcept
|
||||
{
|
||||
return issuerID_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::operator==(STPathElement const& t) const
|
||||
{
|
||||
return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hashValue_ == t.hashValue_ &&
|
||||
accountID_ == t.accountID_ && assetID_ == t.assetID_ && issuerID_ == t.issuerID_;
|
||||
}
|
||||
|
||||
// ------------ STPath ------------
|
||||
|
||||
inline STPath::STPath(std::vector<STPathElement> p) : path_(std::move(p))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -153,13 +154,6 @@ public:
|
||||
[[nodiscard]] std::string
|
||||
getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData) const;
|
||||
|
||||
[[nodiscard]] std::string
|
||||
getMetaSQL(
|
||||
Serializer rawTxn,
|
||||
std::uint32_t inLedger,
|
||||
TxnSql status,
|
||||
std::string const& escapedMetaData) const;
|
||||
|
||||
/**
|
||||
* The IDs of the inner transactions of a Batch.
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ namespace xrpl {
|
||||
// Various protocol and system specific constant globals.
|
||||
|
||||
/* The name of the system. */
|
||||
static inline std::string const&
|
||||
inline std::string const&
|
||||
systemName()
|
||||
{
|
||||
static std::string const kName = "xrpld";
|
||||
@@ -52,11 +52,10 @@ isLegalAmountSigned(XRPAmount const& amount)
|
||||
}
|
||||
|
||||
/* The currency code for the native currency. */
|
||||
static inline std::string const&
|
||||
inline std::string
|
||||
systemCurrencyCode()
|
||||
{
|
||||
static std::string const kCode = "XRP";
|
||||
return kCode;
|
||||
return "XRP";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -238,6 +238,12 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
TF_FLAG(tfSponsorshipEnd, 0x00010000) \
|
||||
TF_FLAG(tfSponsorshipCreate, 0x00020000) \
|
||||
TF_FLAG(tfSponsorshipReassign, 0x00040000), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(ConfidentialMPTHolderKeyUpdate, \
|
||||
TF_FLAG(tfHolderKeyRotation, 0x00010000) \
|
||||
TF_FLAG(tfHolderKeyRecovery, 0x00020000) \
|
||||
TF_FLAG(tfCancelRecovery, 0x00040000), \
|
||||
MASK_ADJ(0))
|
||||
|
||||
// clang-format on
|
||||
|
||||
@@ -61,26 +61,40 @@ using Domain = BaseUInt<256>;
|
||||
/**
|
||||
* XRP currency.
|
||||
*/
|
||||
Currency const&
|
||||
xrpCurrency();
|
||||
constexpr inline Currency const&
|
||||
xrpCurrency() noexcept
|
||||
{
|
||||
static constexpr Currency const kCurrency(beast::kZero);
|
||||
return kCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* A placeholder for empty currencies.
|
||||
*/
|
||||
Currency const&
|
||||
noCurrency();
|
||||
constexpr inline Currency const&
|
||||
noCurrency() noexcept
|
||||
{
|
||||
static constexpr Currency const kCurrency(xrpCurrency().next());
|
||||
return kCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* We deliberately disallow the currency that looks like "XRP" because too
|
||||
* many people were using it instead of the correct XRP currency.
|
||||
*
|
||||
* Note that this doesn't catch "xRP" or "xrp" or other case variations.
|
||||
*/
|
||||
Currency const&
|
||||
badCurrency();
|
||||
|
||||
inline bool
|
||||
isXRP(Currency const& c)
|
||||
constexpr inline Currency const&
|
||||
badCurrency() noexcept
|
||||
{
|
||||
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.
|
||||
*/
|
||||
bool
|
||||
toCurrency(Currency&, std::string const&);
|
||||
toCurrency(Currency&, std::string_view);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* everywhere and may mean having to rewrite some unit test code.
|
||||
*/
|
||||
Currency
|
||||
toCurrency(std::string const&);
|
||||
Currency toCurrency(std::string_view);
|
||||
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& os, Currency const& x)
|
||||
|
||||
@@ -439,6 +439,7 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfIssuerKeyMirrorEpoch, SoeOptional},
|
||||
{sfAuditorKeyMirrorEpoch, SoeOptional},
|
||||
{sfHolderEncryptionKey, SoeOptional},
|
||||
{sfRecoveryKey, SoeOptional},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks Oracle
|
||||
|
||||
@@ -330,6 +330,7 @@ TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
|
||||
TYPED_SFIELD(sfAmountCommitment, VL, 45)
|
||||
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
|
||||
TYPED_SFIELD(sfBytecode, VL, 47)
|
||||
TYPED_SFIELD(sfRecoveryKey, VL, 48)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -1147,12 +1147,27 @@ TRANSACTION(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, 92, ConfidentialMPTMirrorUpdate,
|
||||
{sfZKProof, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction rotates or recovers a confidential MPT holder's ElGamal encryption key,
|
||||
or cancels a recovery. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/token/ConfidentialMPTHolderKeyUpdate.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE, 93, ConfidentialMPTHolderKeyUpdate,
|
||||
({.amendment = featureConfidentialMPTKeyRotation}),
|
||||
({
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfHolderEncryptionKey, SoeOptional},
|
||||
{sfConfidentialBalanceSpending, SoeOptional},
|
||||
{sfConfidentialBalanceInbox, SoeOptional},
|
||||
{sfZKProof, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction posts an unsigned transaction on-ledger as a
|
||||
TransactionProposal, pending multi-signature collection. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/proposal/TransactionProposalCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttTRANSACTION_PROPOSAL_CREATE, 93, TransactionProposalCreate,
|
||||
TRANSACTION(ttTRANSACTION_PROPOSAL_CREATE, 95, TransactionProposalCreate,
|
||||
({.amendment = featureCosign}),
|
||||
({
|
||||
{sfProposedTransaction, SoeRequired},
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -182,7 +183,7 @@ public:
|
||||
operator result_type() noexcept
|
||||
{
|
||||
auto const digest = Sha512Hasher::result_type(h_);
|
||||
return result_type::fromVoid(digest.data());
|
||||
return result_type{std::span{digest}.first<result_type::size()>()};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -392,6 +392,7 @@ JSS(max_ledger); // in/out: LedgerCleaner
|
||||
JSS(max_queue_size); // out: TxQ
|
||||
JSS(max_spend_drops); // out: AccountInfo
|
||||
JSS(max_spend_drops_total); // out: AccountInfo
|
||||
JSS(maximum);
|
||||
JSS(mean); // out: get_aggregate_price
|
||||
JSS(median); // out: get_aggregate_price
|
||||
JSS(median_fee); // out: TxQ
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
namespace xrpl::nft {
|
||||
|
||||
@@ -98,7 +99,8 @@ getTaxon(UInt256 const& id)
|
||||
inline AccountID
|
||||
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
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::nft {
|
||||
|
||||
// NFT directory pages order their contents based only on the low 96 bits of
|
||||
// the NFToken value. This mask provides easy access to the necessary mask.
|
||||
constexpr UInt256 kPageMask(
|
||||
std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"));
|
||||
constexpr UInt256 kPageMask{"0000000000000000000000000000000000000000ffffffffffffffffffffffff"};
|
||||
|
||||
} // namespace xrpl::nft
|
||||
|
||||
@@ -18,7 +18,7 @@ serializeBlob(Object const& o)
|
||||
{
|
||||
Serializer s;
|
||||
o.add(s);
|
||||
return s.peekData();
|
||||
return s.takeData();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -339,6 +339,30 @@ public:
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfHolderEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfRecoveryKey (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getRecoveryKey() const
|
||||
{
|
||||
if (hasRecoveryKey())
|
||||
return this->sle_->at(sfRecoveryKey);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfRecoveryKey is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasRecoveryKey() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfRecoveryKey);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -552,6 +576,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfRecoveryKey (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenBuilder&
|
||||
setRecoveryKey(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRecoveryKey] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed MPToken wrapper.
|
||||
* @param index The ledger entry index.
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class ConfidentialMPTHolderKeyUpdateBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: ConfidentialMPTHolderKeyUpdate
|
||||
*
|
||||
* Type: ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE (93)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureConfidentialMPTKeyRotation
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ConfidentialMPTHolderKeyUpdateBuilder to construct new transactions.
|
||||
*/
|
||||
class ConfidentialMPTHolderKeyUpdate : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE;
|
||||
|
||||
/**
|
||||
* @brief Construct a ConfidentialMPTHolderKeyUpdate transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit ConfidentialMPTHolderKeyUpdate(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for ConfidentialMPTHolderKeyUpdate");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->tx_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfHolderEncryptionKey (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getHolderEncryptionKey() const
|
||||
{
|
||||
if (hasHolderEncryptionKey())
|
||||
{
|
||||
return this->tx_->at(sfHolderEncryptionKey);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfHolderEncryptionKey is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasHolderEncryptionKey() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfHolderEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfConfidentialBalanceSpending (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getConfidentialBalanceSpending() const
|
||||
{
|
||||
if (hasConfidentialBalanceSpending())
|
||||
{
|
||||
return this->tx_->at(sfConfidentialBalanceSpending);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfConfidentialBalanceSpending is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasConfidentialBalanceSpending() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfConfidentialBalanceSpending);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfConfidentialBalanceInbox (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getConfidentialBalanceInbox() const
|
||||
{
|
||||
if (hasConfidentialBalanceInbox())
|
||||
{
|
||||
return this->tx_->at(sfConfidentialBalanceInbox);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfConfidentialBalanceInbox is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasConfidentialBalanceInbox() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfConfidentialBalanceInbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfZKProof (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getZKProof() const
|
||||
{
|
||||
if (hasZKProof())
|
||||
{
|
||||
return this->tx_->at(sfZKProof);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfZKProof is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasZKProof() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfZKProof);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for ConfidentialMPTHolderKeyUpdate transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class ConfidentialMPTHolderKeyUpdateBuilder : public TransactionBuilderBase<ConfidentialMPTHolderKeyUpdateBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new ConfidentialMPTHolderKeyUpdateBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<ConfidentialMPTHolderKeyUpdateBuilder>(ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE, account, sequence, fee)
|
||||
{
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a ConfidentialMPTHolderKeyUpdateBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for ConfidentialMPTHolderKeyUpdateBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfHolderEncryptionKey (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setHolderEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfHolderEncryptionKey] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfConfidentialBalanceSpending (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setConfidentialBalanceSpending(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfConfidentialBalanceSpending] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfConfidentialBalanceInbox (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setConfidentialBalanceInbox(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfConfidentialBalanceInbox] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfZKProof (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdateBuilder&
|
||||
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfZKProof] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the ConfidentialMPTHolderKeyUpdate wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
ConfidentialMPTHolderKeyUpdate
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return ConfidentialMPTHolderKeyUpdate{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -18,7 +18,7 @@ class TransactionProposalCreateBuilder;
|
||||
/**
|
||||
* @brief Transaction: TransactionProposalCreate
|
||||
*
|
||||
* Type: ttTRANSACTION_PROPOSAL_CREATE (93)
|
||||
* Type: ttTRANSACTION_PROPOSAL_CREATE (95)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureCosign
|
||||
* Privileges: Privilege::NoPriv
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/SlabAllocator.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
@@ -15,12 +14,13 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// 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 functions are used internally by boost::intrusive_ptr to handle
|
||||
@@ -101,15 +101,15 @@ namespace detail {
|
||||
// The slab cutoffs and the number of megabytes per allocation are customized
|
||||
// based on the number of objects of each size we expect to need at any point
|
||||
// in time and with an eye to minimize the number of slack bytes in a block.
|
||||
inline SlabAllocatorSet<SHAMapItem> gSlabber({
|
||||
{ 128, megabytes(std::size_t(60)) },
|
||||
{ 192, megabytes(std::size_t(46)) },
|
||||
{ 272, megabytes(std::size_t(60)) },
|
||||
{ 384, megabytes(std::size_t(56)) },
|
||||
{ 564, megabytes(std::size_t(40)) },
|
||||
{ 772, megabytes(std::size_t(46)) },
|
||||
{ 1052, megabytes(std::size_t(60)) },
|
||||
});
|
||||
inline constinit slab::Allocator<SHAMapItem, slab::HeapFallback,
|
||||
slab::Config<1000000, 128>,
|
||||
slab::Config<1000000, 296>,
|
||||
slab::Config<125000, 392>,
|
||||
slab::Config<125000, 520>,
|
||||
slab::Config<62500, 760>,
|
||||
slab::Config<62500, 856>,
|
||||
slab::Config<31250, 1048>
|
||||
> gSlabber;
|
||||
// clang-format on
|
||||
|
||||
} // namespace detail
|
||||
@@ -150,23 +150,13 @@ makeShamapitem(UInt256 const& tag, Slice data)
|
||||
XRPL_ASSERT(
|
||||
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
|
||||
// constructor of SHAMapItem explicitly sets it to 1. We use the fact
|
||||
// that the refcount can never be zero before incrementing as an
|
||||
// invariant.
|
||||
return {new (raw) SHAMapItem{tag, data}, false};
|
||||
}
|
||||
// constructor of SHAMapItem explicitly sets it to 1.
|
||||
if (auto raw = detail::gSlabber.allocate(data.size())) [[likely]]
|
||||
return {new (raw) SHAMapItem{tag, data}, false};
|
||||
|
||||
static_assert(alignof(SHAMapItem) != 40);
|
||||
static_assert(alignof(SHAMapItem) == 8 || alignof(SHAMapItem) == 4);
|
||||
Throw<std::bad_alloc>();
|
||||
}
|
||||
|
||||
inline boost::intrusive_ptr<SHAMapItem>
|
||||
makeShamapitem(SHAMapItem const& other)
|
||||
|
||||
@@ -51,7 +51,8 @@ public:
|
||||
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
|
||||
: 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;
|
||||
|
||||
@@ -59,10 +59,11 @@ public:
|
||||
, parentBatchId(parentBatchId)
|
||||
, 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(
|
||||
(flags & TapProposal) != TapNone,
|
||||
(flags & TapDryRun) != TapNone,
|
||||
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
|
||||
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
|
||||
"xrpl::PreflightContext : proposal preflight implies dry run");
|
||||
}
|
||||
|
||||
@@ -74,10 +75,11 @@ public:
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
: 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(
|
||||
(flags & TapProposal) != TapNone,
|
||||
(flags & TapDryRun) != TapNone,
|
||||
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
|
||||
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
|
||||
"xrpl::PreflightContext : proposal preflight implies dry run");
|
||||
}
|
||||
|
||||
@@ -116,7 +118,7 @@ public:
|
||||
, j(j)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -129,7 +131,8 @@ public:
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
: 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&
|
||||
|
||||
@@ -41,7 +41,7 @@ struct ApplyResult
|
||||
inline bool
|
||||
isTecClaimHardFail(TER ter, ApplyFlags flags)
|
||||
{
|
||||
return isTecClaim(ter) && ((flags & TapRetry) == 0u);
|
||||
return isTecClaim(ter) && ((flags & ApplyFlags::Retry) == ApplyFlags::None);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,9 @@ class ValidMPTIssuance
|
||||
* MPTokens and RippleStates deleted during apply. finalize() checks each
|
||||
* holder's AccountRoot to detect vault pseudo-account holdings deleted
|
||||
* outside VaultDelete. All these checks are gated on fixCleanup3_2_0.
|
||||
*
|
||||
* Under fixCleanup3_5_0, finalize() also rejects any MPToken erased with
|
||||
* a non-zero sfMPTAmount.
|
||||
*/
|
||||
std::vector<std::shared_ptr<SLE const>> deletedHoldings_;
|
||||
|
||||
@@ -138,8 +141,9 @@ public:
|
||||
* - Convert/ConvertBack symmetry:
|
||||
* Regular MPToken balance change (±X) == COA (Confidential Outstanding Amount) change (∓X)
|
||||
* - Cannot delete MPToken with non-zero confidential state:
|
||||
* Cannot delete if sfIssuerEncryptedBalance exists
|
||||
* Cannot delete if sfConfidentialBalanceInbox and sfConfidentialBalanceSpending exist
|
||||
* Cannot delete if any of sfConfidentialBalanceSpending, sfConfidentialBalanceInbox,
|
||||
* sfIssuerEncryptedBalance or sfAuditorEncryptedBalance is present, and the issuance's
|
||||
* sfConfidentialOutstandingAmount is non-zero. Mirrors MPTokenAuthorize::preclaim.
|
||||
* - Privacy flag consistency:
|
||||
* MPToken confidential balance fields can only be created or changed if
|
||||
* lsfMPTCanHoldConfidentialBalance is set on the issuance.
|
||||
@@ -162,6 +166,8 @@ class ValidConfidentialMPToken
|
||||
std::int64_t outstandingDelta = 0;
|
||||
SLE::const_pointer issuance;
|
||||
bool deletedWithEncrypted = false;
|
||||
// True when an erased MPToken had a non-zero pre-tx public balance.
|
||||
bool deletedWithBalanceBefore = false;
|
||||
bool badConsistency = false;
|
||||
bool badCOA = false;
|
||||
bool changesConfidentialFields = false;
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Allows a confidential MPT holder to rotate or recover their ElGamal
|
||||
* encryption key, or to cancel a pending recovery.
|
||||
*
|
||||
* Submitted by the holder. Exactly one of three modes must be selected via
|
||||
* the transaction flags:
|
||||
*
|
||||
* - Rotation (tfHolderKeyRotation): the holder still has their current
|
||||
* ElGamal private key. They provide a new public key along with their
|
||||
* current spending/inbox balances re-encrypted under that new key. The
|
||||
* holder's encryption key and confidential balances are updated
|
||||
* immediately.
|
||||
*
|
||||
* - Recovery (tfHolderKeyRecovery): the holder has lost their current
|
||||
* private key. They provide a new public key but cannot provide
|
||||
* re-encrypted balances. The new key is recorded as a pending
|
||||
* sfRecoveryKey; the confidential balances are left untouched. Completing
|
||||
* the recovery (rewriting the balances) is done separately by the issuer
|
||||
* via ConfidentialMPTRecoverBalance.
|
||||
*
|
||||
* - Cancel (tfCancelRecovery): the holder revokes a pending recovery
|
||||
* authorization created by a prior Recovery-mode transaction. No key,
|
||||
* balances, or proof are carried; authorization is via the holder's
|
||||
* ordinary XRPL signature. sfRecoveryKey is removed and everything else
|
||||
* is left untouched. Fails if no recovery is pending.
|
||||
*
|
||||
* @note Zero-knowledge proof verification for Rotation and Recovery is
|
||||
* deferred to a follow-up once the mpt-crypto constructions are finalized.
|
||||
*/
|
||||
class ConfidentialMPTHolderKeyUpdate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit ConfidentialMPTHolderKeyUpdate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -2,144 +2,144 @@ Detected OS: macos (Darwin arm64)
|
||||
|
||||
Core build tools:
|
||||
✅ cmake
|
||||
cmake version 4.1.2
|
||||
/nix/store/gvabsb4yqb5xsqzqph54rijnn4zpihnp-cmake-4.1.2/bin/cmake
|
||||
cmake version 4.4.3
|
||||
/nix/store/q85csxf4s4shx89zif097h1ql4ax1rrz-cmake-4.4.3/bin/cmake
|
||||
✅ conan
|
||||
Conan version 2.28.1
|
||||
/nix/store/9jiyxmkpwmn6dcqs0765s83riw3l5ail-conan-2.28.1/bin/conan
|
||||
Conan version 2.32.0
|
||||
/nix/store/921jqsgbilixmr3xchj95si0jl8pi13z-conan-2.32.0/bin/conan
|
||||
✅ git
|
||||
git version 2.54.0
|
||||
/nix/store/a14yxcqvv9x2l9mllgpirzhvz93pgprg-git-2.54.0/bin/git
|
||||
git version 2.55.0
|
||||
/nix/store/gw7c7m0dwca5lg4152x9lpmxpj171bzw-git-2.55.0/bin/git
|
||||
✅ python3
|
||||
Python 3.13.13
|
||||
/nix/store/ygxqin6ydzjfawywqpp5pal8wv6sf5bh-python3-3.13.13/bin/python3.13
|
||||
Python 3.14.7
|
||||
/nix/store/0kcws5blwbdyxqxjd1w8hjipz61xny3k-python3-3.14.7/bin/python3.14
|
||||
|
||||
Development tooling:
|
||||
✅ ccache
|
||||
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 version 22.1.7
|
||||
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang
|
||||
clang version 22.1.8
|
||||
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.7
|
||||
/nix/store/rbap7zqq7mw00fyqa02p5rj7gqjp4w5i-clang-22/bin/clang-22
|
||||
clang version 22.1.8
|
||||
/nix/store/w826sqb98nnym6kzackg829664iqdlxj-clang-22/bin/clang-22
|
||||
✅ clang++
|
||||
clang version 22.1.7
|
||||
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang++
|
||||
clang version 22.1.8
|
||||
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.7
|
||||
/nix/store/v9haf787f7bcz0mq1sad4bpyx21pj6li-clang++-22/bin/clang++-22
|
||||
clang version 22.1.8
|
||||
/nix/store/wnwyw3yfqrhpxij65bl3pa1x65q2zg3m-clang++-22/bin/clang++-22
|
||||
✅ ClangBuildAnalyzer
|
||||
ClangBuildAnalyzer 1.6.0
|
||||
/nix/store/4l50ds9fa2mkvh7wg8qzrlbmjs12sb8l-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
/nix/store/c7jjnw29ra78jdlzjww2m374izhy05n8-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
✅ curl
|
||||
curl 8.20.0 (aarch64-apple-darwin25.3.0) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
|
||||
/nix/store/kclq0czaxvsgh4ym9ld7b6iwy50l1snk-curl-8.20.0-bin/bin/curl
|
||||
curl 8.22.0 (aarch64-apple-darwin25.6.0) libcurl/8.22.0 OpenSSL/3.5.8 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.70.0 mit-krb5/1.22.2
|
||||
/nix/store/pq1h17abqy6q5y6k5dn9qj0fwb89flba-curl-8.22.0-bin/bin/curl
|
||||
✅ file
|
||||
file-5.47
|
||||
/nix/store/dax63li7wwcbqxxkkgzc4g2rx7d4w86x-file-5.47/bin/file
|
||||
file-5.48
|
||||
/nix/store/y44vpziwbzfm8020dzlqqbybyyy7lb6g-file-5.48/bin/file
|
||||
✅ less
|
||||
less 692 (PCRE2 regular expressions)
|
||||
/nix/store/lvr16y75r1pdxpdv0aph5ak2yd0hkvqm-less-692/bin/less
|
||||
less 710 (PCRE2 regular expressions)
|
||||
/nix/store/3yn3dx5fwg5i0inqqzwnxq7f6lwhpjqa-less-710/bin/less
|
||||
✅ make
|
||||
GNU Make 4.4.1
|
||||
/nix/store/8wwiw8pwyhrkzyq28hqzxfl4z84lks81-gnumake-4.4.1/bin/make
|
||||
/nix/store/8l4wgxmhdxfvc9g18y76bnxl1ghgcp7n-gnumake-4.4.1/bin/make
|
||||
✅ netstat
|
||||
present
|
||||
/nix/store/qsd1kzqb0ahrk433vmyl245gp623j19s-network_cmds-730.80.3/bin/netstat
|
||||
/nix/store/57fqvgdav8lrmzndkwm7qrgksl01yj23-network_cmds-741.100.2/bin/netstat
|
||||
✅ ninja
|
||||
1.13.2
|
||||
/nix/store/bqykhrblarkj4fl0hz2mf8ngwfv6x6bz-ninja-1.13.2/bin/ninja
|
||||
/nix/store/infm4vlx71q83bqz0k559w1gn4b0rq4i-ninja-1.13.2/bin/ninja
|
||||
✅ perl
|
||||
v5.42.0
|
||||
/nix/store/js13ri9fvm0ajk1fpd3acigys2a9whdv-perl-5.42.0/bin/perl
|
||||
v5.42.3
|
||||
/nix/store/7rvr6qjfzk2ybzn1imjw6c6qq1n1a9cr-perl-5.42.3/bin/perl
|
||||
✅ pkg-config
|
||||
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 - 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 3.0
|
||||
/nix/store/z6ph729vcakbvz3wh8ln1wk6mi06w487-zip-3.0/bin/zip
|
||||
/nix/store/dvawr36npnad4xa6dxi2ss0fw44aa82r-zip-3.0/bin/zip
|
||||
✅ clang-apply-replacements
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-apply-replacements
|
||||
clang-apply-replacements version 22.1.8
|
||||
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/m3ii69rca4077lf4wlk7m3jcag1fs577-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.8
|
||||
/nix/store/w4bz034k4l0w719rrmbwnqcgyjbswy81-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
✅ clang-format
|
||||
clang-format version 22.1.7
|
||||
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-format
|
||||
clang-format version 22.1.8
|
||||
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-format
|
||||
✅ clang-format-22
|
||||
clang-format version 22.1.7
|
||||
/nix/store/4fawqy6ngqcsqd2ygyyzm93q0xy3f5gs-clang-format-22/bin/clang-format-22
|
||||
clang-format version 22.1.8
|
||||
/nix/store/w7z346l0r8y36b6lc5i7jzj3w0581l2y-clang-format-22/bin/clang-format-22
|
||||
✅ clang-tidy
|
||||
LLVM version 22.1.7
|
||||
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-tidy
|
||||
LLVM version 22.1.8
|
||||
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.7
|
||||
/nix/store/jqw4280saixaxxihwdba9ldm2fsm6dr3-clang-tidy-22/bin/clang-tidy-22
|
||||
LLVM version 22.1.8
|
||||
/nix/store/0lv25qd3ddgjm3ndn70q61jfr6krlkl7-clang-tidy-22/bin/clang-tidy-22
|
||||
✅ dot
|
||||
dot - graphviz version 12.2.1 (0)
|
||||
/nix/store/ijb4fbnqa6wzlpqnhb6q9knqpf7qqn5z-graphviz-12.2.1/bin/dot
|
||||
dot - graphviz version 15.1.1 (0)
|
||||
/nix/store/mc99a6bpbk2waym2cnw6ifmxli4ndb4x-graphviz-15.1.1/bin/dot
|
||||
✅ doxygen
|
||||
1.16.1
|
||||
/nix/store/kbryjdpq9jizjb0ws0nzbf2h2ymbdiwm-doxygen-1.16.1/bin/doxygen
|
||||
1.17.0
|
||||
/nix/store/kz43yaj0wg6fx6cbrcmi2442z1ramy0c-doxygen-1.17.0/bin/doxygen
|
||||
✅ gcovr
|
||||
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 version 2.94.0 (nixpkgs)
|
||||
/nix/store/fhnpw0hs0gjms1ha6ap02jq7rx13gkbp-gh-2.94.0/bin/gh
|
||||
gh version 2.102.0 (2026-09-30)
|
||||
/nix/store/s8bahl6zssvry93ihivfmk1l6c58byrr-gh-2.102.0/bin/gh
|
||||
✅ git-cliff
|
||||
git-cliff 2.13.1
|
||||
/nix/store/cy0wwhgxa7yvrz97zydbq6sqmixc90fq-git-cliff-2.13.1/bin/git-cliff
|
||||
git-cliff 2.14.2
|
||||
/nix/store/k4m5yk0qb7hfslwsq1zjajz1d94j12bj-git-cliff-2.14.2/bin/git-cliff
|
||||
✅ git-lfs
|
||||
git-lfs/3.7.1 (3.7.1; darwin arm64; go 1.26.3)
|
||||
/nix/store/k9r7zjfjplqa4d5s71cqvf2iv73jd9mc-git-lfs-3.7.1/bin/git-lfs
|
||||
git-lfs/3.8.0 (3.8.0; darwin arm64; go 1.26.8)
|
||||
/nix/store/wzixjr3yb40a13m2q654hm3bdjn6sal3-git-lfs-3.8.0/bin/git-lfs
|
||||
✅ gpg
|
||||
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 4.5.1
|
||||
/nix/store/z3cca68620w0w10f090szgzdnmh1waf2-pre-commit-4.5.1/bin/pre-commit
|
||||
pre-commit 4.6.2
|
||||
/nix/store/ivh54xypg9qvd3ha9lis0i8p4nkf8n7a-pre-commit-4.6.2/bin/pre-commit
|
||||
✅ run-clang-tidy
|
||||
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
|
||||
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:
|
||||
✅ cargo
|
||||
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-audit 0.22.1
|
||||
/nix/store/snwkga2f5gyf404h7mmp9wriwxb8v65f-cargo-audit-0.22.1/bin/cargo-audit
|
||||
cargo-audit-audit 0.22.2
|
||||
/nix/store/7jgrwj557l91940aj0ag4i924ima1cqn-cargo-audit-0.22.2/bin/cargo-audit
|
||||
✅ cargo-llvm-cov
|
||||
cargo-llvm-cov 0.8.5
|
||||
/nix/store/fpiqdh91gwyxalqp409ynm0s0g086w7w-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
|
||||
cargo-llvm-cov 0.9.0
|
||||
/nix/store/5q1h285z8zzkw2qxig9a0b1avcsk1cf0-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
|
||||
✅ cargo-nextest
|
||||
cargo-nextest 0.9.137
|
||||
/nix/store/ylz7m947mhkgsp6i7611id3s3gcd58nq-cargo-nextest-0.9.137/bin/cargo-nextest
|
||||
cargo-nextest 0.9.146
|
||||
/nix/store/69v3hx3k597hcxd2zy7yad7fg0aw5yxd-cargo-nextest-0.9.146/bin/cargo-nextest
|
||||
✅ clippy-driver
|
||||
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 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
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/fqpjz4l0nsnji8b2pz57mnj0akbp6hcl-rust-nightly/bin/rust-nightly
|
||||
rustc 1.101.0-nightly (282215592 2026-10-04)
|
||||
/nix/store/a9552rf3pylcnkijc1z0hf1lcnv50djq-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
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 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).
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
default = pkgs.buildEnv {
|
||||
|
||||
@@ -20,15 +20,16 @@ let
|
||||
|
||||
# Custom-glibc stdenvs, matching the CI environment. darwin has no custom
|
||||
# glibc, so there they fall back to the plain nixpkgs stdenvs.
|
||||
customGccStdenv = if pkgs.stdenv.isLinux then linux.gccStdenv else plainGccStdenv;
|
||||
customClangStdenv = if pkgs.stdenv.isLinux then linux.clangStdenv else plainClangStdenv;
|
||||
customGccStdenv = if pkgs.stdenv.hostPlatform.isLinux then linux.gccStdenv else plainGccStdenv;
|
||||
customClangStdenv =
|
||||
if pkgs.stdenv.hostPlatform.isLinux then linux.clangStdenv else plainClangStdenv;
|
||||
|
||||
# gcov matching each gcc shell, so `-Dcoverage=ON` builds work in the shell.
|
||||
plainGcov = mkGcov {
|
||||
name = "plain";
|
||||
cc = gccPackage.cc;
|
||||
};
|
||||
customGccGcov = if pkgs.stdenv.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.
|
||||
conanDir = ../conan;
|
||||
@@ -51,7 +52,7 @@ let
|
||||
|
||||
# Not sdkEnv: a shell's stdenv already sets that up. Prepended so the stub
|
||||
# beats the nixpkgs libresolv this shell's tooling drags in.
|
||||
darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.isDarwin (
|
||||
darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isDarwin (
|
||||
pkgs.lib.concatLines (
|
||||
pkgs.lib.mapAttrsToList (
|
||||
name: value: ''export ${name}="${value} ''${${name}:-}"''
|
||||
@@ -126,7 +127,7 @@ let
|
||||
in
|
||||
rec {
|
||||
# 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
|
||||
# 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
|
||||
# gcc/clang are already plain, so these would be redundant and are omitted, which
|
||||
# makes `nix develop .#gcc-plain` fail there rather than silently aliasing gcc.
|
||||
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
|
||||
gcc-plain = makeShell {
|
||||
shellName = "gcc-plain";
|
||||
stdenv = plainGccStdenv;
|
||||
|
||||
@@ -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
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cerrno>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
@@ -99,12 +100,13 @@ uniqueRandomPath(
|
||||
if (ec)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Unable to check path '" + candidate.string() + "': " + ec.message());
|
||||
std::format("Unable to check path '{}': {}", candidate.string(), ec.message()));
|
||||
}
|
||||
if (!exists)
|
||||
return candidate;
|
||||
}
|
||||
Throw<std::runtime_error>("Unable to generate a unique path under '" + base.string() + "'");
|
||||
Throw<std::runtime_error>(
|
||||
std::format("Unable to generate a unique path under '{}'", base.string()));
|
||||
}
|
||||
|
||||
TempDir::TempDir() : path_(uniqueRandomPath(std::filesystem::temp_directory_path()))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/beast/core/LexicalCast.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
|
||||
@@ -20,7 +19,7 @@
|
||||
namespace xrpl {
|
||||
|
||||
std::string
|
||||
sqlBlobLiteral(Blob const& blob)
|
||||
sqlBlobLiteral(Slice blob)
|
||||
{
|
||||
std::string j;
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@ AcceptedLedgerTx::AcceptedLedgerTx(
|
||||
|
||||
Serializer s;
|
||||
met->add(s);
|
||||
rawMeta_ = std::move(s.modData());
|
||||
rawMeta_ = s.takeData();
|
||||
|
||||
json_ = json::ValueType::Object;
|
||||
json_[jss::transaction] = txn_->getJson(JsonOptions::Values::None);
|
||||
|
||||
json_[jss::meta] = meta_.getJson(JsonOptions::Values::None);
|
||||
json_[jss::raw_meta] = strHex(rawMeta_);
|
||||
json_[jss::raw_meta] = strHex(makeSlice(rawMeta_));
|
||||
|
||||
json_[jss::result] = transHuman(meta_.getResultTER());
|
||||
|
||||
@@ -74,7 +74,7 @@ std::string
|
||||
AcceptedLedgerTx::getEscMeta() const
|
||||
{
|
||||
XRPL_ASSERT(!rawMeta_.empty(), "xrpl::AcceptedLedgerTx::getEscMeta : metadata is set");
|
||||
return sqlBlobLiteral(rawMeta_);
|
||||
return sqlBlobLiteral(makeSlice(rawMeta_));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -80,10 +80,10 @@ BookDirs::const_iterator::operator++()
|
||||
XRPL_ASSERT(index_ != kZero, "xrpl::BookDirs::const_iterator::operator++ : nonzero index");
|
||||
if (!cdirNext(*view_, curKey_, sle_, entry_, index_))
|
||||
{
|
||||
if (index_ == 0)
|
||||
if (index_ == kZero)
|
||||
curKey_ = view_->succ(++curKey_, nextQuality_).value_or(kZero);
|
||||
|
||||
if (index_ != 0 || curKey_ == kZero)
|
||||
if (index_ != kZero || curKey_ == kZero)
|
||||
{
|
||||
curKey_ = key_;
|
||||
entry_ = 0;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/LedgerTiming.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
@@ -261,7 +262,7 @@ Ledger::Ledger(Ledger const& prevLedger, NetClock::time_point closeTime)
|
||||
{
|
||||
header_.seq = prevLedger.header_.seq + 1;
|
||||
header_.parentCloseTime = prevLedger.header_.closeTime;
|
||||
header_.hash = prevLedger.header().hash + UInt256(1);
|
||||
header_.hash = prevLedger.header().hash.next();
|
||||
header_.drops = prevLedger.header().drops;
|
||||
header_.closeTimeResolution = prevLedger.header_.closeTimeResolution;
|
||||
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>>
|
||||
Ledger::deserializeTxPlusMeta(SHAMapItem const& item)
|
||||
{
|
||||
std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>> result;
|
||||
SerialIter sit(item.slice());
|
||||
{
|
||||
SerialIter s(sit.getSlice(sit.getVLDataLength()));
|
||||
result.first = std::make_shared<STTx const>(s);
|
||||
}
|
||||
{
|
||||
SerialIter s(sit.getSlice(sit.getVLDataLength()));
|
||||
result.second = std::make_shared<STObject const>(s, sfMetadata);
|
||||
}
|
||||
return result;
|
||||
|
||||
SerialIter txSit(sit.getVL());
|
||||
SerialIter metaSit(sit.getVL());
|
||||
|
||||
return {
|
||||
std::make_shared<STTx const>(txSit), std::make_shared<STObject const>(metaSit, sfMetadata)};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -532,9 +529,9 @@ Ledger::rawTxInsert(
|
||||
XRPL_ASSERT(metaData, "xrpl::Ledger::rawTxInsert : non-null metadata input");
|
||||
|
||||
// low-level - just add to table
|
||||
Serializer s(txn->getDataLength() + metaData->getDataLength() + 16);
|
||||
s.addVL(txn->peekData());
|
||||
s.addVL(metaData->peekData());
|
||||
Serializer s(txn->size() + metaData->size() + 16);
|
||||
s.addVL(txn->slice());
|
||||
s.addVL(metaData->slice());
|
||||
if (!txMap_.addGiveItem(SHAMapNodeType::TnTransactionMd, makeShamapitem(key, s.slice())))
|
||||
logicError("duplicate_tx: " + to_string(key));
|
||||
}
|
||||
@@ -560,7 +557,7 @@ Ledger::setup()
|
||||
|
||||
try
|
||||
{
|
||||
if (auto const sle = read(keylet::feeSettings()))
|
||||
if (auto const sle = FeeSettingsEntryR(*this))
|
||||
{
|
||||
bool oldFees = false;
|
||||
bool newFees = false;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <boost/container/pmr/monotonic_buffer_resource.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <format>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
@@ -264,7 +265,7 @@ OpenView::rawTxInsert(
|
||||
auto const result = txs_.emplace(
|
||||
std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(txn, metaData));
|
||||
if (!result.second)
|
||||
Throw<std::logic_error>("rawTxInsert: duplicate TX id: " + to_string(key));
|
||||
Throw<std::logic_error>(std::format("rawTxInsert: duplicate TX id: {}", to_string(key)));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/LedgerHashesEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
@@ -361,17 +362,16 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
|
||||
if (int const diff = ledger.seq() - seq; diff <= 256)
|
||||
{
|
||||
// Within 256...
|
||||
auto const hashIndex = ledger.read(keylet::skip());
|
||||
LedgerHashesEntryR const hashIndex(ledger, journal);
|
||||
if (hashIndex)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
hashIndex->getFieldU32(sfLastLedgerSequence) == (ledger.seq() - 1),
|
||||
"xrpl::hashOfSeq : matching ledger sequence");
|
||||
STVector256 vec = hashIndex->getFieldV256(sfHashes);
|
||||
if (vec.size() >= diff)
|
||||
return vec[vec.size() - diff];
|
||||
if (auto const hash = hashIndex.hashAt(diff - 1))
|
||||
return hash;
|
||||
JLOG(journal.warn()) << "Ledger " << ledger.seq() << " missing hash for " << seq << " ("
|
||||
<< vec.size() << "," << diff << ")";
|
||||
<< hashIndex->getFieldV256(sfHashes).size() << "," << diff << ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -387,16 +387,14 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
|
||||
}
|
||||
|
||||
// in skiplist
|
||||
auto const hashIndex = ledger.read(keylet::skip(seq));
|
||||
LedgerHashesEntryR const hashIndex(keylet::skip(seq), ledger, journal);
|
||||
if (hashIndex)
|
||||
{
|
||||
auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence);
|
||||
XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger");
|
||||
XRPL_ASSERT((lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger");
|
||||
auto const diff = (lastSeq - seq) >> 8;
|
||||
STVector256 vec = hashIndex->getFieldV256(sfHashes);
|
||||
if (vec.size() > diff)
|
||||
return vec[vec.size() - diff - 1];
|
||||
if (auto const hash = hashIndex.hashAt((lastSeq - seq) >> 8))
|
||||
return hash;
|
||||
}
|
||||
JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq() << " error";
|
||||
return std::nullopt;
|
||||
|
||||
@@ -506,7 +506,7 @@ pseudoAccountAddress(ReadView const& view, UInt256 const& pseudoOwnerKey)
|
||||
RipeshaHasher rsh;
|
||||
auto const hash = sha512Half(i, view.header().parentHash, pseudoOwnerKey);
|
||||
rsh(hash.data(), hash.size());
|
||||
AccountID const ret = AccountID::fromRaw(static_cast<RipeshaHasher::result_type>(rsh));
|
||||
AccountID const ret{static_cast<RipeshaHasher::result_type>(rsh)};
|
||||
if (!view.read(keylet::account(ret)))
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
@@ -394,9 +395,11 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
|
||||
|
||||
if (!page2)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"page " + to_string(page1->key()) + " has a broken " + field.getName() +
|
||||
" field pointing to " + to_string(*id));
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"page {} has a broken {} field pointing to {}",
|
||||
to_string(page1->key()),
|
||||
field.getName(),
|
||||
to_string(*id)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,7 +600,7 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t
|
||||
else
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Offer " + to_string(offerIndexes[i]) + " cannot be deleted!");
|
||||
std::format("Offer {} cannot be deleted!", to_string(offerIndexes[i])));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,9 +737,9 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
|
||||
if (!newPrev)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::runtime_error>(
|
||||
"NFTokenPage directory for " + to_string(owner) +
|
||||
" cannot be repaired. Unexpected link problem.");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"NFTokenPage directory for {} cannot be repaired. Unexpected link problem.",
|
||||
to_string(owner)));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
newPrev->at(sfNextPageMin) = nextPage->key();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <xrpl/nodestore/detail/DecodedBlob.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
@@ -12,8 +11,13 @@
|
||||
|
||||
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:
|
||||
|
||||
Bytes
|
||||
@@ -63,7 +67,7 @@ DecodedBlob::createObject()
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -216,7 +217,7 @@ public:
|
||||
[&hash, pno, &status](void const* data, std::size_t size) {
|
||||
nudb::detail::buffer bf;
|
||||
auto const result = nodeobjectDecompress(data, size, bf);
|
||||
DecodedBlob decoded(hash.data(), result.first, result.second);
|
||||
DecodedBlob decoded(hash, result.first, result.second);
|
||||
if (!decoded.wasOk())
|
||||
{
|
||||
status = Status::DataCorrupt;
|
||||
@@ -240,7 +241,7 @@ public:
|
||||
nudb::error_code ec;
|
||||
nudb::detail::buffer 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)
|
||||
Throw<nudb::system_error>(ec);
|
||||
}
|
||||
@@ -288,14 +289,25 @@ public:
|
||||
Throw<nudb::system_error>(ec);
|
||||
nudb::visit(
|
||||
dp,
|
||||
[&](void const* key,
|
||||
[&](void const* keyData,
|
||||
std::size_t keyBytes,
|
||||
void const* data,
|
||||
std::size_t size,
|
||||
nudb::error_code&) {
|
||||
auto key = uint256::fromRaw(
|
||||
std::span{static_cast<unsigned char const*>(keyData), keyBytes});
|
||||
|
||||
if (!key) [[unlikely]]
|
||||
{
|
||||
ec = make_error_code(nudb::error::invalid_key_size);
|
||||
return;
|
||||
}
|
||||
|
||||
nudb::detail::buffer bf;
|
||||
auto const result = nodeobjectDecompress(data, size, bf);
|
||||
|
||||
DecodedBlob decoded(key, result.first, result.second);
|
||||
|
||||
if (!decoded.wasOk())
|
||||
{
|
||||
ec = make_error_code(nudb::error::missing_value);
|
||||
|
||||
@@ -35,8 +35,10 @@
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
@@ -110,6 +112,9 @@ public:
|
||||
if (!getIfExists(keyValues, Keys::kPath, name))
|
||||
Throw<std::runtime_error>("Missing path in RocksDBFactory backend");
|
||||
|
||||
if (keyBytes != uint256::size())
|
||||
Throw<std::runtime_error>("Incorrect key size: expected 32 bytes");
|
||||
|
||||
rocksdb::BlockBasedTableOptions tableOptions;
|
||||
options.env = env;
|
||||
|
||||
@@ -193,7 +198,7 @@ public:
|
||||
if (!s.ok())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
std::string("Unable to set RocksDB bbt_options: ") + s.ToString());
|
||||
std::format("Unable to set RocksDB bbt_options: {}", s.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +211,7 @@ public:
|
||||
if (!s.ok())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
std::string("Unable to set RocksDB options: ") + s.ToString());
|
||||
std::format("Unable to set RocksDB options: {}", s.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +246,7 @@ public:
|
||||
if (!status.ok() || (localDb == nullptr))
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
std::string("Unable to open/create RocksDB: ") + status.ToString());
|
||||
std::format("Unable to open/create RocksDB: {}", status.ToString()));
|
||||
}
|
||||
db.reset(localDb);
|
||||
}
|
||||
@@ -291,7 +296,7 @@ public:
|
||||
|
||||
if (getStatus.ok())
|
||||
{
|
||||
DecodedBlob decoded(hash.data(), string.data(), string.size());
|
||||
DecodedBlob decoded(hash, string.data(), string.size());
|
||||
|
||||
if (decoded.wasOk())
|
||||
{
|
||||
@@ -316,8 +321,8 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
status = static_cast<Status>(
|
||||
static_cast<int>(Status::CustomCode) + unsafeCast<int>(getStatus.code()));
|
||||
status = checkedCast<Status>(
|
||||
safeCast<int>(Status::CustomCode) + safeCast<int>(getStatus.code()));
|
||||
|
||||
JLOG(journal.error()) << getStatus.ToString();
|
||||
}
|
||||
@@ -346,7 +351,7 @@ public:
|
||||
EncodedBlob const encoded(e);
|
||||
|
||||
wb.Put(
|
||||
rocksdb::Slice(reinterpret_cast<char const*>(encoded.getKey()), keyBytes),
|
||||
rocksdb::Slice(reinterpret_cast<char const*>(encoded.getKey().data()), keyBytes),
|
||||
rocksdb::Slice(
|
||||
reinterpret_cast<char const*>(encoded.getData()), encoded.getSize()));
|
||||
}
|
||||
@@ -356,7 +361,7 @@ public:
|
||||
auto ret = db->Write(options, &wb);
|
||||
|
||||
if (!ret.ok())
|
||||
Throw<std::runtime_error>("storeBatch failed: " + ret.ToString());
|
||||
Throw<std::runtime_error>(std::format("storeBatch failed: {}", ret.ToString()));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -374,9 +379,12 @@ public:
|
||||
|
||||
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())
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -19,95 +21,143 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace detail {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Caches the base58 representations of AccountIDs
|
||||
/** If true, we implement a cache for account ID lookups.
|
||||
|
||||
This is generally a good idea and can improve performance, especially
|
||||
when serving client requests.
|
||||
|
||||
The cache may generate warnings under some sanitizers; if you want to
|
||||
disable caching, set this variable to false.
|
||||
*/
|
||||
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:
|
||||
struct CachedAccountID
|
||||
{
|
||||
AccountID id;
|
||||
char encoding[40] = {0};
|
||||
};
|
||||
/** The sequence lock for the entry.
|
||||
|
||||
// The actual cache
|
||||
std::vector<CachedAccountID> cache_;
|
||||
An even value means that the data is stable, whereas an odd
|
||||
value indicates the data is being actively modified.
|
||||
|
||||
// We use a hash function designed to resist algorithmic complexity attacks
|
||||
HardenedHash<> hasher_;
|
||||
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};
|
||||
|
||||
// 64 spinlocks, packed into a single 64-bit value
|
||||
std::atomic<std::uint64_t> locks_ = 0;
|
||||
/** The account id. */
|
||||
AccountID id{};
|
||||
|
||||
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();
|
||||
}
|
||||
/** The length of the cached encoding.
|
||||
|
||||
std::string
|
||||
toBase58(AccountID const& id)
|
||||
{
|
||||
auto const index = hasher_(id) % cache_.size();
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
/** The actual string encoding. */
|
||||
char encoding[39] = {0};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
static_assert(sizeof(CachedAccountID) == 64 && alignof(CachedAccountID) == 64);
|
||||
|
||||
static std::unique_ptr<detail::AccountIdCache> gAccountIdCache;
|
||||
// The actual cache to use: a simple, direct-mapped, best-effort construct.
|
||||
//
|
||||
// We do not use a hardened hash for this because an AccountID is already
|
||||
// just cryptographically generated (i.e. uniformly distributed) bits and
|
||||
// there is no amplification, since collisions do not carry an additional
|
||||
// cost.
|
||||
//
|
||||
// The size is a "nice" power of 2 because it makes the calculation of an
|
||||
// index easier.
|
||||
constinit std::array<CachedAccountID, 65536> gAccountIdCache{};
|
||||
|
||||
void
|
||||
initAccountIdCache(std::size_t count)
|
||||
{
|
||||
if (!gAccountIdCache && count != 0)
|
||||
gAccountIdCache = std::make_unique<detail::AccountIdCache>(count);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string
|
||||
toBase58(AccountID const& v)
|
||||
{
|
||||
if (gAccountIdCache)
|
||||
return gAccountIdCache->toBase58(v);
|
||||
// Since the input bits are generated by a cryptographically secure
|
||||
// hash construct, we can just pick any subset of them, no muss, no
|
||||
// fuss.
|
||||
auto const index = [&v]() -> std::uint16_t {
|
||||
auto const data = v.data();
|
||||
|
||||
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 <>
|
||||
std::optional<AccountID>
|
||||
parseBase58(std::string const& s)
|
||||
{
|
||||
auto const result = decodeBase58Token(s, TokenType::AccountID);
|
||||
if (result.size() != AccountID::kBytes)
|
||||
return std::nullopt;
|
||||
return AccountID::fromRaw(result);
|
||||
return AccountID::fromRaw(decodeBase58Token(s, TokenType::AccountID));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -148,25 +198,9 @@ parseBase58(std::string const& s)
|
||||
AccountID
|
||||
calcAccountID(PublicKey const& pk)
|
||||
{
|
||||
static_assert(AccountID::kBytes == sizeof(RipeshaHasher::result_type));
|
||||
|
||||
RipeshaHasher rsh;
|
||||
rsh(pk.data(), pk.size());
|
||||
return AccountID::fromRaw(static_cast<RipeshaHasher::result_type>(rsh));
|
||||
}
|
||||
|
||||
AccountID const&
|
||||
xrpAccount()
|
||||
{
|
||||
static AccountID const kAccount(beast::kZero);
|
||||
return kAccount;
|
||||
}
|
||||
|
||||
AccountID const&
|
||||
noAccount()
|
||||
{
|
||||
static AccountID const kAccount(1);
|
||||
return kAccount;
|
||||
return AccountID{static_cast<RipeshaHasher::result_type>(rsh)};
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -185,8 +185,9 @@ getBookBase(Book const& book)
|
||||
UInt256
|
||||
getQualityNext(UInt256 const& uBase)
|
||||
{
|
||||
static constexpr UInt256 kNextQuality(
|
||||
"0000000000000000000000000000000000000000000000010000000000000000");
|
||||
static constexpr UInt256 kNextQuality{
|
||||
"0000000000000000000000000000000000000000000000010000000000000000"};
|
||||
|
||||
return uBase + kNextQuality;
|
||||
}
|
||||
|
||||
@@ -429,9 +430,9 @@ payChannel(AccountID const& src, AccountID const& dst, SeqProxy const& seq) noex
|
||||
Keylet
|
||||
nftokenPageMin(AccountID const& owner)
|
||||
{
|
||||
std::array<std::uint8_t, 32> buf{};
|
||||
std::memcpy(buf.data(), owner.data(), owner.size());
|
||||
return {ltNFTOKEN_PAGE, UInt256::fromRaw(buf)};
|
||||
std::array<std::uint8_t, UInt256::size()> buf{};
|
||||
std::ranges::copy(owner, buf.begin());
|
||||
return {ltNFTOKEN_PAGE, UInt256{buf}};
|
||||
}
|
||||
|
||||
Keylet
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
@@ -20,8 +21,8 @@ addRaw(LedgerHeader const& info, Serializer& s, bool includeHash)
|
||||
s.addBitString(info.accountHash);
|
||||
s.add32(info.parentCloseTime.time_since_epoch().count());
|
||||
s.add32(info.closeTime.time_since_epoch().count());
|
||||
s.add8(info.closeTimeResolution.count());
|
||||
s.add8(info.closeFlags);
|
||||
s.add8(unsafeCast<std::uint8_t>(info.closeTimeResolution.count()));
|
||||
s.add8(unsafeCast<std::uint8_t>(info.closeFlags));
|
||||
|
||||
if (includeHash)
|
||||
s.addBitString(info.hash);
|
||||
@@ -30,7 +31,7 @@ addRaw(LedgerHeader const& info, Serializer& s, bool includeHash)
|
||||
LedgerHeader
|
||||
deserializeHeader(Slice data, bool hasHash)
|
||||
{
|
||||
SerialIter sit(data.data(), data.size());
|
||||
SerialIter sit{data};
|
||||
|
||||
LedgerHeader header;
|
||||
|
||||
@@ -50,12 +51,6 @@ deserializeHeader(Slice data, bool hasHash)
|
||||
return header;
|
||||
}
|
||||
|
||||
LedgerHeader
|
||||
deserializePrefixedHeader(Slice data, bool hasHash)
|
||||
{
|
||||
return deserializeHeader(data + 4, hasHash);
|
||||
}
|
||||
|
||||
UInt256
|
||||
calculateLedgerHash(LedgerHeader const& info)
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
@@ -105,8 +106,8 @@ Permission::Permission()
|
||||
if (type <= UINT16_MAX)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::logic_error>(
|
||||
"Granular permission value must exceed the maximum uint16_t value: " + name);
|
||||
Throw<std::logic_error>(std::format(
|
||||
"Granular permission value must exceed the maximum uint16_t value: {}", name));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,11 +295,9 @@ verify(PublicKey const& publicKey, Slice const& m, Slice const& sig) noexcept
|
||||
NodeID
|
||||
calcNodeID(PublicKey const& pk)
|
||||
{
|
||||
static_assert(NodeID::kBytes == sizeof(RipeshaHasher::result_type));
|
||||
|
||||
RipeshaHasher h;
|
||||
h(pk.data(), pk.size());
|
||||
return NodeID::fromRaw(static_cast<RipeshaHasher::result_type>(h));
|
||||
return NodeID{static_cast<RipeshaHasher::result_type>(h)};
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -17,33 +17,29 @@
|
||||
|
||||
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())
|
||||
return; // Zero is a valid size for a defaulted STAccount.
|
||||
if (auto const s = sit.getVL(); !s.empty())
|
||||
{
|
||||
// Throwing from constructors can be awkward, but this is only
|
||||
// called from STVar::STVar (SerialIter&, SField const&) which
|
||||
// also throws.
|
||||
auto const id = AccountID::fromRaw(s);
|
||||
|
||||
// Is it safe to throw from this constructor? Today (November 2015)
|
||||
// the only place that calls this constructor is
|
||||
// STVar::STVar (SerialIter&, SField const&)
|
||||
// which throws. If STVar can throw in its constructor, then so can
|
||||
// STAccount.
|
||||
if (v.size() != UInt160::kBytes)
|
||||
Throw<std::runtime_error>("Invalid STAccount size");
|
||||
if (!id) [[unlikely]]
|
||||
Throw<std::runtime_error>("Invalid STAccount size");
|
||||
|
||||
default_ = false;
|
||||
memcpy(value_.begin(), v.data(), UInt160::kBytes);
|
||||
}
|
||||
|
||||
STAccount::STAccount(SerialIter& sit, SField const& name) : STAccount(name, sit.getVLBuffer())
|
||||
{
|
||||
value_ = *id;
|
||||
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:
|
||||
// o If we are default (all zeros) serialize as an empty blob.
|
||||
// o Otherwise serialize 160 bits.
|
||||
int const size = isDefault() ? 0 : UInt160::kBytes;
|
||||
s.addVL(value_.data(), size);
|
||||
if (isDefault())
|
||||
{
|
||||
s.addVL(Slice{});
|
||||
return;
|
||||
}
|
||||
|
||||
s.addVL(Slice{value_.data(), value_.size()});
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <format>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
@@ -147,12 +148,12 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name)
|
||||
}
|
||||
|
||||
Issue issue;
|
||||
issue.currency = sit.get160();
|
||||
issue.currency = Currency{sit.get160()};
|
||||
|
||||
if (isXRP(issue.currency))
|
||||
Throw<std::runtime_error>("invalid native currency");
|
||||
|
||||
issue.account = sit.get160();
|
||||
issue.account = AccountID{sit.get160()};
|
||||
|
||||
if (isXRP(issue.account))
|
||||
Throw<std::runtime_error>("invalid native account");
|
||||
@@ -1193,8 +1194,7 @@ muldiv(std::uint64_t multiplier, std::uint64_t multiplicand, std::uint64_t divis
|
||||
if (ret > std::numeric_limits<std::uint64_t>::max())
|
||||
{
|
||||
Throw<std::overflow_error>(
|
||||
"overflow: (" + std::to_string(multiplier) + " * " + std::to_string(multiplicand) +
|
||||
") / " + std::to_string(divisor));
|
||||
std::format("overflow: ({} * {}) / {}", multiplier, multiplicand, divisor));
|
||||
}
|
||||
|
||||
return static_cast<uint64_t>(ret);
|
||||
@@ -1215,9 +1215,8 @@ muldivRound(
|
||||
|
||||
if (ret > std::numeric_limits<std::uint64_t>::max())
|
||||
{
|
||||
Throw<std::overflow_error>(
|
||||
"overflow: ((" + std::to_string(multiplier) + " * " + std::to_string(multiplicand) +
|
||||
") + " + std::to_string(rounding) + ") / " + std::to_string(divisor));
|
||||
Throw<std::overflow_error>(std::format(
|
||||
"overflow: (({} * {}) + {}) / {}", multiplier, multiplicand, rounding, divisor));
|
||||
}
|
||||
|
||||
return static_cast<uint64_t>(ret);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
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(
|
||||
(getFName().fieldType == STI_VL) || (getFName().fieldType == STI_ACCOUNT),
|
||||
"xrpl::STBlob::add : valid field type");
|
||||
s.addVL(value_.data(), value_.size());
|
||||
s.addVL(Slice{value_.data(), value_.size()});
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -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)
|
||||
: STBase{name}, currency_{currency}
|
||||
{
|
||||
}
|
||||
|
||||
STCurrency::STCurrency(SerialIter& sit, SField const& name)
|
||||
: STCurrency{name, Currency{sit.get160()}}
|
||||
{
|
||||
}
|
||||
|
||||
SerializedTypeID
|
||||
STCurrency::getSType() const
|
||||
{
|
||||
|
||||
@@ -64,7 +64,8 @@ STUInt8::getJson(JsonOptions) const
|
||||
return token;
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -221,7 +222,7 @@ STUInt64::getJson(JsonOptions) const
|
||||
|
||||
template <>
|
||||
STInteger<std::int32_t>::STInteger(SerialIter& sit, SField const& name)
|
||||
: STInteger(name, sit.get32())
|
||||
: STInteger(name, sit.geti32())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -28,48 +28,38 @@ STIssue::STIssue(SField const& name) : STBase{name}
|
||||
|
||||
STIssue::STIssue(SerialIter& sit, SField const& name) : STBase{name}
|
||||
{
|
||||
// Either the currency of an Issue or the issuer of an MPT; the
|
||||
// following 160 bits disambiguate.
|
||||
auto const currencyOrAccount = sit.get160();
|
||||
|
||||
if (isXRP(Currency::fromRaw(currencyOrAccount)))
|
||||
if (isXRP(Currency{currencyOrAccount}))
|
||||
{
|
||||
asset_ = xrpIssue();
|
||||
return;
|
||||
}
|
||||
// Check if MPT
|
||||
else
|
||||
|
||||
// MPT is serialized as:
|
||||
// - 160 bits MPT issuer account
|
||||
// - 160 bits black hole account
|
||||
// - 32 bits sequence
|
||||
AccountID const account{sit.get160()};
|
||||
|
||||
// MPT
|
||||
if (noAccount() == account)
|
||||
{
|
||||
// MPT is serialized as:
|
||||
// - 160 bits MPT issuer account
|
||||
// - 160 bits black hole account
|
||||
// - 32 bits sequence
|
||||
AccountID const account = AccountID::fromRaw(sit.get160());
|
||||
// MPT
|
||||
if (noAccount() == account)
|
||||
{
|
||||
MPTID mptID;
|
||||
std::uint32_t sequence = sit.get32();
|
||||
// MPTID stores the sequence in canonical big-endian bytes. STIssue
|
||||
// ledger bytes are the legacy LE-host encoding, so convert the
|
||||
// native get32() value to LE bytes before copying into the MPTID.
|
||||
sequence = boost::endian::native_to_little(sequence);
|
||||
static_assert(MPTID::size() == sizeof(sequence) + sizeof(currencyOrAccount));
|
||||
memcpy(mptID.data(), &sequence, sizeof(sequence));
|
||||
memcpy(
|
||||
mptID.data() + sizeof(sequence),
|
||||
currencyOrAccount.data(),
|
||||
sizeof(currencyOrAccount));
|
||||
MPTIssue const issue{mptID};
|
||||
asset_ = issue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Issue issue;
|
||||
issue.currency = currencyOrAccount;
|
||||
issue.account = account;
|
||||
if (!isConsistent(issue))
|
||||
Throw<std::runtime_error>("invalid issue: currency and account native mismatch");
|
||||
asset_ = issue;
|
||||
}
|
||||
// MPT: issuer (160), noAccount() (160), sequence (32). The sequence
|
||||
// is stored in little-endian and get32() reads big-endian, so
|
||||
// reverse it before building the canonical ID.
|
||||
asset_ = MPTIssue{std::byteswap(sit.get32()), AccountID{currencyOrAccount}};
|
||||
return;
|
||||
}
|
||||
|
||||
Issue const issue{Currency{currencyOrAccount}, account};
|
||||
|
||||
if (!isConsistent(issue))
|
||||
Throw<std::runtime_error>("invalid issue: currency and account native mismatch");
|
||||
|
||||
asset_ = issue;
|
||||
}
|
||||
|
||||
SerializedTypeID
|
||||
|
||||
@@ -35,9 +35,8 @@ STLedgerEntry::STLedgerEntry(Keylet const& k) : STObject(sfLedgerEntry), key_(k.
|
||||
|
||||
if (format == nullptr)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Attempt to create a SLE of unknown type " +
|
||||
std::to_string(safeCast<std::uint16_t>(k.type)));
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Attempt to create a SLE of unknown type {}", safeCast<std::uint16_t>(k.type)));
|
||||
}
|
||||
|
||||
set(format->getSOTemplate());
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
@@ -170,7 +171,7 @@ partsFromString(std::string const& number)
|
||||
boost::smatch match;
|
||||
|
||||
if (!boost::regex_match(number, match, kReNumber))
|
||||
Throw<std::runtime_error>("'" + number + "' is not a number");
|
||||
Throw<std::runtime_error>(std::format("'{}' is not a number", number));
|
||||
|
||||
// Match fields:
|
||||
// 0 = whole input
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/InnerObjectFormats.h>
|
||||
@@ -28,6 +26,7 @@
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/detail/STVar.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
@@ -377,7 +376,7 @@ STObject::getHash(HashPrefix prefix) const
|
||||
Serializer s;
|
||||
s.add32(prefix);
|
||||
add(s, WhichFields::WithAllFields);
|
||||
return s.getSHA512Half();
|
||||
return sha512Half(s);
|
||||
}
|
||||
|
||||
UInt256
|
||||
@@ -386,7 +385,7 @@ STObject::getSigningHash(HashPrefix prefix) const
|
||||
Serializer s;
|
||||
s.add32(prefix);
|
||||
add(s, WhichFields::OmitSigningFields);
|
||||
return s.getSHA512Half();
|
||||
return sha512Half(s);
|
||||
}
|
||||
|
||||
int
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/hash/uhash.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
@@ -17,9 +18,13 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -36,10 +41,10 @@ STPathElement::getHash(STPathElement const& element)
|
||||
// important. We don't even really need to fully hash the whole
|
||||
// base_uint here, as a few bytes would do for our use.
|
||||
//
|
||||
// The note above is only true because the result of this function reaches
|
||||
// nothing but STPathElement::operator==, where it is a fast-reject
|
||||
// prefilter ahead of the field comparisons that decide the answer. Do not
|
||||
// use it to key a container.
|
||||
// The note above is only true because the result of this function is only
|
||||
// used by the STPathElement equality operator as a fast-reject prefilter,
|
||||
// ahead of the field comparisons that then decide the answer. Do not use
|
||||
// it to key a container.
|
||||
|
||||
for (auto const x : element.getAccountID())
|
||||
hashAccount += (hashAccount * 257) ^ x;
|
||||
@@ -106,12 +111,38 @@ STPathSet::operator=(STPathSet const& other)
|
||||
|
||||
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
|
||||
{
|
||||
std::vector<STPathElement> path;
|
||||
for (;;)
|
||||
{
|
||||
int const iType = sit.get8();
|
||||
using enum STPathElement::Type;
|
||||
|
||||
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())
|
||||
{
|
||||
@@ -122,28 +153,11 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
|
||||
// Move rather than converting the vector to an STPath by copy.
|
||||
value_.emplace_back(std::move(path));
|
||||
path.clear();
|
||||
|
||||
if (iType == STPathElement::TypeNone)
|
||||
return;
|
||||
}
|
||||
else if ((iType & ~STPathElement::TypeAll) != 0)
|
||||
{
|
||||
JLOG(debugLog().error()) << "Bad path element " << iType << " in pathset";
|
||||
Throw<std::runtime_error>("bad path element");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const hasAccount = (iType & STPathElement::TypeAccount) != 0u;
|
||||
auto const hasCurrency = (iType & STPathElement::TypeCurrency) != 0u;
|
||||
auto const hasIssuer = (iType & STPathElement::TypeIssuer) != 0u;
|
||||
auto const hasMPT = (iType & STPathElement::TypeMpt) != 0u;
|
||||
|
||||
AccountID account;
|
||||
PathAsset asset;
|
||||
AccountID issuer;
|
||||
|
||||
if (hasAccount)
|
||||
account = sit.get160();
|
||||
bool const hasCurrency = ((*type & TypeCurrency) == TypeCurrency);
|
||||
bool const hasMPT = ((*type & TypeMpt) == TypeMpt);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
AccountID account;
|
||||
AccountID issuer;
|
||||
|
||||
if ((*type & TypeAccount) == TypeAccount)
|
||||
account = sit.getBitString<AccountID>();
|
||||
|
||||
PathAsset asset;
|
||||
|
||||
if (hasCurrency)
|
||||
asset = Currency::fromRaw(sit.get160());
|
||||
asset = sit.getBitString<Currency>();
|
||||
|
||||
if (hasMPT)
|
||||
asset = sit.get192();
|
||||
asset = sit.getBitString<MPTID>();
|
||||
|
||||
if (hasIssuer)
|
||||
issuer = sit.get160();
|
||||
if ((*type & TypeIssuer) == TypeIssuer)
|
||||
issuer = sit.getBitString<AccountID>();
|
||||
|
||||
path.emplace_back(account, asset, issuer, hasCurrency || hasMPT);
|
||||
}
|
||||
}
|
||||
} while (type != TypeNone);
|
||||
}
|
||||
|
||||
STBase*
|
||||
@@ -216,24 +238,22 @@ STPath::getJson(JsonOptions) const
|
||||
for (auto const& it : path_)
|
||||
{
|
||||
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());
|
||||
|
||||
XRPL_ASSERT(
|
||||
((iType & STPathElement::TypeCurrency) == 0u) ||
|
||||
((iType & STPathElement::TypeMpt) == 0u),
|
||||
!(it.hasCurrency() && it.hasMPT()),
|
||||
"xrpl::STPath::getJson : not type Currency and MPT");
|
||||
if ((iType & STPathElement::TypeCurrency) != 0u)
|
||||
if (it.hasCurrency())
|
||||
elem[jss::currency] = to_string(it.getCurrency());
|
||||
|
||||
if ((iType & STPathElement::TypeMpt) != 0u)
|
||||
if (it.hasMPT())
|
||||
elem[jss::mpt_issuance_id] = to_string(it.getMPTID());
|
||||
|
||||
if ((iType & STPathElement::TypeIssuer) != 0u)
|
||||
if (it.hasIssuer())
|
||||
elem[jss::issuer] = to_string(it.getIssuerID());
|
||||
|
||||
ret.append(elem);
|
||||
@@ -265,34 +285,42 @@ STPathSet::add(Serializer& s) const
|
||||
XRPL_ASSERT(getFName().fieldType == STI_PATHSET, "xrpl::STPathSet::add : valid field type");
|
||||
bool first = true;
|
||||
|
||||
// Workaround, since `Serializer` does not accept strongly-typed enums
|
||||
// yet. When support is added, the static_assert will trigger, and the
|
||||
// lambda can be removed entirely.
|
||||
auto const toByte = [](std::same_as<STPathElement::Type> auto t) noexcept {
|
||||
static_assert(
|
||||
!requires(Serializer& ser) { ser.add8(t); },
|
||||
"Serializer::add8 now accepts STPathElement::Type directly; remove toByte.");
|
||||
return std::to_underlying(t);
|
||||
};
|
||||
|
||||
for (auto const& spPath : value_)
|
||||
{
|
||||
if (!first)
|
||||
s.add8(STPathElement::TypeBoundary);
|
||||
s.add8(toByte(STPathElement::TypeBoundary));
|
||||
|
||||
for (auto const& speElement : spPath)
|
||||
{
|
||||
int const iType = speElement.getNodeType();
|
||||
s.add8(toByte(speElement.getNodeType()));
|
||||
|
||||
s.add8(iType);
|
||||
|
||||
if ((iType & STPathElement::TypeAccount) != 0u)
|
||||
if (speElement.isType(STPathElement::TypeAccount))
|
||||
s.addBitString(speElement.getAccountID());
|
||||
|
||||
if ((iType & STPathElement::TypeMpt) != 0u)
|
||||
if (speElement.hasMPT())
|
||||
s.addBitString(speElement.getMPTID());
|
||||
|
||||
if ((iType & STPathElement::TypeCurrency) != 0u)
|
||||
if (speElement.hasCurrency())
|
||||
s.addBitString(speElement.getCurrency());
|
||||
|
||||
if ((iType & STPathElement::TypeIssuer) != 0u)
|
||||
if (speElement.hasIssuer())
|
||||
s.addBitString(speElement.getIssuerID());
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
s.add8(STPathElement::TypeNone);
|
||||
s.add8(toByte(STPathElement::TypeNone));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -59,9 +59,8 @@ getTxFormat(TxType type)
|
||||
|
||||
if (format == nullptr)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Invalid transaction type " +
|
||||
std::to_string(safeCast<std::underlying_type_t<TxType>>(type)));
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Invalid transaction type {}", safeCast<std::underlying_type_t<TxType>>(type)));
|
||||
}
|
||||
|
||||
return format;
|
||||
@@ -77,9 +76,7 @@ STTx::STTx(STObject&& object)
|
||||
|
||||
STTx::STTx(SerialIter& sit) : STObject(sfTransaction)
|
||||
{
|
||||
int const length = sit.getBytesLeft();
|
||||
|
||||
if ((length < kTxMinSizeBytes) || (length > kTxMaxSizeBytes))
|
||||
if (auto const sz = sit.size(); (sz < kTxMinSizeBytes) || (sz > kTxMaxSizeBytes))
|
||||
Throw<std::runtime_error>("Transaction length invalid");
|
||||
|
||||
if (set(sit))
|
||||
@@ -173,7 +170,7 @@ getSigningData(STTx const& that, HashPrefix prefix)
|
||||
Serializer s;
|
||||
s.add32(prefix);
|
||||
that.addWithoutSigningFields(s);
|
||||
return s.getData();
|
||||
return s.takeData();
|
||||
}
|
||||
|
||||
UInt256
|
||||
@@ -364,8 +361,7 @@ STTx::getJson(JsonOptions options, bool binary) const
|
||||
|
||||
if (binary)
|
||||
{
|
||||
Serializer const s = STObject::getSerializer();
|
||||
std::string const dataBin = strHex(s.peekData());
|
||||
std::string const dataBin = strHex(getSerializer().slice());
|
||||
|
||||
if (v1)
|
||||
{
|
||||
@@ -400,22 +396,7 @@ STTx::getMetaSQLInsertReplaceHeader()
|
||||
std::string
|
||||
STTx::getMetaSQL(std::uint32_t inLedger, std::string const& escapedMetaData) const
|
||||
{
|
||||
Serializer s;
|
||||
add(s);
|
||||
return getMetaSQL(s, inLedger, TxnSql::Validated, escapedMetaData);
|
||||
}
|
||||
|
||||
// VFALCO This could be a free function elsewhere
|
||||
std::string
|
||||
STTx::getMetaSQL(
|
||||
Serializer rawTxn,
|
||||
std::uint32_t inLedger,
|
||||
TxnSql status,
|
||||
std::string const& escapedMetaData) const
|
||||
{
|
||||
std::string rTxn = sqlBlobLiteral(rawTxn.peekData());
|
||||
|
||||
auto format = TxFormats::getInstance().findByType(txType_);
|
||||
auto const format = TxFormats::getInstance().findByType(txType_);
|
||||
XRPL_ASSERT(format, "xrpl::STTx::getMetaSQL : non-null type format");
|
||||
|
||||
return std::format(
|
||||
@@ -425,8 +406,8 @@ STTx::getMetaSQL(
|
||||
toBase58(getAccountID(sfAccount)),
|
||||
getFieldU32(sfSequence),
|
||||
inLedger,
|
||||
safeCast<char>(status),
|
||||
rTxn,
|
||||
safeCast<char>(TxnSql::Validated),
|
||||
sqlBlobLiteral(getSerializer().slice()),
|
||||
escapedMetaData);
|
||||
}
|
||||
|
||||
@@ -695,7 +676,7 @@ isMemoOkay(STObject const& st, std::string& reason)
|
||||
memos.add(s);
|
||||
|
||||
// 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.";
|
||||
return false;
|
||||
|
||||
@@ -168,7 +168,7 @@ STValidation::getSerialized() const
|
||||
{
|
||||
Serializer s;
|
||||
add(s);
|
||||
return s.peekData();
|
||||
return s.takeData();
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -9,28 +9,30 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
STVector256::STVector256(SerialIter& sit, SField const& name) : STBase(name)
|
||||
{
|
||||
auto const slice = sit.getSlice(sit.getVLDataLength());
|
||||
auto const slice = sit.getVL();
|
||||
|
||||
if (slice.size() % UInt256::size() != 0)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Bad serialization for STVector256: " + std::to_string(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)
|
||||
value_.push_back(UInt256::fromRaw(slice.substr(i * UInt256::size(), UInt256::size())));
|
||||
while (!inner.empty())
|
||||
value_.push_back(inner.get256());
|
||||
}
|
||||
|
||||
STBase*
|
||||
@@ -62,7 +64,11 @@ STVector256::add(Serializer& s) const
|
||||
{
|
||||
XRPL_ASSERT(getFName().isBinary(), "xrpl::STVector256::add : field is binary");
|
||||
XRPL_ASSERT(getFName().fieldType == STI_VECTOR256, "xrpl::STVector256::add : valid field type");
|
||||
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
|
||||
|
||||
@@ -70,7 +70,8 @@ STXChainBridge::STXChainBridge(SField const& name, json::Value const& v) : STBas
|
||||
std::string const name = it.memberName();
|
||||
if (!kBridgeJson.isMember(name))
|
||||
{
|
||||
Throw<std::runtime_error>("STXChainBridge extra field detected: " + name);
|
||||
Throw<std::runtime_error>(
|
||||
std::format("STXChainBridge extra field detected: {}", name));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -104,8 +104,9 @@ parseGenericSeed(std::string const& str, bool rfc1751)
|
||||
std::string key;
|
||||
if (RFC1751::getKeyFromEnglish(key, str) == 1)
|
||||
{
|
||||
Blob const blob(key.rbegin(), key.rend());
|
||||
return Seed{UInt128::fromRaw(blob)};
|
||||
std::array<std::uint8_t, UInt128::size()> bytes{};
|
||||
std::ranges::reverse_copy(key, bytes.begin());
|
||||
return Seed{UInt128{bytes}};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
int
|
||||
Serializer::add16(std::uint16_t i)
|
||||
{
|
||||
int const ret = data_.size();
|
||||
data_.push_back(static_cast<unsigned char>(i >> 8));
|
||||
data_.push_back(static_cast<unsigned char>(i & 0xff));
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::add32(HashPrefix p)
|
||||
{
|
||||
// This should never trigger; the size & type of a hash prefix are
|
||||
// integral parts of the protocol and unlikely to ever change.
|
||||
static_assert(std::is_same_v<std::uint32_t, std::underlying_type_t<decltype(p)>>);
|
||||
|
||||
return add32(safeCast<std::uint32_t>(p));
|
||||
}
|
||||
|
||||
template <>
|
||||
int
|
||||
Serializer::addInteger(unsigned char i)
|
||||
{
|
||||
return add8(i);
|
||||
}
|
||||
template <>
|
||||
int
|
||||
Serializer::addInteger(std::uint16_t i)
|
||||
{
|
||||
return add16(i);
|
||||
}
|
||||
template <>
|
||||
int
|
||||
Serializer::addInteger(std::uint32_t i)
|
||||
{
|
||||
return add32(i);
|
||||
}
|
||||
template <>
|
||||
int
|
||||
Serializer::addInteger(std::uint64_t i)
|
||||
{
|
||||
return add64(i);
|
||||
}
|
||||
template <>
|
||||
int
|
||||
Serializer::addInteger(std::int32_t i)
|
||||
{
|
||||
return add32(i);
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::addRaw(Blob const& vector)
|
||||
{
|
||||
int const ret = data_.size();
|
||||
data_.insert(data_.end(), vector.begin(), vector.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::addRaw(Slice slice)
|
||||
{
|
||||
int const ret = data_.size();
|
||||
data_.insert(data_.end(), slice.begin(), slice.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::addRaw(Serializer const& s)
|
||||
{
|
||||
int const ret = data_.size();
|
||||
data_.insert(data_.end(), s.begin(), s.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::addRaw(void const* ptr, int len)
|
||||
{
|
||||
int const ret = data_.size();
|
||||
data_.insert(data_.end(), static_cast<char const*>(ptr), static_cast<char const*>(ptr) + len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::addFieldID(int type, int name)
|
||||
{
|
||||
int const ret = data_.size();
|
||||
XRPL_ASSERT(
|
||||
(type > 0) && (type < 256) && (name > 0) && (name < 256),
|
||||
"xrpl::Serializer::addFieldID : inputs inside range");
|
||||
|
||||
if (type < 16)
|
||||
{
|
||||
if (name < 16)
|
||||
{
|
||||
// common type, common name
|
||||
data_.push_back(static_cast<unsigned char>((type << 4) | name));
|
||||
}
|
||||
else
|
||||
{
|
||||
// common type, uncommon name
|
||||
data_.push_back(static_cast<unsigned char>(type << 4));
|
||||
data_.push_back(static_cast<unsigned char>(name));
|
||||
}
|
||||
}
|
||||
else if (name < 16)
|
||||
{
|
||||
// uncommon type, common name
|
||||
data_.push_back(static_cast<unsigned char>(name));
|
||||
data_.push_back(static_cast<unsigned char>(type));
|
||||
}
|
||||
else
|
||||
{
|
||||
// uncommon type, uncommon name
|
||||
data_.push_back(static_cast<unsigned char>(0));
|
||||
data_.push_back(static_cast<unsigned char>(type));
|
||||
data_.push_back(static_cast<unsigned char>(name));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::add8(unsigned char byteValue)
|
||||
{
|
||||
int const ret = data_.size();
|
||||
data_.push_back(byteValue);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
Serializer::get8(int& byte, int offset) const
|
||||
{
|
||||
if (offset >= data_.size())
|
||||
return false;
|
||||
|
||||
byte = data_[offset];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Serializer::chop(int bytes)
|
||||
{
|
||||
if (bytes > data_.size())
|
||||
return false;
|
||||
|
||||
data_.resize(data_.size() - bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
UInt256
|
||||
Serializer::getSHA512Half() const
|
||||
{
|
||||
return sha512Half(makeSlice(data_));
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::addVL(Blob const& vector)
|
||||
{
|
||||
int const ret = addEncoded(vector.size());
|
||||
addRaw(vector);
|
||||
XRPL_ASSERT(
|
||||
data_.size() == (ret + vector.size() + encodeLengthLength(vector.size())),
|
||||
"xrpl::Serializer::addVL : size matches expected");
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::addVL(Slice const& slice)
|
||||
{
|
||||
int const ret = addEncoded(slice.size());
|
||||
if (!slice.empty())
|
||||
addRaw(slice.data(), slice.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::addVL(void const* ptr, int len)
|
||||
{
|
||||
int const ret = addEncoded(len);
|
||||
|
||||
if (len != 0)
|
||||
addRaw(ptr, len);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::addEncoded(int length)
|
||||
{
|
||||
// Without this, a negative length would fall into the 1 byte case below and
|
||||
// be cast to a first byte no header uses. A size too big for int arrives
|
||||
// here negative as well, since callers pass sizes through this parameter.
|
||||
if (length < kMinValueOfLengthFor1ByteHeader)
|
||||
Throw<std::overflow_error>("addEncoded: length is negative or did not fit in an int");
|
||||
|
||||
std::array<std::byte, kMaxNumberOfBytesInHeader> bytes{};
|
||||
int numBytes = 0;
|
||||
|
||||
if (length <= kMaxValueOfLengthFor1ByteHeader)
|
||||
{
|
||||
bytes[0] = static_cast<std::byte>(length);
|
||||
numBytes = 1;
|
||||
}
|
||||
else if (length <= kMaxValueOfLengthFor2ByteHeader)
|
||||
{
|
||||
// Count from the smallest length a 2 byte header covers.
|
||||
int const offset = length - kMinValueOfLengthFor2ByteHeader;
|
||||
bytes[0] = static_cast<std::byte>(
|
||||
kMinValueOfFirstByteFor2ByteHeader + (offset / kNumberOfValuesInOneByte));
|
||||
bytes[1] = static_cast<std::byte>(offset % kNumberOfValuesInOneByte);
|
||||
numBytes = 2;
|
||||
}
|
||||
else if (length <= kMaxValueOfLengthFor3ByteHeader)
|
||||
{
|
||||
int const offset = length - kMinValueOfLengthFor3ByteHeader;
|
||||
bytes[0] = static_cast<std::byte>(
|
||||
kMinValueOfFirstByteFor3ByteHeader + (offset / kNumberOfValuesInTwoBytes));
|
||||
bytes[1] =
|
||||
static_cast<std::byte>((offset / kNumberOfValuesInOneByte) % kNumberOfValuesInOneByte);
|
||||
bytes[2] = static_cast<std::byte>(offset % kNumberOfValuesInOneByte);
|
||||
numBytes = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw<std::overflow_error>("addEncoded: length is too large to encode");
|
||||
}
|
||||
|
||||
return addRaw(bytes.data(), numBytes);
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::encodeLengthLength(int length)
|
||||
{
|
||||
if (length < kMinValueOfLengthFor1ByteHeader)
|
||||
{
|
||||
Throw<std::overflow_error>(
|
||||
"encodeLengthLength: length is negative or did not fit in an int");
|
||||
}
|
||||
|
||||
if (length <= kMaxValueOfLengthFor1ByteHeader)
|
||||
return 1;
|
||||
|
||||
if (length <= kMaxValueOfLengthFor2ByteHeader)
|
||||
return 2;
|
||||
|
||||
if (length <= kMaxValueOfLengthFor3ByteHeader)
|
||||
return 3;
|
||||
|
||||
Throw<std::overflow_error>("encodeLengthLength: length is too large to encode");
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::decodeLengthLength(std::byte firstByte)
|
||||
{
|
||||
int const firstByteValue = std::to_integer<int>(firstByte);
|
||||
|
||||
if (firstByteValue <= kMaxValueOfFirstByteFor1ByteHeader)
|
||||
return 1;
|
||||
|
||||
if (firstByteValue <= kMaxValueOfFirstByteFor2ByteHeader)
|
||||
return 2;
|
||||
|
||||
if (firstByteValue <= kMaxValueOfFirstByteFor3ByteHeader)
|
||||
return 3;
|
||||
|
||||
Throw<std::overflow_error>("decodeLengthLength: first byte does not start any header");
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::decodeVLLength(std::byte firstByte)
|
||||
{
|
||||
int const length = std::to_integer<int>(firstByte);
|
||||
|
||||
// A bigger value means a longer header, so it is not a length by itself.
|
||||
if (length > kMaxValueOfLengthFor1ByteHeader)
|
||||
Throw<std::overflow_error>("decodeVLLength 1 byte: first byte is not a length");
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::decodeVLLength(std::byte firstByte, std::byte secondByte)
|
||||
{
|
||||
int const firstByteValue = std::to_integer<int>(firstByte);
|
||||
|
||||
if (firstByteValue < kMinValueOfFirstByteFor2ByteHeader)
|
||||
Throw<std::overflow_error>("decodeVLLength 2 byte: first byte is below the range");
|
||||
|
||||
if (firstByteValue > kMaxValueOfFirstByteFor2ByteHeader)
|
||||
Throw<std::overflow_error>("decodeVLLength 2 byte: first byte is above the range");
|
||||
|
||||
// Both bytes are bounded by their own type, and the first one is bounded to
|
||||
// the 2 byte range above, so this cannot leave the range the header covers.
|
||||
return kMinValueOfLengthFor2ByteHeader +
|
||||
((firstByteValue - kMinValueOfFirstByteFor2ByteHeader) * kNumberOfValuesInOneByte) +
|
||||
std::to_integer<int>(secondByte);
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::decodeVLLength(std::byte firstByte, std::byte secondByte, std::byte thirdByte)
|
||||
{
|
||||
int const firstByteValue = std::to_integer<int>(firstByte);
|
||||
|
||||
if (firstByteValue < kMinValueOfFirstByteFor3ByteHeader)
|
||||
Throw<std::overflow_error>("decodeVLLength 3 byte: first byte is below the range");
|
||||
|
||||
if (firstByteValue > kMaxValueOfFirstByteFor3ByteHeader)
|
||||
Throw<std::overflow_error>("decodeVLLength 3 byte: first byte is above the range");
|
||||
|
||||
int const length = kMinValueOfLengthFor3ByteHeader +
|
||||
((firstByteValue - kMinValueOfFirstByteFor3ByteHeader) * kNumberOfValuesInTwoBytes) +
|
||||
(std::to_integer<int>(secondByte) * kNumberOfValuesInOneByte) +
|
||||
std::to_integer<int>(thirdByte);
|
||||
|
||||
// A 3 byte header reaches further than kMaxValueOfLengthFor3ByteHeader, which
|
||||
// is as far as the encoder goes. Refuse the rest, so every length accepted
|
||||
// here is one that can be written back.
|
||||
if (length > kMaxValueOfLengthFor3ByteHeader)
|
||||
Throw<std::overflow_error>("decodeVLLength 3 byte: length is too large to re-encode");
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
SerialIter::SerialIter(void const* data, std::size_t size) noexcept
|
||||
: p_(reinterpret_cast<std::uint8_t const*>(data)), remain_(size)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
SerialIter::reset() noexcept
|
||||
{
|
||||
p_ -= used_;
|
||||
remain_ += used_;
|
||||
used_ = 0;
|
||||
}
|
||||
|
||||
void
|
||||
SerialIter::skip(int length)
|
||||
{
|
||||
if (remain_ < length)
|
||||
Throw<std::runtime_error>("invalid SerialIter skip");
|
||||
p_ += length;
|
||||
used_ += length;
|
||||
remain_ -= length;
|
||||
}
|
||||
|
||||
unsigned char
|
||||
SerialIter::get8()
|
||||
{
|
||||
if (remain_ < 1)
|
||||
Throw<std::runtime_error>("invalid SerialIter get8");
|
||||
unsigned char const t = *p_;
|
||||
++p_;
|
||||
++used_;
|
||||
--remain_;
|
||||
return t;
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
SerialIter::get16()
|
||||
{
|
||||
if (remain_ < 2)
|
||||
Throw<std::runtime_error>("invalid SerialIter get16");
|
||||
auto t = p_;
|
||||
p_ += 2;
|
||||
used_ += 2;
|
||||
remain_ -= 2;
|
||||
return (std::uint64_t(t[0]) << 8) + std::uint64_t(t[1]);
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
SerialIter::get32()
|
||||
{
|
||||
if (remain_ < 4)
|
||||
Throw<std::runtime_error>("invalid SerialIter get32");
|
||||
auto t = p_;
|
||||
p_ += 4;
|
||||
used_ += 4;
|
||||
remain_ -= 4;
|
||||
return (std::uint64_t(t[0]) << 24) + (std::uint64_t(t[1]) << 16) + (std::uint64_t(t[2]) << 8) +
|
||||
std::uint64_t(t[3]);
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
SerialIter::get64()
|
||||
{
|
||||
if (remain_ < 8)
|
||||
Throw<std::runtime_error>("invalid SerialIter get64");
|
||||
auto t = p_;
|
||||
p_ += 8;
|
||||
used_ += 8;
|
||||
remain_ -= 8;
|
||||
return (std::uint64_t(t[0]) << 56) + (std::uint64_t(t[1]) << 48) + (std::uint64_t(t[2]) << 40) +
|
||||
(std::uint64_t(t[3]) << 32) + (std::uint64_t(t[4]) << 24) + (std::uint64_t(t[5]) << 16) +
|
||||
(std::uint64_t(t[6]) << 8) + std::uint64_t(t[7]);
|
||||
}
|
||||
|
||||
std::int32_t
|
||||
SerialIter::geti32()
|
||||
{
|
||||
if (remain_ < 4)
|
||||
Throw<std::runtime_error>("invalid SerialIter geti32");
|
||||
auto t = p_;
|
||||
p_ += 4;
|
||||
used_ += 4;
|
||||
remain_ -= 4;
|
||||
return boost::endian::load_big_s32(t);
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
SerialIter::geti64()
|
||||
{
|
||||
if (remain_ < 8)
|
||||
Throw<std::runtime_error>("invalid SerialIter geti64");
|
||||
auto t = p_;
|
||||
p_ += 8;
|
||||
used_ += 8;
|
||||
remain_ -= 8;
|
||||
return boost::endian::load_big_s64(t);
|
||||
}
|
||||
|
||||
void
|
||||
SerialIter::getFieldID(int& type, int& name)
|
||||
{
|
||||
type = get8();
|
||||
name = type & 15;
|
||||
type >>= 4;
|
||||
|
||||
if (type == 0)
|
||||
{
|
||||
// uncommon type
|
||||
type = get8();
|
||||
if (type < 16)
|
||||
Throw<std::runtime_error>("gFID: uncommon type out of range " + std::to_string(type));
|
||||
}
|
||||
|
||||
if (name == 0)
|
||||
{
|
||||
// uncommon name
|
||||
name = get8();
|
||||
if (name < 16)
|
||||
Throw<std::runtime_error>("gFID: uncommon name out of range " + std::to_string(name));
|
||||
}
|
||||
}
|
||||
|
||||
// getRaw for blob or buffer
|
||||
template <class T>
|
||||
T
|
||||
SerialIter::getRawHelper(int size)
|
||||
{
|
||||
static_assert(std::is_same_v<T, Blob> || std::is_same_v<T, Buffer>);
|
||||
if (remain_ < size)
|
||||
Throw<std::runtime_error>("invalid SerialIter getRaw");
|
||||
T result(size);
|
||||
if (size != 0)
|
||||
{
|
||||
// It's normally safe to call memcpy with size set to 0 (see the
|
||||
// C99 standard 7.21.1/2). However, here this could mean that
|
||||
// result.data would be null, which would trigger undefined behavior.
|
||||
std::memcpy(result.data(), p_, size);
|
||||
p_ += size;
|
||||
used_ += size;
|
||||
remain_ -= size;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// VFALCO DEPRECATED Returns a copy
|
||||
Blob
|
||||
SerialIter::getRaw(int size)
|
||||
{
|
||||
return getRawHelper<Blob>(size);
|
||||
}
|
||||
|
||||
int
|
||||
SerialIter::getVLDataLength()
|
||||
{
|
||||
std::byte const firstByte{get8()};
|
||||
int datLen = 0;
|
||||
int const lenLen = Serializer::decodeLengthLength(firstByte);
|
||||
if (lenLen == 1)
|
||||
{
|
||||
datLen = Serializer::decodeVLLength(firstByte);
|
||||
}
|
||||
else if (lenLen == 2)
|
||||
{
|
||||
std::byte const secondByte{get8()};
|
||||
datLen = Serializer::decodeVLLength(firstByte, secondByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
XRPL_ASSERT(lenLen == 3, "xrpl::SerialIter::getVLDataLength : lenLen is 3");
|
||||
std::byte const secondByte{get8()};
|
||||
std::byte const thirdByte{get8()};
|
||||
datLen = Serializer::decodeVLLength(firstByte, secondByte, thirdByte);
|
||||
}
|
||||
return datLen;
|
||||
}
|
||||
|
||||
Slice
|
||||
SerialIter::getSlice(std::size_t bytes)
|
||||
{
|
||||
if (bytes > remain_)
|
||||
Throw<std::runtime_error>("invalid SerialIter getSlice");
|
||||
Slice const s(p_, bytes);
|
||||
p_ += bytes;
|
||||
used_ += bytes;
|
||||
remain_ -= bytes;
|
||||
return s;
|
||||
}
|
||||
|
||||
// VFALCO DEPRECATED Returns a copy
|
||||
Blob
|
||||
SerialIter::getVL()
|
||||
{
|
||||
return getRaw(getVLDataLength());
|
||||
}
|
||||
|
||||
Buffer
|
||||
SerialIter::getVLBuffer()
|
||||
{
|
||||
return getRawHelper<Buffer>(getVLDataLength());
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -95,7 +95,7 @@ verify(STObject const& st, HashPrefix const& prefix, PublicKey const& pk, SF_VL
|
||||
Serializer ss;
|
||||
ss.add32(prefix);
|
||||
st.addWithoutSigningFields(ss);
|
||||
return verify(pk, Slice(ss.data(), ss.size()), Slice(sig->data(), sig->size()));
|
||||
return verify(pk, ss.slice(), *sig);
|
||||
}
|
||||
|
||||
// Questions regarding buildMultiSigningData:
|
||||
|
||||
@@ -35,7 +35,7 @@ constexpr std::size_t kIsoCodeLength = 3;
|
||||
std::string
|
||||
to_string(Currency const& currency)
|
||||
{
|
||||
if (currency == beast::kZero)
|
||||
if (currency == xrpCurrency())
|
||||
return systemCurrencyCode();
|
||||
|
||||
if (currency == noCurrency())
|
||||
@@ -62,7 +62,7 @@ to_string(Currency const& currency)
|
||||
}
|
||||
|
||||
bool
|
||||
toCurrency(Currency& currency, std::string const& code)
|
||||
toCurrency(Currency& currency, std::string_view code)
|
||||
{
|
||||
if (code.empty() || code == systemCurrencyCode())
|
||||
{
|
||||
@@ -87,7 +87,7 @@ toCurrency(Currency& currency, std::string const& code)
|
||||
}
|
||||
|
||||
Currency
|
||||
toCurrency(std::string const& code)
|
||||
toCurrency(std::string_view code)
|
||||
{
|
||||
Currency currency;
|
||||
if (!toCurrency(currency, code))
|
||||
@@ -95,25 +95,4 @@ toCurrency(std::string const& code)
|
||||
return currency;
|
||||
}
|
||||
|
||||
Currency const&
|
||||
xrpCurrency()
|
||||
{
|
||||
static Currency const kCurrency(beast::kZero);
|
||||
return kCurrency;
|
||||
}
|
||||
|
||||
Currency const&
|
||||
noCurrency()
|
||||
{
|
||||
static Currency const kCurrency(1);
|
||||
return kCurrency;
|
||||
}
|
||||
|
||||
Currency const&
|
||||
badCurrency()
|
||||
{
|
||||
static Currency const kCurrency(0x5852500000000000);
|
||||
return kCurrency;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -209,7 +209,7 @@ AttestationClaim::message(
|
||||
Serializer s;
|
||||
o.add(s);
|
||||
|
||||
return std::move(s.modData());
|
||||
return s.takeData();
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
@@ -346,7 +346,7 @@ AttestationCreateAccount::message(
|
||||
Serializer s;
|
||||
o.add(s);
|
||||
|
||||
return std::move(s.modData());
|
||||
return s.takeData();
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <soci/sqlite3/soci-sqlite3.h> // IWYU pragma: keep
|
||||
|
||||
#include <format>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -40,8 +41,8 @@ getSociSqliteInit(std::string const& name, std::string const& dir, std::string c
|
||||
{
|
||||
if (name.empty())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Sqlite databases must specify a dir and a name. Name: " + name + " Dir: " + dir);
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Sqlite databases must specify a dir and a name. Name: {} Dir: {}", name, dir));
|
||||
}
|
||||
std::filesystem::path file(dir);
|
||||
if (std::filesystem::is_directory(file))
|
||||
@@ -56,7 +57,7 @@ getSociInit(BasicConfig const& config, std::string const& dbName)
|
||||
auto const backendName = get(section, Keys::kBackend, "sqlite");
|
||||
|
||||
if (backendName != "sqlite")
|
||||
Throw<std::runtime_error>("Unsupported soci backend: " + backendName);
|
||||
Throw<std::runtime_error>(std::format("Unsupported soci backend: {}", backendName));
|
||||
|
||||
auto const path = config.legacy(Sections::kDatabasePath);
|
||||
auto const ext = dbName == "validators" || dbName == "peerfinder" ? ".sqlite" : ".db";
|
||||
@@ -101,7 +102,7 @@ open(soci::session& s, std::string const& beName, std::string const& connectionS
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw<std::runtime_error>("Unsupported soci backend: " + beName);
|
||||
Throw<std::runtime_error>(std::format("Unsupported soci backend: {}", beName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
@@ -48,7 +49,7 @@ to_string(Manifest const& m)
|
||||
return "Revocation Manifest " + mk;
|
||||
|
||||
if (!m.signingKey)
|
||||
Throw<std::runtime_error>("No SigningKey in manifest " + mk);
|
||||
Throw<std::runtime_error>(std::format("No SigningKey in manifest {}", mk));
|
||||
|
||||
return "Manifest " + mk + " (" + std::to_string(m.sequence) + ": " +
|
||||
toBase58(TokenType::NodePublic, *m.signingKey) + ")";
|
||||
@@ -196,9 +197,7 @@ logMftAct(
|
||||
bool
|
||||
Manifest::verify() const
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
SerialIter sit(serialized.data(), serialized.size());
|
||||
st.set(sit);
|
||||
STObject const st(SerialIter{makeSlice(serialized)}, sfGeneric);
|
||||
|
||||
// The manifest must either have a signing key or be revoked. This check
|
||||
// prevents us from accessing an unseated signingKey in the next check.
|
||||
@@ -216,9 +215,7 @@ Manifest::verify() const
|
||||
UInt256
|
||||
Manifest::hash() const
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
SerialIter sit(serialized.data(), serialized.size());
|
||||
st.set(sit);
|
||||
STObject const st(SerialIter{makeSlice(serialized)}, sfGeneric);
|
||||
return st.getHash(HashPrefix::Manifest);
|
||||
}
|
||||
|
||||
@@ -244,7 +241,7 @@ std::optional<Blob>
|
||||
Manifest::getSignature() const
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
SerialIter sit(serialized.data(), serialized.size());
|
||||
SerialIter sit(makeSlice(serialized));
|
||||
st.set(sit);
|
||||
if (!get(st, sfSignature))
|
||||
return std::nullopt;
|
||||
@@ -255,7 +252,7 @@ Blob
|
||||
Manifest::getMasterSignature() const
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
SerialIter sit(serialized.data(), serialized.size());
|
||||
SerialIter sit(makeSlice(serialized));
|
||||
st.set(sit);
|
||||
return st.getFieldVL(sfMasterSignature);
|
||||
}
|
||||
|
||||
@@ -901,7 +901,7 @@ SHAMap::writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const
|
||||
|
||||
Serializer 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashVali
|
||||
|
||||
for (auto i = 0u; i < kBranchFactor; ++i)
|
||||
{
|
||||
hashes[i].asUInt256() = si.getBitString<256>();
|
||||
hashes[i].asUInt256() = si.get256();
|
||||
|
||||
if (hashes[i].isNonZero())
|
||||
ret->isBranch_ |= (1u << i);
|
||||
@@ -173,7 +173,7 @@ SHAMapInnerNode::makeCompressedInner(Slice data)
|
||||
|
||||
while (!si.empty())
|
||||
{
|
||||
auto const hash = si.getBitString<256>();
|
||||
auto const hash = si.get256();
|
||||
auto const pos = si.get8();
|
||||
|
||||
if (pos >= kBranchFactor)
|
||||
@@ -231,7 +231,7 @@ SHAMapInnerNode::serializeForWire(Serializer& s) const
|
||||
auto hashes = hashesAndChildren_.getHashes();
|
||||
iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
|
||||
s.addBitString(hashes[indexNum].asUInt256());
|
||||
s.add8(branchNum);
|
||||
s.add8(checkedCast<std::uint8_t>(branchNum));
|
||||
});
|
||||
s.add8(kWireTypeCompressedInner);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <format>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
@@ -85,8 +88,8 @@ SHAMapNodeID::getRawString() const
|
||||
{
|
||||
Serializer s(33);
|
||||
s.addBitString(id_);
|
||||
s.add8(depth_);
|
||||
return s.getString();
|
||||
s.add8(checkedCast<std::uint8_t>(depth_));
|
||||
return std::string(static_cast<char const*>(s.data()), s.size());
|
||||
}
|
||||
|
||||
SHAMapNodeID
|
||||
@@ -107,10 +110,10 @@ SHAMapNodeID::getChildNodeID(unsigned int branch) const
|
||||
depth_ <= SHAMap::kLeafDepth, "xrpl::SHAMapNodeID::getChildNodeID : maximum leaf depth");
|
||||
|
||||
if (depth_ >= SHAMap::kLeafDepth)
|
||||
Throw<std::logic_error>("Request for child node ID of " + to_string(*this));
|
||||
Throw<std::logic_error>(std::format("Request for child node ID of {}", to_string(*this)));
|
||||
|
||||
if (!isPrefixOf(id_))
|
||||
Throw<std::logic_error>("Incorrect mask for " + to_string(*this));
|
||||
Throw<std::logic_error>(std::format("Incorrect mask for {}", to_string(*this)));
|
||||
|
||||
SHAMapNodeID node{depth_ + 1, id_};
|
||||
node.id_.begin()[depth_ / 2] |= ((depth_ & 1) != 0u) ? branch : (branch << 4);
|
||||
@@ -128,14 +131,15 @@ deserializeSHAMapNodeID(void const* data, std::size_t size)
|
||||
{
|
||||
std::optional<SHAMapNodeID> ret;
|
||||
|
||||
if (size == 33)
|
||||
if (size == UInt256::size() + 1)
|
||||
{
|
||||
unsigned int const depth = *(static_cast<unsigned char const*>(data) + 32);
|
||||
if (depth <= SHAMap::kLeafDepth)
|
||||
std::span const bytes{static_cast<unsigned char const*>(data), size};
|
||||
|
||||
if (unsigned int const depth = bytes[UInt256::size()]; depth <= SHAMap::kLeafDepth)
|
||||
{
|
||||
// Reject a serialized ID carrying bits below its own depth. Checked before
|
||||
// constructing, since the constructor asserts that same property.
|
||||
if (auto const id = UInt256::fromVoid(data); isPrefixOfAtDepth(id, depth, id))
|
||||
// constructing the node, since the constructor asserts that same property.
|
||||
if (UInt256 const id{bytes.first<UInt256::size()>()}; isPrefixOfAtDepth(id, depth, id))
|
||||
ret.emplace(depth, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,17 +461,18 @@ SHAMap::getNodeFat(
|
||||
std::stack<std::tuple<SHAMapTreeNode*, SHAMapNodeID, std::uint32_t>> stack;
|
||||
stack.emplace(node, nodeID, depth);
|
||||
|
||||
Serializer s(8192);
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
std::tie(node, nodeID, depth) = stack.top();
|
||||
stack.pop();
|
||||
|
||||
// Add this node to the reply
|
||||
s.erase();
|
||||
node->serializeForWire(s);
|
||||
data.emplace_back(nodeID, node->isLeaf(), s.getData());
|
||||
|
||||
data.emplace_back(nodeID, node->isLeaf(), [&node]() {
|
||||
Serializer s(1024);
|
||||
node->serializeForWire(s);
|
||||
return s.takeData();
|
||||
}());
|
||||
|
||||
if (node->isInner())
|
||||
{
|
||||
@@ -499,9 +500,9 @@ SHAMap::getNodeFat(
|
||||
else if (childNode->isInner() || fatLeaves)
|
||||
{
|
||||
// Just include this node
|
||||
s.erase();
|
||||
Serializer s(1024);
|
||||
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;
|
||||
root_->serializeWithPrefix(s);
|
||||
filter->gotNode(
|
||||
false, root_->getHash(), ledgerSeq_, std::move(s.modData()), root_->getType());
|
||||
filter->gotNode(false, root_->getHash(), ledgerSeq_, s.takeData(), root_->getType());
|
||||
}
|
||||
|
||||
return SHAMapAddNode::useful();
|
||||
@@ -646,8 +646,7 @@ SHAMap::addKnownNode(
|
||||
{
|
||||
Serializer s;
|
||||
treeNode->serializeWithPrefix(s);
|
||||
filter->gotNode(
|
||||
false, childHash, ledgerSeq_, std::move(s.modData()), treeNode->getType());
|
||||
filter->gotNode(false, childHash, ledgerSeq_, s.takeData(), treeNode->getType());
|
||||
}
|
||||
|
||||
return SHAMapAddNode::useful();
|
||||
@@ -815,7 +814,7 @@ SHAMap::getProofPath(UInt256 const& key) const
|
||||
{
|
||||
Serializer s;
|
||||
stack.top().first->serializeForWire(s);
|
||||
path.emplace_back(std::move(s.modData()));
|
||||
path.emplace_back(s.takeData());
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <xrpl/shamap/SHAMapTxPlusMetaLeafNode.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -25,14 +26,36 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
// A leaf node is serialized as the item data followed by a 32-byte tag.
|
||||
// Returns {item, tag}. Throws if there isn't room for the tag.
|
||||
std::pair<Slice, UInt256>
|
||||
splitTag(Slice data, char const* nodeType)
|
||||
{
|
||||
if (data.size() < kMinShaMapItemBytes + UInt256::kBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Short {} node: {} bytes (minimum {} required)",
|
||||
nodeType,
|
||||
data.size(),
|
||||
(kMinShaMapItemBytes + UInt256::kBytes)));
|
||||
}
|
||||
|
||||
auto const itemSize = data.size() - UInt256::kBytes;
|
||||
|
||||
return {data.substr(0, itemSize), SerialIter{data + itemSize}.get256()};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
if (data.size() < kMinShaMapItemBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short TXN node: " + std::to_string(data.size()) + " bytes (minimum " +
|
||||
std::to_string(kMinShaMapItemBytes) + " required)");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"Short TXN node: {} bytes (minimum {} required)", data.size(), kMinShaMapItemBytes));
|
||||
}
|
||||
|
||||
auto item = makeShamapitem(sha512Half(HashPrefix::TransactionId, data), data);
|
||||
@@ -46,82 +69,26 @@ SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashVal
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
Serializer s(data.data(), data.size());
|
||||
|
||||
UInt256 tag;
|
||||
|
||||
if (s.size() < tag.kBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short TXN+MD node: " + std::to_string(s.size()) + " bytes (minimum " +
|
||||
std::to_string(tag.kBytes) + " required for tag)");
|
||||
}
|
||||
|
||||
// FIXME: improve this interface so that the above check isn't needed
|
||||
if (!s.getBitString(tag, s.size() - tag.kBytes))
|
||||
{
|
||||
Throw<std::out_of_range>(
|
||||
"Short TXN+MD node: failed to read tag at offset " +
|
||||
std::to_string(s.size() - tag.kBytes));
|
||||
}
|
||||
|
||||
s.chop(tag.kBytes);
|
||||
|
||||
if (s.size() < kMinShaMapItemBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short TXN+MD node: " + std::to_string(s.size()) +
|
||||
" bytes after tag removal (minimum " + std::to_string(kMinShaMapItemBytes) +
|
||||
" required)");
|
||||
}
|
||||
|
||||
auto item = makeShamapitem(tag, s.slice());
|
||||
auto const [item, tag] = splitTag(data, "TXN+MD");
|
||||
|
||||
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
|
||||
SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
Serializer s(data.data(), data.size());
|
||||
|
||||
UInt256 tag;
|
||||
|
||||
if (s.size() < tag.kBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short AS node: " + std::to_string(s.size()) + " bytes (minimum " +
|
||||
std::to_string(tag.kBytes) + " required for tag)");
|
||||
}
|
||||
|
||||
// FIXME: improve this interface so that the above check isn't needed
|
||||
if (!s.getBitString(tag, s.size() - tag.kBytes))
|
||||
{
|
||||
Throw<std::out_of_range>(
|
||||
"Short AS node: failed to read tag at offset " + std::to_string(s.size() - tag.kBytes));
|
||||
}
|
||||
|
||||
s.chop(tag.kBytes);
|
||||
auto const [item, tag] = splitTag(data, "AS");
|
||||
|
||||
if (tag.isZero())
|
||||
Throw<std::runtime_error>("Invalid AS node");
|
||||
|
||||
if (s.size() < kMinShaMapItemBytes)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"Short AS node: " + std::to_string(s.size()) + " bytes after tag removal (minimum " +
|
||||
std::to_string(kMinShaMapItemBytes) + " required)");
|
||||
}
|
||||
|
||||
auto item = makeShamapitem(tag, s.slice());
|
||||
|
||||
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
|
||||
@@ -152,7 +119,7 @@ SHAMapTreeNode::makeFromWire(Slice rawNode)
|
||||
if (type == kWireTypeTransactionWithMeta)
|
||||
return makeTransactionWithMeta(rawNode, hash, hashValid);
|
||||
|
||||
Throw<std::runtime_error>("wire: Unknown type (" + std::to_string(type) + ")");
|
||||
Throw<std::runtime_error>(std::format("wire: Unknown type ({})", type));
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
@@ -161,11 +128,7 @@ SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash)
|
||||
if (rawNode.size() < 4)
|
||||
Throw<std::runtime_error>("prefix: short node");
|
||||
|
||||
// FIXME: Use SerialIter::get32?
|
||||
// Extract the prefix
|
||||
auto const type = safeCast<HashPrefix>(
|
||||
(safeCast<std::uint32_t>(rawNode[0]) << 24) + (safeCast<std::uint32_t>(rawNode[1]) << 16) +
|
||||
(safeCast<std::uint32_t>(rawNode[2]) << 8) + (safeCast<std::uint32_t>(rawNode[3])));
|
||||
auto const type = safeCast<HashPrefix>(SerialIter{rawNode}.get32());
|
||||
|
||||
rawNode.removePrefix(4);
|
||||
|
||||
@@ -183,9 +146,8 @@ SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash)
|
||||
if (type == HashPrefix::TxNode)
|
||||
return makeTransactionWithMeta(rawNode, hash, hashValid);
|
||||
|
||||
Throw<std::runtime_error>(
|
||||
"prefix: unknown type (" +
|
||||
std::to_string(safeCast<std::underlying_type_t<HashPrefix>>(type)) + ")");
|
||||
Throw<std::runtime_error>(std::format(
|
||||
"prefix: unknown type ({})", safeCast<std::underlying_type_t<HashPrefix>>(type)));
|
||||
}
|
||||
|
||||
std::string
|
||||
|
||||
@@ -38,7 +38,7 @@ ApplyContext::ApplyContext(
|
||||
, parentBatchId_(parentBatchId)
|
||||
{
|
||||
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");
|
||||
view_.emplace(&base_, flags_);
|
||||
}
|
||||
@@ -53,13 +53,14 @@ std::optional<TxMeta>
|
||||
ApplyContext::apply(TER ter)
|
||||
{
|
||||
// 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
|
||||
ApplyContext::addOrderBook(Book const& book)
|
||||
{
|
||||
if ((flags_ & TapDryRun) == TapNone)
|
||||
if ((flags_ & ApplyFlags::DryRun) == ApplyFlags::None)
|
||||
registry.get().getOrderBookDB().addOrderBook(book);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ preflightCheckSigningKey(STObject const& sigObject, beast::Journal j)
|
||||
std::optional<NotTEC>
|
||||
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];
|
||||
if (signature && !signature->empty())
|
||||
@@ -510,7 +510,8 @@ Transactor::minimumFee(
|
||||
Fees const& fees,
|
||||
ApplyFlags flags)
|
||||
{
|
||||
return scaleFeeLoad(baseFee, registry.getFeeTrack(), fees, (flags & TapUnlimited) != 0u);
|
||||
return scaleFeeLoad(
|
||||
baseFee, registry.getFeeTrack(), fees, (flags & ApplyFlags::Unlimited) != ApplyFlags::None);
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -521,7 +522,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
|
||||
auto const feePaid = ctx.tx[sfFee].xrp();
|
||||
|
||||
if ((ctx.flags & TapBatch) != 0u)
|
||||
if ((ctx.flags & ApplyFlags::Batch) != ApplyFlags::None)
|
||||
{
|
||||
if (feePaid == beast::kZero)
|
||||
return tesSUCCESS;
|
||||
@@ -951,7 +952,8 @@ Transactor::checkSign(
|
||||
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
|
||||
// Signers are provided
|
||||
@@ -1122,7 +1124,7 @@ Transactor::checkMultiSign(
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
(flags & TapDryRun) || !spk.empty(),
|
||||
(flags & ApplyFlags::DryRun) != ApplyFlags::None || !spk.empty(),
|
||||
"xrpl::Transactor::checkMultiSign : non-empty signer or "
|
||||
"simulation");
|
||||
AccountID const signingAcctIDFromPubKey =
|
||||
@@ -1604,7 +1606,7 @@ Transactor::operator()()
|
||||
if (ctx_.size() > kOversizeMetaDataCap)
|
||||
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
|
||||
// must not do anything
|
||||
@@ -1684,7 +1686,7 @@ Transactor::operator()()
|
||||
// Once we call apply, we will no longer be able to look at view()
|
||||
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, canApply, std::move(metadata));
|
||||
|
||||
@@ -215,7 +215,7 @@ applyBatchTransactions(
|
||||
auto applyOneTransaction = [®istry, &j, &parentBatchId, &batchView](STTx const& tx) {
|
||||
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(
|
||||
ret.applied == (isTesSuccess(ret.ter) || isTecClaim(ret.ter)),
|
||||
"Inner transaction should not be applied");
|
||||
@@ -274,7 +274,7 @@ applyTransaction(
|
||||
{
|
||||
// Returns false if the transaction has need not be retried.
|
||||
if (retryAssured)
|
||||
flags = flags | TapRetry;
|
||||
flags = flags | ApplyFlags::Retry;
|
||||
|
||||
JLOG(j.debug()) << "TXN " << txn.getTransactionID() << (retryAssured ? "/retry" : "/final");
|
||||
|
||||
|
||||
@@ -346,7 +346,8 @@ preflight(
|
||||
// builds; re-check the same invariant here so a release build can't
|
||||
// silently skip a proposed transaction's signature-presence checks
|
||||
// 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
|
||||
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";
|
||||
@@ -377,7 +378,8 @@ preflight(
|
||||
PreflightContext const pfCtx(registry, tx, parentBatchId, rules, flags, j);
|
||||
|
||||
// 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
|
||||
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";
|
||||
|
||||
@@ -41,6 +41,7 @@ constexpr auto kConfidentialMptTxTypes = std::to_array<TxType>({
|
||||
ttCONFIDENTIAL_MPT_MERGE_INBOX,
|
||||
ttCONFIDENTIAL_MPT_CLAWBACK,
|
||||
ttCONFIDENTIAL_MPT_MIRROR_UPDATE,
|
||||
ttCONFIDENTIAL_MPT_HOLDER_KEY_UPDATE,
|
||||
});
|
||||
|
||||
// Clamp to the cap (== INT64_MAX) before the signed conversion. Invariant
|
||||
@@ -73,7 +74,7 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef
|
||||
// only meaningful post-fixCleanup3_2_0 (the field is never set
|
||||
// pre-amendment, and the holding-deletion rule does not apply).
|
||||
// Skip both blocks when the amendment is off so we avoid wasted work
|
||||
// on the hot path.
|
||||
// on the hot path, except where noted for fixCleanup3_5_0 below.
|
||||
bool const fix320Enabled = isFeatureEnabled(fixCleanup3_2_0);
|
||||
|
||||
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
|
||||
@@ -108,7 +109,10 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef
|
||||
if (isDelete)
|
||||
{
|
||||
mptokensDeleted_++;
|
||||
if (fix320Enabled)
|
||||
// deletedHoldings_ also feeds finalize()'s erase-time public
|
||||
// balance check, gated on fixCleanup3_5_0 independently of
|
||||
// fixCleanup3_2_0.
|
||||
if (fix320Enabled || isFeatureEnabled(fixCleanup3_5_0))
|
||||
deletedHoldings_.push_back(after);
|
||||
}
|
||||
else if (!before)
|
||||
@@ -195,6 +199,19 @@ ValidMPTIssuance::finalize(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deleting an MPToken with a non-zero MPTAmount is rejected.
|
||||
if (rules.enabled(fixCleanup3_5_0))
|
||||
{
|
||||
for (auto const& sleHolding : deletedHoldings_)
|
||||
{
|
||||
if (sleHolding->getType() == ltMPTOKEN && sleHolding->getFieldU64(sfMPTAmount) > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPToken deleted with non-zero balance";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isTesSuccess(result) || (mptV2Enabled && result == tecINCOMPLETE))
|
||||
{
|
||||
[[maybe_unused]]
|
||||
@@ -579,16 +596,27 @@ ValidConfidentialMPToken::visitEntry(
|
||||
change.mptAmountDelta =
|
||||
subtractMPTAmountDelta(change.mptAmountDelta, before->getFieldU64(sfMPTAmount));
|
||||
|
||||
// Cannot delete MPToken with non-zero confidential state or non-zero public amount
|
||||
// Cannot delete MPToken with non-zero confidential state.
|
||||
if (isDelete)
|
||||
{
|
||||
bool const hasPublicBalance = before->getFieldU64(sfMPTAmount) > 0;
|
||||
bool const hasEncryptedFields = before->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
// changes_ is keyed by issuance, so sibling holders erased by the
|
||||
// same transaction share this entry. Only ever set these flags,
|
||||
// never clear them, or an empty sibling visited later would mask
|
||||
// a funded MPToken.
|
||||
|
||||
// Retired by fixCleanup3_5_0, which moved the public balance
|
||||
// check to ValidMPTIssuance::finalize. Kept pre-amendment for
|
||||
// consensus safety: a non-zero public balance used to feed the
|
||||
// confidential gate below, rejecting the erase whenever the
|
||||
// issuance's COA was non-zero, and already-validated ledgers
|
||||
// depend on that.
|
||||
if (!isFeatureEnabled(fixCleanup3_5_0) && before->getFieldU64(sfMPTAmount) > 0)
|
||||
changes_[id].deletedWithBalanceBefore = true;
|
||||
|
||||
if (before->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
before->isFieldPresent(sfConfidentialBalanceInbox) ||
|
||||
before->isFieldPresent(sfIssuerEncryptedBalance) ||
|
||||
before->isFieldPresent(sfAuditorEncryptedBalance);
|
||||
|
||||
if (hasPublicBalance || hasEncryptedFields)
|
||||
before->isFieldPresent(sfAuditorEncryptedBalance))
|
||||
changes_[id].deletedWithEncrypted = true;
|
||||
}
|
||||
}
|
||||
@@ -694,6 +722,8 @@ ValidConfidentialMPToken::finalize(
|
||||
if (result != tesSUCCESS)
|
||||
return true;
|
||||
|
||||
bool const fix350Enabled = view.rules().enabled(fixCleanup3_5_0);
|
||||
|
||||
for (auto const& [id, checks] : changes_)
|
||||
{
|
||||
// Find the MPTokenIssuance
|
||||
@@ -708,8 +738,20 @@ ValidConfidentialMPToken::finalize(
|
||||
if (!issuance)
|
||||
continue;
|
||||
|
||||
// Cannot delete MPToken with non-zero confidential state
|
||||
if (checks.deletedWithEncrypted)
|
||||
// Cannot delete MPToken with non-zero confidential state.
|
||||
//
|
||||
// Before fixCleanup3_5_0 this gate also absorbed the pre-transaction
|
||||
// public balance, so any drain-then-erase of an MPToken -- an
|
||||
// AMMWithdraw of the whole pool, a LoanBrokerDelete returning cover --
|
||||
// was rejected whenever some unrelated holder of the same issuance
|
||||
// held a confidential balance. The COA gate itself is correct for
|
||||
// ciphertext and mirrors MPTokenAuthorize::preclaim; only the public
|
||||
// balance leg was misplaced.
|
||||
bool const deletedWithEncrypted = fix350Enabled
|
||||
? checks.deletedWithEncrypted
|
||||
: (checks.deletedWithEncrypted || checks.deletedWithBalanceBefore);
|
||||
|
||||
if (deletedWithEncrypted)
|
||||
{
|
||||
if ((*issuance)[~sfConfidentialOutstandingAmount].value_or(0) > 0)
|
||||
{
|
||||
@@ -784,10 +826,10 @@ ValidConfidentialMPToken::finalize(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Among confidential MPT transactions, only ConfidentialMPTSend and
|
||||
// ConfidentialMPTMergeInbox leave coaDelta unmodified. Therefore, if a confidential MPT
|
||||
// transaction reaches here, it must be one of these two types, neither of which will
|
||||
// modify sfOutstandingAmount
|
||||
// Reaching here means this confidential MPT transaction left coaDelta
|
||||
// unmodified (e.g. ConfidentialMPTSend, ConfidentialMPTMergeInbox, or
|
||||
// ConfidentialMPTHolderKeyUpdate/ConfidentialMPTMirrorUpdate, none of which touch
|
||||
// sfConfidentialOutstandingAmount), so it must not modify sfOutstandingAmount either.
|
||||
if (checks.outstandingDelta != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: OutstandingAmount changed "
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user