mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-21 19:18:22 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02e40e7a00 | ||
|
|
f14395427e | ||
|
|
b707bac74f | ||
|
|
da2b5d5eaf | ||
|
|
4de4902200 | ||
|
|
2ea1f09dcb | ||
|
|
68b4b894de | ||
|
|
defd83e5a6 | ||
|
|
452e31a05f |
No files matched your search
@@ -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;
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <boost/align.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/core/type_name.hpp>
|
||||
#include <boost/predef.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
@@ -335,7 +336,7 @@ public:
|
||||
}) != cfg.end())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"SlabAllocatorSet<" + beast::typeName<Type>() + ">: duplicate slab size");
|
||||
"SlabAllocatorSet<" + boost::core::type_name<Type>() + ">: duplicate slab size");
|
||||
}
|
||||
|
||||
for (auto const& c : cfg)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Opt-in bitwise operators for scoped enumerations.
|
||||
*
|
||||
* Provides `&`, `|`, `^`, `~` and the corresponding compound assignment forms
|
||||
* for any scoped enumeration with an unsigned underlying type that opts in by
|
||||
* specializing @ref OptIn:
|
||||
*
|
||||
* @code
|
||||
* namespace xrpl {
|
||||
*
|
||||
* enum class MyFlags : std::uint32_t { a = 1, b = 2, c = 4 };
|
||||
*
|
||||
* template <>
|
||||
* struct enum_bitops::OptIn<MyFlags> : std::true_type {};
|
||||
*
|
||||
* } // namespace xrpl
|
||||
* @endcode
|
||||
*
|
||||
* The operators are constexpr and noexcept, and return the enumeration type.
|
||||
*
|
||||
* @par Where the specialization may be declared
|
||||
* In namespace xrpl, or at global scope with full qualification, and after
|
||||
* the enumeration is defined but before the operators are first used. This
|
||||
* cannot be done in a nested namespace or at class scope; for enumerations
|
||||
* nested in a class this means after the class definition.
|
||||
*
|
||||
* @par Where the operators are found
|
||||
* The operators are declared in namespace xrpl, so argument-dependent lookup
|
||||
* will find them only for enumerations declared in xrpl or nested in a class
|
||||
* declared directly in xrpl. Enumerations in a nested namespace will only be
|
||||
* found from code inside xrpl, and only if no enclosing scope declares an
|
||||
* operator of the same name; elsewhere they require using-declarations.
|
||||
*/
|
||||
namespace enum_bitops {
|
||||
|
||||
/**
|
||||
* Types that may be opted in to the bitwise operators.
|
||||
*
|
||||
* Satisfied by a scoped enumeration whose underlying type is an unsigned
|
||||
* integer type.
|
||||
*
|
||||
* @tparam T The type to test.
|
||||
*/
|
||||
template <typename T>
|
||||
concept Eligible = std::is_scoped_enum_v<T> && std::unsigned_integral<std::underlying_type_t<T>>;
|
||||
|
||||
/**
|
||||
* Opt-in switch for the bitwise operators.
|
||||
*
|
||||
* The primary template derives from std::false_type. Specialize it to derive
|
||||
* from std::true_type to enable the operators for an @ref Eligible enumeration.
|
||||
*
|
||||
* @tparam T The scoped enumeration to opt in.
|
||||
*/
|
||||
template <Eligible T>
|
||||
struct OptIn : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* Enumerations for which the bitwise operators are enabled.
|
||||
*
|
||||
* Satisfied when @p T satisfies @ref Eligible and @ref OptIn has been
|
||||
* specialized for it to derive from std::true_type.
|
||||
*
|
||||
* @tparam T The type to test.
|
||||
*/
|
||||
template <typename T>
|
||||
concept Candidate = Eligible<T> && OptIn<T>::value;
|
||||
|
||||
} // namespace enum_bitops
|
||||
|
||||
// These are declared in xrpl, not in enum_bitops, so that argument-dependent
|
||||
// lookup finds them for enumerations whose associated namespace is xrpl.
|
||||
|
||||
/**
|
||||
* @name Bitwise operators for opted-in scoped enumerations
|
||||
*
|
||||
* Each operator applies the corresponding built-in operator to the
|
||||
* underlying values and converts the result back to the enumeration type.
|
||||
* Both operands must have the same enumeration type; there is no implicit
|
||||
* conversion to or from the underlying type.
|
||||
*/
|
||||
/** @{ */
|
||||
|
||||
/**
|
||||
* Bitwise AND.
|
||||
*
|
||||
* @param lhs The left operand.
|
||||
* @param rhs The right operand.
|
||||
* @return The bits set in both @p lhs and @p rhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T
|
||||
operator&(T lhs, T rhs) noexcept
|
||||
{
|
||||
return static_cast<T>(std::to_underlying(lhs) & std::to_underlying(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise OR.
|
||||
*
|
||||
* @param lhs The left operand.
|
||||
* @param rhs The right operand.
|
||||
* @return The bits set in @p lhs, in @p rhs, or in both.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T
|
||||
operator|(T lhs, T rhs) noexcept
|
||||
{
|
||||
return static_cast<T>(std::to_underlying(lhs) | std::to_underlying(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise exclusive OR.
|
||||
*
|
||||
* @param lhs The left operand.
|
||||
* @param rhs The right operand.
|
||||
* @return The bits set in exactly one of @p lhs and @p rhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T
|
||||
operator^(T lhs, T rhs) noexcept
|
||||
{
|
||||
return static_cast<T>(std::to_underlying(lhs) ^ std::to_underlying(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise complement.
|
||||
*
|
||||
* The result has every bit of the underlying type that is clear in
|
||||
* @p val, including bits that no enumerator names. It is intended for
|
||||
* clearing flags, as in `flags & ~flag`.
|
||||
*
|
||||
* @param val The operand.
|
||||
* @return The complement of @p val.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T
|
||||
operator~(T val) noexcept
|
||||
{
|
||||
return static_cast<T>(~std::to_underlying(val));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise AND assignment.
|
||||
*
|
||||
* @param lhs The value to modify.
|
||||
* @param rhs The right operand.
|
||||
* @return A reference to @p lhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T&
|
||||
operator&=(T& lhs, T rhs) noexcept
|
||||
{
|
||||
lhs = lhs & rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise OR assignment.
|
||||
*
|
||||
* @param lhs The value to modify.
|
||||
* @param rhs The right operand.
|
||||
* @return A reference to @p lhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T&
|
||||
operator|=(T& lhs, T rhs) noexcept
|
||||
{
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitwise exclusive OR assignment.
|
||||
*
|
||||
* @param lhs The value to modify.
|
||||
* @param rhs The right operand.
|
||||
* @return A reference to @p lhs.
|
||||
*/
|
||||
template <enum_bitops::Candidate T>
|
||||
constexpr T&
|
||||
operator^=(T& lhs, T rhs) noexcept
|
||||
{
|
||||
lhs = lhs ^ rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl
|
||||
+152
-47
@@ -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
|
||||
+209
-153
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -101,22 +101,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>())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -317,8 +317,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();
|
||||
}
|
||||
|
||||
@@ -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,85 +21,136 @@
|
||||
|
||||
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 <>
|
||||
|
||||
@@ -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.get160();
|
||||
|
||||
PathAsset asset;
|
||||
|
||||
if (hasCurrency)
|
||||
asset = Currency::fromRaw(sit.get160());
|
||||
|
||||
if (hasMPT)
|
||||
asset = sit.get192();
|
||||
|
||||
if (hasIssuer)
|
||||
if ((*type & TypeIssuer) == TypeIssuer)
|
||||
issuer = sit.get160();
|
||||
|
||||
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
|
||||
@@ -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.";
|
||||
|
||||
@@ -985,7 +985,7 @@ std::optional<std::variant<Quality, AMMOffer<TIn, TOut>>>
|
||||
BookStep<TIn, TOut, TDerived>::tip(ReadView const& view) const
|
||||
{
|
||||
// This can be simplified (and sped up) if directories are never empty.
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
BookTip bt(sb, book_);
|
||||
auto const lobQuality = bt.step(j_) ? std::optional<Quality>(bt.quality()) : std::nullopt;
|
||||
// Multi-path offer generates an offer with the quality
|
||||
|
||||
@@ -57,10 +57,8 @@ checkNear(IOUAmount const& expected, IOUAmount const& actual)
|
||||
static bool
|
||||
isXRPAccount(STPathElement const& pe)
|
||||
{
|
||||
if (pe.getNodeType() != STPathElement::TypeAccount)
|
||||
return false;
|
||||
return isXRP(pe.getAccountID());
|
||||
};
|
||||
return pe.getNodeType() == STPathElement::TypeAccount && isXRP(pe.getAccountID());
|
||||
}
|
||||
|
||||
static std::pair<TER, std::unique_ptr<Step>>
|
||||
toStep(
|
||||
@@ -71,11 +69,8 @@ toStep(
|
||||
{
|
||||
auto& j = ctx.j;
|
||||
|
||||
if (ctx.isFirst && e1->isAccount() &&
|
||||
((e1->getNodeType() & STPathElement::TypeCurrency) != 0u) && e1->getPathAsset().isXRP())
|
||||
{
|
||||
if (ctx.isFirst && e1->isAccount() && e1->hasCurrency() && e1->getPathAsset().isXRP())
|
||||
return makeXrpEndpointStep(ctx, e1->getAccountID());
|
||||
}
|
||||
|
||||
if (ctx.isLast && isXRPAccount(*e1) && e2->isAccount())
|
||||
return makeXrpEndpointStep(ctx, e2->getAccountID());
|
||||
@@ -117,15 +112,9 @@ toStep(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
(e2->getNodeType() & STPathElement::TypeAsset) ||
|
||||
(e2->getNodeType() & STPathElement::TypeIssuer),
|
||||
"xrpl::toStep : currency or issuer");
|
||||
PathAsset const outAsset =
|
||||
((e2->getNodeType() & STPathElement::TypeAsset) != 0u) ? e2->getPathAsset() : curAsset;
|
||||
auto const outIssuer = ((e2->getNodeType() & STPathElement::TypeIssuer) != 0u)
|
||||
? e2->getIssuerID()
|
||||
: curAsset.getIssuer();
|
||||
XRPL_ASSERT(e2->hasAsset() || e2->hasIssuer(), "xrpl::toStep : currency or issuer");
|
||||
PathAsset const outAsset = e2->hasAsset() ? e2->getPathAsset() : curAsset;
|
||||
auto const outIssuer = e2->hasIssuer() ? e2->getIssuerID() : curAsset.getIssuer();
|
||||
|
||||
if (isXRP(curAsset) && outAsset.isXRP())
|
||||
{
|
||||
@@ -197,16 +186,15 @@ toStrand(
|
||||
for (std::size_t i = 0; i < path.size(); ++i)
|
||||
{
|
||||
auto const& pe = path[i];
|
||||
auto const t = pe.getNodeType();
|
||||
|
||||
if (((t & ~STPathElement::TypeAll) != 0u) || (t == 0u))
|
||||
if ((pe.getNodeType() & ~STPathElement::TypeAll) != STPathElement::TypeNone || pe.isNone())
|
||||
return {temBAD_PATH, Strand{}};
|
||||
|
||||
bool const hasAccount = (t & STPathElement::TypeAccount) != 0u;
|
||||
bool const hasIssuer = (t & STPathElement::TypeIssuer) != 0u;
|
||||
bool const hasCurrency = (t & STPathElement::TypeCurrency) != 0u;
|
||||
bool const hasMPT = (t & STPathElement::TypeMpt) != 0u;
|
||||
bool const hasAsset = (t & STPathElement::TypeAsset) != 0u;
|
||||
bool const hasAccount = pe.isType(STPathElement::TypeAccount);
|
||||
bool const hasIssuer = pe.hasIssuer();
|
||||
bool const hasCurrency = pe.hasCurrency();
|
||||
bool const hasMPT = pe.hasMPT();
|
||||
bool const hasAsset = pe.hasAsset();
|
||||
|
||||
if (hasAccount && (hasIssuer || hasCurrency))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
@@ -250,9 +238,7 @@ toStrand(
|
||||
}();
|
||||
|
||||
// Currency or MPT
|
||||
auto hasAsset = [](STPathElement const pe) {
|
||||
return pe.getNodeType() & STPathElement::TypeAsset;
|
||||
};
|
||||
auto hasAsset = [](STPathElement const& pe) { return pe.hasAsset(); };
|
||||
|
||||
std::vector<STPathElement> normPath;
|
||||
// reserve enough for the path, the implied source, destination,
|
||||
|
||||
@@ -187,7 +187,8 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
if (!dirIsEmpty(ctx.view, keylet::ownerDir(id)))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Retry: Owner directory not empty.";
|
||||
return ((ctx.flags & TapRetry) != 0u) ? TER{terOWNERS} : TER{tecOWNERS};
|
||||
return ((ctx.flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terOWNERS}
|
||||
: TER{tecOWNERS};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -295,7 +295,8 @@ OfferCreate::checkAcceptAsset(
|
||||
JLOG(j.debug()) << "delay: can't receive IOUs from non-existent issuer: "
|
||||
<< to_string(asset.getIssuer());
|
||||
|
||||
return ((flags & TapRetry) != 0u) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER};
|
||||
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_ACCOUNT}
|
||||
: TER{tecNO_ISSUER};
|
||||
}
|
||||
|
||||
// An account cannot create a trustline to itself, so no line can exist
|
||||
@@ -318,7 +319,8 @@ OfferCreate::checkAcceptAsset(
|
||||
{
|
||||
JLOG(j.debug()) << "delay: can't receive IOUs from issuer with "
|
||||
"DisallowIncomingTrustline set";
|
||||
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
|
||||
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_LINE}
|
||||
: TER{tecNO_LINE};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +328,8 @@ OfferCreate::checkAcceptAsset(
|
||||
{
|
||||
if (!trustLine)
|
||||
{
|
||||
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
|
||||
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_LINE}
|
||||
: TER{tecNO_LINE};
|
||||
}
|
||||
|
||||
// Entries have a canonical representation, determined by a
|
||||
@@ -342,7 +345,8 @@ OfferCreate::checkAcceptAsset(
|
||||
JLOG(j.debug()) << "delay: can't receive IOUs from "
|
||||
"issuer without auth.";
|
||||
|
||||
return ((flags & TapRetry) != 0u) ? TER{terNO_AUTH} : TER{tecNO_AUTH};
|
||||
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_AUTH}
|
||||
: TER{tecNO_AUTH};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,8 @@ LoanSet::preflight(PreflightContext const& ctx)
|
||||
// A proposed LoanSet is stored unsigned; its CounterpartySignature is
|
||||
// collected on-ledger afterward, so its absence here is expected, not an
|
||||
// error (On-Chain Cosigner spec §5.3.1.2).
|
||||
if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig && (ctx.flags & TapProposal) == 0)
|
||||
if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig &&
|
||||
(ctx.flags & ApplyFlags::Proposal) == ApplyFlags::None)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "LoanSet transaction must have a CounterpartySignature.";
|
||||
return temBAD_SIGNER;
|
||||
|
||||
@@ -54,8 +54,8 @@ TransactionProposalCreate::preflight(PreflightContext const& ctx)
|
||||
try
|
||||
{
|
||||
STTx const stx{STObject{proposedTx}};
|
||||
auto const inner =
|
||||
xrpl::preflight(ctx.registry, ctx.rules, stx, TapDryRun | TapProposal, ctx.j);
|
||||
auto const inner = xrpl::preflight(
|
||||
ctx.registry, ctx.rules, stx, ApplyFlags::DryRun | ApplyFlags::Proposal, ctx.j);
|
||||
if (!isTesSuccess(inner.ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "TransactionProposalCreate: proposed txn "
|
||||
|
||||
@@ -205,7 +205,7 @@ SponsorshipTransfer::preflight(PreflightContext const& ctx)
|
||||
// is collected on-ledger afterward, so its absence here is expected, not
|
||||
// an error (On-Chain Cosigner spec §5.3.1.2).
|
||||
if (isAccountReserveSponsorship && !ctx.tx.isFieldPresent(sfSponsorSignature) &&
|
||||
(ctx.flags & TapProposal) == 0)
|
||||
(ctx.flags & ApplyFlags::Proposal) == ApplyFlags::None)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "preflight: account sponsorship requires sfSponsorSignature";
|
||||
return temMALFORMED;
|
||||
|
||||
@@ -359,7 +359,8 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
// TapBatch — PreflightContext with a parentBatchId requires it.
|
||||
// LoanSet already short-circuits on tfInnerBatchTxn; it is also in
|
||||
// kDisabledTxTypes, so it never reaches this call.
|
||||
ApplyFlags const innerFlags = TapBatch | (ctx.flags & (TapProposal | TapDryRun));
|
||||
ApplyFlags const innerFlags =
|
||||
ApplyFlags::Batch | (ctx.flags & (ApplyFlags::Proposal | ApplyFlags::DryRun));
|
||||
if (auto const preflightResult =
|
||||
xrpl::preflight(ctx.registry, ctx.rules, parentBatchId, stx, innerFlags, ctx.j);
|
||||
!isTesSuccess(preflightResult.ter))
|
||||
@@ -431,7 +432,7 @@ Batch::preflightSigValidated(PreflightContext const& ctx)
|
||||
// A proposed Batch is stored unsigned; its BatchSigners are collected
|
||||
// on-ledger afterward, so the signer-presence match belongs to submission
|
||||
// time, not proposal creation (On-Chain Cosigner spec §5.3.1.2).
|
||||
if ((ctx.flags & TapProposal) != 0)
|
||||
if ((ctx.flags & ApplyFlags::Proposal) != ApplyFlags::None)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const parentBatchId = ctx.tx.getTransactionID();
|
||||
|
||||
@@ -1890,7 +1890,7 @@ private:
|
||||
auto const flowResult = [&] {
|
||||
STAmount const deliver(eth(51'000'000));
|
||||
STAmount smax(btc(61'000'000));
|
||||
PaymentSandbox sb(env.current().get(), TapNone);
|
||||
PaymentSandbox sb(env.current().get(), ApplyFlags::None);
|
||||
STPathSet paths;
|
||||
auto ipe = [](MPTTester const& iss) {
|
||||
return STPathElement(
|
||||
@@ -1928,7 +1928,7 @@ private:
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
if (flowResult.removableOffers.empty())
|
||||
return false;
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
for (auto const& o : flowResult.removableOffers)
|
||||
{
|
||||
if (auto ok = sb.peek(keylet::offer(o)))
|
||||
|
||||
@@ -2049,7 +2049,7 @@ private:
|
||||
auto const flowResult = [&] {
|
||||
STAmount const deliver(USD(51));
|
||||
STAmount smax(BTC(61));
|
||||
PaymentSandbox sb(env.current().get(), TapNone);
|
||||
PaymentSandbox sb(env.current().get(), ApplyFlags::None);
|
||||
STPathSet paths;
|
||||
auto ipe = [](Issue const& iss) {
|
||||
return STPathElement(
|
||||
@@ -2087,7 +2087,7 @@ private:
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
if (flowResult.removableOffers.empty())
|
||||
return false;
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
for (auto const& o : flowResult.removableOffers)
|
||||
{
|
||||
if (auto ok = sb.peek(keylet::offer(o)))
|
||||
|
||||
@@ -4158,7 +4158,7 @@ private:
|
||||
auto jtx = env.jt(tx, Seq(1), Fee(10));
|
||||
env.app().config().features.erase(featureMPTokensV2);
|
||||
PreflightContext const ctx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = AMMBid::checkExtraFeatures(ctx);
|
||||
BEAST_EXPECT(pf == false);
|
||||
env.app().config().features.insert(featureMPTokensV2);
|
||||
@@ -4170,7 +4170,7 @@ private:
|
||||
jtx.jv["Asset2"].removeMember("mpt_issuance_id");
|
||||
jtx.stx = env.ust(jtx);
|
||||
PreflightContext const ctx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = AMMBid::preflight(ctx);
|
||||
BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
|
||||
}
|
||||
|
||||
@@ -3263,7 +3263,7 @@ private:
|
||||
auto jtx = env.jt(tx, Seq(1), Fee(baseFee));
|
||||
env.app().config().features.erase(featureAMM);
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
|
||||
BEAST_EXPECT(pf == temDISABLED);
|
||||
env.app().config().features.insert(featureAMM);
|
||||
@@ -3274,7 +3274,7 @@ private:
|
||||
jtx.jv["TxnSignature"] = "deadbeef";
|
||||
jtx.stx = env.ust(jtx);
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
|
||||
BEAST_EXPECT(!isTesSuccess(pf));
|
||||
}
|
||||
@@ -3285,7 +3285,7 @@ private:
|
||||
jtx.jv["Asset2"].removeMember("issuer");
|
||||
jtx.stx = env.ust(jtx);
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto pf = Transactor::invokePreflight<AMMBid>(pfCtx);
|
||||
BEAST_EXPECT(pf == temBAD_AMM_TOKENS);
|
||||
}
|
||||
|
||||
@@ -594,7 +594,7 @@ public:
|
||||
stx->at(sfSigningPubKey) = makeSlice(std::string("badkey"));
|
||||
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const result = xrpl::apply(env.app(), view, *stx, TapNone, j);
|
||||
auto const result = xrpl::apply(env.app(), view, *stx, ApplyFlags::None, j);
|
||||
BEAST_EXPECT(result.ter == temBAD_SIGNATURE);
|
||||
BEAST_EXPECT(!result.applied);
|
||||
return result.applied;
|
||||
|
||||
@@ -436,7 +436,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
{
|
||||
auto jtx = env.jt(pay(alice, bob, XRP(1)), Txflags(tfInnerBatchTxn));
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, env.current()->rules(), TapNone, env.journal);
|
||||
env.app(), *jtx.stx, env.current()->rules(), ApplyFlags::None, env.journal);
|
||||
auto const pf = Transactor::invokePreflight<Payment>(pfCtx);
|
||||
BEAST_EXPECT(pf == temINVALID_INNER_BATCH);
|
||||
}
|
||||
@@ -451,7 +451,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
disabledEnv.app(),
|
||||
*jtx.stx,
|
||||
disabledEnv.current()->rules(),
|
||||
TapNone,
|
||||
ApplyFlags::None,
|
||||
disabledEnv.journal);
|
||||
auto const pf = Transactor::invokePreflight<Payment>(pfCtx);
|
||||
BEAST_EXPECT(pf == temINVALID_FLAG);
|
||||
@@ -462,7 +462,12 @@ class Batch_test : public beast::unit_test::Suite
|
||||
{
|
||||
auto jtx = env.jt(pay(alice, bob, XRP(1)));
|
||||
PreflightContext const pfCtx(
|
||||
env.app(), *jtx.stx, UInt256{1}, env.current()->rules(), TapBatch, env.journal);
|
||||
env.app(),
|
||||
*jtx.stx,
|
||||
UInt256{1},
|
||||
env.current()->rules(),
|
||||
ApplyFlags::Batch,
|
||||
env.journal);
|
||||
auto const pf = Transactor::invokePreflight<Payment>(pfCtx);
|
||||
BEAST_EXPECT(pf == temINVALID_INNER_BATCH);
|
||||
}
|
||||
@@ -1590,7 +1595,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
batch::Sig(std::vector<Reg>(kMaxBatchSigners + 1, bob)));
|
||||
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const result = xrpl::apply(env.app(), view, *jt.stx, TapNone, j);
|
||||
auto const result = xrpl::apply(env.app(), view, *jt.stx, ApplyFlags::None, j);
|
||||
BEAST_EXPECT(!result.applied && result.ter == temARRAY_TOO_LARGE);
|
||||
return result.applied;
|
||||
});
|
||||
@@ -4391,7 +4396,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(!passesLocalChecks(stx, reason));
|
||||
BEAST_EXPECT(reason == "Cannot submit pseudo transactions.");
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const result = xrpl::apply(env.app(), view, stx, TapNone, j);
|
||||
auto const result = xrpl::apply(env.app(), view, stx, ApplyFlags::None, j);
|
||||
BEAST_EXPECT(!result.applied && result.ter == temINVALID_FLAG);
|
||||
return result.applied;
|
||||
});
|
||||
@@ -5637,7 +5642,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
obj.setFieldU32(sfFlags, tfInnerBatchTxn);
|
||||
});
|
||||
|
||||
auto const result = xrpl::apply(env.app(), view, stx, TapNone, j);
|
||||
auto const result = xrpl::apply(env.app(), view, stx, ApplyFlags::None, j);
|
||||
// Must NOT be applied — signature was never checked
|
||||
BEAST_EXPECT(!result.applied);
|
||||
return false;
|
||||
|
||||
@@ -1745,7 +1745,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
(*sle)[sfConfidentialBalanceVersion] = wrappedFrom;
|
||||
view.rawReplace(sle);
|
||||
|
||||
auto const result = xrpl::apply(env.app(), view, *jt.stx, TapNone, env.journal);
|
||||
auto const result =
|
||||
xrpl::apply(env.app(), view, *jt.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(result.ter == tesSUCCESS);
|
||||
return result.applied;
|
||||
}));
|
||||
|
||||
@@ -901,8 +901,7 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
testcase("deleteSLE fail, bad SLE.");
|
||||
auto view =
|
||||
std::make_shared<ApplyViewImpl>(env.current().get(), ApplyFlags::TapNone);
|
||||
auto view = std::make_shared<ApplyViewImpl>(env.current().get(), ApplyFlags::None);
|
||||
auto ter = xrpl::credentials::deleteSLE(*view, {}, env.journal);
|
||||
BEAST_EXPECT(ter == tecNO_ENTRY);
|
||||
}
|
||||
@@ -1125,7 +1124,7 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
// Create an ApplyViewImpl on top of the current closed ledger
|
||||
// and corrupt it by erasing the issuer's account SLE
|
||||
auto const open = env.current();
|
||||
ApplyViewImpl av(&*open, TapNone);
|
||||
ApplyViewImpl av(&*open, ApplyFlags::None);
|
||||
|
||||
// Erase the issuer's account to simulate ledger corruption
|
||||
auto sleIssuer = av.peek(keylet::account(issuer.id()));
|
||||
|
||||
@@ -2795,7 +2795,7 @@ struct EscrowToken_test : public beast::unit_test::Suite
|
||||
|
||||
auto const seq1 = env.seq(alice);
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
auto sleNew =
|
||||
std::make_shared<SLE>(keylet::escrow(alice, SeqProxy::rawSequence(seq1)));
|
||||
MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
@@ -3023,7 +3023,7 @@ struct EscrowToken_test : public beast::unit_test::Suite
|
||||
|
||||
auto const seq1 = env.seq(alice);
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
auto sleNew =
|
||||
std::make_shared<SLE>(keylet::escrow(alice, SeqProxy::rawSequence(seq1)));
|
||||
MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
|
||||
@@ -1302,8 +1302,8 @@ struct Escrow_test : public beast::unit_test::Suite
|
||||
escrow::kFinishTime(env.now() + 1s),
|
||||
Seq(1),
|
||||
Fee(baseFee));
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jtx.stx, TapNone, env.journal);
|
||||
auto const pf = preflight(
|
||||
env.app(), env.current()->rules(), *jtx.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
BEAST_EXPECT(!pf.consequences.isBlocker());
|
||||
BEAST_EXPECT(pf.consequences.fee() == drops(baseFee));
|
||||
@@ -1312,8 +1312,8 @@ struct Escrow_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
auto const jtx = env.jt(escrow::cancel("bob", "alice", 3), Seq(1), Fee(baseFee));
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jtx.stx, TapNone, env.journal);
|
||||
auto const pf = preflight(
|
||||
env.app(), env.current()->rules(), *jtx.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
BEAST_EXPECT(!pf.consequences.isBlocker());
|
||||
BEAST_EXPECT(pf.consequences.fee() == drops(baseFee));
|
||||
@@ -1322,8 +1322,8 @@ struct Escrow_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
auto const jtx = env.jt(escrow::finish("bob", "alice", 3), Seq(1), Fee(baseFee));
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jtx.stx, TapNone, env.journal);
|
||||
auto const pf = preflight(
|
||||
env.app(), env.current()->rules(), *jtx.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
BEAST_EXPECT(!pf.consequences.isBlocker());
|
||||
BEAST_EXPECT(pf.consequences.fee() == drops(baseFee));
|
||||
|
||||
@@ -134,7 +134,7 @@ createInvalidFeeTx(
|
||||
bool
|
||||
applyFeeAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx)
|
||||
{
|
||||
auto const res = apply(env.app(), view, tx, ApplyFlags::TapNone, env.journal);
|
||||
auto const res = apply(env.app(), view, tx, ApplyFlags::None, env.journal);
|
||||
return isTesSuccess(res.ter);
|
||||
}
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ struct FlowMPT_test : public beast::unit_test::Suite
|
||||
auto const flowResult = [&] {
|
||||
STAmount const deliver(usd(5'100));
|
||||
STAmount smax(btc(61));
|
||||
PaymentSandbox sb(env.current().get(), TapNone);
|
||||
PaymentSandbox sb(env.current().get(), ApplyFlags::None);
|
||||
STPathSet paths;
|
||||
auto ipe = [](Asset const& asset) {
|
||||
return STPathElement(
|
||||
@@ -371,7 +371,7 @@ struct FlowMPT_test : public beast::unit_test::Suite
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
if (flowResult.removableOffers.empty())
|
||||
return false;
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
for (auto const& o : flowResult.removableOffers)
|
||||
{
|
||||
if (auto ok = sb.peek(keylet::offer(o)))
|
||||
|
||||
@@ -476,7 +476,7 @@ struct Flow_test : public beast::unit_test::Suite
|
||||
auto const flowResult = [&] {
|
||||
STAmount const deliver(usd(51));
|
||||
STAmount smax(btc(61));
|
||||
PaymentSandbox sb(env.current().get(), TapNone);
|
||||
PaymentSandbox sb(env.current().get(), ApplyFlags::None);
|
||||
STPathSet paths;
|
||||
auto ipe = [](Issue const& iss) {
|
||||
return STPathElement(
|
||||
@@ -514,7 +514,7 @@ struct Flow_test : public beast::unit_test::Suite
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
if (flowResult.removableOffers.empty())
|
||||
return false;
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
for (auto const& o : flowResult.removableOffers)
|
||||
{
|
||||
if (auto ok = sb.peek(keylet::offer(o)))
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
if (stx)
|
||||
{
|
||||
OpenView accum(&*res);
|
||||
applyTransaction(env.app(), accum, *stx, false, TapNone, env.journal);
|
||||
applyTransaction(env.app(), accum, *stx, false, ApplyFlags::None, env.journal);
|
||||
accum.apply(*res);
|
||||
}
|
||||
res->updateSkipList();
|
||||
|
||||
@@ -90,7 +90,7 @@ struct LedgerReplay_test : public beast::unit_test::Suite
|
||||
auto const lastClosedParent = ledgerMaster.getLedgerByHash(lastClosed->header().parentHash);
|
||||
|
||||
auto const replayed = buildLedger(
|
||||
LedgerReplay(lastClosedParent, lastClosed), TapNone, env.app(), env.journal);
|
||||
LedgerReplay(lastClosedParent, lastClosed), ApplyFlags::None, env.app(), env.journal);
|
||||
|
||||
BEAST_EXPECT(replayed->header().hash == lastClosed->header().hash);
|
||||
}
|
||||
@@ -122,7 +122,7 @@ struct LedgerReplay_test : public beast::unit_test::Suite
|
||||
auto const lastClosedParent = ledgerMaster.getLedgerByHash(lastClosed->header().parentHash);
|
||||
|
||||
auto const replayed = buildLedger(
|
||||
LedgerReplay(lastClosedParent, lastClosed), TapNone, env.app(), env.journal);
|
||||
LedgerReplay(lastClosedParent, lastClosed), ApplyFlags::None, env.app(), env.journal);
|
||||
|
||||
BEAST_EXPECT(replayed->header().hash == lastClosed->header().hash);
|
||||
}
|
||||
|
||||
@@ -4059,7 +4059,7 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
TER expectedTer,
|
||||
std::optional<std::uint64_t> expectedOutstanding,
|
||||
std::string const& label) {
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
auto const ter =
|
||||
accountSendMulti(av, issuer.id(), asset, receivers, env.app().getJournal("View"));
|
||||
BEAST_EXPECTS(ter == expectedTer, label);
|
||||
|
||||
@@ -776,7 +776,13 @@ class NFTokenBurn_test : public beast::unit_test::Suite
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(),
|
||||
ov,
|
||||
tx,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
ApplyFlags::None,
|
||||
jlog};
|
||||
|
||||
// Verify that the last page is present and contains one NFT.
|
||||
auto lastNFTokenPage = ac.view().peek(keylet::nftokenPageMax(alice));
|
||||
@@ -808,7 +814,13 @@ class NFTokenBurn_test : public beast::unit_test::Suite
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(),
|
||||
ov,
|
||||
tx,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
ApplyFlags::None,
|
||||
jlog};
|
||||
|
||||
// Verify that the middle page is present.
|
||||
auto lastNFTokenPage = ac.view().peek(keylet::nftokenPageMax(alice));
|
||||
|
||||
@@ -572,7 +572,7 @@ struct PayStrandMPT_test : public beast::unit_test::Suite
|
||||
inputs.defaultPathsAllowed = true;
|
||||
try
|
||||
{
|
||||
PaymentSandbox sb{env.current().get(), TapNone};
|
||||
PaymentSandbox sb{env.current().get(), ApplyFlags::None};
|
||||
{
|
||||
auto const r = ::xrpl::path::RippleCalc::rippleCalculate(
|
||||
sb,
|
||||
|
||||
@@ -768,7 +768,8 @@ struct PayStrand_test : public beast::unit_test::Suite
|
||||
env,
|
||||
usd,
|
||||
std::nullopt,
|
||||
STPath({STPathElement(0, xrpAccount(), xrpCurrency(), xrpAccount())}),
|
||||
STPath({STPathElement(
|
||||
STPathElement::Type::TypeNone, xrpAccount(), xrpCurrency(), xrpAccount())}),
|
||||
temBAD_PATH);
|
||||
|
||||
// The same account can't appear more than once on a path
|
||||
@@ -1076,7 +1077,7 @@ struct PayStrand_test : public beast::unit_test::Suite
|
||||
inputs.defaultPathsAllowed = true;
|
||||
try
|
||||
{
|
||||
PaymentSandbox sb{env.current().get(), TapNone};
|
||||
PaymentSandbox sb{env.current().get(), ApplyFlags::None};
|
||||
{
|
||||
auto const r = ::xrpl::path::RippleCalc::rippleCalculate(
|
||||
sb,
|
||||
|
||||
@@ -82,7 +82,7 @@ struct PseudoTx_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(!passesLocalChecks(stx, reason));
|
||||
BEAST_EXPECT(reason == "Cannot submit pseudo transactions.");
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const result = xrpl::apply(env.app(), view, stx, TapNone, j);
|
||||
auto const result = xrpl::apply(env.app(), view, stx, ApplyFlags::None, j);
|
||||
BEAST_EXPECT(!result.applied && result.ter == temINVALID);
|
||||
return result.applied;
|
||||
});
|
||||
|
||||
@@ -103,7 +103,8 @@ struct Regression_test : public beast::unit_test::Suite
|
||||
auto const jt = env.jt(pay(env.master, "alice", aliceAmount));
|
||||
OpenView accum(&*next);
|
||||
|
||||
auto const result = xrpl::apply(env.app(), accum, *jt.stx, TapNone, env.journal);
|
||||
auto const result =
|
||||
xrpl::apply(env.app(), accum, *jt.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(result.ter));
|
||||
BEAST_EXPECT(result.applied);
|
||||
|
||||
@@ -126,7 +127,8 @@ struct Regression_test : public beast::unit_test::Suite
|
||||
|
||||
OpenView accum(&*next);
|
||||
|
||||
auto const result = xrpl::apply(env.app(), accum, *jt.stx, TapNone, env.journal);
|
||||
auto const result =
|
||||
xrpl::apply(env.app(), accum, *jt.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(result.ter == tecINSUFF_FEE);
|
||||
BEAST_EXPECT(result.applied);
|
||||
|
||||
@@ -303,12 +305,11 @@ struct Regression_test : public beast::unit_test::Suite
|
||||
return digest.asUInt256();
|
||||
}();
|
||||
|
||||
auto const mapCounts = [&](CountedObjects::List const& list) {
|
||||
std::map<std::string, int> result;
|
||||
for (auto const& e : list)
|
||||
{
|
||||
result[e.first] = e.second;
|
||||
}
|
||||
auto const mapCounts = [] {
|
||||
std::map<std::string, std::uint32_t> result;
|
||||
|
||||
for (auto const& c : gCountedObjects)
|
||||
result.emplace(c.name(), c.count());
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -317,11 +318,11 @@ struct Regression_test : public beast::unit_test::Suite
|
||||
{
|
||||
auto& cache = env.app().getCachedSLEs();
|
||||
cache.del(*digest, false); // NOLINT(bugprone-unchecked-optional-access)
|
||||
auto const beforeCounts = mapCounts(CountedObjects::getInstance().getCounts(0));
|
||||
auto const beforeCounts = mapCounts();
|
||||
|
||||
env(check::cash(alice, bobIndex, check::DeliverMin(XRP(100))), Ter(tecNO_ENTRY));
|
||||
|
||||
auto const afterCounts = mapCounts(CountedObjects::getInstance().getCounts(0));
|
||||
auto const afterCounts = mapCounts();
|
||||
|
||||
using namespace std::string_literals;
|
||||
BEAST_EXPECT(
|
||||
|
||||
@@ -915,8 +915,8 @@ public:
|
||||
sponsor::SponseeAcc(alice),
|
||||
Seq(1),
|
||||
Fee(baseFee));
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jt.stx, TapNone, env.journal);
|
||||
auto const pf = preflight(
|
||||
env.app(), env.current()->rules(), *jt.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
BEAST_EXPECT(!pf.consequences.isBlocker());
|
||||
BEAST_EXPECT(pf.consequences.fee() == drops(baseFee));
|
||||
@@ -931,8 +931,8 @@ public:
|
||||
sponsor::SponseeAcc(alice),
|
||||
Seq(1),
|
||||
Fee(baseFee));
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jt.stx, TapNone, env.journal);
|
||||
auto const pf = preflight(
|
||||
env.app(), env.current()->rules(), *jt.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
BEAST_EXPECT(!pf.consequences.isBlocker());
|
||||
BEAST_EXPECT(pf.consequences.fee() == drops(baseFee));
|
||||
@@ -946,8 +946,8 @@ public:
|
||||
sponsor::SponseeAcc(alice),
|
||||
Seq(1),
|
||||
Fee(baseFee));
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jt.stx, TapNone, env.journal);
|
||||
auto const pf = preflight(
|
||||
env.app(), env.current()->rules(), *jt.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
BEAST_EXPECT(!pf.consequences.isBlocker());
|
||||
BEAST_EXPECT(pf.consequences.fee() == drops(baseFee));
|
||||
@@ -2242,7 +2242,8 @@ public:
|
||||
Seq(env.seq(alice)),
|
||||
sponsor::As(carol, spfSponsorFee));
|
||||
|
||||
auto const result = xrpl::apply(env.app(), overlay, *jt.stx, TapNone, env.journal);
|
||||
auto const result =
|
||||
xrpl::apply(env.app(), overlay, *jt.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(result.ter == tecINSUFF_FEE);
|
||||
BEAST_EXPECT(result.applied);
|
||||
|
||||
@@ -2295,7 +2296,8 @@ public:
|
||||
Fee(fixFee),
|
||||
sponsor::As(sponsor, spfSponsorFee));
|
||||
|
||||
auto const pf = preflight(env.app(), overlay.rules(), *jt.stx, TapNone, env.journal);
|
||||
auto const pf =
|
||||
preflight(env.app(), overlay.rules(), *jt.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
auto const pc = preclaim(pf, env.app(), overlay);
|
||||
BEAST_EXPECT(isTesSuccess(pc.ter));
|
||||
|
||||
@@ -252,7 +252,7 @@ class TheoreticalQuality_test : public beast::unit_test::Suite
|
||||
std::shared_ptr<ReadView const> closed,
|
||||
std::optional<Quality> const& expectedQ = {})
|
||||
{
|
||||
PaymentSandbox const sb(closed.get(), TapNone);
|
||||
PaymentSandbox const sb(closed.get(), ApplyFlags::None);
|
||||
AMMContext ammContext(rcp.srcAccount, false);
|
||||
|
||||
auto const sendMaxIssue = [&rcp]() -> std::optional<Asset> {
|
||||
|
||||
@@ -989,7 +989,8 @@ public:
|
||||
Env::ParsedResult parsed;
|
||||
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const result = xrpl::apply(env.app(), view, *jt.stx, TapNone, env.journal);
|
||||
auto const result =
|
||||
xrpl::apply(env.app(), view, *jt.stx, ApplyFlags::None, env.journal);
|
||||
parsed.ter = result.ter;
|
||||
return result.applied;
|
||||
});
|
||||
@@ -2424,8 +2425,8 @@ public:
|
||||
env.memoize("carol");
|
||||
{
|
||||
auto const jtx = env.jt(offerCancel(alice, 3), Seq(5), Fee(10));
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jtx.stx, TapNone, env.journal);
|
||||
auto const pf = preflight(
|
||||
env.app(), env.current()->rules(), *jtx.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
BEAST_EXPECT(!pf.consequences.isBlocker());
|
||||
BEAST_EXPECT(pf.consequences.fee() == drops(10));
|
||||
@@ -2436,8 +2437,8 @@ public:
|
||||
auto usd = alice["USD"];
|
||||
|
||||
auto const jtx = env.jt(trust("carol", usd(50000000)), Seq(1), Fee(10));
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jtx.stx, TapNone, env.journal);
|
||||
auto const pf = preflight(
|
||||
env.app(), env.current()->rules(), *jtx.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
BEAST_EXPECT(!pf.consequences.isBlocker());
|
||||
BEAST_EXPECT(pf.consequences.fee() == drops(10));
|
||||
@@ -2446,8 +2447,8 @@ public:
|
||||
|
||||
{
|
||||
auto const jtx = env.jt(ticket::create(alice, 1), Seq(1), Fee(10));
|
||||
auto const pf =
|
||||
preflight(env.app(), env.current()->rules(), *jtx.stx, TapNone, env.journal);
|
||||
auto const pf = preflight(
|
||||
env.app(), env.current()->rules(), *jtx.stx, ApplyFlags::None, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
BEAST_EXPECT(!pf.consequences.isBlocker());
|
||||
BEAST_EXPECT(pf.consequences.fee() == drops(10));
|
||||
@@ -4002,7 +4003,7 @@ public:
|
||||
// be callable via RPC.)
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const tx = env.jt(noop(alice), Seq(aliceSeq), Fee(openLedgerCost(env)));
|
||||
auto const result = xrpl::apply(env.app(), view, *tx.stx, TapUnlimited, j);
|
||||
auto const result = xrpl::apply(env.app(), view, *tx.stx, ApplyFlags::Unlimited, j);
|
||||
BEAST_EXPECT(isTesSuccess(result.ter) && result.applied);
|
||||
return result.applied;
|
||||
});
|
||||
@@ -4070,7 +4071,7 @@ public:
|
||||
// be callable via RPC.)
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const tx = env.jt(noop(alice), ticket::Use(tktSeq0 + 1), Fee(openLedgerCost(env)));
|
||||
auto const result = xrpl::apply(env.app(), view, *tx.stx, TapUnlimited, j);
|
||||
auto const result = xrpl::apply(env.app(), view, *tx.stx, ApplyFlags::Unlimited, j);
|
||||
BEAST_EXPECT(isTesSuccess(result.ter) && result.applied);
|
||||
return result.applied;
|
||||
});
|
||||
|
||||
@@ -122,7 +122,8 @@ InvariantsBase::doInvariantCheck(
|
||||
OpenView ov{*env.current()};
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
|
||||
// Invariants normally run in the Transaction's "apply" (operator()) context, and can always
|
||||
// access global Rules.
|
||||
|
||||
@@ -811,7 +811,13 @@ class InvariantsMisc_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(),
|
||||
ov,
|
||||
tx,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
ApplyFlags::None,
|
||||
jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleLoan = ac.view().peek(loanKeylet);
|
||||
@@ -866,7 +872,13 @@ class InvariantsMisc_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(),
|
||||
ov,
|
||||
tx,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
ApplyFlags::None,
|
||||
jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleLoan = ac.view().peek(loanKeylet);
|
||||
@@ -979,7 +991,13 @@ class InvariantsMisc_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(),
|
||||
ov,
|
||||
tx,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
ApplyFlags::None,
|
||||
jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleLoan = ac.view().peek(loanKeylet);
|
||||
@@ -1417,7 +1435,7 @@ class InvariantsMisc_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sle = ac.view().peek(vaultKeylet);
|
||||
@@ -1484,7 +1502,7 @@ class InvariantsMisc_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
// visitEntry only runs for entries the transaction touched, so
|
||||
@@ -1541,7 +1559,8 @@ class InvariantsMisc_test : public InvariantsBase
|
||||
STTx const tx{ttACCOUNT_SET, [](STObject&) {}};
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
FailingTxInvariantCheck failing;
|
||||
|
||||
@@ -581,7 +581,7 @@ class InvariantsPseudoAccount_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleBroker = ac.view().peek(brokerKeylet);
|
||||
@@ -633,7 +633,7 @@ class InvariantsPseudoAccount_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleBroker = ac.view().peek(brokerKeylet);
|
||||
@@ -681,7 +681,7 @@ class InvariantsPseudoAccount_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleBroker = ac.view().peek(brokerKeylet);
|
||||
|
||||
@@ -1088,7 +1088,13 @@ class InvariantsVault_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(),
|
||||
ov,
|
||||
tx,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
ApplyFlags::None,
|
||||
jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleLoan = ac.view().peek(loanKeylet);
|
||||
@@ -1140,7 +1146,7 @@ class InvariantsVault_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleLoan = ac.view().peek(loanKeylet);
|
||||
@@ -1302,7 +1308,13 @@ class InvariantsVault_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(),
|
||||
ov,
|
||||
tx,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
ApplyFlags::None,
|
||||
jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleLoan = ac.view().peek(loanKeylet);
|
||||
@@ -1357,7 +1369,13 @@ class InvariantsVault_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(),
|
||||
ov,
|
||||
tx,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
ApplyFlags::None,
|
||||
jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sleLoan = ac.view().peek(loanKeylet);
|
||||
@@ -1457,7 +1475,7 @@ class InvariantsVault_test : public InvariantsBase
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
{
|
||||
|
||||
@@ -1249,7 +1249,8 @@ class LoanBroker_test : public beast::unit_test::Suite
|
||||
OpenView ov{*env.current()};
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, ApplyFlags::None, jlog};
|
||||
|
||||
if (auto sleBroker = ac.view().peek(keylet::loanBroker(brokerKeylet.key)))
|
||||
{
|
||||
@@ -1262,7 +1263,7 @@ class LoanBroker_test : public beast::unit_test::Suite
|
||||
|
||||
// Invoke preclaim against the mutated (ApplyView) view; triggers
|
||||
// nullptr deref
|
||||
PreclaimContext const pctx{env.app(), ac.view(), tesSUCCESS, tx, TapNone, jlog};
|
||||
PreclaimContext const pctx{env.app(), ac.view(), tesSUCCESS, tx, ApplyFlags::None, jlog};
|
||||
(void)LoanBrokerCoverDeposit::preclaim(pctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ private:
|
||||
auto const peek = [keylet, &env, this](std::function<bool(SLE&, SLE&)> fn) -> bool {
|
||||
return env.app().getOpenLedger().modify(
|
||||
[&](OpenView& view, beast::Journal j) -> bool {
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
auto vault = sb.peek(keylet::vault(keylet.key));
|
||||
if (!BEAST_EXPECT(vault))
|
||||
return false;
|
||||
|
||||
@@ -300,7 +300,7 @@ private:
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
for (auto const field : encryptedBalanceFields)
|
||||
{
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
auto const token = sb.peek(tokenKeylet);
|
||||
if (!BEAST_EXPECT(token))
|
||||
return false;
|
||||
|
||||
@@ -1902,7 +1902,7 @@ negUnlSizeTest(
|
||||
bool
|
||||
applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
|
||||
{
|
||||
auto const res = apply(env.app(), view, tx, ApplyFlags::TapNone, env.journal);
|
||||
auto const res = apply(env.app(), view, tx, ApplyFlags::None, env.journal);
|
||||
if (pass)
|
||||
{
|
||||
return isTesSuccess(res.ter);
|
||||
|
||||
@@ -32,7 +32,7 @@ bumpLastPage(
|
||||
{
|
||||
std::expected<void, Error> res{};
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) -> bool {
|
||||
Sandbox sb(&view, TapNone);
|
||||
Sandbox sb(&view, ApplyFlags::None);
|
||||
|
||||
// Find the root page
|
||||
auto sleRoot = sb.peek(directory);
|
||||
|
||||
@@ -313,7 +313,7 @@ struct Directory_test : public beast::unit_test::Suite
|
||||
// All the offers have been cancelled, so the book
|
||||
// should have no entries and be empty:
|
||||
{
|
||||
Sandbox const sb(env.closed().get(), TapNone);
|
||||
Sandbox const sb(env.closed().get(), ApplyFlags::None);
|
||||
UInt256 const bookBase = getBookBase({xrpIssue(), usd, std::nullopt});
|
||||
|
||||
BEAST_EXPECT(dirIsEmpty(sb, keylet::page(bookBase)));
|
||||
@@ -352,7 +352,7 @@ struct Directory_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
// Create a chain of three pages:
|
||||
Sandbox sb(env.closed().get(), TapNone);
|
||||
Sandbox sb(env.closed().get(), ApplyFlags::None);
|
||||
makePages(sb, kBase, 3);
|
||||
|
||||
// Insert an item in the middle page:
|
||||
@@ -376,7 +376,7 @@ struct Directory_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
// Create a chain of four pages:
|
||||
Sandbox sb(env.closed().get(), TapNone);
|
||||
Sandbox sb(env.closed().get(), ApplyFlags::None);
|
||||
makePages(sb, kBase, 4);
|
||||
|
||||
// Now add items on pages 1 and 2:
|
||||
|
||||
@@ -131,7 +131,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
STAmount const toDebit(usdGw1(20));
|
||||
{
|
||||
// accountSend, no deferredCredits
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
|
||||
auto const iss = usdGw1;
|
||||
auto const startingAmount =
|
||||
@@ -156,7 +156,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
// directSendNoFee, no deferredCredits
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
|
||||
auto const iss = usdGw1;
|
||||
auto const startingAmount =
|
||||
@@ -177,7 +177,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
// accountSend, w/ deferredCredits
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
PaymentSandbox pv(&av);
|
||||
|
||||
auto const iss = usdGw1;
|
||||
@@ -205,7 +205,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
// directSendNoFee, w/ deferredCredits
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
PaymentSandbox pv(&av);
|
||||
|
||||
auto const iss = usdGw1;
|
||||
@@ -221,7 +221,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
// redeemIOU, w/ deferredCredits
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
PaymentSandbox pv(&av);
|
||||
|
||||
auto const iss = usdGw1;
|
||||
@@ -237,7 +237,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
// issueIOU, w/ deferredCredits
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
PaymentSandbox pv(&av);
|
||||
|
||||
auto const iss = usdGw1;
|
||||
@@ -253,7 +253,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
// accountSend, w/ deferredCredits and stacked views
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
PaymentSandbox pv(&av);
|
||||
|
||||
auto const iss = usdGw1;
|
||||
@@ -319,7 +319,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
STAmount const hugeAmt(
|
||||
issue, STAmount::kMaxValue, STAmount::kMaxOffset - 1, false, STAmount::Unchecked{});
|
||||
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
PaymentSandbox pv(&av);
|
||||
pv.creditHookIOU(gw, alice, hugeAmt, -tinyAmt);
|
||||
BEAST_EXPECT(pv.balanceHookIOU(alice, gw, hugeAmt) == tinyAmt);
|
||||
@@ -347,7 +347,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
env.fund(reserve(env, 1), alice);
|
||||
|
||||
env.close();
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
PaymentSandbox sb(&av);
|
||||
{
|
||||
// Send alice an amount and spend it. The deferredCredits will cause
|
||||
@@ -380,7 +380,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
auto const usd = gw["USD"];
|
||||
Account const alice("alice");
|
||||
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
PaymentSandbox sb(&av);
|
||||
|
||||
// The currency we pass for the last argument mimics the currency that
|
||||
@@ -412,7 +412,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
env.fund(XRP(10000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
ApplyViewImpl av(&*env.current(), ApplyFlags::None);
|
||||
PaymentSandbox sb(&av);
|
||||
|
||||
// Test basic owner count hook without sponsor
|
||||
|
||||
@@ -189,7 +189,7 @@ class View_test : public beast::unit_test::Suite
|
||||
Env env(*this);
|
||||
wipe(env.app().getOpenLedger());
|
||||
auto const open = env.current();
|
||||
ApplyViewImpl v(&*open, TapNone);
|
||||
ApplyViewImpl v(&*open, ApplyFlags::None);
|
||||
succ(v, 0, std::nullopt);
|
||||
v.insert(sle(1));
|
||||
BEAST_EXPECT(v.exists(k(1)));
|
||||
@@ -222,7 +222,7 @@ class View_test : public beast::unit_test::Suite
|
||||
Env env(*this);
|
||||
wipe(env.app().getOpenLedger());
|
||||
auto const open = env.current();
|
||||
ApplyViewImpl v0(&*open, TapNone);
|
||||
ApplyViewImpl v0(&*open, ApplyFlags::None);
|
||||
v0.insert(sle(1));
|
||||
v0.insert(sle(2));
|
||||
v0.insert(sle(4));
|
||||
@@ -288,7 +288,7 @@ class View_test : public beast::unit_test::Suite
|
||||
Env env(*this);
|
||||
wipe(env.app().getOpenLedger());
|
||||
auto const open = env.current();
|
||||
ApplyViewImpl v0(&*open, TapNone);
|
||||
ApplyViewImpl v0(&*open, ApplyFlags::None);
|
||||
v0.rawInsert(sle(1, 1));
|
||||
v0.rawInsert(sle(2, 2));
|
||||
v0.rawInsert(sle(4, 4));
|
||||
@@ -368,22 +368,22 @@ class View_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(v1.seq() == v0.seq());
|
||||
BEAST_EXPECT(v1.parentCloseTime() == v1.parentCloseTime());
|
||||
|
||||
ApplyViewImpl v2(&v1, TapRetry);
|
||||
ApplyViewImpl v2(&v1, ApplyFlags::Retry);
|
||||
BEAST_EXPECT(v2.parentCloseTime() == v1.parentCloseTime());
|
||||
BEAST_EXPECT(v2.seq() == v1.seq());
|
||||
BEAST_EXPECT(v2.flags() == TapRetry);
|
||||
BEAST_EXPECT(v2.flags() == ApplyFlags::Retry);
|
||||
|
||||
Sandbox const v3(&v2);
|
||||
BEAST_EXPECT(v3.seq() == v2.seq());
|
||||
BEAST_EXPECT(v3.parentCloseTime() == v2.parentCloseTime());
|
||||
BEAST_EXPECT(v3.flags() == TapRetry);
|
||||
BEAST_EXPECT(v3.flags() == ApplyFlags::Retry);
|
||||
}
|
||||
{
|
||||
ApplyViewImpl v1(&v0, TapRetry);
|
||||
ApplyViewImpl v1(&v0, ApplyFlags::Retry);
|
||||
PaymentSandbox v2(&v1);
|
||||
BEAST_EXPECT(v2.seq() == v0.seq());
|
||||
BEAST_EXPECT(v2.parentCloseTime() == v0.parentCloseTime());
|
||||
BEAST_EXPECT(v2.flags() == TapRetry);
|
||||
BEAST_EXPECT(v2.flags() == ApplyFlags::Retry);
|
||||
PaymentSandbox const v3(&v2);
|
||||
BEAST_EXPECT(v3.seq() == v2.seq());
|
||||
BEAST_EXPECT(v3.parentCloseTime() == v2.parentCloseTime());
|
||||
@@ -1028,7 +1028,7 @@ class View_test : public beast::unit_test::Suite
|
||||
wipe(*ledger);
|
||||
ledger->rawInsert(sle(1));
|
||||
ReadView const& v0 = *ledger;
|
||||
ApplyViewImpl v1(&v0, TapNone);
|
||||
ApplyViewImpl v1(&v0, ApplyFlags::None);
|
||||
{
|
||||
Sandbox v2(&v1);
|
||||
v2.erase(v2.peek(k(1)));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -52,36 +53,59 @@ class GetCounts_test : public beast::unit_test::Suite
|
||||
// check counts, default params
|
||||
result = env.rpc("get_counts")[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == "success");
|
||||
// compare with values reported by CountedObjects
|
||||
auto const& objectCounts = CountedObjects::getInstance().getCounts(10);
|
||||
for (auto const& it : objectCounts)
|
||||
|
||||
// compare with values reported by the registry
|
||||
auto const& counters = result[jss::counters];
|
||||
|
||||
for (auto const& c : gCountedObjects)
|
||||
{
|
||||
BEAST_EXPECTS(result.isMember(it.first), it.first);
|
||||
BEAST_EXPECTS(result[it.first].asInt() == it.second, it.first);
|
||||
if (auto const count = c.count(); count >= 10)
|
||||
{
|
||||
BEAST_EXPECTS(counters.isMember(c.name()), c.name());
|
||||
BEAST_EXPECTS(counters[c.name()][jss::current].asUInt() == count, c.name());
|
||||
}
|
||||
}
|
||||
|
||||
BEAST_EXPECT(!result.isMember(jss::local_txs));
|
||||
}
|
||||
|
||||
{
|
||||
// make request with min threshold 100 and verify
|
||||
// that only STObject and NodeObject are reported
|
||||
// that only counters at or above the threshold are reported
|
||||
result = env.rpc("get_counts", "100")[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == "success");
|
||||
|
||||
// compare with values reported by CountedObjects
|
||||
auto const& objectCounts = CountedObjects::getInstance().getCounts(100);
|
||||
for (auto const& it : objectCounts)
|
||||
{
|
||||
BEAST_EXPECTS(result.isMember(it.first), it.first);
|
||||
BEAST_EXPECTS(result[it.first].asInt() == it.second, it.first);
|
||||
}
|
||||
BEAST_EXPECT(!result.isMember("Transaction"));
|
||||
BEAST_EXPECT(!result.isMember("STTx"));
|
||||
BEAST_EXPECT(!result.isMember("STArray"));
|
||||
BEAST_EXPECT(!result.isMember("HashRouterEntry"));
|
||||
BEAST_EXPECT(!result.isMember("STLedgerEntry"));
|
||||
}
|
||||
auto const& counters = result[jss::counters];
|
||||
|
||||
// every registry counter at/above threshold must be reported, with
|
||||
// a matching current value
|
||||
for (auto const& c : gCountedObjects)
|
||||
{
|
||||
if (auto const count = c.count(); count >= 100)
|
||||
{
|
||||
BEAST_EXPECTS(counters.isMember(c.name()), c.name());
|
||||
BEAST_EXPECTS(counters[c.name()][jss::current].asUInt() == count, c.name());
|
||||
}
|
||||
}
|
||||
|
||||
// conversely, every reported entry must be a known counter that met
|
||||
// the threshold, and its maximum must be consistent
|
||||
for (auto const& name : counters.getMemberNames())
|
||||
{
|
||||
auto const it = std::ranges::find_if(
|
||||
gCountedObjects, [&](auto const& c) { return c.name() == name; });
|
||||
BEAST_EXPECTS(it != gCountedObjects.end(), name);
|
||||
|
||||
auto const& entry = counters[name];
|
||||
BEAST_EXPECTS(entry[jss::current].asUInt() >= 100, name);
|
||||
BEAST_EXPECTS(entry[jss::maximum].asUInt() >= entry[jss::current].asUInt(), name);
|
||||
}
|
||||
|
||||
BEAST_EXPECT(!counters.isMember("xrpl::Transaction"));
|
||||
BEAST_EXPECT(!counters.isMember("xrpl::STTx"));
|
||||
BEAST_EXPECT(!counters.isMember("xrpl::STArray"));
|
||||
BEAST_EXPECT(!counters.isMember("xrpl::HashRouterEntry"));
|
||||
BEAST_EXPECT(!counters.isMember("xrpl::STLedgerEntry"));
|
||||
}
|
||||
{
|
||||
// local_txs field will exist when there are open Txs
|
||||
env(pay(alice, bob, alice["USD"](5)));
|
||||
|
||||
@@ -107,7 +107,8 @@ TxTest::getRules() const
|
||||
[[nodiscard]] TxResult
|
||||
TxTest::submit(std::shared_ptr<STTx const> stx)
|
||||
{
|
||||
auto result = apply(registry_, *openLedger_, *stx, TapNone, registry_.getJournal("apply"));
|
||||
auto result =
|
||||
apply(registry_, *openLedger_, *stx, ApplyFlags::None, registry_.getJournal("apply"));
|
||||
|
||||
// Track successfully applied transactions for canonical reordering on close
|
||||
// We make a copy since the TransactionBase doesn't own the STTx
|
||||
@@ -198,7 +199,8 @@ TxTest::close()
|
||||
OpenView accum(&*newLedger);
|
||||
for (auto const& [key, tx] : txSet)
|
||||
{
|
||||
auto result = apply(registry_, accum, *tx, TapNone, registry_.getJournal("apply"));
|
||||
auto result =
|
||||
apply(registry_, accum, *tx, ApplyFlags::None, registry_.getJournal("apply"));
|
||||
if (!result.applied)
|
||||
{
|
||||
throw std::runtime_error("TxTest::close: failed to apply transaction");
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
EntryTestEnv() : av_(&fundAndClose(), TapNone)
|
||||
EntryTestEnv() : av_(&fundAndClose(), ApplyFlags::None)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ TEST_F(SLEBaseTests, adopt_sle)
|
||||
|
||||
TEST_F(SLEBaseTests, writable_accessors)
|
||||
{
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), ApplyFlags::None);
|
||||
beast::Journal const j{beast::Journal::getNullSink()};
|
||||
|
||||
AccountRootEntryW account(alice_.id(), av, j);
|
||||
@@ -239,7 +239,7 @@ TEST_F(SLEBaseTests, writable_accessors)
|
||||
|
||||
TEST_F(SLEBaseTests, apply_view_context_ctor)
|
||||
{
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), ApplyFlags::None);
|
||||
beast::Journal const j{beast::Journal::getNullSink()};
|
||||
|
||||
transactions::AccountSetBuilder builder{alice_.id()};
|
||||
@@ -264,7 +264,7 @@ TEST_F(SLEBaseTests, apply_view_context_ctor)
|
||||
TEST_F(SLEBaseTests, writable_lifecycle)
|
||||
{
|
||||
// A view we never apply, so nothing here reaches the ledger.
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), ApplyFlags::None);
|
||||
|
||||
// Entry that does not exist yet: newSLE() -> insert().
|
||||
{
|
||||
@@ -289,7 +289,7 @@ TEST_F(SLEBaseTests, writable_lifecycle)
|
||||
// Erase but not Cache, so it shows the difference: building the entry
|
||||
// only peeks, and the write is invisible to the view until update().
|
||||
{
|
||||
ApplyViewImpl fresh(&env_.getClosedLedger(), TapNone);
|
||||
ApplyViewImpl fresh(&env_.getClosedLedger(), ApplyFlags::None);
|
||||
|
||||
AccountRootEntryW account(alice_.id(), fresh);
|
||||
EXPECT_TRUE(account.exists());
|
||||
@@ -321,7 +321,7 @@ TEST_F(SLEBaseTests, writable_lifecycle)
|
||||
|
||||
TEST_F(SLEBaseTests, conversion)
|
||||
{
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), ApplyFlags::None);
|
||||
|
||||
AccountRootEntryW const writable(alice_.id(), av);
|
||||
EXPECT_TRUE(writable.exists());
|
||||
@@ -346,7 +346,7 @@ TEST_F(SLEBaseTests, resolve_entry_peeks)
|
||||
AccountRootEntryR const overLedger(alice_.id(), ledger);
|
||||
EXPECT_TRUE(overLedger.exists());
|
||||
|
||||
ApplyViewImpl av(&ledger, TapNone);
|
||||
ApplyViewImpl av(&ledger, ApplyFlags::None);
|
||||
|
||||
// ReadView const& binds an ApplyViewImpl just as happily, and there the
|
||||
// dynamic_cast succeeds, so this one resolves through ApplyView::peek().
|
||||
@@ -393,7 +393,7 @@ TEST_F(SLEBaseTests, throws_on_missing_entry)
|
||||
TEST_F(SLEBaseTests, throws_on_missing_writable_entry)
|
||||
{
|
||||
// A view we never apply, so nothing here reaches the ledger.
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), ApplyFlags::None);
|
||||
|
||||
// bob is unfunded, so this resolves to nothing and every operation that
|
||||
// needs an SLE has to throw instead of dereferencing null. These are the
|
||||
|
||||
@@ -655,7 +655,7 @@ RCLConsensus::Adaptor::doAccept(
|
||||
localTxs_.getTxSet(),
|
||||
anyDisputes,
|
||||
retriableTxs,
|
||||
TapNone,
|
||||
ApplyFlags::None,
|
||||
"consensus",
|
||||
[&](OpenView& view, beast::Journal j) {
|
||||
// Stuff the ledger with transactions from the queue.
|
||||
@@ -760,7 +760,7 @@ RCLConsensus::Adaptor::buildLCL(
|
||||
XRPL_ASSERT(
|
||||
replayData->parent()->header().hash == previousLedger.id(),
|
||||
"xrpl::RCLConsensus::Adaptor::buildLCL : parent hash match");
|
||||
return buildLedger(*replayData, TapNone, app_, j_);
|
||||
return buildLedger(*replayData, ApplyFlags::None, app_, j_);
|
||||
}
|
||||
return buildLedger(
|
||||
previousLedger.ledger,
|
||||
|
||||
@@ -126,7 +126,7 @@ applyTransactions(
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (applyTransaction(app, view, *it->second, certainRetry, TapNone, j))
|
||||
switch (applyTransaction(app, view, *it->second, certainRetry, ApplyFlags::None, j))
|
||||
{
|
||||
case ApplyTransactionResult::Success:
|
||||
it = txns.erase(it);
|
||||
|
||||
@@ -205,7 +205,7 @@ LedgerDeltaAcquire::tryBuild(std::shared_ptr<Ledger const> const& parent)
|
||||
"xrpl::LedgerDeltaAcquire::tryBuild : parent hash match");
|
||||
// build ledger
|
||||
LedgerReplay const replayData(parent, replayTemp_, std::move(orderedTxns_));
|
||||
fullLedger_ = buildLedger(replayData, TapNone, app_, journal_);
|
||||
fullLedger_ = buildLedger(replayData, ApplyFlags::None, app_, journal_);
|
||||
if (fullLedger_ && fullLedger_->header().hash == hash_)
|
||||
{
|
||||
JLOG(journal_.info()) << "Built " << hash_;
|
||||
|
||||
@@ -178,7 +178,7 @@ OpenLedger::applyOne(
|
||||
beast::Journal j) -> Result
|
||||
{
|
||||
if (retry)
|
||||
flags = flags | TapRetry;
|
||||
flags = flags | ApplyFlags::Retry;
|
||||
// If it's in anybody's proposed set, try to keep it in the ledger
|
||||
auto const result = xrpl::apply(app, view, *tx, flags, j);
|
||||
if (result.applied || result.ter == terQUEUED)
|
||||
|
||||
@@ -473,8 +473,6 @@ public:
|
||||
getIoContext())
|
||||
, grpcServer_(std::make_unique<GRPCServer>(*this))
|
||||
{
|
||||
initAccountIdCache(config_->getValueFor(SizedItem::AccountIdCacheSize));
|
||||
|
||||
add(resourceManager_.get());
|
||||
|
||||
//
|
||||
|
||||
@@ -226,27 +226,22 @@ FeeVoteImpl::doVoting(
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue& value,
|
||||
auto const& valueField) {
|
||||
if (auto const field = val->at(~valueField))
|
||||
using XRPType = XRPAmount::value_type;
|
||||
|
||||
if (auto const field = val->at(~valueField); field && std::in_range<XRPType>(*field))
|
||||
{
|
||||
using XRPType = XRPAmount::value_type;
|
||||
auto const vote = *field;
|
||||
if (vote <= std::numeric_limits<XRPType>::max() &&
|
||||
isLegalAmountSigned(XRPAmount{unsafeCast<XRPType>(vote)}))
|
||||
if (auto const amount = XRPAmount{checkedCast<XRPType>(*field)};
|
||||
isLegalAmountSigned(amount))
|
||||
{
|
||||
value.addVote(XRPAmount{unsafeCast<XRPType>(vote)});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid amounts will be treated as if they're
|
||||
// not provided. Don't throw because this value is
|
||||
// provided by an external entity.
|
||||
value.noVote();
|
||||
value.addVote(amount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value.noVote();
|
||||
}
|
||||
|
||||
// Invalid or missing amounts are treated as if they're not
|
||||
// provided. Don't throw because this value is provided by an
|
||||
// external entity.
|
||||
value.noVote();
|
||||
};
|
||||
|
||||
for (auto const& val : set)
|
||||
|
||||
@@ -1706,12 +1706,12 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
|
||||
for (TransactionStatus& e : transactions)
|
||||
{
|
||||
// we check before adding to the batch
|
||||
ApplyFlags flags = TapNone;
|
||||
ApplyFlags flags = ApplyFlags::None;
|
||||
if (e.admin)
|
||||
flags |= TapUnlimited;
|
||||
flags |= ApplyFlags::Unlimited;
|
||||
|
||||
if (e.failType == FailHard::Yes)
|
||||
flags |= TapFailHard;
|
||||
flags |= ApplyFlags::FailHard;
|
||||
|
||||
auto const result = registry_.get().getTxQ().apply(
|
||||
registry_.get().getApp(), view, e.transaction->getSTransaction(), flags, j);
|
||||
@@ -2207,7 +2207,7 @@ NetworkOPsImp::switchLastClosedLedger(std::shared_ptr<Ledger const> const& newLC
|
||||
OrderedTxs({}),
|
||||
false,
|
||||
retries,
|
||||
TapNone,
|
||||
ApplyFlags::None,
|
||||
"jump",
|
||||
[&](OpenView& view, beast::Journal j) {
|
||||
// Stuff the ledger with transactions from the queue.
|
||||
|
||||
@@ -333,8 +333,9 @@ TxQ::MaybeTx::apply(Application& app, OpenView& view, beast::Journal j)
|
||||
if (pfResult->rules != view.rules() || pfResult->flags != flags)
|
||||
{
|
||||
JLOG(j.debug()) << "Queued transaction " << txID
|
||||
<< " rules or flags have changed. Flags from " << pfResult->flags << " to "
|
||||
<< flags;
|
||||
<< " rules or flags have changed. Flags from "
|
||||
<< std::to_underlying(pfResult->flags) << " to "
|
||||
<< std::to_underlying(flags);
|
||||
|
||||
pfResult.emplace(preflight(app, view.rules(), pfResult->tx, flags, pfResult->j));
|
||||
}
|
||||
@@ -425,7 +426,7 @@ TxQ::canBeHeld(
|
||||
// queue yet, but should be added in the future.
|
||||
// TapFailHard transactions are never held
|
||||
if (tx.isFieldPresent(sfPreviousTxnID) || tx.isFieldPresent(sfAccountTxnID) ||
|
||||
((flags & TapFailHard) != 0u))
|
||||
((flags & ApplyFlags::FailHard) != ApplyFlags::None))
|
||||
return telCAN_NOT_QUEUE;
|
||||
|
||||
// Disallow delegated transactions from being queued.
|
||||
@@ -777,7 +778,7 @@ TxQ::apply(
|
||||
if (auto directApplied = tryDirectApply(app, view, tx, flags, j))
|
||||
return *directApplied;
|
||||
|
||||
if ((flags & TapDryRun) != 0u)
|
||||
if ((flags & ApplyFlags::DryRun) != ApplyFlags::None)
|
||||
return {telCAN_NOT_QUEUE, false};
|
||||
|
||||
// If we get past tryDirectApply() without returning then we expect
|
||||
@@ -1345,7 +1346,7 @@ TxQ::apply(
|
||||
// will not be checked again, so the cost should be minimal.
|
||||
|
||||
// Don't allow soft failures, which can lead to retries
|
||||
flags &= ~TapRetry;
|
||||
flags &= ~ApplyFlags::Retry;
|
||||
|
||||
auto& candidate = accountIter->second.add({tx, transactionID, feeLevelPaid, flags, pfResult});
|
||||
|
||||
@@ -1354,8 +1355,7 @@ TxQ::apply(
|
||||
JLOG(j_.debug()) << "Added transaction " << candidate.txID << " with result "
|
||||
<< transToken(pfResult.ter) << " from "
|
||||
<< (accountIsInQueue ? "existing" : "new") << " account " << candidate.account
|
||||
<< " to queue."
|
||||
<< " Flags: " << flags;
|
||||
<< " to queue. Flags: " << std::to_underlying(flags);
|
||||
|
||||
return {terQUEUED, false};
|
||||
}
|
||||
@@ -1473,7 +1473,8 @@ TxQ::accept(Application& app, OpenView& view)
|
||||
candidateIter++;
|
||||
continue;
|
||||
}
|
||||
auto const requiredFeeLevel = getRequiredFeeLevel(view, TapNone, metricsSnapshot, lock);
|
||||
auto const requiredFeeLevel =
|
||||
getRequiredFeeLevel(view, ApplyFlags::None, metricsSnapshot, lock);
|
||||
auto const feeLevelPaid = candidateIter->feeLevel;
|
||||
JLOG(j_.trace()) << "Queued transaction " << candidateIter->txID << " from account "
|
||||
<< candidateIter->account << " has fee level of " << feeLevelPaid
|
||||
@@ -1515,7 +1516,8 @@ TxQ::accept(Application& app, OpenView& view)
|
||||
{
|
||||
JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID << " failed with "
|
||||
<< transToken(txnResult) << ". Leave in queue."
|
||||
<< " Applied: " << didApply << ". Flags: " << candidateIter->flags;
|
||||
<< " Applied: " << didApply
|
||||
<< ". Flags: " << std::to_underlying(candidateIter->flags);
|
||||
if (account.retryPenalty && candidateIter->retriesRemaining > 2)
|
||||
{
|
||||
candidateIter->retriesRemaining = 1;
|
||||
|
||||
@@ -41,7 +41,6 @@ enum class SizedItem : std::size_t {
|
||||
OpenFinalLimit,
|
||||
BurstSize,
|
||||
RamSizeGb,
|
||||
AccountIdCacheSize,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace xrpl {
|
||||
|
||||
// clang-format off
|
||||
// The configurable node sizes are "tiny", "small", "medium", "large", "huge"
|
||||
inline constexpr std::array<std::pair<SizedItem, std::array<int, 5>>, 13>
|
||||
inline constexpr std::array<std::pair<SizedItem, std::array<int, 5>>, 12>
|
||||
kSizedItems
|
||||
{{
|
||||
// FIXME: We should document each of these items, explaining exactly
|
||||
@@ -130,8 +130,7 @@ kSizedItems
|
||||
{SizedItem::LgrDbCache, {{ 4, 8, 16, 32, 128 }}},
|
||||
{SizedItem::OpenFinalLimit, {{ 8, 16, 32, 64, 128 }}},
|
||||
{SizedItem::BurstSize, {{ 4, 8, 16, 32, 48 }}},
|
||||
{SizedItem::RamSizeGb, {{ 6, 8, 12, 24, 0 }}},
|
||||
{SizedItem::AccountIdCacheSize, {{ 20047, 50053, 77081, 150061, 300007 }}}
|
||||
{SizedItem::RamSizeGb, {{ 6, 8, 12, 24, 0 }}}
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -633,7 +633,7 @@ PathRequest::findPaths(
|
||||
path::RippleCalc::Input rcInput;
|
||||
if (convertAll_)
|
||||
rcInput.partialPaymentAllowed = true;
|
||||
auto sandbox = std::make_unique<PaymentSandbox>(&*cache->getLedger(), TapNone);
|
||||
auto sandbox = std::make_unique<PaymentSandbox>(&*cache->getLedger(), ApplyFlags::None);
|
||||
auto rc = path::RippleCalc::rippleCalculate(
|
||||
*sandbox,
|
||||
saMaxAmount, // --> Amount to send is unlimited
|
||||
@@ -654,7 +654,7 @@ PathRequest::findPaths(
|
||||
JLOG(journal_.debug()) << iIdentifier_ << " Trying with an extra path element";
|
||||
|
||||
ps.pushBack(fullLiquidityPath);
|
||||
sandbox = std::make_unique<PaymentSandbox>(&*cache->getLedger(), TapNone);
|
||||
sandbox = std::make_unique<PaymentSandbox>(&*cache->getLedger(), ApplyFlags::None);
|
||||
rc = path::RippleCalc::rippleCalculate(
|
||||
*sandbox,
|
||||
saMaxAmount, // --> Amount to send is unlimited
|
||||
|
||||
@@ -392,7 +392,7 @@ Pathfinder::getPathLiquidity(
|
||||
path::RippleCalc::Input rcInput;
|
||||
rcInput.defaultPathsAllowed = false;
|
||||
|
||||
PaymentSandbox sandbox(&*ledger_, TapNone);
|
||||
PaymentSandbox sandbox(&*ledger_, ApplyFlags::None);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -455,7 +455,7 @@ Pathfinder::computePathRanks(int maxPaths, std::function<bool(void)> const& cont
|
||||
// Must subtract liquidity in default path from remaining amount.
|
||||
try
|
||||
{
|
||||
PaymentSandbox sandbox(&*ledger_, TapNone);
|
||||
PaymentSandbox sandbox(&*ledger_, ApplyFlags::None);
|
||||
|
||||
path::RippleCalc::Input rcInput;
|
||||
rcInput.partialPaymentAllowed = true;
|
||||
@@ -948,7 +948,7 @@ Pathfinder::isNoRippleOut(STPath const& currentPath)
|
||||
|
||||
// Last link must be an account.
|
||||
STPathElement const& endElement = currentPath.back();
|
||||
if ((endElement.getNodeType() & STPathElement::TypeAccount) == 0u)
|
||||
if (!endElement.isType(STPathElement::TypeAccount))
|
||||
return false;
|
||||
|
||||
// If there's only one item in the path, return true if that item specifies
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/UptimeClock.h>
|
||||
#include <xrpl/json/json_forwards.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
@@ -11,9 +12,11 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -43,16 +46,26 @@ textTime(
|
||||
}
|
||||
|
||||
json::Value
|
||||
getCountsJson(Application& app, int minObjectCount)
|
||||
getCountsJson(Application& app, std::uint32_t minObjectCount)
|
||||
{
|
||||
auto objectCounts = CountedObjects::getInstance().getCounts(minObjectCount);
|
||||
json::Value ret = json::ValueType::Object;
|
||||
|
||||
json::Value ret(json::ValueType::Object);
|
||||
ret[jss::counters] = [minObjectCount] {
|
||||
json::Value ctrs = json::ValueType::Object;
|
||||
|
||||
for (auto const& [k, v] : objectCounts)
|
||||
{
|
||||
ret[k] = v;
|
||||
}
|
||||
for (auto const& c : gCountedObjects)
|
||||
{
|
||||
if (auto const count = c.count(); count >= minObjectCount)
|
||||
{
|
||||
json::Value obj(json::ValueType::Object);
|
||||
obj[jss::current] = count;
|
||||
obj[jss::maximum] = std::max(count, c.max());
|
||||
ctrs[c.name()] = std::move(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return ctrs;
|
||||
}();
|
||||
|
||||
if (app.config().useTxTables())
|
||||
{
|
||||
@@ -113,7 +126,7 @@ getCountsJson(Application& app, int minObjectCount)
|
||||
json::Value
|
||||
doGetCounts(rpc::JsonContext& context)
|
||||
{
|
||||
int minCount = 10;
|
||||
std::uint32_t minCount = 10;
|
||||
|
||||
if (context.params.isMember(jss::min_count))
|
||||
minCount = context.params[jss::min_count].asUInt();
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
json::Value
|
||||
getCountsJson(Application& app, int minObjectCount);
|
||||
getCountsJson(Application& app, std::uint32_t minObjectCount);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -247,7 +247,7 @@ simulateTxn(rpc::JsonContext& context, std::shared_ptr<Transaction> transaction)
|
||||
// Process the transaction
|
||||
OpenView view = *context.app.getOpenLedger().current();
|
||||
auto const result = context.app.getTxQ().apply(
|
||||
context.app, view, transaction->getSTransaction(), TapDryRun, context.j);
|
||||
context.app, view, transaction->getSTransaction(), ApplyFlags::DryRun, context.j);
|
||||
|
||||
jvResult[jss::applied] = result.applied;
|
||||
jvResult[jss::ledger_index] = view.seq();
|
||||
|
||||
Reference in new issue
Block a user