Compare commits

..
Author SHA1 Message Date
Richard Holland 51304bf04f testnet genesis fix (#902)
* testnet genesis fix

* ordering
2026-10-17 09:26:53 +10:00
Richard Holland 68d69ff9d0 ignore amendment cfg option (#900) (#903)
Use like so, on testnet:
[ignore_amendments]
HookOnV2
2026-10-17 09:20:37 +10:00
87 changed files with 8937 additions and 6960 deletions

No files matched your search

+1 -1
View File
@@ -41,7 +41,7 @@ Loop: xrpld.core xrpld.net
xrpld.net > xrpld.core
Loop: xrpld.core xrpld.perflog
xrpld.perflog ~= xrpld.core
xrpld.perflog == xrpld.core
Loop: xrpld.net xrpld.rpc
xrpld.rpc > xrpld.net
+2
View File
@@ -52,6 +52,8 @@ test.core > test.toplevel
test.core > test.unit_test
test.core > xrpl.basics
test.core > xrpld.core
test.core > xrpld.perflog
test.core > xrpl.json
test.core > xrpl.server
test.csf > xrpl.basics
test.csf > xrpld.consensus
+1 -1
View File
@@ -88,7 +88,7 @@ git remote -v >> release.info &&
echo "Git status:" >> release.info &&
git status -v >> release.info &&
echo "Git log [last 20]:" >> release.info &&
git log -n 20 >> release.info || exit 1
git log -n 20 >> release.info;
if [[ "$4" == "" ]]; then
# Non GH, local building
+83 -124
View File
@@ -1,33 +1,75 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
#define RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
#include <xrpl/beast/type_name.h>
#include <atomic>
#include <cstddef>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
namespace ripple {
/** Manages all counted object types. */
class CountedObjects
{
public:
static CountedObjects&
getInstance() noexcept;
using Entry = std::pair<std::string, int>;
using List = std::vector<Entry>;
List
getCounts(int minimumThreshold) const;
public:
/** Implementation for @ref CountedObject.
@internal
*/
class Counter
{
public:
Counter(std::string name) noexcept;
Counter(std::string name) noexcept : name_(std::move(name)), count_(0)
{
// Insert ourselves at the front of the lock-free linked list
CountedObjects& instance = CountedObjects::getInstance();
Counter* head;
do
{
head = instance.m_head.load();
next_ = head;
} while (instance.m_head.exchange(this) != head);
++instance.m_count;
}
~Counter() noexcept = default;
int
increment() noexcept
{
auto const newCount = ++count_;
if (auto maxCount = maxCount_.load(); newCount > maxCount)
maxCount_.compare_exchange_strong(maxCount, newCount);
return newCount;
return ++count_;
}
int
@@ -37,161 +79,78 @@ public:
}
int
count() const noexcept
getCount() const noexcept
{
return count_.load();
}
int
max() const noexcept
Counter*
getNext() const noexcept
{
return std::max(count_.load(), maxCount_.load());
return next_;
}
std::string const&
name() const noexcept
getName() const noexcept
{
return name_;
}
private:
friend class CountedObjects;
Counter* next_;
std::atomic<std::uint32_t> count_ = 0;
std::atomic<std::uint32_t> maxCount_ = 0;
std::string const name_;
std::atomic<int> count_;
Counter* next_;
};
class Iterator
{
public:
using value_type = Counter const;
using reference = value_type&;
using pointer = value_type*;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
explicit Iterator(Counter* c = nullptr) noexcept : current_(c)
{
}
reference
operator*() const noexcept
{
return *current_;
}
pointer
operator->() const noexcept
{
return current_;
}
Iterator&
operator++() noexcept
{
current_ = current_->next_;
return *this;
}
Iterator
operator++(int) noexcept
{
auto tmp = *this;
++*this;
return tmp;
}
bool
operator==(Iterator const&) const noexcept = default;
private:
Counter* current_;
};
using List = std::vector<std::pair<std::string, int>>;
constexpr CountedObjects() noexcept = default;
static CountedObjects&
getInstance() noexcept;
/** Returns the name and count of every counter with at least
`minimumThreshold` live instances. */
List
getCounts(int minimumThreshold) const
{
List ret;
for (auto c = head_.load(); c != nullptr; c = c->next_)
if (c->count() >= minimumThreshold)
ret.emplace_back(c->name(), c->count());
return ret;
}
auto
begin() const noexcept
{
return Iterator{head_.load()};
}
auto
end() const noexcept
{
return Iterator{};
}
private:
friend class Counter;
CountedObjects() noexcept;
~CountedObjects() noexcept = default;
std::atomic<Counter*> head_ = nullptr;
private:
std::atomic<int> m_count;
std::atomic<Counter*> m_head;
};
inline constinit CountedObjects countedObjects;
inline CountedObjects&
CountedObjects::getInstance() noexcept
{
return countedObjects;
}
inline CountedObjects::Counter::Counter(std::string name) noexcept
: name_(std::move(name))
{
do
next_ = countedObjects.head_.load();
while (!countedObjects.head_.compare_exchange_weak(next_, this));
}
//------------------------------------------------------------------------------
/** Tracks the number of instances of an object.
Derived classes have their instances counted automatically. This is used
for reporting purposes.
@ingroup ripple_basics
*/
template <class Object>
class CountedObject
{
static CountedObjects::Counter counter_;
private:
static auto&
getCounter() noexcept
{
static CountedObjects::Counter c{beast::type_name<Object>()};
return c;
}
public:
CountedObject() noexcept
{
counter_.increment();
getCounter().increment();
}
CountedObject(CountedObject const&) noexcept
{
counter_.increment();
getCounter().increment();
}
CountedObject&
operator=(CountedObject const&) noexcept = default;
~CountedObject() noexcept
{
counter_.decrement();
getCounter().decrement();
}
};
// Instantiation of the static CountedObject<T>::counter_
template <class Object>
CountedObjects::Counter CountedObject<Object>::counter_{
beast::type_name<Object>()};
} // namespace ripple
#endif
+1 -1
View File
@@ -113,7 +113,7 @@ template <>
inline std::size_t
extract(SHAMapHash const& key)
{
return extract(key.as_uint256());
return *reinterpret_cast<std::size_t const*>(key.as_uint256().data());
}
} // namespace ripple
+300 -523
View File
@@ -21,443 +21,195 @@
#define RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/spinlock.h>
#include <xrpl/beast/type_name.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <boost/align.hpp>
#include <boost/predef/os.h>
#include <boost/container/static_vector.hpp>
#include <boost/predef.h>
#include <array>
#include <algorithm>
#include <atomic>
#include <bit>
#include <cstdint>
#include <cstring>
#include <mutex>
#include <vector>
#include <limits>
#include <new>
#ifdef BOOST_OS_LINUX
#if BOOST_OS_LINUX
#include <sys/mman.h>
#endif
namespace ripple {
namespace slab {
namespace detail {
/// Alignment for hugepage support.
inline constexpr std::size_t pageSize = megabytes(std::size_t(2));
/** Allocate a new 2MB-aligned data buffer.
@param size Size of the buffer to allocate in bytes.
@return Pointer to allocated memory, or nullptr on failure.
@note On Linux, advises the kernel to back the allocation with
transparent huge pages if available.
*/
[[nodiscard, gnu::malloc]] inline std::uint8_t*
allocateBuffer(std::size_t size) noexcept
template <typename Type>
class SlabAllocator
{
auto ptr = reinterpret_cast<std::uint8_t*>(
boost::alignment::aligned_alloc(pageSize, size));
static_assert(
sizeof(Type) >= sizeof(std::uint8_t*),
"SlabAllocator: the requested object must be larger than a pointer.");
#if BOOST_OS_LINUX
if (ptr != nullptr) [[likely]]
madvise(ptr, size, MADV_HUGEPAGE);
#endif
static_assert(alignof(Type) == 8 || alignof(Type) == 4);
return ptr;
}
/** Deallocate a buffer previously allocated by allocateBuffer.
@param ptr Pointer to buffer, or nullptr (no-op).
*/
inline void
deallocateBuffer(std::uint8_t* ptr) noexcept
{
boost::alignment::aligned_free(ptr);
}
//------------------------------------------------------------------------------
/** A block of memory metadata for slab allocators.
Each block manages a single slab buffer and its associated free list.
Blocks are linked together to form a list of slabs per allocator.
@note slab_t instances are intentionally never destroyed or reclaimed
once allocated, to support lock-free iteration of lists of slabs
without introducing ABA hazards. The data buffer the class holds
can be released independently.
*/
struct slab_t
{
/** A node in the intrusive free list.
Constructed in-place within free memory blocks. The next pointer
is const because nodes are never modified after construction;
they are simply constructed anew when reused.
*/
struct item_t
/** A block of memory that is owned by a slab allocator */
struct SlabBlock
{
item_t* const next;
// A mutex to protect the freelist for this block:
std::mutex m_;
constexpr explicit item_t(item_t* n) noexcept : next(n)
// A linked list of appropriately sized free buffers:
std::uint8_t* l_ = nullptr;
// The next memory block
SlabBlock* next_;
// The underlying memory block:
std::uint8_t const* const p_ = nullptr;
// The extent of the underlying memory block:
std::size_t const size_;
SlabBlock(
SlabBlock* next,
std::uint8_t* data,
std::size_t size,
std::size_t item)
: next_(next), p_(data), size_(size)
{
}
};
// We don't need to grab the mutex here, since we're the only
// ones with access at this moment.
// We need this to be trivially destructible so that we do not
// have to explicitly invoke the destructor when popping items
// from the free list or when deallocating the slab buffer.
static_assert(std::is_trivially_destructible_v<item_t>);
item_t* head_ = nullptr;
slab_t* const next_ = nullptr;
std::atomic<std::uint8_t*> data_{nullptr};
std::atomic<std::uint8_t> lock_{0};
std::uint16_t cycles_ = 0;
std::uint32_t outstanding_ = 0;
/** Construct a block that is unlinked. */
constexpr slab_t() noexcept = default;
/** Construct a block with a link to the next block in the chain.
@param next Pointer to the next block, or nullptr for the tail.
*/
constexpr explicit slab_t(slab_t* next) noexcept : next_(next)
{
}
~slab_t() = default;
slab_t(slab_t const&) = delete;
slab_t&
operator=(slab_t const&) = delete;
slab_t(slab_t&&) = delete;
slab_t&
operator=(slab_t&&) = delete;
/** Attempt to acquire the block's spinlock.
@return true if lock was acquired, false if already held.
*/
[[nodiscard]] bool
try_lock() noexcept
{
return spin_try_lock(lock_);
}
/** Acquire the block's spinlock, blocking until available. */
void
lock() noexcept
{
spin_lock(lock_);
}
/** Release the block's spinlock. */
void
unlock() noexcept
{
spin_unlock(lock_);
}
/** Attempt to assign a buffer to this block.
If the block has no buffer, takes ownership and initializes
the free list.
@param buffer Pointer to the buffer to assign.
@param slabSize Size of the buffer in bytes.
@param itemSize Size of each item in bytes.
@return Pointer to first item if assigned, nullptr if block already
has a buffer.
*/
[[nodiscard]] std::uint8_t*
try_assign(
std::uint8_t* buffer,
std::size_t slabSize,
std::size_t itemSize) noexcept
{
std::uint8_t* expected = nullptr;
if (!data_.compare_exchange_strong(
expected, buffer, std::memory_order::acquire))
return nullptr;
spin_lock(lock_);
head_ = nullptr;
outstanding_ = 1;
auto p = buffer;
for (std::size_t n = (slabSize / itemSize) - 1; n--; p += itemSize)
head_ = std::construct_at(reinterpret_cast<item_t*>(p), head_);
spin_unlock(lock_);
return p;
}
/** Attempt to allocate from this block.
@return Pointer to allocated memory, or nullptr if block is empty.
*/
[[nodiscard]] std::uint8_t*
try_allocate() noexcept
{
spin_lock(lock_);
auto ret = head_;
if (ret)
{
head_ = ret->next;
++outstanding_;
}
spin_unlock(lock_);
return reinterpret_cast<std::uint8_t*>(ret);
}
/** Attempt to return a pointer to this block.
@param ptr Pointer to memory block.
@param slabSize Size of the slab's data buffer.
@param releaseBuffer Callback invoked with a pointer to the block's
buffer so that it can be released.
@return true if ptr belonged to this block and was freed,
false otherwise.
*/
template <typename ReleaseFunc>
[[nodiscard]] bool
try_deallocate(
std::uint8_t* ptr,
std::size_t slabSize,
ReleaseFunc&& releaseBuffer) noexcept
{
auto data = data_.load(std::memory_order::acquire);
if (!data || ptr < data || ptr >= data + slabSize)
return false;
std::uint8_t* buf = nullptr;
spin_lock(lock_);
assert(ptr != nullptr);
assert(outstanding_ > 0);
head_ = std::construct_at(reinterpret_cast<item_t*>(ptr), head_);
// If this block became empty and it is not the first block, we
// release its buffer. The first block (at the tail of the list
// with next_ == nullptr) is exempt to keep one block ready.
if (--outstanding_ == 0 && next_ != nullptr)
{
head_ = nullptr;
buf = data_.exchange(buf, std::memory_order::relaxed);
cycles_ += (cycles_ < std::numeric_limits<std::uint16_t>::max());
}
spin_unlock(lock_);
if (buf != nullptr)
releaseBuffer(buf);
return true;
}
};
//------------------------------------------------------------------------------
/** Global pool of slab_t objects shared by all slab allocators.
Provides a fixed-size array of pre-allocated slabs with fallback
to heap allocation if exhausted.
*/
class slab_pool_t
{
std::array<slab_t, 32768> blocks_{};
std::atomic<std::size_t> count_{0};
public:
constexpr slab_pool_t() = default;
slab_pool_t(slab_pool_t const&) = delete;
slab_pool_t&
operator=(slab_pool_t const&) = delete;
slab_pool_t(slab_pool_t&&) = delete;
slab_pool_t&
operator=(slab_pool_t&&) = delete;
/** Acquire a slab from the pool.
@param next Pointer to link as the slab's next pointer.
@return Pointer to acquired block, or nullptr if allocation failed.
*/
[[nodiscard]] slab_t*
acquire(slab_t* next) noexcept
{
auto idx = count_.load(std::memory_order::relaxed);
while (idx < blocks_.size())
{
if (count_.compare_exchange_weak(
idx, idx + 1, std::memory_order::relaxed))
while (data + item <= p_ + size_)
{
return std::construct_at(&blocks_[idx], next);
// Use memcpy to avoid unaligned UB
// (will optimize to equivalent code)
std::memcpy(data, &l_, sizeof(std::uint8_t*));
l_ = data;
data += item;
}
}
// Pool exhausted (unlikely!) so fall back to heap
return new (std::nothrow) slab_t(next);
}
};
/// Single global block pool for all slab allocators.
inline constinit slab_pool_t globalSlabPool;
} // namespace detail
//------------------------------------------------------------------------------
/** A slab allocator for fixed-size memory blocks.
Allocates memory in large slabs (multiples of 2MB) and sub-allocates
fixed-size blocks from them. Provides fast allocation with minimal
fragmentation for objects of uniform size.
@tparam Type The type used to determine minimum block size and alignment.
@tparam Align Alignment for allocated blocks (must be >= alignof(Type)).
*/
template <typename Type, std::size_t Align = alignof(Type)>
requires(
sizeof(Type) >= sizeof(void*) && Align >= alignof(Type) &&
std::has_single_bit(Align))
class sized_allocator_t
{
/// Linked list of slabs for this allocator.
std::atomic<detail::slab_t*> slabs_{nullptr};
/// Cached buffer to avoid thrashing when oscillating around block
/// boundaries.
std::atomic<std::uint8_t*> cachedBuffer_{nullptr};
/// Spinlock to serialize the slow allocation path.
std::atomic<std::uint8_t> blockLock_{0};
/// Item size (sizeof(Type) + extra, rounded up for alignment).
std::size_t itemSize_;
/// Size of each slab's data buffer (multiple of 2 MB).
std::size_t slabSize_;
/** Allocate a memory block from the existing slabs.
Fast path allocation that doesn't acquire the global lock.
@return Pointer to allocated memory, or nullptr if all slabs are full.
*/
[[nodiscard]] std::uint8_t*
fast_allocate() noexcept
{
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
slab = slab->next_)
~SlabBlock()
{
if (auto ret = slab->try_allocate())
return ret;
// Calling this destructor will release the allocated memory but
// will not properly destroy any objects that are constructed in
// the block itself.
}
return nullptr;
}
SlabBlock(SlabBlock const& other) = delete;
SlabBlock&
operator=(SlabBlock const& other) = delete;
/** Allocate a memory block, potentially creating new capacity.
SlabBlock(SlabBlock&& other) = delete;
SlabBlock&
operator=(SlabBlock&& other) = delete;
Slow path allocation that may allocate a new buffer or slab.
Must be called with blockLock_ held.
/** Determines whether the given pointer belongs to this allocator */
bool
own(std::uint8_t const* p) const noexcept
{
return (p >= p_) && (p < p_ + size_);
}
std::uint8_t*
allocate() noexcept
{
std::uint8_t* ret;
{
std::lock_guard l(m_);
ret = l_;
if (ret)
{
// Use memcpy to avoid unaligned UB
// (will optimize to equivalent code)
std::memcpy(&l_, ret, sizeof(std::uint8_t*));
}
}
@return Pointer to allocated memory, or nullptr on failure.
*/
[[nodiscard]] std::uint8_t*
slow_allocate() noexcept
{
if (auto ret = fast_allocate())
return ret;
// Use a cached buffer first, if we have one, or allocate memory
// from the operating system.
auto buf = cachedBuffer_.exchange(nullptr, std::memory_order::acquire);
if (!buf)
buf = detail::allocateBuffer(slabSize_);
if (!buf) [[unlikely]]
return nullptr;
// Try to give buffer to an existing empty slab
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
slab = slab->next_)
{
if (auto ret = slab->try_assign(buf, slabSize_, itemSize_))
return ret;
}
// Create new slab from global pool
auto* slab = detail::globalSlabPool.acquire(
slabs_.load(std::memory_order::relaxed));
/** Return an item to this allocator's freelist.
if (!slab) [[unlikely]]
@param ptr The pointer to the chunk of memory being deallocated.
@note This is a dangerous, private interface; the item being
returned should belong to this allocator. Debug builds
will check and assert if this is not the case. Release
builds will not.
*/
void
deallocate(std::uint8_t* ptr) noexcept
{
detail::deallocateBuffer(buf);
return nullptr;
XRPL_ASSERT(
own(ptr),
"ripple::SlabAllocator::SlabBlock::deallocate : own input");
std::lock_guard l(m_);
// Use memcpy to avoid unaligned UB
// (will optimize to equivalent code)
std::memcpy(ptr, &l_, sizeof(std::uint8_t*));
l_ = ptr;
}
};
auto ret = slab->try_assign(buf, slabSize_, itemSize_);
assert(ret);
private:
// A linked list of slabs
std::atomic<SlabBlock*> slabs_ = nullptr;
// Link the new slab. Since we hold the lock, we can just do a
// direct assignment here.
slabs_.store(slab, std::memory_order::release);
// The alignment requirements of the item we're allocating:
std::size_t const itemAlignment_;
return ret;
}
// The size of an item, including the extra bytes requested and
// any padding needed for alignment purposes:
std::size_t const itemSize_;
// The size of each individual slab:
std::size_t const slabSize_;
public:
/** Constructs a slab allocator able to allocate objects of a fixed size
@param extra Extra bytes per item beyond sizeof(Type).
@param minItems Minimum number of items per slab (rounded up to 2MB).
@param count the number of items the slab allocator can allocate; note
that a count of 0 is valid and means that the allocator
is, effectively, disabled. This can be very useful in some
contexts (e.g. when mimimal memory usage is needed) and
allows for graceful failure.
*/
constexpr explicit sized_allocator_t(
constexpr explicit SlabAllocator(
std::size_t extra,
std::size_t minItems)
: itemSize_(boost::alignment::align_up(sizeof(Type) + extra, Align))
, slabSize_(boost::alignment::align_up(
itemSize_ * minItems,
detail::pageSize))
std::size_t alloc = 0,
std::size_t align = 0)
: itemAlignment_(align ? align : alignof(Type))
, itemSize_(
boost::alignment::align_up(sizeof(Type) + extra, itemAlignment_))
, slabSize_(alloc)
{
XRPL_ASSERT(
(itemAlignment_ & (itemAlignment_ - 1)) == 0,
"ripple::SlabAllocator::SlabAllocator : valid alignment");
}
// Data buffers are intentionally not released. C++ destruction order
// does not guarantee that all items allocated from this allocator
// have been freed by the time this destructor runs.
~sized_allocator_t() = default;
SlabAllocator(SlabAllocator const& other) = delete;
SlabAllocator&
operator=(SlabAllocator const& other) = delete;
sized_allocator_t(sized_allocator_t const&) = delete;
sized_allocator_t&
operator=(sized_allocator_t const&) = delete;
sized_allocator_t(sized_allocator_t&&) = delete;
sized_allocator_t&
operator=(sized_allocator_t&&) = delete;
SlabAllocator(SlabAllocator&& other) = delete;
SlabAllocator&
operator=(SlabAllocator&& other) = delete;
/** Returns the size of memory blocks returned by this allocator.
~SlabAllocator()
{
// FIXME: We can't destroy the memory blocks we've allocated, because
// we can't be sure that they are not being used. Cleaning the
// shutdown process up could make this possible.
}
@return Size of each allocated block in bytes.
*/
[[nodiscard]] constexpr std::size_t
/** Returns the size of the memory block this allocator returns. */
constexpr std::size_t
size() const noexcept
{
return itemSize_;
@@ -465,36 +217,87 @@ public:
/** Returns a suitably aligned pointer, if one is available.
@return Pointer to allocated memory, or nullptr on failure.
@note The gnu::malloc attribute is an optimization hint that can
be leveraged by GCC and Clang.
@return a pointer to a block of memory from the allocator, or
nullptr if the allocator can't satisfy this request.
*/
[[nodiscard, gnu::malloc]] std::uint8_t*
std::uint8_t*
allocate() noexcept
{
// Fast path: try existing slabs
auto ret = fast_allocate();
auto slab = slabs_.load();
if (ret == nullptr)
while (slab != nullptr)
{
// Slow path: need new capacity. Grab the lock to serialize access
// to this code path because it is expensive. Then re-check in case
// another thread added capacity while we waited.
spin_lock(blockLock_);
ret = slow_allocate();
spin_unlock(blockLock_);
if (auto ret = slab->allocate())
return ret;
slab = slab->next_;
}
return ret;
// No slab can satisfy our request, so we attempt to allocate a new
// one here:
std::size_t size = slabSize_;
// We want to allocate the memory at a 2 MiB boundary, to make it
// possible to use hugepage mappings on Linux:
auto buf =
boost::alignment::aligned_alloc(megabytes(std::size_t(2)), size);
// clang-format off
if (!buf) [[unlikely]]
return nullptr;
// clang-format on
#if BOOST_OS_LINUX
// When allocating large blocks, attempt to leverage Linux's
// transparent hugepage support. It is unclear and difficult
// to accurately determine if doing this impacts performance
// enough to justify using platform-specific tricks.
if (size >= megabytes(std::size_t(4)))
madvise(buf, size, MADV_HUGEPAGE);
#endif
// We need to carve out a bit of memory for the slab header
// and then align the rest appropriately:
auto slabData = reinterpret_cast<void*>(
reinterpret_cast<std::uint8_t*>(buf) + sizeof(SlabBlock));
auto slabSize = size - sizeof(SlabBlock);
// This operation is essentially guaranteed not to fail but
// let's be careful anyways.
if (!boost::alignment::align(
itemAlignment_, itemSize_, slabData, slabSize))
{
boost::alignment::aligned_free(buf);
return nullptr;
}
slab = new (buf) SlabBlock(
slabs_.load(),
reinterpret_cast<std::uint8_t*>(slabData),
slabSize,
itemSize_);
// Link the new slab
while (!slabs_.compare_exchange_weak(
slab->next_,
slab,
std::memory_order_release,
std::memory_order_relaxed))
{
; // Nothing to do
}
return slab->allocate();
}
/** Returns the memory block to the allocator.
@param ptr Pointer to memory block.
@return true if the block belonged to this allocator and was freed.
@param ptr A pointer to a memory block.
@param size If non-zero, a hint as to the size of the block.
@return true if this memory block belonged to the allocator and has
been released; false otherwise.
*/
[[nodiscard]] bool
bool
deallocate(std::uint8_t* ptr) noexcept
{
XRPL_ASSERT(
@@ -502,135 +305,112 @@ public:
"ripple::SlabAllocator::SlabAllocator::deallocate : non-null "
"input");
auto release = [this](std::uint8_t* buf) {
std::uint8_t* expected = nullptr;
if (buf != nullptr &&
!cachedBuffer_.compare_exchange_strong(
expected, buf, std::memory_order::release))
detail::deallocateBuffer(buf);
};
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
slab = slab->next_)
for (auto slab = slabs_.load(); slab != nullptr; slab = slab->next_)
{
if (slab->try_deallocate(ptr, slabSize_, release))
if (slab->own(ptr))
{
slab->deallocate(ptr);
return true;
}
}
return false;
}
};
//------------------------------------------------------------------------------
/** Configuration for a single slab allocator.
@tparam MinItems Minimum number of items per slab (must be > 0).
@tparam Extra Extra bytes per item beyond sizeof(Type).
*/
template <std::size_t MinItems, std::size_t Extra = 0>
requires(MinItems > 0)
struct config
/** A collection of slab allocators of various sizes for a given type. */
template <typename Type>
class SlabAllocatorSet
{
static constexpr std::size_t minItems = MinItems;
static constexpr std::size_t extra = Extra;
};
private:
// The list of allocators that belong to this set
boost::container::static_vector<SlabAllocator<Type>, 64> allocators_;
/** Concept for valid slab configuration types. */
template <typename T>
concept SlabConfig = requires {
{ T::minItems } -> std::convertible_to<std::size_t>;
{ T::extra } -> std::convertible_to<std::size_t>;
requires T::minItems > 0;
};
/** Validate slab configurations at compile time.
Checks that configurations produce strictly increasing sizes after
alignment. This catches both unsorted configs and configs that
collapse to the same size after alignment.
@tparam Type The type used to determine base size.
@tparam Align Alignment for allocated blocks.
@tparam Configs Configuration types to validate.
@return true if configurations are valid, false otherwise.
*/
template <typename Type, std::size_t Align, SlabConfig... Configs>
consteval bool
validate_slab_config()
{
constexpr auto align_up = [](std::size_t n, std::size_t a) {
return (n + a - 1) & ~(a - 1);
};
constexpr std::array<std::size_t, sizeof...(Configs)> sizes{
align_up(sizeof(Type) + Configs::extra, Align)...};
for (std::size_t i = 1; i < sizes.size(); ++i)
if (sizes[i - 1] >= sizes[i])
return false;
return true;
}
//------------------------------------------------------------------------------
/** A collection of slab allocators for different sizes.
Manages multiple sized_allocator_t instances, each configured for a
different block size. Allocations are routed to the smallest allocator
that can satisfy the request.
@tparam Type The type used to determine minimum block size and alignment.
@tparam Align Alignment for allocated blocks (must be >= alignof(Type)).
@tparam Configs Configuration types specifying each size class.
*/
template <typename Type, std::size_t Align, SlabConfig... Configs>
requires(
sizeof(Type) >= sizeof(void*) && Align >= alignof(Type) &&
std::has_single_bit(Align) && sizeof...(Configs) > 0 &&
validate_slab_config<Type, Align, Configs...>())
class aligned_allocator_t
{
std::array<sized_allocator_t<Type, Align>, sizeof...(Configs)> allocators_;
std::size_t maxSize_ = 0;
public:
/** Construct an allocator set. */
constexpr aligned_allocator_t()
: allocators_{sized_allocator_t<Type, Align>(
Configs::extra,
Configs::minItems)...}
class SlabConfig
{
friend class SlabAllocatorSet;
private:
std::size_t extra;
std::size_t alloc;
std::size_t align;
public:
constexpr SlabConfig(
std::size_t extra_,
std::size_t alloc_ = 0,
std::size_t align_ = alignof(Type))
: extra(extra_), alloc(alloc_), align(align_)
{
}
};
constexpr SlabAllocatorSet(std::vector<SlabConfig> cfg)
{
// Ensure that the specified allocators are sorted from smallest to
// largest by size:
std::sort(
std::begin(cfg),
std::end(cfg),
[](SlabConfig const& a, SlabConfig const& b) {
return a.extra < b.extra;
});
// We should never have two slabs of the same size
if (std::adjacent_find(
std::begin(cfg),
std::end(cfg),
[](SlabConfig const& a, SlabConfig const& b) {
return a.extra == b.extra;
}) != cfg.end())
{
throw std::runtime_error(
"SlabAllocatorSet<" + beast::type_name<Type>() +
">: duplicate slab size");
}
for (auto const& c : cfg)
{
auto& a = allocators_.emplace_back(c.extra, c.alloc, c.align);
if (a.size() > maxSize_)
maxSize_ = a.size();
}
}
SlabAllocatorSet(SlabAllocatorSet const& other) = delete;
SlabAllocatorSet&
operator=(SlabAllocatorSet const& other) = delete;
SlabAllocatorSet(SlabAllocatorSet&& other) = delete;
SlabAllocatorSet&
operator=(SlabAllocatorSet&& other) = delete;
~SlabAllocatorSet()
{
}
~aligned_allocator_t() = default;
/** Returns a suitably aligned pointer, if one is available.
aligned_allocator_t(aligned_allocator_t const&) = delete;
aligned_allocator_t&
operator=(aligned_allocator_t const&) = delete;
aligned_allocator_t(aligned_allocator_t&&) = delete;
aligned_allocator_t&
operator=(aligned_allocator_t&&) = delete;
@param extra The number of extra bytes, above and beyond the size of
the object, that should be returned by the allocator.
/** Allocate memory for an object with extra bytes.
@param extra Extra bytes needed beyond sizeof(Type).
@return Pointer to memory, or nullptr if no suitable allocator
or allocation failed.
@note The gnu::malloc attribute is an optimization hint that can
be leveraged by GCC and Clang.
@return a pointer to a block of memory, or nullptr if the allocator
can't satisfy this request.
*/
[[nodiscard, gnu::malloc]] std::uint8_t*
std::uint8_t*
allocate(std::size_t extra) noexcept
{
auto const size = sizeof(Type) + extra;
for (auto& a : allocators_)
if (auto const size = sizeof(Type) + extra; size <= maxSize_)
{
if (a.size() >= size)
return a.allocate();
for (auto& a : allocators_)
{
if (a.size() >= size)
return a.allocate();
}
}
return nullptr;
@@ -638,10 +418,12 @@ public:
/** Returns the memory block to the allocator.
@param ptr Pointer to memory block.
@return true if memory belonged to this set and was freed.
@param ptr A pointer to a memory block.
@return true if this memory block belonged to one of the allocators
in this set and has been released; false otherwise.
*/
[[nodiscard]] bool
bool
deallocate(std::uint8_t* ptr) noexcept
{
for (auto& a : allocators_)
@@ -649,16 +431,11 @@ public:
if (a.deallocate(ptr))
return true;
}
return false;
}
};
/** Alias for aligned_allocator_t with default alignment. */
template <typename Type, SlabConfig... Configs>
using allocator_t = aligned_allocator_t<Type, alignof(Type), Configs...>;
} // namespace slab
} // namespace ripple
#endif // RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
File diff suppressed because it is too large. Load diff
+5 -11
View File
@@ -23,7 +23,6 @@
#include <xrpl/beast/type_name.h>
#include <exception>
#include <string>
#include <type_traits>
#include <typeinfo>
#include <utility>
@@ -53,7 +52,7 @@ Rethrow()
}
template <class E, class... Args>
[[noreturn]] constexpr void
[[noreturn]] inline void
Throw(Args&&... args)
{
static_assert(
@@ -61,15 +60,10 @@ Throw(Args&&... args)
"Exception must derive from std::exception.");
E e(std::forward<Args>(args)...);
// Skip logging when evaluated at compile time, so that `Throw` can be
// called from constexpr/consteval functions.
if (!std::is_constant_evaluated())
LogThrow(
std::string(
"Throwing exception of type " + beast::type_name<E>() + ": ") +
e.what());
LogThrow(
std::string(
"Throwing exception of type " + beast::type_name<E>() + ": ") +
e.what());
throw e;
}
+96 -139
View File
@@ -20,7 +20,6 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <atomic>
#include <concepts>
#include <limits>
#include <type_traits>
@@ -31,7 +30,6 @@
namespace ripple {
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
@@ -54,130 +52,11 @@ spin_pause() noexcept
} // namespace detail
/** Attempt to acquire a spinlock without blocking.
/** @{ */
/** Classes to handle arrays of spinlocks packed into a single atomic integer:
@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 <typename T>
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
[[nodiscard]] bool
spin_try_lock(std::atomic<T>& lock) noexcept
{
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 to reduce cache coherency
traffic during contention.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
*/
template <typename T>
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
void
spin_lock(std::atomic<T>& lock) noexcept
{
T expected = 0;
while (!lock.compare_exchange_weak(
expected,
std::numeric_limits<T>::max(),
std::memory_order::acquire,
std::memory_order::relaxed))
{
expected = 0;
while (lock.load(std::memory_order::relaxed) != 0)
detail::spin_pause();
}
}
/** Release a spinlock.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
*/
template <typename T>
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
void
spin_unlock(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 `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 <typename T>
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
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
{
return spin_try_lock(lock_);
}
void
lock() noexcept
{
spin_lock(lock_);
}
void
unlock() noexcept
{
spin_unlock(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.
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
@@ -199,19 +78,26 @@ public:
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
*/
template <typename T>
requires(
std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free &&
requires(std::atomic<T>& a, T v) {
{ a.fetch_or(v) } -> std::same_as<T>;
{ a.fetch_and(v) } -> std::same_as<T>;
})
template <class T>
class packed_spinlock
{
// 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_;
@@ -228,7 +114,7 @@ public:
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
*/
packed_spinlock(std::atomic<T>& lock, int index) noexcept
packed_spinlock(std::atomic<T>& lock, int index)
: bits_(lock), mask_(static_cast<T>(1) << index)
{
XRPL_ASSERT(
@@ -237,13 +123,13 @@ public:
}
[[nodiscard]] bool
try_lock() noexcept
try_lock()
{
return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
return (bits_.fetch_or(mask_, std::memory_order_acquire) & mask_) == 0;
}
void
lock() noexcept
lock()
{
while (!try_lock())
{
@@ -251,18 +137,89 @@ public:
// 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)
while ((bits_.load(std::memory_order_relaxed) & mask_) != 0)
detail::spin_pause();
}
}
void
unlock() noexcept
unlock()
{
bits_.fetch_and(~mask_, std::memory_order::release);
bits_.fetch_and(~mask_, std::memory_order_release);
}
};
/** A spinlock implemented on top of an atomic integer.
@note Using `packed_spinlock` and `spinlock` against the same underlying
atomic integer can result in `spinlock` not being able to actually
acquire the lock during periods of high contention, because of how
the two locks operate: `spinlock` will spin trying to grab all the
bits at once, whereas any given `packed_spinlock` will only try to
grab one bit at a time. Caveat emptor.
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <class T>
class spinlock
{
static_assert(std::is_unsigned_v<T>);
static_assert(std::atomic<T>::is_always_lock_free);
private:
std::atomic<T>& lock_;
public:
spinlock(spinlock const&) = delete;
spinlock&
operator=(spinlock const&) = delete;
/** Grabs the
@param lock The atomic integer to spin against.
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
*/
spinlock(std::atomic<T>& lock) : lock_(lock)
{
}
[[nodiscard]] bool
try_lock()
{
T expected = 0;
return lock_.compare_exchange_weak(
expected,
std::numeric_limits<T>::max(),
std::memory_order_acquire,
std::memory_order_relaxed);
}
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 (lock_.load(std::memory_order_relaxed) != 0)
detail::spin_pause();
}
}
void
unlock()
{
lock_.store(0, std::memory_order_release);
}
};
/** @} */
} // namespace ripple
#endif
+307
View File
@@ -0,0 +1,307 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <[email protected]>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_INTRUSIVE_LOCKFREESTACK_H_INCLUDED
#define BEAST_INTRUSIVE_LOCKFREESTACK_H_INCLUDED
#include <atomic>
#include <iterator>
#include <type_traits>
namespace beast {
//------------------------------------------------------------------------------
template <class Container, bool IsConst>
class LockFreeStackIterator
{
protected:
using Node = typename Container::Node;
using NodePtr =
typename std::conditional<IsConst, Node const*, Node*>::type;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = typename Container::value_type;
using difference_type = typename Container::difference_type;
using pointer = typename std::conditional<
IsConst,
typename Container::const_pointer,
typename Container::pointer>::type;
using reference = typename std::conditional<
IsConst,
typename Container::const_reference,
typename Container::reference>::type;
LockFreeStackIterator() : m_node()
{
}
LockFreeStackIterator(NodePtr node) : m_node(node)
{
}
template <bool OtherIsConst>
explicit LockFreeStackIterator(
LockFreeStackIterator<Container, OtherIsConst> const& other)
: m_node(other.m_node)
{
}
LockFreeStackIterator&
operator=(NodePtr node)
{
m_node = node;
return static_cast<LockFreeStackIterator&>(*this);
}
LockFreeStackIterator&
operator++()
{
m_node = m_node->m_next.load();
return static_cast<LockFreeStackIterator&>(*this);
}
LockFreeStackIterator
operator++(int)
{
LockFreeStackIterator result(*this);
m_node = m_node->m_next;
return result;
}
NodePtr
node() const
{
return m_node;
}
reference
operator*() const
{
return *this->operator->();
}
pointer
operator->() const
{
return static_cast<pointer>(m_node);
}
private:
NodePtr m_node;
};
//------------------------------------------------------------------------------
template <class Container, bool LhsIsConst, bool RhsIsConst>
bool
operator==(
LockFreeStackIterator<Container, LhsIsConst> const& lhs,
LockFreeStackIterator<Container, RhsIsConst> const& rhs)
{
return lhs.node() == rhs.node();
}
template <class Container, bool LhsIsConst, bool RhsIsConst>
bool
operator!=(
LockFreeStackIterator<Container, LhsIsConst> const& lhs,
LockFreeStackIterator<Container, RhsIsConst> const& rhs)
{
return lhs.node() != rhs.node();
}
//------------------------------------------------------------------------------
/** Multiple Producer, Multiple Consumer (MPMC) intrusive stack.
This stack is implemented using the same intrusive interface as List.
All mutations are lock-free.
The caller is responsible for preventing the "ABA" problem:
http://en.wikipedia.org/wiki/ABA_problem
@param Tag A type name used to distinguish lists and nodes, for
putting objects in multiple lists. If this parameter is
omitted, the default tag is used.
*/
template <class Element, class Tag = void>
class LockFreeStack
{
public:
class Node
{
public:
Node() : m_next(nullptr)
{
}
explicit Node(Node* next) : m_next(next)
{
}
Node(Node const&) = delete;
Node&
operator=(Node const&) = delete;
private:
friend class LockFreeStack;
template <class Container, bool IsConst>
friend class LockFreeStackIterator;
std::atomic<Node*> m_next;
};
public:
using value_type = Element;
using pointer = Element*;
using reference = Element&;
using const_pointer = Element const*;
using const_reference = Element const&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using iterator = LockFreeStackIterator<LockFreeStack<Element, Tag>, false>;
using const_iterator =
LockFreeStackIterator<LockFreeStack<Element, Tag>, true>;
LockFreeStack() : m_end(nullptr), m_head(&m_end)
{
}
LockFreeStack(LockFreeStack const&) = delete;
LockFreeStack&
operator=(LockFreeStack const&) = delete;
/** Returns true if the stack is empty. */
bool
empty() const
{
return m_head.load() == &m_end;
}
/** Push a node onto the stack.
The caller is responsible for preventing the ABA problem.
This operation is lock-free.
Thread safety:
Safe to call from any thread.
@param node The node to push.
@return `true` if the stack was previously empty. If multiple threads
are attempting to push, only one will receive `true`.
*/
// VFALCO NOTE Fix this, shouldn't it be a reference like intrusive list?
bool
push_front(Node* node)
{
bool first;
Node* old_head = m_head.load(std::memory_order_relaxed);
do
{
first = (old_head == &m_end);
node->m_next = old_head;
} while (!m_head.compare_exchange_strong(
old_head,
node,
std::memory_order_release,
std::memory_order_relaxed));
return first;
}
/** Pop an element off the stack.
The caller is responsible for preventing the ABA problem.
This operation is lock-free.
Thread safety:
Safe to call from any thread.
@return The element that was popped, or `nullptr` if the stack
was empty.
*/
Element*
pop_front()
{
Node* node = m_head.load();
Node* new_head;
do
{
if (node == &m_end)
return nullptr;
new_head = node->m_next.load();
} while (!m_head.compare_exchange_strong(
node,
new_head,
std::memory_order_release,
std::memory_order_relaxed));
return static_cast<Element*>(node);
}
/** Return a forward iterator to the beginning or end of the stack.
Undefined behavior results if push_front or pop_front is called
while an iteration is in progress.
Thread safety:
Caller is responsible for synchronization.
*/
/** @{ */
iterator
begin()
{
return iterator(m_head.load());
}
iterator
end()
{
return iterator(&m_end);
}
const_iterator
begin() const
{
return const_iterator(m_head.load());
}
const_iterator
end() const
{
return const_iterator(&m_end);
}
const_iterator
cbegin() const
{
return const_iterator(m_head.load());
}
const_iterator
cend() const
{
return const_iterator(&m_end);
}
/** @} */
private:
Node m_end;
std::atomic<Node*> m_head;
};
} // namespace beast
#endif
+516 -24
View File
@@ -1,31 +1,523 @@
#ifndef HOOKMISC_INCLUDED
#define HOOKMISC_INCLUDED 1
#include <xrpl/basics/base_uint.h>
#include <array>
#include <cstdint>
namespace ripple {
// RH TODO: there's definitely a mucher nicer way to do this, but it involves
// modifying the base_uint class and we don't want to do that yet.
/** UINT256_BIT[i] is the 256-bit value with only bit i set (LSB is bit 0).
Used to encode and test the per-transaction-type bits of the HookOn and
HookCanEmit fields. Built at compile time.
*/
inline constexpr auto UINT256_BIT = []() consteval {
std::array<ripple::uint256, 256> table{};
for (int i = 0; i < 256; ++i)
{
std::array<std::uint8_t, 32> bytes{};
bytes[31 - (i / 8)] = 1 << (i % 8);
table[i] = ripple::uint256(bytes);
}
return table;
}();
static const std::array<ripple::uint256, 256> UINT256_BIT = {
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000001"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000002"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000004"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000008"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000010"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000020"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000040"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000080"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000100"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000200"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000400"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000000800"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000001000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000002000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000004000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000008000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000010000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000020000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000040000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000080000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000100000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000200000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000400000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000000800000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000001000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000002000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000004000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000008000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000010000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000020000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000040000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000080000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000100000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000200000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000400000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000000800000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000001000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000002000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000004000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000008000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000010000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000020000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000040000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000080000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000100000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000200000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000400000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000000800000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000001000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000002000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000004000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000008000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000010000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000020000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000040000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000080000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000100000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000200000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000400000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000000800000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000001000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000002000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000004000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000008000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000010000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000020000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000040000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000080000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000100000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000200000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000400000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000000800000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000001000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000002000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000004000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000008000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000010000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000020000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000040000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000080000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000100000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000200000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000400000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000000800000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000001000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000002000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000004000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000008000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000010000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000020000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000040000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000080000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000100000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000200000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000400000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000000800000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000001000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000002000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000004000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000008000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000010000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000020000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000040000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000080000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000100000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000200000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000400000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000000800000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000001000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000002000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000004000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000008000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000010000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000020000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000040000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000080000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000100000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000200000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000400000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000000800000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000001000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000002000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000004000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000008000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000010000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000020000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000040000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000080000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000100000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000200000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000400000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000000800000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000001000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000002000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000004000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000008000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000010000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000020000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000040000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000080000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000100000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000200000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000400000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000000800000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000001000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000002000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000004000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000008000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000010000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000020000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000040000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000080000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000100000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000200000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000400000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000000800000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000001000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000002000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000004000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000008000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000010000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000020000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000040000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000080000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000100000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000200000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000400000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000000800000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000001000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000002000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000004000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000008000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000010000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000020000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000040000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000080000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000100000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000200000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000400000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000000800000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000001000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000002000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000004000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000008000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000010000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000020000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000040000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000080000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000100000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000200000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000400000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000000800000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000001000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000002000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000004000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000008000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000010000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000020000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000040000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000080000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000100000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000200000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000400000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000000800000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000001000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000002000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000004000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000008000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000010000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000020000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000040000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000080000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000100000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000200000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000400000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000000800000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000001000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000002000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000004000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000008000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000010000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000020000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000040000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000080000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000100000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000200000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000400000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000000800000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000001000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000002000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000004000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000008000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000010000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000020000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000040000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000080000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000100000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000200000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000400000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0000800000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0001000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0002000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0004000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0008000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0010000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0020000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0040000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0080000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0100000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0200000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0400000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"0800000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"1000000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"2000000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"4000000000000000000000000000000000000000000000000000000000000000"),
ripple::uint256(
"8000000000000000000000000000000000000000000000000000000000000000")};
} // namespace ripple
#endif
+1 -6
View File
@@ -157,12 +157,7 @@ enum error_code_i {
// Simulate
rpcTX_SIGNED = 97,
// ledger_entry
rpcENTRY_NOT_FOUND = 98,
rpcUNEXPECTED_LEDGER_TYPE = 99,
rpcLAST =
rpcUNEXPECTED_LEDGER_TYPE // rpcLAST should always equal the last code.
rpcLAST = rpcTX_SIGNED // rpcLAST should always equal the last code.
};
/** Codes returned in the `warnings` array of certain RPC commands.
-3
View File
@@ -84,9 +84,6 @@ std::uint16_t constexpr maxTransferFee = 50000;
/** The maximum length of a URI inside an NFT */
std::size_t constexpr maxTokenURILength = 256;
/** The maximum length of a URI inside an URIToken */
std::size_t constexpr maxURITokenURILength = 256;
/** The maximum length of a Data element inside a DID */
std::size_t constexpr maxDIDDocumentLength = 256;
+83 -42
View File
@@ -24,7 +24,6 @@
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/STExchange.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/detail/KeyBase.h>
#include <xrpl/protocol/json_get_or_throw.h>
#include <xrpl/protocol/tokens.h>
@@ -37,26 +36,6 @@
namespace ripple {
/** Returns the type of public key.
@return std::nullopt If the public key does not
represent a known type.
*/
[[nodiscard]] inline std::optional<KeyType>
publicKeyType(Slice const& slice)
{
if (slice.size() == 33)
{
if (slice[0] == 0xED)
return KeyType::ed25519;
if (slice[0] == 0x02 || slice[0] == 0x03)
return KeyType::secp256k1;
}
return std::nullopt;
}
/** A public key.
Public keys are used in the public-key cryptography
@@ -79,47 +58,77 @@ publicKeyType(Slice const& slice)
prefix constant 0xED, followed by 32 bytes of
public key data.
*/
class PublicKey : public detail::KeyBase<PublicKey, 33>
class PublicKey
{
protected:
// All the constructed public keys are valid, non-empty and contain 33
// bytes of data.
static constexpr std::size_t size_ = 33;
std::uint8_t buf_[size_]; // should be large enough
public:
using const_iterator = std::uint8_t const*;
public:
PublicKey() = delete;
PublicKey(PublicKey const&) = default;
PublicKey(PublicKey const& other);
PublicKey&
operator=(PublicKey const&) = default;
operator=(PublicKey const& other);
explicit PublicKey(Slice const& slice) noexcept
/** Create a public key.
Preconditions:
publicKeyType(slice) != std::nullopt
*/
explicit PublicKey(Slice const& slice);
std::uint8_t const*
data() const noexcept
{
if (slice.size() < buf_.size())
LogicError("PublicKey::PublicKey: undersized buffer");
if (!publicKeyType(slice))
LogicError("PublicKey::PublicKey: invalid type");
std::copy_n(slice.data(), buf_.size(), buf_.data());
return buf_;
}
explicit PublicKey(value_t const& data) noexcept
: PublicKey(makeSlice(data))
std::size_t
size() const noexcept
{
return size_;
}
[[nodiscard]] Slice
const_iterator
begin() const noexcept
{
return buf_;
}
const_iterator
cbegin() const noexcept
{
return buf_;
}
const_iterator
end() const noexcept
{
return buf_ + size_;
}
const_iterator
cend() const noexcept
{
return buf_ + size_;
}
Slice
slice() const noexcept
{
return {buf_.data(), buf_.size()};
return {buf_, size_};
}
operator Slice() const noexcept
{
return slice();
}
[[nodiscard]] auto
operator<=>(PublicKey const& rhs) const
{
return buf_ <=> rhs.buf_;
}
};
/** Print the public key to a stream.
@@ -127,6 +136,22 @@ public:
std::ostream&
operator<<(std::ostream& os, PublicKey const& pk);
inline bool
operator==(PublicKey const& lhs, PublicKey const& rhs)
{
return std::memcmp(lhs.data(), rhs.data(), rhs.size()) == 0;
}
inline bool
operator<(PublicKey const& lhs, PublicKey const& rhs)
{
return std::lexicographical_compare(
lhs.data(),
lhs.data() + lhs.size(),
rhs.data(),
rhs.data() + rhs.size());
}
template <class Hasher>
void
hash_append(Hasher& h, PublicKey const& pk)
@@ -196,6 +221,22 @@ enum class ECDSACanonicality { canonical, fullyCanonical };
std::optional<ECDSACanonicality>
ecdsaCanonicality(Slice const& sig);
/** Returns the type of public key.
@return std::nullopt If the public key does not
represent a known type.
*/
/** @{ */
[[nodiscard]] std::optional<KeyType>
publicKeyType(Slice const& slice);
[[nodiscard]] inline std::optional<KeyType>
publicKeyType(PublicKey const& publicKey)
{
return publicKeyType(publicKey.slice());
}
/** @} */
/** Verify a secp256k1 signature on the digest of a message. */
[[nodiscard]] bool
verifyDigest(
+62 -11
View File
@@ -25,39 +25,90 @@
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Seed.h>
#include <xrpl/protocol/detail/KeyBase.h>
#include <xrpl/protocol/tokens.h>
#include <array>
#include <cstring>
#include <string>
namespace ripple {
/** A secret key. */
class SecretKey : public detail::KeyBase<SecretKey, 32>
class SecretKey
{
public:
SecretKey() = delete;
private:
std::uint8_t buf_[32];
public:
using const_iterator = std::uint8_t const*;
SecretKey() = delete;
SecretKey(SecretKey const&) = default;
SecretKey&
operator=(SecretKey const&) = default;
/** Destroy the secret key. This will try to securely erase the buffer. */
~SecretKey();
SecretKey(Slice const& slice)
{
if (slice.size() != buf_.size())
LogicError("SecretKey::SecretKey: invalid size");
SecretKey(std::array<std::uint8_t, 32> const& data);
SecretKey(Slice const& slice);
std::copy_n(slice.data(), buf_.size(), buf_.data());
std::uint8_t const*
data() const
{
return buf_;
}
explicit SecretKey(span_t const& data) noexcept : KeyBase(data)
std::size_t
size() const
{
return sizeof(buf_);
}
/** Convert the secret key to a hexadecimal string.
@note The operator<< function is deliberately omitted
to avoid accidental exposure of secret key material.
*/
std::string
to_string() const;
const_iterator
begin() const noexcept
{
return buf_;
}
const_iterator
cbegin() const noexcept
{
return buf_;
}
const_iterator
end() const noexcept
{
return buf_ + sizeof(buf_);
}
const_iterator
cend() const noexcept
{
return buf_ + sizeof(buf_);
}
};
inline bool
operator==(SecretKey const& lhs, SecretKey const& rhs)
{
return lhs.size() == rhs.size() &&
std::memcmp(lhs.data(), rhs.data(), rhs.size()) == 0;
}
inline bool
operator!=(SecretKey const& lhs, SecretKey const& rhs)
{
return !(lhs == rhs);
}
//------------------------------------------------------------------------------
/** Parse a secret key */
+46 -11
View File
@@ -22,37 +22,72 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/detail/KeyBase.h>
#include <xrpl/protocol/tokens.h>
#include <algorithm>
#include <array>
#include <optional>
namespace ripple {
/** Seeds are used to generate deterministic secret keys. */
class Seed : public detail::KeyBase<Seed, 16>
class Seed
{
private:
std::array<uint8_t, 16> buf_;
public:
using const_iterator = std::array<uint8_t, 16>::const_iterator;
Seed() = delete;
Seed(Seed const&) = default;
Seed&
operator=(Seed const&) = default;
/** Destroy the seed. This will attempt to securely erase the buffer. */
/** Destroy the seed.
The buffer will first be securely erased.
*/
~Seed();
explicit Seed(Slice const& slice)
{
if (slice.size() != buf_.size())
LogicError("Seed::Seed: invalid size");
/** Construct a seed */
/** @{ */
explicit Seed(Slice const& slice);
explicit Seed(uint128 const& seed);
/** @} */
std::copy_n(slice.data(), buf_.size(), buf_.data());
std::uint8_t const*
data() const
{
return buf_.data();
}
explicit Seed(span_t seed) noexcept : KeyBase(seed)
std::size_t
size() const
{
return buf_.size();
}
const_iterator
begin() const noexcept
{
return buf_.begin();
}
const_iterator
cbegin() const noexcept
{
return buf_.cbegin();
}
const_iterator
end() const noexcept
{
return buf_.end();
}
const_iterator
cend() const noexcept
{
return buf_.cend();
}
};
+12 -3
View File
@@ -24,14 +24,18 @@
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
#include <string>
#include <string_view>
namespace ripple {
// Various protocol and system specific constant globals.
/* The name of the system. */
inline constexpr std::string_view systemName = "xahau";
static inline std::string const&
systemName()
{
static std::string const name = "xahau";
return name;
}
/** Configure the native currency. */
@@ -54,7 +58,12 @@ isLegalAmountSigned(XRPAmount const& amount)
}
/* The currency code for the native currency. */
inline constexpr std::string_view systemCurrencyCode = "XAH";
static inline std::string const&
systemCurrencyCode()
{
static std::string const code = "XAH";
return code;
}
/** The XRP ledger network's earliest allowed sequence */
static constexpr std::uint32_t XRP_LEDGER_EARLIEST_SEQ{1U};
+60 -169
View File
@@ -24,166 +24,84 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SystemParameters.h>
namespace ripple {
using LedgerHash = uint256;
/** Directory is an index into the directory of offer books.
The last 64 bits of this are the quality. */
using Directory = base_uint<256, struct DirectoryTag>;
/** Currency is a hash representing a specific currency. */
using Currency = base_uint<160, struct CurrencyTag>;
/** NodeID is a 160-bit hash representing one node. */
using NodeID = base_uint<160, struct NodeIDTag>;
/** A Multi-Purpose Token Issuance ID,
The ID is the a concatenation of a 32-bit sequence number,
in big endian, and a 160-bit account.
@note This type is, unfortunately, untagged because the authors
of the original code used a deserialization APIs that did
not support tags. It should be fixed.
*/
using MPTID = base_uint<192>;
namespace detail {
// A table mapping which characters we are willing to allow in the ASCII
// representation of a three-letter currency code.
inline constexpr auto validIsoChars = []() consteval {
std::string_view isoCharSet =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"<>(){}[]|?!@#$%^&*";
std::array<bool, 256> table{};
for (unsigned char c : isoCharSet)
table[c] = true;
return table;
}();
// Determines if the given code is composed entirely of letters valid in
// ISO 4217 codes.
[[nodiscard]] inline constexpr bool
isIsoCode(std::string_view code) noexcept
class CurrencyTag
{
return std::all_of(code.begin(), code.end(), [](char c) {
return validIsoChars[static_cast<unsigned char>(c)];
});
}
public:
explicit CurrencyTag() = default;
};
// The location (in bytes) of the 3 digit currency inside a 160-bit value
inline constexpr std::size_t isoCodeOffset = 12;
class DirectoryTag
{
public:
explicit DirectoryTag() = default;
};
// The length of an ISO-4217 like code
inline constexpr std::size_t isoCodeLength = 3;
inline constexpr Currency isoMaskBits = ~Currency(0xFFFFFF0000000000);
// The special currency identifier for the system currency: all-zero
inline constexpr Currency xrpCurrency{0x0000000000000000};
// The special currency identifier meaning "no currency"
inline constexpr Currency noCurrency{0x0000000000000001};
// The system currency identifier in "ISO4217 format" which is reserved.
// We derive this value dynamically from the system currency code.
inline constexpr Currency badCurrency = []() consteval {
if (systemCurrencyCode.size() != isoCodeLength)
throw "Incorrect systemCurrencyCode size (must be 3 digits)";
std::array<std::uint8_t, Currency::bytes> bytes{};
for (std::size_t i = 0; i < isoCodeLength; ++i)
bytes[isoCodeOffset + i] =
static_cast<std::uint8_t>(systemCurrencyCode[i]);
return Currency(bytes);
}();
// We take advantage of the fact that an ASCII value in the [a-z] range
// transforms into the equivalent character in the [A-Z] range when you
// AND it with 0xDF, so we can compare against all possible variants of
// "XAH" in one go. Every byte outside the ISO code position is kept as-is
// by the mask, so a currency is only "bad" when those bytes are all zero
// (i.e. the value is exactly one of the case variants of the code).
inline constexpr Currency badCurrencyCodeMask =
isoMaskBits | Currency{0xDFDFDF0000000000};
class NodeIDTag
{
public:
explicit NodeIDTag() = default;
};
} // namespace detail
/** XRP currency. */
[[nodiscard]] constexpr Currency const&
xrpCurrency() noexcept
{
return detail::xrpCurrency;
}
/** Directory is an index into the directory of offer books.
The last 64 bits of this are the quality. */
using Directory = base_uint<256, detail::DirectoryTag>;
[[nodiscard]] constexpr bool
isXRP(Currency const& c) noexcept
{
return c == detail::xrpCurrency;
}
/** Currency is a hash representing a specific currency. */
using Currency = base_uint<160, detail::CurrencyTag>;
/** NodeID is a 160-bit hash representing one node. */
using NodeID = base_uint<160, detail::NodeIDTag>;
/** MPTID is a 192-bit value representing MPT Issuance ID,
* which is a concatenation of a 32-bit sequence (big endian)
* and a 160-bit account */
using MPTID = base_uint<192>;
/** XRP currency. */
Currency const&
xrpCurrency();
/** A placeholder for empty currencies. */
[[nodiscard]] constexpr Currency const&
noCurrency() noexcept
{
return detail::noCurrency;
}
Currency const&
noCurrency();
/** We deliberately disallow the currency that looks like "XAH" because too
many people were using it instead of the correct XAH currency. */
[[nodiscard]] constexpr Currency const&
badCurrency() noexcept
Currency const&
badCurrency();
inline bool
isXRP(Currency const& c)
{
return detail::badCurrency;
return c == beast::zero;
}
[[nodiscard]] constexpr bool
isBadCurrency(Currency const& c) noexcept
inline bool
isBadCurrency(Currency const& c)
{
// We take advantage of the fact that an ASCII value in the [a-z] range
// transforms into the equivalent character in the [A-Z] range when you
// AND it with 0xDF, so we can compare against all possible variants of
// the bad currency code in one go:
return (c & detail::badCurrencyCodeMask) == badCurrency();
static const std::set<Currency> badCurrencies{
Currency(0x7861680000000000), // xah
Currency(0x7861480000000000), // xaH
Currency(0x7841680000000000), // xAh
Currency(0x7841480000000000), // xAH
Currency(0x5861680000000000), // Xah
Currency(0x5861480000000000), // XaH
Currency(0x5841680000000000), // XAh
Currency(0x5841480000000000) // XAH
};
return badCurrencies.find(c) != badCurrencies.end();
}
/** Returns "", "XAH", three letter ISO code or the hex representation. */
[[nodiscard]] inline std::string
to_string(Currency const& currency)
{
if (currency == xrpCurrency())
return std::string{systemCurrencyCode};
if (currency == noCurrency())
return "1";
if ((currency & detail::isoMaskBits) == beast::zero)
{
std::string_view const iso(
reinterpret_cast<char const*>(currency.data()) +
detail::isoCodeOffset,
detail::isoCodeLength);
// Specifying the system currency code using ISO-style representation
// is not allowed. Note that the check is case-sensitive; yet another
// instance of legacy code smell.
if (detail::isIsoCode(iso) && iso != systemCurrencyCode)
return std::string{iso};
}
return strHex(currency);
}
/** Returns "", "XAH", or three letter ISO code. */
std::string
to_string(Currency const& c);
/** Tries to convert a string to a Currency, returns true on success.
@@ -192,29 +110,8 @@ to_string(Currency const& currency)
will require very careful checking everywhere and may mean having
to rewrite some unit test code.
*/
[[nodiscard]] constexpr bool
to_currency(Currency& currency, std::string_view code) noexcept
{
if (code.empty() || code == systemCurrencyCode)
{
currency = xrpCurrency();
return true;
}
// Handle ISO-4217-like 3-digit character codes.
if (code.size() != detail::isoCodeLength)
return currency.parseHex(code);
if (!detail::isIsoCode(code))
return false;
currency = beast::zero;
std::copy_n(
code.data(), code.size(), currency.begin() + detail::isoCodeOffset);
return true;
}
bool
to_currency(Currency&, std::string const&);
/** Tries to convert a string to a Currency, returns noCurrency() on failure.
@@ -222,14 +119,8 @@ to_currency(Currency& currency, std::string_view code) noexcept
unfortunate; changing this will require very careful checking
everywhere and may mean having to rewrite some unit test code.
*/
[[nodiscard]] constexpr Currency
to_currency(std::string_view code) noexcept
{
if (Currency currency; to_currency(currency, code))
return currency;
return noCurrency();
}
Currency
to_currency(std::string const&);
inline std::ostream&
operator<<(std::ostream& os, Currency const& x)
-114
View File
@@ -1,114 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of xahaud: https://github.com/xahau/xahaud
Copyright (c) 2026, the Xahaud developers.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef XAHAU_PROTOCOL_KEYBASE_H_INCLUDED
#define XAHAU_PROTOCOL_KEYBASE_H_INCLUDED
#include <algorithm>
#include <array>
#include <cstdint>
#include <span>
namespace ripple {
namespace detail {
/** Common base for fixed-size key and seed types.
This CRTP base provides storage, accessors, and equality comparison
for types like seeds and secret/public keys, which wrap a fixed-size
byte array. The Derived parameter ensures that unrelated types with
the same size do not implicitly interoperate with one another.
Equality comparison is provided automatically. Derived classes that
want to can opt in to ordering by declaring operator<=> as a
hidden friend.
@tparam Derived The concrete type inheriting from this base (CRTP).
@tparam N The size of the underlying byte array.
*/
template <class Derived, std::size_t N>
class KeyBase
{
public:
/** The type we use to store the data. */
using value_t = std::array<std::uint8_t, N>;
/** The span type corresponding to the type we use to store the data. */
using span_t = std::span<std::uint8_t, N>;
protected:
value_t buf_;
KeyBase() noexcept = default;
explicit KeyBase(span_t data) noexcept
{
std::copy_n(data.data(), N, buf_.data());
}
public:
[[nodiscard]] std::uint8_t const*
data() const
{
return buf_.data();
}
[[nodiscard]] std::size_t
size() const
{
return buf_.size();
}
[[nodiscard]] auto
begin() const noexcept
{
return buf_.begin();
}
[[nodiscard]] auto
cbegin() const noexcept
{
return buf_.cbegin();
}
[[nodiscard]] auto
end() const noexcept
{
return buf_.end();
}
[[nodiscard]] auto
cend() const noexcept
{
return buf_.cend();
}
[[nodiscard]] friend bool
operator==(Derived const& lhs, Derived const& rhs)
{
// This is not a constant time comparison. This is probably OK
// for the xahaud codebase.
return lhs.buf_ == rhs.buf_;
}
};
} // namespace detail
} // namespace ripple
#endif
@@ -34,6 +34,10 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
// Xahau testnet (NetworkID 21338) only. Registered as unsupported here; the
// server promotes it to Supported::yes / DefaultYes at startup if and only
// if it is configured with network_id 21338 (see Application.cpp).
XRPL_FIX (XahauTestnetGenesis, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(RNG, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PWALoader, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (20261005, Supported::yes, VoteBehavior::DefaultYes)
-4
View File
@@ -100,15 +100,11 @@ JSS(isVLEncoded); // out: RPC server_definitions
// matches definitions.json format
JSS(Invalid); //
JSS(Issuer); // in: Credential transactions
JSS(IssuingChainDoor); // field.
JSS(IssuingChainIssue); // field.
JSS(InvoiceID); // field
JSS(LastLedgerSequence); // in: TransactionSign; field
JSS(FirstLedgerSequence); // in: TransactionSign; field
JSS(LastUpdateTime); // field.
JSS(LimitAmount); // field.
JSS(LockingChainDoor); // field.
JSS(LockingChainIssue); // field.
JSS(NetworkID); // field.
JSS(LPTokenOut); // in: AMM Liquidity Provider deposit tokens
JSS(LPTokenIn); // in: AMM Liquidity Provider withdraw tokens
+58
View File
@@ -0,0 +1,58 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/CountedObject.h>
#include <algorithm>
#include <type_traits>
namespace ripple {
CountedObjects&
CountedObjects::getInstance() noexcept
{
static CountedObjects instance;
return instance;
}
CountedObjects::CountedObjects() noexcept : m_count(0), m_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(m_count.load());
for (auto* ctr = m_head.load(); ctr != nullptr; ctr = ctr->getNext())
{
if (ctr->getCount() >= minimumThreshold)
counts.emplace_back(ctr->getName(), ctr->getCount());
}
std::sort(counts.begin(), counts.end());
return counts;
}
} // namespace ripple
+14 -1
View File
@@ -25,6 +25,19 @@
namespace ripple {
namespace detail {
[[noreturn]] void
accessViolation() noexcept
{
// dereference memory location zero
int volatile* j = 0;
(void)*j;
std::abort();
}
} // namespace detail
void
LogThrow(std::string const& title)
{
@@ -42,7 +55,7 @@ LogicError(std::string const& s) noexcept
// here.
// For the above reasons, we want this contract to stand out.
UNREACHABLE("LogicError", {{"message", s}});
std::abort();
detail::accessViolation();
}
} // namespace ripple
+7 -7
View File
@@ -33,7 +33,7 @@
namespace beast::detail {
inline void
setCurrentThreadNameImpl(char const* name)
setCurrentThreadNameImpl(std::string_view name)
{
#if DEBUG && BOOST_COMP_MSVC
// This technique is documented by Microsoft and works for all versions
@@ -54,7 +54,7 @@ setCurrentThreadNameImpl(char const* name)
THREADNAME_INFO ni;
ni.dwType = 0x1000;
ni.szName = name;
ni.szName = name.data();
ni.dwThreadID = GetCurrentThreadId();
ni.dwFlags = 0;
@@ -81,9 +81,9 @@ setCurrentThreadNameImpl(char const* name)
namespace beast::detail {
inline void
setCurrentThreadNameImpl(char const* name)
setCurrentThreadNameImpl(std::string_view name)
{
pthread_setname_np(name);
pthread_setname_np(name.data());
}
} // namespace beast::detail
@@ -95,9 +95,9 @@ setCurrentThreadNameImpl(char const* name)
namespace beast::detail {
inline void
setCurrentThreadNameImpl(char const* name)
setCurrentThreadNameImpl(std::string_view name)
{
pthread_setname_np(pthread_self(), name);
pthread_setname_np(pthread_self(), name.data());
}
} // namespace beast::detail
@@ -119,7 +119,7 @@ void
setCurrentThreadName(std::string_view name)
{
detail::threadName = name;
detail::setCurrentThreadNameImpl(detail::threadName.c_str());
detail::setCurrentThreadNameImpl(name);
}
} // namespace beast
+3 -4
View File
@@ -29,9 +29,7 @@ bool
is_private(AddressV6 const& addr)
{
return (
addr.is_link_local() || // fe80::/10
addr.is_loopback() || // ::1
((addr.to_bytes()[0] & 0xfe) == 0xfc) || // fc00::/7 (all ULA)
(addr.to_bytes()[0] & 0xfd) || // TODO fc00::/8 too ?
(addr.is_v4_mapped() &&
is_private(boost::asio::ip::make_address_v4(
boost::asio::ip::v4_mapped, addr))));
@@ -40,7 +38,8 @@ is_private(AddressV6 const& addr)
bool
is_public(AddressV6 const& addr)
{
return !is_private(addr) && !addr.is_multicast() && !addr.is_unspecified();
// TODO is this correct?
return !is_private(addr) && !addr.is_multicast();
}
} // namespace IP
+1 -5
View File
@@ -23,8 +23,6 @@
#include <xrpl/json/json_writer.h>
#include <xrpl/json/to_string.h>
#include <cmath>
namespace Json {
const Value Value::null;
@@ -674,9 +672,7 @@ Value::isConvertibleTo(ValueType other) const
(other == intValue && value_.real_ >= minInt &&
value_.real_ <= maxInt) ||
(other == uintValue && value_.real_ >= 0 &&
value_.real_ <= maxUInt &&
std::fabs(round(value_.real_) - value_.real_) <
std::numeric_limits<double>::epsilon()) ||
value_.real_ <= maxUInt) ||
other == realValue || other == stringValue ||
other == booleanValue;
+1 -4
View File
@@ -113,10 +113,7 @@ constexpr static ErrorInfo unorderedErrorInfos[]{
{rpcLEDGER_MISSING, "ledgerMissing", "One or more ledgers in the specified range is missing", 406},
{rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400},
{rpcBAD_CREDENTIALS, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400},
{rpcENTRY_NOT_FOUND, "entryNotFound", "Entry not found.", 400},
{rpcUNEXPECTED_LEDGER_TYPE, "unexpectedLedgerType", "Unexpected ledger type.", 400},
};
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400}};
// clang-format on
// Sort and validate unorderedErrorInfos at compile time. Should be
+47
View File
@@ -171,6 +171,53 @@ ed25519Canonical(Slice const& sig)
return std::lexicographical_compare(S, S + 32, Order, Order + 32);
}
//------------------------------------------------------------------------------
PublicKey::PublicKey(Slice const& slice)
{
if (slice.size() < size_)
LogicError(
"PublicKey::PublicKey - Input slice cannot be an undersized "
"buffer");
if (!publicKeyType(slice))
LogicError("PublicKey::PublicKey invalid type");
std::memcpy(buf_, slice.data(), size_);
}
PublicKey::PublicKey(PublicKey const& other)
{
std::memcpy(buf_, other.buf_, size_);
}
PublicKey&
PublicKey::operator=(PublicKey const& other)
{
if (this != &other)
{
std::memcpy(buf_, other.buf_, size_);
}
return *this;
}
//------------------------------------------------------------------------------
std::optional<KeyType>
publicKeyType(Slice const& slice)
{
if (slice.size() == 33)
{
if (slice[0] == 0xED)
return KeyType::ed25519;
if (slice[0] == 0x02 || slice[0] == 0x03)
return KeyType::secp256k1;
}
return std::nullopt;
}
bool
verifyDigest(
PublicKey const& publicKey,
+1 -1
View File
@@ -961,7 +961,7 @@ amountFromJson(SField const& name, Json::Value const& v)
bool const native = !currencyOrMPTID.isString() ||
currencyOrMPTID.asString().empty() ||
(currencyOrMPTID.asString() == systemCurrencyCode);
(currencyOrMPTID.asString() == systemCurrencyCode());
if (native)
{
+10 -8
View File
@@ -95,10 +95,12 @@ STXChainBridge::STXChainBridge(SField const& name, Json::Value const& v)
};
checkExtra(v);
Json::Value const& lockingChainDoorStr = v[jss::LockingChainDoor];
Json::Value const& lockingChainIssue = v[jss::LockingChainIssue];
Json::Value const& issuingChainDoorStr = v[jss::IssuingChainDoor];
Json::Value const& issuingChainIssue = v[jss::IssuingChainIssue];
Json::Value const& lockingChainDoorStr =
v[sfLockingChainDoor.getJsonName()];
Json::Value const& lockingChainIssue = v[sfLockingChainIssue.getJsonName()];
Json::Value const& issuingChainDoorStr =
v[sfIssuingChainDoor.getJsonName()];
Json::Value const& issuingChainIssue = v[sfIssuingChainIssue.getJsonName()];
if (!lockingChainDoorStr.isString())
{
@@ -156,10 +158,10 @@ Json::Value
STXChainBridge::getJson(JsonOptions jo) const
{
Json::Value v;
v[jss::LockingChainDoor] = lockingChainDoor_.getJson(jo);
v[jss::LockingChainIssue] = lockingChainIssue_.getJson(jo);
v[jss::IssuingChainDoor] = issuingChainDoor_.getJson(jo);
v[jss::IssuingChainIssue] = issuingChainIssue_.getJson(jo);
v[sfLockingChainDoor.getJsonName()] = lockingChainDoor_.getJson(jo);
v[sfLockingChainIssue.getJsonName()] = lockingChainIssue_.getJson(jo);
v[sfIssuingChainDoor.getJsonName()] = issuingChainDoor_.getJson(jo);
v[sfIssuingChainIssue.getJsonName()] = issuingChainIssue_.getJson(jo);
return v;
}
+21 -3
View File
@@ -25,7 +25,6 @@
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/detail/secp256k1.h>
#include <xrpl/protocol/digest.h>
#include <cstring>
#include <ed25519.h>
@@ -33,7 +32,25 @@ namespace ripple {
SecretKey::~SecretKey()
{
secure_erase(buf_.data(), buf_.size());
secure_erase(buf_, sizeof(buf_));
}
SecretKey::SecretKey(std::array<std::uint8_t, 32> const& key)
{
std::memcpy(buf_, key.data(), key.size());
}
SecretKey::SecretKey(Slice const& slice)
{
if (slice.size() != sizeof(buf_))
LogicError("SecretKey::SecretKey: invalid size");
std::memcpy(buf_, slice.data(), sizeof(buf_));
}
std::string
SecretKey::to_string() const
{
return strHex(*this);
}
namespace detail {
@@ -197,6 +214,7 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
if (publicKeyType(pk.slice()) != KeyType::secp256k1)
LogicError("sign: secp256k1 required for digest signing");
BOOST_ASSERT(sk.size() == 32);
secp256k1_ecdsa_signature sig_imp;
if (secp256k1_ecdsa_sign(
secp256k1Context(),
@@ -208,7 +226,7 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
LogicError("sign: secp256k1_ecdsa_sign failed");
unsigned char sig[72];
auto len = sizeof(sig);
size_t len = sizeof(sig);
if (secp256k1_ecdsa_signature_serialize_der(
secp256k1Context(), sig, &len, &sig_imp) != 1)
LogicError("sign: secp256k1_ecdsa_signature_serialize_der failed");
+19 -3
View File
@@ -39,12 +39,28 @@ Seed::~Seed()
secure_erase(buf_.data(), buf_.size());
}
Seed::Seed(Slice const& slice)
{
if (slice.size() != buf_.size())
LogicError("Seed::Seed: invalid size");
std::memcpy(buf_.data(), slice.data(), buf_.size());
}
Seed::Seed(uint128 const& seed)
{
if (seed.size() != buf_.size())
LogicError("Seed::Seed: invalid size");
std::memcpy(buf_.data(), seed.data(), buf_.size());
}
//------------------------------------------------------------------------------
Seed
randomSeed()
{
std::array<std::uint8_t, 16> buffer;
beast::rngfill(buffer, crypto_prng());
Seed seed(buffer);
beast::rngfill(buffer.data(), buffer.size(), crypto_prng());
Seed seed(makeSlice(buffer));
secure_erase(buffer.data(), buffer.size());
return seed;
}
@@ -101,7 +117,7 @@ parseGenericSeed(std::string const& str, bool rfc1751)
if (RFC1751::getKeyFromEnglish(key, str) == 1)
{
Blob const blob(key.rbegin(), key.rend());
return Seed{makeSlice(blob)};
return Seed{uint128{blob}};
}
}
+135
View File
@@ -0,0 +1,135 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/UintTypes.h>
#include <string_view>
namespace ripple {
// For details on the protocol-level serialization please visit
// https://xrpl.org/serialization.html#currency-codes
namespace detail {
// Characters we are willing to allow in the ASCII representation of a
// three-letter currency code.
constexpr std::string_view isoCharSet =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"<>(){}[]|?!@#$%^&*";
// The location (in bytes) of the 3 digit currency inside a 160-bit value
constexpr std::size_t isoCodeOffset = 12;
// The length of an ISO-4217 like code
constexpr std::size_t isoCodeLength = 3;
} // namespace detail
std::string
to_string(Currency const& currency)
{
if (currency == beast::zero)
return systemCurrencyCode();
if (currency == noCurrency())
return "1";
static constexpr Currency sIsoBits(
"FFFFFFFFFFFFFFFFFFFFFFFF000000FFFFFFFFFF");
if ((currency & sIsoBits).isZero())
{
std::string const iso(
currency.data() + detail::isoCodeOffset,
currency.data() + detail::isoCodeOffset + detail::isoCodeLength);
// Specifying the system currency code using ISO-style representation
// is not allowed.
if ((iso != systemCurrencyCode()) &&
(iso.find_first_not_of(detail::isoCharSet) == std::string::npos))
{
return iso;
}
}
return strHex(currency);
}
bool
to_currency(Currency& currency, std::string const& code)
{
if (code.empty() || !code.compare(systemCurrencyCode()))
{
currency = beast::zero;
return true;
}
// Handle ISO-4217-like 3-digit character codes.
if (code.size() == detail::isoCodeLength)
{
if (code.find_first_not_of(detail::isoCharSet) != std::string::npos)
return false;
currency = beast::zero;
std::copy(
code.begin(), code.end(), currency.begin() + detail::isoCodeOffset);
return true;
}
return currency.parseHex(code);
}
Currency
to_currency(std::string const& code)
{
Currency currency;
if (!to_currency(currency, code))
currency = noCurrency();
return currency;
}
Currency const&
xrpCurrency()
{
static Currency const currency(beast::zero);
return currency;
}
Currency const&
noCurrency()
{
static Currency const currency(1);
return currency;
}
Currency const&
badCurrency()
{
static Currency const currency(0x5841480000000000);
return currency;
}
} // namespace ripple
+2 -2
View File
@@ -69,7 +69,7 @@ HTTPReply(
// CHECKME this returns a different version than the replies below. Is
// this by design or an accident or should it be using
// BuildInfo::getFullVersionString () as well?
output("Server: " + std::string(systemName) + "-json-rpc/v1");
output("Server: " + systemName() + "-json-rpc/v1");
output("\r\n");
// Be careful in modifying this! If you change the contents you MUST
@@ -147,7 +147,7 @@ HTTPReply(
"\r\n"
"Content-Type: application/json; charset=UTF-8\r\n");
output("Server: " + std::string(systemName) + "-json-rpc/");
output("Server: " + systemName() + "-json-rpc/");
output(BuildInfo::getFullVersionString());
output(
"\r\n"
+232
View File
@@ -18,6 +18,7 @@
#include <test/jtx.h>
#include <test/jtx/envconfig.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/misc/AmendmentTable.h>
#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/tx/apply.h>
#include <xrpld/app/tx/detail/XahauGenesis.h>
@@ -5891,6 +5892,236 @@ struct XahauGenesis_test : public beast::unit_test::suite
testNoClaimLate(features);
}
// inject an enabling ttAMENDMENT pseudo for `amendment` and close
void
injectAmendment(jtx::Env& env, uint256 const& amendment)
{
uint32_t const seq =
env.app().getLedgerMaster().getClosedLedger()->info().seq + 1;
env.app().openLedger().modify(
[&](OpenView& view, beast::Journal j) -> bool {
STTx tx(ttAMENDMENT, [&](auto& obj) {
obj.setAccountID(sfAccount, AccountID());
obj.setFieldH256(sfAmendment, amendment);
obj.setFieldU32(sfLedgerSequence, seq);
});
uint256 txID = tx.getTransactionID();
auto s = std::make_shared<ripple::Serializer>();
tx.add(*s);
env.app().getHashRouter().setFlags(txID, SF_PRIVATE2);
view.rawTxInsert(txID, std::move(s), nullptr);
return true;
});
env.close();
}
// check the genesis account is in the shape fixXahauTestnetGenesis
// leaves it in: blackholed, governance + reward hooks, Evernode-only seat,
// no hook state
void
checkTestnetGenesis(jtx::Env& env, AccountID const& evernode)
{
auto const acc = env.le(keylet::account(genesisAccID));
BEAST_REQUIRE(!!acc);
BEAST_EXPECT(acc->getAccountID(sfRegularKey) == noAccount());
BEAST_EXPECT(acc->getFieldU32(sfFlags) == lsfDisableMaster);
BEAST_EXPECT(!acc->isFieldPresent(sfHookStateCount));
BEAST_EXPECT(!acc->isFieldPresent(sfHookNamespaces));
// governance: 1 + IRR, IRD, IS\0, IMC; reward: 1
BEAST_EXPECT(acc->getFieldU32(sfOwnerCount) == 6);
BEAST_EXPECT(!env.le(keylet::signers(genesisAccID)));
BEAST_EXPECT(!env.le(keylet::hookStateDir(genesisAccID, beast::zero)));
auto const hooksLE = env.le(keylet::hook(genesisAccID));
BEAST_REQUIRE(!!hooksLE);
auto const& hooks = hooksLE->getFieldArray(sfHooks);
BEAST_REQUIRE(hooks.size() == 2);
BEAST_EXPECT(hooks[0].getFieldH256(sfHookHash) == governHookHash);
BEAST_EXPECT(hooks[1].getFieldH256(sfHookHash) == rewardHookHash);
std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>
expected{XahauGenesis::GovernanceParameters};
expected.emplace_back(
std::vector<uint8_t>{'I', 'S', 0},
std::vector<uint8_t>(evernode.data(), evernode.data() + 20));
expected.emplace_back(
std::vector<uint8_t>{'I', 'M', 'C'}, std::vector<uint8_t>{1});
auto const& params = hooks[0].getFieldArray(sfHookParameters);
BEAST_REQUIRE(params.size() == expected.size());
for (std::size_t i = 0; i < expected.size(); ++i)
{
BEAST_EXPECT(
params[i].getFieldVL(sfHookParameterName) == expected[i].first);
BEAST_EXPECT(
params[i].getFieldVL(sfHookParameterValue) ==
expected[i].second);
}
BEAST_EXPECT(hooks[1].getFieldArray(sfHookParameters).empty());
for (auto const& h : {governHookHash, rewardHookHash})
{
auto const def = env.le(keylet::hookDefinition(h));
BEAST_EXPECT(def && def->getFieldU64(sfReferenceCount) == 1);
}
}
void
testXahauTestnetGenesisFix(FeatureBitset features)
{
using namespace jtx;
testcase("fixXahauTestnetGenesis");
auto const evernodePK = parseBase58<PublicKey>(
TokenType::NodePublic,
"nHUetpkzTgf8c6A1DTGemRrhL4nMjsiJrtcsmmg75ufzV5EDMbWU");
BEAST_REQUIRE(!!evernodePK);
AccountID const evernode = calcAccountID(*evernodePK);
auto stateAt = [&](Env& env, std::vector<uint8_t> const& k) {
uint8_t key[32]{};
std::copy(k.begin(), k.end(), key + 32 - k.size());
return env.le(keylet::hookState(
genesisAccID, uint256::fromVoid(key), beast::zero));
};
std::vector<uint8_t> const mcKey{'M', 'C'};
// only supported on 21338
{
Env env{*this, makeNetworkConfig(21337), features};
BEAST_EXPECT(!env.app().getAmendmentTable().isSupported(
fixXahauTestnetGenesis));
}
{
Env env{*this, makeNetworkConfig(21338), features};
BEAST_EXPECT(env.app().getAmendmentTable().isSupported(
fixXahauTestnetGenesis));
}
// wrongly seated governance with stale hook state gets reset
{
Env env{
*this,
makeNetworkConfig(21338),
features - featureXahauGenesis};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const invoker = Account("invoker");
auto const user = Account("user");
env.fund(XRP(10000), alice, bob, invoker, user);
env.close();
XahauGenesis::TestNonGovernanceDistribution.clear();
XahauGenesis::TestL1Membership.clear();
XahauGenesis::TestL2Membership.clear();
XahauGenesis::TestL1Membership.emplace_back(
alice.human(), XRPAmount(10000000000));
XahauGenesis::TestL1Membership.emplace_back(
bob.human(), XRPAmount(10000000000));
activate(__LINE__, env, true, false, true, {}, 2 + 3 + 2);
XahauGenesis::TestL1Membership.clear();
Json::Value invoke;
invoke[jss::TransactionType] = "Invoke";
invoke[jss::Account] = invoker.human();
invoke[jss::Destination] = env.master.human();
env(invoke, fee(XRP(10)));
env.close();
{
auto const mc = stateAt(env, mcKey);
BEAST_REQUIRE(!!mc);
BEAST_EXPECT(
mc->getFieldVL(sfHookStateData) == std::vector<uint8_t>{2});
}
injectAmendment(env, fixXahauTestnetGenesis);
BEAST_EXPECT(
env.current()->rules().enabled(fixXahauTestnetGenesis));
checkTestnetGenesis(env, evernode);
BEAST_EXPECT(!stateAt(env, mcKey));
// governance re-initialises from the new parameters
env(invoke, fee(XRP(10)));
env.close();
{
auto const mc = stateAt(env, mcKey);
BEAST_REQUIRE(!!mc);
BEAST_EXPECT(
mc->getFieldVL(sfHookStateData) == std::vector<uint8_t>{1});
auto const seat = stateAt(
env,
std::vector<uint8_t>(
evernode.data(), evernode.data() + 20));
BEAST_EXPECT(
seat &&
seat->getFieldVL(sfHookStateData) ==
std::vector<uint8_t>{0});
BEAST_EXPECT(!stateAt(
env,
std::vector<uint8_t>(
alice.id().data(), alice.id().data() + 20)));
}
// alice no longer holds a seat
env(vote(
__LINE__,
alice,
env.master,
'R',
'R',
{1, 2, 3, 4, 5, 6, 7, 8}),
fee(XRP(1)),
ter(tecHOOK_REJECTED));
env.close();
// reward claim reaches the reward hook
env(claimReward(user, env.master), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// enabling it again is refused and nothing is reset
auto const ownerCount = env.le(keylet::account(genesisAccID))
->getFieldU32(sfOwnerCount);
injectAmendment(env, fixXahauTestnetGenesis);
BEAST_EXPECT(stateAt(env, mcKey));
BEAST_EXPECT(
env.le(keylet::account(genesisAccID))
->getFieldU32(sfOwnerCount) == ownerCount);
}
// never-configured genesis with a regular key, signer list and a
// foreign hook gets configured and blackholed
{
Env env{*this, makeNetworkConfig(21338), features};
auto const alice = Account("alice");
auto const user = Account("user");
env.fund(XRP(10000), alice, user);
env.memoize(env.master);
env(genesis::setAcceptHook(env.master), fee(XRP(10)));
env(signers(env.master, 1, {{alice, 1}}));
env(regkey(env.master, alice));
env.close();
BEAST_EXPECT(env.le(keylet::hookDefinition(acceptHookHash)));
injectAmendment(env, fixXahauTestnetGenesis);
checkTestnetGenesis(env, evernode);
BEAST_EXPECT(!env.le(keylet::hookDefinition(acceptHookHash)));
env(claimReward(user, env.master), fee(XRP(1)), ter(tesSUCCESS));
env.close();
}
// never touches state off 21338 even if enabled
{
Env env{*this, makeNetworkConfig(21337), features};
injectAmendment(env, fixXahauTestnetGenesis);
BEAST_EXPECT(!env.le(keylet::hook(genesisAccID)));
}
}
void
testGovernHookWithFeats(FeatureBitset features)
{
@@ -5900,6 +6131,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
testWithRegularKey(features);
testGovernanceL1(features);
testGovernanceL2(features);
testXahauTestnetGenesisFix(features);
}
void
+9 -4
View File
@@ -529,10 +529,11 @@ public:
std::vector<JobName> jobs;
{
auto const& jobTypes = JobTypes::instance();
jobs.reserve(jobTypes.size());
for (auto const& job : jobTypes)
{
jobs.emplace_back(job.type, std::string(job.name));
jobs.emplace_back(job.first, job.second.name());
}
}
std::shuffle(jobs.begin(), jobs.end(), default_prng());
@@ -864,10 +865,14 @@ public:
JobType jobType;
std::string jobTypeName;
{
auto index = rand_int<std::size_t>(0, jobTypes.size() - 1);
auto const& jobTypes = JobTypes::instance();
jobType = jobTypes[index].type;
jobTypeName = jobTypes[index].name;
std::uniform_int_distribution<> dis(0, jobTypes.size() - 1);
auto iter{jobTypes.begin()};
std::advance(iter, dis(default_prng()));
jobType = iter->second.type();
jobTypeName = iter->second.name();
}
// Say there's one worker thread.
+7 -4
View File
@@ -307,9 +307,11 @@ struct base_uint_test : beast::unit_test::suite
std::string_view sView(str.data(), str.size());
[[maybe_unused]] test96 t96(sView);
}
catch (std::exception const& e)
catch (std::invalid_argument const& e)
{
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
BEAST_EXPECT(
e.what() ==
std::string("invalid length for hex string"));
caught = true;
}
BEAST_EXPECT(caught);
@@ -325,9 +327,10 @@ struct base_uint_test : beast::unit_test::suite
std::string_view sView(str.data(), str.size());
[[maybe_unused]] test96 t96(sView);
}
catch (std::exception const& e)
catch (std::range_error const& e)
{
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
BEAST_EXPECT(
e.what() == std::string("invalid hex character"));
caught = true;
}
BEAST_EXPECT(caught);
-29
View File
@@ -324,35 +324,6 @@ public:
BEAST_EXPECT(!is_loopback(ep));
BEAST_EXPECTS(to_string(ep) == "fd00::1", to_string(ep));
// a link-local IPv6 is private
ep = Endpoint(boost::asio::ip::make_address_v6("fe80::1"));
BEAST_EXPECT(!is_public(ep));
BEAST_EXPECT(is_private(ep));
BEAST_EXPECT(!is_loopback(ep));
// the IPv6 loopback is private
ep = Endpoint(boost::asio::ip::make_address_v6("::1"));
BEAST_EXPECT(!is_public(ep));
BEAST_EXPECT(is_private(ep));
BEAST_EXPECT(is_loopback(ep));
// a global unicast IPv6 is public
ep = Endpoint(boost::asio::ip::make_address_v6("2001:db8::1"));
BEAST_EXPECT(!is_unspecified(ep));
BEAST_EXPECT(is_public(ep));
BEAST_EXPECT(!is_private(ep));
BEAST_EXPECT(!is_multicast(ep));
// multicast and unspecified IPv6 are neither public nor private
ep = Endpoint(boost::asio::ip::make_address_v6("ff02::1"));
BEAST_EXPECT(!is_public(ep));
BEAST_EXPECT(!is_private(ep));
BEAST_EXPECT(is_multicast(ep));
ep = Endpoint(AddressV6{});
BEAST_EXPECT(is_unspecified(ep));
BEAST_EXPECT(!is_public(ep));
BEAST_EXPECT(!is_private(ep));
{
ep = Endpoint::from_string("192.0.2.112");
BEAST_EXPECT(!is_unspecified(ep));
+3 -11
View File
@@ -120,28 +120,23 @@ public:
auto& jq = env.app().getJobQueue();
// The test assumes that all the threads and coroutines it launches
// all run in a single thread.
BEAST_EXPECT(jq.thread_count() == 1);
std::array<std::shared_ptr<JobQueue::Coro>, 4> a;
static int const N = 4;
std::array<std::shared_ptr<JobQueue::Coro>, N> a;
LocalValue<int> lv(-1);
BEAST_EXPECT(*lv == -1);
gate g;
jq.addJob(jtCLIENT, "LocalValue-Test", [&]() {
this->BEAST_EXPECT(*lv == -1);
*lv = -2;
this->BEAST_EXPECT(*lv == -2);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(*lv == -1);
for (int i = 0; i < a.size(); ++i)
for (int i = 0; i < N; ++i)
{
jq.postCoro(jtCLIENT, "Coroutine-Test", [&, id = i](auto const& c) {
a[id] = c;
@@ -159,14 +154,12 @@ public:
BEAST_EXPECT(g.wait_for(5s));
a[i]->join();
}
for (auto const& c : a)
{
c->post();
BEAST_EXPECT(g.wait_for(5s));
c->join();
}
for (auto const& c : a)
{
c->post();
@@ -177,7 +170,6 @@ public:
this->BEAST_EXPECT(*lv == -2);
g.signal();
});
BEAST_EXPECT(g.wait_for(5s));
BEAST_EXPECT(*lv == -1);
}
+33 -81
View File
@@ -21,11 +21,6 @@
#include <xrpld/core/JobQueue.h>
#include <xrpl/beast/unit_test.h>
#include <future>
#include <mutex>
#include <string>
#include <vector>
namespace ripple {
namespace test {
@@ -50,19 +45,22 @@ class JobQueue_test : public beast::unit_test::suite
while (jobRan == false)
;
}
{
// If the JobQueue is stopped, we should no
// longer be able to add Jobs (and calling addJob() should
// return false).
using namespace std::chrono_literals;
jQueue.stop();
// If the JobQueue is stopped or in the process of stopping, attempting
// to schedule a coroutine should fail.
jQueue.stop();
BEAST_EXPECT(jQueue.isStopping());
auto const coro = jQueue.postCoro(
jtCLIENT,
"JobAddTest2",
[](std::shared_ptr<JobQueue::Coro> const&) {
LogicError("Unscheduled coroutine improperly invoked!");
});
BEAST_EXPECT(coro == nullptr);
// The Job should never run, so having the Job access this
// unprotected variable on the stack should be completely safe.
// Not recommended for the faint of heart...
bool unprotected;
BEAST_EXPECT(
jQueue.addJob(jtCLIENT, "JobAddTest2", [&unprotected]() {
unprotected = false;
}) == false);
}
}
void
@@ -130,77 +128,31 @@ class JobQueue_test : public beast::unit_test::suite
}
BEAST_EXPECT(yieldCount == 4);
}
{
// If the JobQueue is stopped, we should no
// longer be able to add a Coro (and calling postCoro() should
// return false).
using namespace std::chrono_literals;
jQueue.stop();
// If the JobQueue is stopped or in the process of stopping, attempting
// to schedule a coroutine should fail.
jQueue.stop();
BEAST_EXPECT(jQueue.isStopping());
auto const coro = jQueue.postCoro(
jtCLIENT,
"PostCoroTest3",
[](std::shared_ptr<JobQueue::Coro> const&) {
LogicError("Unscheduled coroutine improperly invoked!");
});
BEAST_EXPECT(coro == nullptr);
}
void
testPriority()
{
testcase("priority order");
jtx::Env env{*this};
JobQueue& jQueue = env.app().getJobQueue();
// A standalone Env runs the JobQueue on a single worker, which makes
// dispatch order deterministic.
if (!BEAST_EXPECT(jQueue.thread_count() == 1))
return;
std::promise<void> started;
auto startedFuture = started.get_future();
std::promise<void> release;
std::shared_future<void> const releaseFuture =
release.get_future().share();
// Occupy the only worker so the jobs below queue up behind it.
BEAST_EXPECT(
jQueue.addJob(jtCLIENT, "blocker", [&started, releaseFuture]() {
started.set_value();
releaseFuture.wait();
}));
startedFuture.wait();
std::mutex m;
std::vector<std::string> order;
auto record = [&](std::string tag) {
return [&m, &order, tag]() {
std::lock_guard lock(m);
order.push_back(tag);
};
};
BEAST_EXPECT(jQueue.addJob(jtCLIENT, "low1", record("low1")));
BEAST_EXPECT(jQueue.addJob(jtPUBLEDGER, "high", record("high")));
BEAST_EXPECT(jQueue.addJob(jtCLIENT, "low2", record("low2")));
BEAST_EXPECT(jQueue.addJob(jtADMIN, "highest", record("highest")));
release.set_value();
jQueue.rendezvous();
// Higher job types run first; equal types run in FIFO order.
std::vector<std::string> const expected{
"highest", "high", "low1", "low2"};
std::lock_guard lock(m);
BEAST_EXPECT(order == expected);
// The Coro should never run, so having the Coro access this
// unprotected variable on the stack should be completely safe.
// Not recommended for the faint of heart...
bool unprotected;
auto const coro = jQueue.postCoro(
jtCLIENT,
"PostCoroTest3",
[&unprotected](std::shared_ptr<JobQueue::Coro> const&) {
unprotected = false;
});
BEAST_EXPECT(coro == nullptr);
}
}
public:
void
run() override
{
testPriority();
testAddJob();
testPostCoro();
}
+179
View File
@@ -0,0 +1,179 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/core/JobTypes.h>
#include <xrpld/core/detail/Workers.h>
#include <xrpld/perflog/PerfLog.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/json/json_value.h>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
namespace ripple {
/**
* Dummy class for unit tests.
*/
namespace perf {
class PerfLogTest : public PerfLog
{
void
rpcStart(std::string const& method, std::uint64_t requestId) override
{
}
void
rpcFinish(std::string const& method, std::uint64_t requestId) override
{
}
void
rpcError(std::string const& method, std::uint64_t dur) override
{
}
void
jobQueue(JobType const type) override
{
}
void
jobStart(
JobType const type,
std::chrono::microseconds dur,
std::chrono::time_point<std::chrono::steady_clock> startTime,
int instance) override
{
}
void
jobFinish(JobType const type, std::chrono::microseconds dur, int instance)
override
{
}
Json::Value
countersJson() const override
{
return Json::Value();
}
Json::Value
currentJson() const override
{
return Json::Value();
}
void
resizeJobs(int const resize) override
{
}
void
rotate() override
{
}
};
} // namespace perf
//------------------------------------------------------------------------------
class Workers_test : public beast::unit_test::suite
{
public:
struct TestCallback : Workers::Callback
{
void
processTask(int instance) override
{
std::lock_guard lk{mut};
if (--count == 0)
cv.notify_all();
}
std::condition_variable cv;
std::mutex mut;
int count = 0;
};
void
testThreads(int const tc1, int const tc2, int const tc3)
{
testcase(
"threadCounts: " + std::to_string(tc1) + " -> " +
std::to_string(tc2) + " -> " + std::to_string(tc3));
TestCallback cb;
std::unique_ptr<perf::PerfLog> perfLog =
std::make_unique<perf::PerfLogTest>();
Workers w(cb, perfLog.get(), "Test", tc1);
BEAST_EXPECT(w.getNumberOfThreads() == tc1);
auto testForThreadCount = [this, &cb, &w](int const threadCount) {
// Prepare the callback.
cb.count = threadCount;
// Execute the test.
w.setNumberOfThreads(threadCount);
BEAST_EXPECT(w.getNumberOfThreads() == threadCount);
for (int i = 0; i < threadCount; ++i)
w.addTask();
// 10 seconds should be enough to finish on any system
//
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lk{cb.mut};
bool const signaled =
cb.cv.wait_for(lk, 10s, [&cb] { return cb.count == 0; });
BEAST_EXPECT(signaled);
BEAST_EXPECT(cb.count == 0);
};
testForThreadCount(tc1);
testForThreadCount(tc2);
testForThreadCount(tc3);
w.stop();
// We had better finished all our work!
BEAST_EXPECT(cb.count == 0);
}
void
run() override
{
testThreads(0, 0, 0);
testThreads(1, 0, 1);
testThreads(2, 1, 2);
testThreads(4, 3, 5);
testThreads(16, 4, 15);
testThreads(64, 3, 65);
}
};
BEAST_DEFINE_TESTSUITE(Workers, core, ripple);
} // namespace ripple
+8 -8
View File
@@ -44,10 +44,10 @@ bridge(
Issue const& issuingChainIssue)
{
Json::Value jv;
jv[jss::LockingChainDoor] = lockingChainDoor.human();
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
return jv;
}
@@ -60,10 +60,10 @@ bridge_rpc(
Issue const& issuingChainIssue)
{
Json::Value jv;
jv[jss::LockingChainDoor] = lockingChainDoor.human();
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
return jv;
}
+9 -1
View File
@@ -52,10 +52,18 @@ public:
for (std::uint8_t i = 0; i < 64; i++)
{
beast::rngfill(src, sizeof(src), default_prng());
Seed const seed(src);
Seed const seed({src, sizeof(src)});
BEAST_EXPECT(memcmp(seed.data(), src, sizeof(src)) == 0);
}
}
for (int i = 0; i < 64; i++)
{
uint128 src;
beast::rngfill(src.data(), src.size(), default_prng());
Seed const seed(src);
BEAST_EXPECT(memcmp(seed.data(), src.data(), src.size()) == 0);
}
}
std::string
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -469,7 +469,7 @@ class PWAServe_test : public beast::unit_test::suite,
BEAST_EXPECT(html(r));
BEAST_EXPECT(r.body() == doc());
BEAST_EXPECT(hdr(r, "Connection") == "close");
BEAST_EXPECT(hdr(r, "Server") == systemName);
BEAST_EXPECT(hdr(r, "Server") == systemName());
BEAST_EXPECT(
hdr(r, "Content-Security-Policy").rfind("sandbox ", 0) == 0);
BEAST_EXPECT(hdr(r, "X-Frame-Options") == "DENY");
@@ -695,7 +695,7 @@ class PWAServe_test : public beast::unit_test::suite,
yield);
BEAST_EXPECT(s.rfind("HTTP/1.1 403 ", 0) == 0);
BEAST_EXPECT(
s.find("\r\nServer: " + std::string(systemName) + "\r\n") !=
s.find("\r\nServer: " + systemName() + "\r\n") !=
std::string::npos);
}
}
+12 -2
View File
@@ -1242,10 +1242,10 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
// Configure the amendments the server supports
{
auto const supported = []() {
auto const supported = [this]() {
auto const& amendments = detail::supportedAmendments();
std::vector<AmendmentTable::FeatureInfo> supported;
supported.reserve(amendments.size());
supported.reserve(amendments.size() + 1);
for (auto const& [a, vote] : amendments)
{
auto const f = ripple::getRegisteredFeature(a);
@@ -1254,6 +1254,16 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
if (f)
supported.emplace_back(a, *f, vote);
}
// fixXahauTestnetGenesis is registered Supported::no. It is only
// supported (and voted for) by servers configured for the Xahau
// testnet. Everywhere else it stays unsupported.
if (config_->NETWORK_ID == 21338)
supported.emplace_back(
"fixXahauTestnetGenesis",
fixXahauTestnetGenesis,
VoteBehavior::DefaultYes);
return supported;
}();
Section const& downVoted = config_->section(SECTION_VETO_AMENDMENTS);
+1 -1
View File
@@ -132,7 +132,7 @@ void
printHelp(const po::options_description& desc)
{
std::cerr
<< systemName << "d [options] <command> <params>\n"
<< systemName() << "d [options] <command> <params>\n"
<< desc << std::endl
<< "Commands: \n"
" account_currencies <account> [<ledger>]\n"
+5 -3
View File
@@ -1621,10 +1621,12 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
#ifdef DEBUG
if (!isTesSuccess(e.result))
{
if (std::string token = transToken(e.result); token != "-")
std::string token, human;
if (transResultInfo(e.result, token, human))
{
JLOG(m_journal.debug()) << "Tx " << e.transaction->getID()
<< ", result: " << token;
JLOG(m_journal.info())
<< "TransactionResult: " << token << ": " << human;
}
}
#endif
+1 -1
View File
@@ -59,7 +59,7 @@ parseSection(Section const& section)
uint256 id;
if (!id.parseHex(match[1].str()))
if (!id.parseHex(match[1]))
Throw<std::runtime_error>(
"Invalid amendment ID '" + match[1] + "' in [" +
section.name() + "]");
+1 -1
View File
@@ -198,7 +198,7 @@ PathRequest::isValid(std::shared_ptr<RippleLineCache> const& crCache)
if (!sleDest)
{
jvDestCur.append(Json::Value(std::string(systemCurrencyCode)));
jvDestCur.append(Json::Value(systemCurrencyCode()));
if (!saDstAmount.native())
{
// Only XRP can be send to a non-existent account.
+1 -2
View File
@@ -26,7 +26,6 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STPathSet.h>
#include <optional>
namespace ripple {
@@ -207,7 +206,7 @@ private:
bool convert_all_;
std::shared_ptr<ReadView const> mLedger;
std::optional<LoadEvent> m_loadEvent;
std::unique_ptr<LoadEvent> m_loadEvent;
std::shared_ptr<RippleLineCache> mRLCache;
STPathElement mSource;
+331
View File
@@ -988,6 +988,335 @@ Change::activateXahauGenesis()
ctx_.rawView().rawDestroyXRP(destroyedXRP);
}
// fixXahauTestnetGenesis: one-off repair of the genesis account on the Xahau
// testnet (NetworkID 21338). The genesis hooks there were never set up the
// way featureXahauGenesis sets them up on mainnet, so reward claims misbehave.
//
// This runs exactly once: only from applyAmendment(), at the moment the
// amendment is enabled, which is after the tefALREADY check on the
// Amendments object. It is never reached from any rules().enabled() path.
//
// It resets the genesis account to the mainnet shape:
// - signer list removed, regular key blackholed, master disabled
// - every existing hook removed and every hook state entry wiped, so the
// governance hook re-runs its initial setup on the next Invoke
// - governance + reward hooks installed from the current binaries, with
// only Evernode's L1 seat filled (IMC = 1) and no L2 tables
// Balances are not touched and nothing is minted or burned.
void
Change::activateXahauTestnetGenesisFix()
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis amendment activation starting";
using namespace XahauGenesis;
// Belt and braces: the amendment is only Supported on 21338, but never
// touch state on any other network even if it were somehow enabled.
if (ctx_.app.config().NETWORK_ID != 21338)
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: NetworkID is not 21338, "
"no changes made";
return;
}
auto [ng_entries, l1_entries, l2_entries, gov_params] =
normalizeXahauGenesis(
{}, TestnetL1Membership, {}, GovernanceParameters, j_);
if (l1_entries.size() != 1 || !l2_entries.empty())
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: expected exactly one L1 "
"seat and no L2 tables, bailing";
return;
}
std::vector<std::tuple<
uint256, // hook on
std::vector<uint8_t>, // hook code
std::vector<
std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>>> const
genesis_hooks = {
{GovernanceHookOn, GovernanceHook, gov_params},
{RewardHookOn, RewardHook, {}}};
Sandbox sb(&view());
static auto const accid = calcAccountID(
generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase"))
.first);
if (!sb.exists(keylet::account(accid)))
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: genesis account doesn't "
"exist, bailing";
return;
}
// Step 1: remove any signer list. This adjusts OwnerCount itself, so it
// happens before we start our own OwnerCount bookkeeping below.
if (sb.exists(keylet::signers(accid)))
{
if (auto const ter =
SetSignerList::removeFromLedger(ctx_.app, sb, accid, j_);
!isTesSuccess(ter))
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: could not remove "
"signer list: "
<< transToken(ter) << ", bailing";
return;
}
}
auto sle = sb.peek(keylet::account(accid));
std::int64_t ownerCount = sle->getFieldU32(sfOwnerCount);
// Step 2: blackhole
sle->setAccountID(sfRegularKey, noAccount());
sle->setFieldU32(sfFlags, lsfDisableMaster);
// Step 3: release every existing hook. Definitions are only decremented
// here; zero-reference ones are erased after the new hooks are installed
// so a definition we are about to reuse is never erased and re-inserted.
std::set<uint256> releasedDefs;
auto sleHooks = sb.peek(keylet::hook(accid));
if (sleHooks && sleHooks->isFieldPresent(sfHooks))
{
for (auto const& h : sleHooks->getFieldArray(sfHooks))
{
ownerCount -= SetHook::computeHookReserve(h);
if (!h.isFieldPresent(sfHookHash))
continue;
auto const defKL =
keylet::hookDefinition(h.getFieldH256(sfHookHash));
auto def = sb.peek(defKL);
if (!def)
continue;
std::uint64_t const rc = def->isFieldPresent(sfReferenceCount)
? def->getFieldU64(sfReferenceCount)
: 0;
def->setFieldU64(sfReferenceCount, rc > 0 ? rc - 1 : 0);
sb.update(def);
releasedDefs.emplace(defKL.key);
}
}
// Step 4: wipe all hook state, in every namespace
{
std::uint16_t const scale = sle->isFieldPresent(sfHookStateScale)
? sle->getFieldU16(sfHookStateScale)
: 1;
std::uint32_t const stateCount = sle->isFieldPresent(sfHookStateCount)
? sle->getFieldU32(sfHookStateCount)
: 0;
std::set<uint256> namespaces{uint256{beast::zero}};
if (sle->isFieldPresent(sfHookNamespaces))
for (auto const& ns : sle->getFieldV256(sfHookNamespaces))
namespaces.emplace(ns);
for (auto const& ns : namespaces)
{
auto const dirKL = keylet::hookStateDir(accid, ns);
if (!sb.exists(dirKL))
continue;
std::vector<uint256> entries;
std::shared_ptr<SLE const> page;
unsigned int index{0};
uint256 entry;
if (cdirFirst(sb, dirKL.key, page, index, entry))
{
do
{
entries.push_back(entry);
} while (cdirNext(sb, dirKL.key, page, index, entry));
}
for (auto const& key : entries)
{
auto item = sb.peek(Keylet{ltHOOK_STATE, key});
if (!item)
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: hook state "
"directory references a missing or "
"non-state entry, bailing";
return;
}
if (!sb.dirRemove(dirKL, (*item)[sfOwnerNode], key, false))
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: could not "
"remove hook state entry, bailing";
return;
}
sb.erase(item);
}
// dirRemove drops the root once empty; this is just in case
if (auto dir = sb.peek(dirKL))
sb.erase(dir);
}
ownerCount -= static_cast<std::int64_t>(stateCount) * scale;
if (sle->isFieldPresent(sfHookStateCount))
sle->makeFieldAbsent(sfHookStateCount);
if (sle->isFieldPresent(sfHookStateScale))
sle->makeFieldAbsent(sfHookStateScale);
if (sle->isFieldPresent(sfHookNamespaces))
sle->makeFieldAbsent(sfHookNamespaces);
}
// Step 5: install governance + reward hooks
STArray hooks{sfHooks, genesis_hooks.size()};
std::int64_t hookReserve = 0;
for (auto const& [hookOn, wasmBytes, params] : genesis_hooks)
{
auto const hookHash = ripple::sha512Half_s(
ripple::Slice(wasmBytes.data(), wasmBytes.size()));
auto const defKL = keylet::hookDefinition(hookHash);
STObject hookObj{sfHook};
hookObj.setFieldH256(sfHookHash, hookHash);
if (auto def = sb.peek(defKL))
{
// Reuse the existing definition. It may have been created by an
// ordinary SetHook with other defaults, so pin the fields the
// genesis hooks depend on at the hook level where they differ.
std::uint64_t const rc = def->isFieldPresent(sfReferenceCount)
? def->getFieldU64(sfReferenceCount)
: 0;
def->setFieldU64(sfReferenceCount, rc + 1);
sb.update(def);
if (!def->isFieldPresent(sfHookOn) ||
def->getFieldH256(sfHookOn) != hookOn ||
def->isFieldPresent(sfHookOnIncoming) ||
def->isFieldPresent(sfHookOnOutgoing))
hookObj.setFieldH256(sfHookOn, hookOn);
if (def->getFieldH256(sfHookNamespace) != beast::zero)
hookObj.setFieldH256(sfHookNamespace, uint256{beast::zero});
if (def->isFieldPresent(sfHookCanEmit))
hookObj.setFieldH256(sfHookCanEmit, UINT256_BIT[ttHOOK_SET]);
}
else
{
std::ostringstream loggerStream;
auto result = validateGuards(
wasmBytes,
loggerStream,
"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
hook_api::getImportWhitelist(ctx_.view().rules()),
hook_api::getGuardRulesVersion(ctx_.view().rules()));
if (!result)
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: genesis hook "
"failed guard validation, bailing: "
<< loggerStream.str();
return;
}
if (auto err = hook::HookExecutor::validateWasm(
wasmBytes.data(), (size_t)wasmBytes.size()))
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: genesis hook "
"failed wasm validation, bailing: "
<< *err;
return;
}
// def is null here (the peek above found nothing)
def = std::make_shared<SLE>(defKL);
def->setFieldH256(sfHookHash, hookHash);
def->setFieldH256(sfHookOn, hookOn);
def->setFieldH256(sfHookNamespace, uint256{beast::zero});
def->setFieldU16(sfHookApiVersion, 0);
def->setFieldVL(sfCreateCode, wasmBytes);
def->setFieldH256(sfHookSetTxnID, ctx_.tx.getTransactionID());
def->setFieldU64(sfReferenceCount, 1);
def->setFieldAmount(
sfFee, XRPAmount{hook::computeExecutionFee(result->first)});
if (result->second > 0)
def->setFieldAmount(
sfHookCallbackFee,
XRPAmount{hook::computeExecutionFee(result->second)});
sb.insert(def);
}
{
std::vector<STObject> vec;
for (auto const& [k, v] : params)
{
STObject param(sfHookParameter);
param.setFieldVL(sfHookParameterName, k);
param.setFieldVL(sfHookParameterValue, v);
vec.emplace_back(std::move(param));
}
hookObj.setFieldArray(
sfHookParameters, STArray(vec, sfHookParameters));
}
hookReserve += SetHook::computeHookReserve(hookObj);
hooks.push_back(hookObj);
}
if (sleHooks)
{
sleHooks->setFieldArray(sfHooks, hooks);
sb.update(sleHooks);
}
else
{
sleHooks = std::make_shared<SLE>(keylet::hook(accid));
sleHooks->setFieldArray(sfHooks, hooks);
sleHooks->setAccountID(sfAccount, accid);
auto const page = sb.dirInsert(
keylet::ownerDir(accid),
keylet::hook(accid),
describeOwnerDir(accid));
if (!page)
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: genesis directory full "
"when inserting hooks object, bailing";
return;
}
sleHooks->setFieldU64(sfOwnerNode, *page);
sb.insert(sleHooks);
}
// Step 6: drop definitions nothing references any more
for (auto const& key : releasedDefs)
{
auto def = sb.peek(Keylet{ltHOOK_DEFINITION, key});
if (def && def->isFieldPresent(sfReferenceCount) &&
def->getFieldU64(sfReferenceCount) == 0)
sb.erase(def);
}
// Step 7: owner count
ownerCount += hookReserve;
if (ownerCount < 0)
{
JLOG(j_.warn()) << "fixXahauTestnetGenesis: owner count went negative ("
<< ownerCount << "), clamping to hook reserve";
ownerCount = hookReserve;
}
sle->setFieldU32(sfOwnerCount, static_cast<std::uint32_t>(ownerCount));
sb.update(sle);
sb.apply(ctx_.rawView());
JLOG(j_.warn()) << "fixXahauTestnetGenesis amendment executed successfully";
}
void
Change::activateTrustLinesToSelfFix()
{
@@ -1150,6 +1479,8 @@ Change::applyAmendment()
activateTrustLinesToSelfFix();
else if (amendment == featureXahauGenesis)
activateXahauGenesis();
else if (amendment == fixXahauTestnetGenesis)
activateXahauTestnetGenesisFix();
ctx_.app.getAmendmentTable().enable(amendment);
+3
View File
@@ -62,6 +62,9 @@ private:
void
activateXahauGenesis();
void
activateXahauTestnetGenesisFix();
TER
applyAmendment();
+1 -1
View File
@@ -91,7 +91,7 @@ URIToken::preflight(PreflightContext const& ctx)
{
auto const uri = ctx.tx.getFieldVL(sfURI);
if (uri.size() < 1 || uri.size() > maxURITokenURILength)
if (uri.size() < 1 || uri.size() > 256)
{
JLOG(ctx.j.warn())
<< "Malformed transaction. URI must be at least 1 "
+9
View File
@@ -115,6 +115,15 @@ inline std::vector<std::pair<std::string, XRPAmount>> const L1Membership{
TableAmount}, // L2 Table Exchanges
};
// Governance seating used by fixXahauTestnetGenesis on the Xahau testnet
// (NetworkID 21338). Only Evernode's L1 seat is filled, every other seat is
// left empty and there are no L2 tables. The amount is unused: the fix does
// not mint or burn anything.
inline std::vector<std::pair<std::string, XRPAmount>> const TestnetL1Membership{
{"nHUetpkzTgf8c6A1DTGemRrhL4nMjsiJrtcsmmg75ufzV5EDMbWU",
InfraAmount}, // Evernode
};
// this data structure is used for testing the amendment only,
// if the ttEnableAmendment has an optional flag (that cannot be added in
// production) then whatever is in this array is used
+13 -8
View File
@@ -26,7 +26,7 @@ namespace ripple {
template <class F>
JobQueue::Coro::Coro(
JobQueue::CoroCreator,
Coro_create_t,
JobQueue& jq,
JobType type,
std::string const& name,
@@ -48,7 +48,6 @@ JobQueue::Coro::Coro(
},
boost::coroutines::attributes(megabytes(1)))
{
++jq_.totalCoroutines_;
}
inline JobQueue::Coro::~Coro()
@@ -56,14 +55,15 @@ inline JobQueue::Coro::~Coro()
#ifndef NDEBUG
XRPL_ASSERT(finished_, "ripple::JobQueue::Coro::~Coro : is finished");
#endif
--jq_.totalCoroutines_;
}
inline void
JobQueue::Coro::yield() const
{
++jq_.suspendedCoroutines_;
{
std::lock_guard lock(jq_.m_mutex);
++jq_.nSuspend_;
}
(*yield_)();
}
@@ -78,7 +78,9 @@ JobQueue::Coro::post()
// sp keeps 'this' alive
if (jq_.addJob(
type_, name_, [this, sp = shared_from_this()]() { resume(); }))
{
return true;
}
// The coroutine will not run. Clean up running_.
std::lock_guard lk(mutex_run_);
@@ -94,7 +96,10 @@ JobQueue::Coro::resume()
std::lock_guard lk(mutex_run_);
running_ = true;
}
--jq_.suspendedCoroutines_;
{
std::lock_guard lock(jq_.m_mutex);
--jq_.nSuspend_;
}
auto saved = detail::getLocalValues().release();
detail::getLocalValues().reset(&lvs_);
std::lock_guard lock(mutex_);
@@ -128,8 +133,8 @@ JobQueue::Coro::expectEarlyExit()
//
// That said, since we're outside the Coro's stack, we need to
// decrement the nSuspend that the Coro's call to yield caused.
--jq_.suspendedCoroutines_;
std::lock_guard lock(jq_.m_mutex);
--jq_.nSuspend_;
#ifndef NDEBUG
finished_ = true;
#endif
+41 -33
View File
@@ -21,8 +21,9 @@
#define RIPPLE_CORE_JOB_H_INCLUDED
#include <xrpld/core/ClosureCounter.h>
#include <xrpld/core/LoadEvent.h>
#include <xrpld/core/LoadMonitor.h>
#include <xrpl/basics/CountedObject.h>
#include <functional>
#include <functional>
@@ -31,7 +32,10 @@ namespace ripple {
// Note that this queue should only be used for CPU-bound jobs
// It is primarily intended for signature checking
enum JobType : std::uint32_t {
enum JobType {
// Special type indicating an invalid job - will go away soon.
jtINVALID = -1,
// Job types - the position in this enum indicates the job priority with
// earlier jobs having lower priority than later jobs. If you wish to
// insert a job at a specific priority, simply add it at the right location.
@@ -95,15 +99,27 @@ class Job : public CountedObject<Job>
public:
using clock_type = std::chrono::steady_clock;
Job() = delete;
/** Default constructor.
Allows Job to be used as a container type.
This is used to allow things like jobMap [key] = value.
*/
// VFALCO NOTE I'd prefer not to have a default constructed object.
// What is the semantic meaning of a Job with no associated
// function? Having the invariant "all Job objects refer to
// a job" would reduce the number of states.
//
Job();
Job(JobType type, std::uint64_t index);
// VFALCO TODO try to remove the dependency on LoadMonitor.
Job(JobType type,
std::string name,
std::string const& name,
std::uint64_t index,
std::reference_wrapper<LoadSampler const> sampler,
std::function<void()> job);
Job(Job&& other);
LoadMonitor& lm,
std::function<void()> const& job);
JobType
getType() const;
@@ -112,35 +128,27 @@ public:
clock_type::time_point const&
queue_time() const;
/** A description of this specific job.
Unlike the name associated with the type of this job, which is fixed,
the description may include additional information or context that
distinguishes this from other jobs of the same type.
*/
[[maybe_unused]] std::string const&
description() const;
void
execute();
doJob();
// Sort jobs in dispatch order: JobQueue runs the first runnable job in
// the set, so higher-priority types (later in the JobType enum) must sort
// first, and jobs of the same type run in the order they were queued.
auto
operator<=>(Job const& other) const
{
if (type_ != other.type_)
return other.type_ <=> type_;
return index_ <=> other.index_;
}
// These comparison operators make the jobs sort in priority order
// in the job set
bool
operator<(const Job& j) const;
bool
operator>(const Job& j) const;
bool
operator<=(const Job& j) const;
bool
operator>=(const Job& j) const;
private:
JobType const type_;
std::uint64_t const index_;
clock_type::time_point const queued_;
std::function<void()> work_;
LoadEvent loadEvent_;
JobType mType;
std::uint64_t mJobIndex;
std::function<void()> mJob;
std::shared_ptr<LoadEvent> m_loadEvent;
std::string mName;
clock_type::time_point m_queue_time;
};
using JobCounter = ClosureCounter<void>;
+108 -127
View File
@@ -21,15 +21,14 @@
#define RIPPLE_CORE_JOBQUEUE_H_INCLUDED
#include <xrpld/core/ClosureCounter.h>
#include <xrpld/core/JobTypeData.h>
#include <xrpld/core/JobTypes.h>
#include <xrpld/core/detail/Workers.h>
#include <xrpl/basics/LocalValue.h>
#include <xrpl/beast/insight/Collector.h>
#include <xrpl/json/json_value.h>
#include <boost/coroutine/all.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/begin.hpp> // workaround for boost 1.72 bug
#include <boost/range/end.hpp> // workaround for boost 1.72 bug
namespace ripple {
@@ -38,6 +37,10 @@ class PerfLog;
}
class Logs;
struct Coro_create_t
{
explicit Coro_create_t() = default;
};
/** A pool of threads to perform work.
@@ -51,23 +54,15 @@ class Logs;
*/
class JobQueue : private Workers::Callback
{
// A small class used to prevent construction of coroutines without
// going through the JobQueue APIs.
struct CoroCreator
{
explicit constexpr CoroCreator() = default;
};
public:
/** Coroutines must run to completion. */
class Coro : public std::enable_shared_from_this<Coro>,
public CountedObject<Coro>
class Coro : public std::enable_shared_from_this<Coro>
{
private:
detail::LocalValues lvs_;
JobQueue& jq_;
JobType const type_;
std::string const name_;
JobType type_;
std::string name_;
bool running_;
std::mutex mutex_;
std::mutex mutex_run_;
@@ -81,23 +76,14 @@ public:
public:
// Private: Used in the implementation
template <class F>
Coro(
JobQueue::CoroCreator,
JobQueue&,
JobType,
std::string const&,
F&&);
Coro(Coro_create_t, JobQueue&, JobType, std::string const&, F&&);
// Not copy-constructible or assignable
Coro(Coro const&) = delete;
Coro&
operator=(Coro const&) = delete;
Coro(Coro&&) = delete;
Coro&
operator=(Coro&&) = delete;
virtual ~Coro();
~Coro();
/** Suspend coroutine execution.
Effects:
@@ -181,7 +167,9 @@ public:
{
if (auto optionalCountedJob =
jobCounter_.wrap(std::forward<JobHandler>(jobHandler)))
{
return addRefCountedJob(type, name, std::move(*optionalCountedJob));
}
return false;
}
@@ -189,10 +177,10 @@ public:
@param t The type of job.
@param name Name of the job.
@param f The actual coroutine body; has a signature of:
void(std::shared_ptr<Coro>)
@param f Has a signature of void(std::shared_ptr<Coro>). Called when the
job executes.
@return shared_ptr to posted Coro. nullptr if post was not successful.
@return shared_ptr to posted Coro. nullptr if post was not successful.
*/
template <class F>
std::shared_ptr<Coro>
@@ -213,9 +201,10 @@ public:
int
getJobCountGE(JobType t) const;
/** Returns a new load event for the particular job. */
LoadEvent
makeLoadEvent(JobType t, std::string name);
/** Return a scoped LoadEvent.
*/
std::unique_ptr<LoadEvent>
makeLoadEvent(JobType t, std::string const& name);
/** Add multiple load events.
*/
@@ -237,96 +226,52 @@ public:
void
stop();
[[nodiscard]] bool
isStopping() const noexcept
bool
isStopping() const
{
return state_.load(std::memory_order_acquire) >= state::stopping;
return stopping_;
}
[[nodiscard]] bool
isStopped() const noexcept
{
return state_.load(std::memory_order_acquire) == state::stopped;
}
/** Returns the number of threads that this job queue is configured with. */
unsigned int
thread_count() const noexcept
{
return m_workers.count();
}
// We may be able to move away from this, but we can keep it during the
// transition.
bool
isStopped() const;
private:
friend class Coro;
struct Data
{
int waiting = 0;
int running = 0;
int deferred = 0;
beast::insight::Event dequeue;
beast::insight::Event execute;
LoadMonitor load;
Data(
JobTypeInfo const& info,
beast::insight::Collector::ptr const& collector,
Logs& logs)
: load(
info.averageLatency,
info.peakLatency,
logs.journal("LoadMonitor"))
{
if (!info.special())
{
dequeue = collector->make_event(std::string(info.name) + "_q");
execute = collector->make_event(std::string(info.name));
}
}
Data(Data const&) = delete;
Data&
operator=(Data const&) = delete;
Data(Data&&) = delete;
Data&
operator=(Data&&) = delete;
};
using JobDataMap = std::map<JobType, JobTypeData>;
beast::Journal m_journal;
mutable std::mutex m_mutex;
std::uint64_t m_lastJob = 0;
std::uint64_t m_lastJob;
std::set<Job> m_jobSet;
JobCounter jobCounter_;
std::atomic_bool stopping_{false};
std::atomic_bool stopped_{false};
JobDataMap m_jobData;
JobTypeData m_invalidJobData;
enum class state : std::uint8_t { running = 0, stopping = 1, stopped = 2 };
std::atomic<state> state_ = state::running;
std::array<Data, jobTypes.size()> data_;
int activeThreads_ = 0;
// The number of coroutines (active or suspended)
std::atomic<int> totalCoroutines_ = 0;
// The number of jobs currently in processTask()
int m_processCount;
// The number of suspended coroutines
std::atomic<int> suspendedCoroutines_ = 0;
int nSuspend_ = 0;
Workers m_workers;
perf::PerfLog& perfLog_;
// Statistics tracking
perf::PerfLog& perfLog_;
beast::insight::Collector::ptr m_collector;
beast::insight::Gauge job_count;
beast::insight::Gauge activeThreadsGauge_;
beast::insight::Hook hook;
std::condition_variable cv_;
void
collect();
JobTypeData&
getJobTypeData(JobType type);
// Adds a reference counted job to the JobQueue.
//
@@ -337,7 +282,46 @@ private:
//
// return true if func added to queue.
bool
addRefCountedJob(JobType type, std::string const& name, JobFunction func);
addRefCountedJob(
JobType type,
std::string const& name,
JobFunction const& func);
// Returns the next Job we should run now.
//
// RunnableJob:
// A Job in the JobSet whose slots count for its type is greater than zero.
//
// Pre-conditions:
// mJobSet must not be empty.
// mJobSet holds at least one RunnableJob
//
// Post-conditions:
// job is a valid Job object.
// job is removed from mJobQueue.
// Waiting job count of its type is decremented
// Running job count of its type is incremented
//
// Invariants:
// The calling thread owns the JobLock
void
getNextJob(Job& job);
// Indicates that a running Job has completed its task.
//
// Pre-conditions:
// Job must not exist in mJobSet.
// The JobType must not be invalid.
//
// Post-conditions:
// The running count of that JobType is decremented
// A new task is signaled if there are more waiting Jobs than the limit, if
// any.
//
// Invariants:
// <none>
void
finishJob(JobType type);
// Runs the next appropriate waiting Job.
//
@@ -350,14 +334,13 @@ private:
// Invariants:
// <none>
void
processTask(unsigned int instance) override;
processTask(int instance) override;
// Called when an uncaught exception occurs while processing a task
//
// Invariants:
// <none>
void
uncaughtException(unsigned int instance, std::exception_ptr eptr) override;
// Returns the limit of running jobs for the given job type.
// For jobs with no limit, we return the largest int. Hopefully that
// will be enough.
int
getJobLimit(JobType type);
};
/*
@@ -371,11 +354,11 @@ private:
coro_ member is initialized (a boost::coroutines::pull_type), execution
automatically passes to the coroutine, which we don't want at this point,
since we are still in the handler thread context. It's important to note
here that construction of a boost pull_type automatically passes execution
to the coroutine. A pull_type object automatically generates a push_type
that is passed as a parameter (do_yield) in the signature of the function
the pull_type was created with. This function is immediately called during
coro_ construction and within it, Coro::yield_ is assigned the push_type
here that construction of a boost pull_type automatically passes execution to
the coroutine. A pull_type object automatically generates a push_type that is
passed as a parameter (do_yield) in the signature of the function the
pull_type was created with. This function is immediately called during coro_
construction and within it, Coro::yield_ is assigned the push_type
parameter (do_yield) address and called (yield()) so we can return execution
back to the caller's stack.
@@ -402,20 +385,19 @@ private:
The race condition occurs as follows:
1. The coroutine is running.
2. The coroutine is about to suspend, but before it can do so, it must
arrange for some event to wake it up.
3. The coroutine arranges for some event to wake it up.
4. Before the coroutine can suspend, that event occurs and the
resumption of the coroutine is scheduled on the job queue.
5. Again, before the coroutine can suspend, the resumption of the coroutine
is dispatched.
6. Again, before the coroutine can suspend, the resumption code runs the
coroutine.
1- The coroutine is running.
2- The coroutine is about to suspend, but before it can do so, it must
arrange for some event to wake it up.
3- The coroutine arranges for some event to wake it up.
4- Before the coroutine can suspend, that event occurs and the
resumption of the coroutine is scheduled on the job queue. 5- Again, before
the coroutine can suspend, the resumption of the coroutine is dispatched. 6-
Again, before the coroutine can suspend, the resumption code runs the
coroutine.
The coroutine is now running in two threads.
The coroutine is now running in two threads. The lock prevents this from
happening as step 6 will block until the lock is released which only
happens after the coroutine completes.
The lock prevents this from happening as step 6 will block until the
lock is released which only happens after the coroutine completes.
*/
} // namespace ripple
@@ -428,12 +410,12 @@ template <class F>
std::shared_ptr<JobQueue::Coro>
JobQueue::postCoro(JobType t, std::string const& name, F&& f)
{
if (state_.load(std::memory_order_acquire) != state::running)
return nullptr;
/* First param is a detail type to make construction private.
Last param is the function the coroutine runs. Signature of
void(std::shared_ptr<Coro>).
*/
auto coro = std::make_shared<Coro>(
CoroCreator{}, *this, t, name, std::forward<F>(f));
Coro_create_t{}, *this, t, name, std::forward<F>(f));
if (!coro->post())
{
// The Coro was not successfully posted. Disable it so it's destructor
@@ -441,7 +423,6 @@ JobQueue::postCoro(JobType t, std::string const& name, F&& f)
coro->expectEarlyExit();
coro.reset();
}
return coro;
}
+107
View File
@@ -0,0 +1,107 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_CORE_JOBTYPEDATA_H_INCLUDED
#define RIPPLE_CORE_JOBTYPEDATA_H_INCLUDED
#include <xrpld/core/JobTypeInfo.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/insight/Collector.h>
namespace ripple {
struct JobTypeData
{
private:
LoadMonitor m_load;
/* Support for insight */
beast::insight::Collector::ptr m_collector;
public:
/* The job category which we represent */
JobTypeInfo const& info;
/* The number of jobs waiting */
int waiting;
/* The number presently running */
int running;
/* And the number we deferred executing because of job limits */
int deferred;
/* Notification callbacks */
beast::insight::Event dequeue;
beast::insight::Event execute;
JobTypeData(
JobTypeInfo const& info_,
beast::insight::Collector::ptr const& collector,
Logs& logs) noexcept
: m_load(logs.journal("LoadMonitor"))
, m_collector(collector)
, info(info_)
, waiting(0)
, running(0)
, deferred(0)
{
m_load.setTargetLatency(
info.getAverageLatency(), info.getPeakLatency());
if (!info.special())
{
dequeue = m_collector->make_event(info.name() + "_q");
execute = m_collector->make_event(info.name());
}
}
/* Not copy-constructible or assignable */
JobTypeData(JobTypeData const& other) = delete;
JobTypeData&
operator=(JobTypeData const& other) = delete;
std::string
name() const
{
return info.name();
}
JobType
type() const
{
return info.type();
}
LoadMonitor&
load()
{
return m_load;
}
LoadMonitor::Stats
stats()
{
return m_load.getStats();
}
};
} // namespace ripple
#endif
+102
View File
@@ -0,0 +1,102 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_CORE_JOBTYPEINFO_H_INCLUDED
#define RIPPLE_CORE_JOBTYPEINFO_H_INCLUDED
#include <xrpld/core/Job.h>
namespace ripple {
/** Holds all the 'static' information about a job, which does not change */
class JobTypeInfo
{
private:
JobType const m_type;
std::string const m_name;
/** The limit on the number of running jobs for this job type.
A limit of 0 marks this as a "special job" which is not
dispatched via the job queue.
*/
int const m_limit;
/** Average and peak latencies for this job type. 0 is none specified */
std::chrono::milliseconds const m_avgLatency;
std::chrono::milliseconds const m_peakLatency;
public:
// Not default constructible
JobTypeInfo() = delete;
JobTypeInfo(
JobType type,
std::string name,
int limit,
std::chrono::milliseconds avgLatency,
std::chrono::milliseconds peakLatency)
: m_type(type)
, m_name(std::move(name))
, m_limit(limit)
, m_avgLatency(avgLatency)
, m_peakLatency(peakLatency)
{
}
JobType
type() const
{
return m_type;
}
std::string const&
name() const
{
return m_name;
}
int
limit() const
{
return m_limit;
}
bool
special() const
{
return m_limit == 0;
}
std::chrono::milliseconds
getAverageLatency() const
{
return m_avgLatency;
}
std::chrono::milliseconds
getPeakLatency() const
{
return m_peakLatency;
}
};
} // namespace ripple
#endif
+155 -114
View File
@@ -21,137 +21,178 @@
#define RIPPLE_CORE_JOBTYPES_H_INCLUDED
#include <xrpld/core/Job.h>
#include <array>
#include <chrono>
#include <xrpld/core/JobTypeInfo.h>
#include <map>
#include <string>
#include <type_traits>
#include <unordered_map>
namespace ripple {
/** Defines the fixed attributes of a particular type of job.
These attributes are defined at compile time and never change. They
include things like the name of the job, the limit of concurrent jobs
of this type, etc.
*/
struct JobTypeInfo
class JobTypes
{
public:
JobType const type;
using Map = std::map<JobType, JobTypeInfo>;
using const_iterator = Map::const_iterator;
/// The job's display name
std::string_view const name;
/** The limit on the number of running jobs for this job type.
A limit of 0 marks this as a "special job" which is not
dispatched via the job queue.
*/
int const limit;
/** Average and peak latencies for this job type, if relevant. */
/** @{ */
std::optional<std::chrono::milliseconds> const averageLatency;
std::optional<std::chrono::milliseconds> const peakLatency;
/** @} */
// Not default constructible
JobTypeInfo() = delete;
constexpr JobTypeInfo(
JobType type_,
std::string_view const name_,
int limit_,
std::optional<std::chrono::milliseconds> averageLatency_ = {},
std::optional<std::chrono::milliseconds> peakLatency_ = {})
: type(type_)
, name(name_)
, limit(limit_)
, averageLatency(averageLatency_)
, peakLatency(peakLatency_)
private:
JobTypes()
: m_unknown(
jtINVALID,
"invalid",
0,
std::chrono::milliseconds{0},
std::chrono::milliseconds{0})
{
}
using namespace std::chrono_literals;
int maxLimit = std::numeric_limits<int>::max();
bool
special() const
{
return limit == 0;
}
};
auto add = [this](
JobType jt,
std::string name,
int limit,
std::chrono::milliseconds avgLatency,
std::chrono::milliseconds peakLatency) {
XRPL_ASSERT(
m_map.find(jt) == m_map.end(),
"ripple::JobTypes::JobTypes::add : unique job type input");
inline auto constexpr jobTypes = []() consteval {
using namespace std::chrono_literals;
[[maybe_unused]] auto const inserted =
m_map
.emplace(
std::piecewise_construct,
std::forward_as_tuple(jt),
std::forward_as_tuple(
jt, name, limit, avgLatency, peakLatency))
.second;
XRPL_ASSERT(
inserted == true,
"ripple::JobTypes::JobTypes::add : input is inserted");
};
constexpr JobTypeInfo jobs[] = {
// clang-format off
// avg peak
// JobType name limit latency latency
{ jtPACK, "makeFetchPack", 1, },
{ jtPUBOLDLEDGER, "publishAcqLedger", 2, 10000ms, 15000ms },
{ jtCLIENT, "clientCommand", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_SUBSCRIBE, "clientSubscribe", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_FEE_CHANGE, "clientFeeChange", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_CONSENSUS, "clientConsensus", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_ACCT_HIST, "clientAccountHistory", std::numeric_limits<int>::max(), 2000ms, 5000ms },
// avg peak
// JobType name limit latency latency
add(jtPACK, "makeFetchPack", 1, 0ms, 0ms);
add(jtPUBOLDLEDGER, "publishAcqLedger", 2, 10000ms, 15000ms);
add(jtVALIDATION_ut, "untrustedValidation", maxLimit, 2000ms, 5000ms);
add(jtMANIFEST, "manifest", maxLimit, 2000ms, 5000ms);
add(jtTRANSACTION_l, "localTransaction", maxLimit, 100ms, 500ms);
add(jtREPLAY_REQ, "ledgerReplayRequest", 10, 250ms, 1000ms);
add(jtLEDGER_REQ, "ledgerRequest", 3, 0ms, 0ms);
add(jtPROPOSAL_ut, "untrustedProposal", maxLimit, 500ms, 1250ms);
add(jtREPLAY_TASK, "ledgerReplayTask", maxLimit, 0ms, 0ms);
add(jtLEDGER_DATA, "ledgerData", 3, 0ms, 0ms);
add(jtCLIENT, "clientCommand", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_SUBSCRIBE, "clientSubscribe", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_FEE_CHANGE, "clientFeeChange", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_CONSENSUS, "clientConsensus", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_ACCT_HIST, "clientAccountHistory", maxLimit, 2000ms, 5000ms);
add(jtCLIENT_RPC, "clientRPC", maxLimit, 2000ms, 5000ms);
// Web traffic from pwa ports: at most two workers, and no latency
// targets, so that it can never move the node's load factor.
{ jtCLIENT_PWA, "clientPWA", 2, },
{ jtCLIENT_PWADNS, "clientPWADNS", 2, },
{ jtCLIENT_RPC, "clientRPC", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtCLIENT_WEBSOCKET, "clientWebsocket", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtRPC, "RPC", std::numeric_limits<int>::max(), },
{ jtSWEEP, "sweep", 1, },
{ jtVALIDATION_ut, "untrustedValidation", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtMANIFEST, "manifest", std::numeric_limits<int>::max(), 2000ms, 5000ms },
{ jtUPDATE_PF, "updatePaths", 1, },
{ jtTRANSACTION_l, "localTransaction", std::numeric_limits<int>::max(), 100ms, 500ms },
{ jtREPLAY_REQ, "ledgerReplayRequest", 10, 250ms, 1000ms },
{ jtLEDGER_REQ, "ledgerRequest", 3, },
{ jtPROPOSAL_ut, "untrustedProposal", std::numeric_limits<int>::max(), 500ms, 1250ms },
{ jtREPLAY_TASK, "ledgerReplayTask", std::numeric_limits<int>::max(), },
{ jtTRANSACTION, "transaction", std::numeric_limits<int>::max(), 250ms, 1000ms },
{ jtMISSING_TXN, "handleHaveTransactions", 1200, },
{ jtREQUESTED_TXN, "doTransactions", 1200, },
{ jtBATCH, "batch", std::numeric_limits<int>::max(), 250ms, 1000ms },
{ jtLEDGER_DATA, "ledgerData", 3, },
{ jtADVANCE, "advanceLedger", std::numeric_limits<int>::max(), },
{ jtPUBLEDGER, "publishNewLedger", std::numeric_limits<int>::max(), 3000ms, 4500ms },
{ jtTXN_DATA, "fetchTxnData", 5, },
{ jtWAL, "writeAhead", std::numeric_limits<int>::max(), 1000ms, 2500ms },
{ jtVALIDATION_t, "trustedValidation", std::numeric_limits<int>::max(), 500ms, 1500ms },
{ jtWRITE, "writeObjects", std::numeric_limits<int>::max(), 1750ms, 2500ms },
{ jtACCEPT, "acceptLedger", std::numeric_limits<int>::max(), },
{ jtPROPOSAL_t, "trustedProposal", std::numeric_limits<int>::max(), 100ms, 500ms },
{ jtNETOP_CLUSTER, "clusterReport", 1, 9999ms, 9999ms },
{ jtNETOP_TIMER, "heartbeat", 1, 999ms, 999ms },
{ jtADMIN, "administration", std::numeric_limits<int>::max(), },
{ jtPEER, "peerCommand", 0, 200ms, 2500ms },
{ jtDISK, "diskAccess", 0, 500ms, 1000ms },
{ jtTXN_PROC, "processTransaction", 0 },
{ jtOB_SETUP, "orderBookSetup", 0 },
{ jtPATH_FIND, "pathFind", 0 },
{ jtHO_READ, "nodeRead", 0 },
{ jtHO_WRITE, "nodeWrite", 0 },
{ jtGENERIC, "generic", 0 },
{ jtNS_SYNC_READ, "SyncReadNode", 0 },
{ jtNS_ASYNC_READ, "AsyncReadNode", 0 },
{ jtNS_WRITE, "WriteNode", 0 }
add(jtCLIENT_PWA, "clientPWA", 2, 0ms, 0ms);
add(jtCLIENT_PWADNS, "clientPWADNS", 2, 0ms, 0ms);
add(jtCLIENT_WEBSOCKET, "clientWebsocket", maxLimit, 2000ms, 5000ms);
add(jtRPC, "RPC", maxLimit, 0ms, 0ms);
add(jtUPDATE_PF, "updatePaths", 1, 0ms, 0ms);
add(jtTRANSACTION, "transaction", maxLimit, 250ms, 1000ms);
add(jtBATCH, "batch", maxLimit, 250ms, 1000ms);
add(jtADVANCE, "advanceLedger", maxLimit, 0ms, 0ms);
add(jtPUBLEDGER, "publishNewLedger", maxLimit, 3000ms, 4500ms);
add(jtTXN_DATA, "fetchTxnData", 5, 0ms, 0ms);
add(jtWAL, "writeAhead", maxLimit, 1000ms, 2500ms);
add(jtVALIDATION_t, "trustedValidation", maxLimit, 500ms, 1500ms);
add(jtWRITE, "writeObjects", maxLimit, 1750ms, 2500ms);
add(jtACCEPT, "acceptLedger", maxLimit, 0ms, 0ms);
add(jtPROPOSAL_t, "trustedProposal", maxLimit, 100ms, 500ms);
add(jtSWEEP, "sweep", 1, 0ms, 0ms);
add(jtNETOP_CLUSTER, "clusterReport", 1, 9999ms, 9999ms);
add(jtNETOP_TIMER, "heartbeat", 1, 999ms, 999ms);
add(jtADMIN, "administration", maxLimit, 0ms, 0ms);
add(jtMISSING_TXN, "handleHaveTransactions", 1200, 0ms, 0ms);
add(jtREQUESTED_TXN, "doTransactions", 1200, 0ms, 0ms);
add(jtPEER, "peerCommand", 0, 200ms, 2500ms);
add(jtDISK, "diskAccess", 0, 500ms, 1000ms);
add(jtTXN_PROC, "processTransaction", 0, 0ms, 0ms);
add(jtOB_SETUP, "orderBookSetup", 0, 0ms, 0ms);
add(jtPATH_FIND, "pathFind", 0, 0ms, 0ms);
add(jtHO_READ, "nodeRead", 0, 0ms, 0ms);
add(jtHO_WRITE, "nodeWrite", 0, 0ms, 0ms);
add(jtGENERIC, "generic", 0, 0ms, 0ms);
add(jtNS_SYNC_READ, "SyncReadNode", 0, 0ms, 0ms);
add(jtNS_ASYNC_READ, "AsyncReadNode", 0, 0ms, 0ms);
add(jtNS_WRITE, "WriteNode", 0, 0ms, 0ms);
// clang-format on
};
// Ensure that the order of entries in the table corresponds to the
// order of entries in the enum:
std::underlying_type_t<JobType> idx = 0;
for (auto const& jt : jobs)
{
if (static_cast<std::underlying_type_t<JobType>>(jt.type) != idx)
throw "The JobTypes array is improperly ordered";
++idx;
}
return std::to_array(jobs);
}();
public:
static JobTypes const&
instance()
{
static JobTypes const types;
return types;
}
static std::string const&
name(JobType jt)
{
return instance().get(jt).name();
}
JobTypeInfo const&
get(JobType jt) const
{
Map::const_iterator const iter(m_map.find(jt));
XRPL_ASSERT(iter != m_map.end(), "ripple::JobTypes::get : valid input");
if (iter != m_map.end())
return iter->second;
return m_unknown;
}
JobTypeInfo const&
getInvalid() const
{
return m_unknown;
}
Map::size_type
size() const
{
return m_map.size();
}
const_iterator
begin() const
{
return m_map.cbegin();
}
const_iterator
cbegin() const
{
return m_map.cbegin();
}
const_iterator
end() const
{
return m_map.cend();
}
const_iterator
cend() const
{
return m_map.cend();
}
JobTypeInfo m_unknown;
Map m_map;
};
} // namespace ripple
+26 -23
View File
@@ -20,27 +20,24 @@
#ifndef RIPPLE_CORE_LOADEVENT_H_INCLUDED
#define RIPPLE_CORE_LOADEVENT_H_INCLUDED
#include <xrpld/core/LoadMonitor.h>
#include <chrono>
#include <memory>
#include <string>
namespace ripple {
class LoadMonitor;
// VFALCO NOTE What is the difference between a LoadEvent and a LoadMonitor?
// VFALCO TODO Rename LoadEvent to ScopedLoadSample
//
// This looks like a scoped elapsed time measuring class
//
class LoadEvent
{
public:
LoadEvent(
std::reference_wrapper<LoadSampler const> callback,
std::string name,
bool shouldStart);
LoadEvent(LoadEvent&& other);
LoadEvent&
operator=(LoadEvent&& other);
LoadEvent&
operator=(LoadEvent const&) = delete;
// VFALCO TODO remove the dependency on LoadMonitor. Is that possible?
LoadEvent(LoadMonitor& monitor, std::string const& name, bool shouldStart);
LoadEvent(LoadEvent const&) = delete;
~LoadEvent();
@@ -48,6 +45,17 @@ public:
std::string const&
name() const;
// The time spent waiting.
std::chrono::steady_clock::duration
waitTime() const;
// The time spent running.
std::chrono::steady_clock::duration
runTime() const;
void
setName(std::string const& name);
// Start the measurement. If already started, then
// restart, assigning the elapsed time to the "waiting"
// state.
@@ -61,12 +69,13 @@ public:
stop();
private:
// The name for this event.
std::string name_;
LoadMonitor& monitor_;
// The callback to invoke when we stop. This will only
// be invoked if `neutered_` is `false`.
std::reference_wrapper<LoadSampler const> callback_;
// Represents our current state
bool running_;
// The name associated with this event, if any.
std::string name_;
// Represents the time we last transitioned states
std::chrono::steady_clock::time_point mark_;
@@ -74,12 +83,6 @@ private:
// The time we spent waiting and running respectively
std::chrono::steady_clock::duration timeWaiting_;
std::chrono::steady_clock::duration timeRunning_;
// Represents our current state
bool running_;
// Determines whether the callback should be invoked
bool neutered_;
};
} // namespace ripple
+34 -47
View File
@@ -20,82 +20,69 @@
#ifndef RIPPLE_CORE_LOADMONITOR_H_INCLUDED
#define RIPPLE_CORE_LOADMONITOR_H_INCLUDED
#include <xrpld/core/LoadEvent.h>
#include <xrpl/basics/UptimeClock.h>
#include <xrpl/beast/utility/Journal.h>
#include <chrono>
#include <functional>
#include <mutex>
#include <optional>
namespace ripple {
using LoadSampler = std::function<void(
char const*,
std::chrono::steady_clock::duration,
std::chrono::steady_clock::duration)>;
// Monitors load levels and response times
/** Monitors load levels and response times
@todo Consider renaming this, since having both a LoadManager and a
LoadMonitor is confusing, especially considering that they do
different things.
*/
// VFALCO TODO Rename this. Having both LoadManager and LoadMonitor is
// confusing.
//
class LoadMonitor
{
public:
struct Stats
{
std::uint64_t count = 0;
explicit LoadMonitor(beast::Journal j);
std::chrono::milliseconds averageLatency{};
std::chrono::milliseconds peakLatency{};
bool isOverloaded = false;
};
explicit LoadMonitor(
std::optional<std::chrono::milliseconds> targetAverageLatency,
std::optional<std::chrono::milliseconds> targetPeakLatency,
beast::Journal j);
LoadMonitor(LoadMonitor const& other) = delete;
LoadMonitor&
operator=(LoadMonitor const& other) = delete;
LoadMonitor(LoadMonitor&& other) = delete;
LoadMonitor&
operator=(LoadMonitor&& other) = delete;
void
addLoadSample(LoadEvent const& sample);
void
addSamples(int count, std::chrono::milliseconds latency);
void
setTargetLatency(
std::chrono::milliseconds avg,
std::chrono::milliseconds pk);
bool
isOverTarget(std::chrono::milliseconds avg, std::chrono::milliseconds peak);
// VFALCO TODO make this return the values in a struct.
struct Stats
{
Stats();
std::uint64_t count;
std::chrono::milliseconds latencyAvg;
std::chrono::milliseconds latencyPeak;
bool isOverloaded;
};
Stats
getStats();
bool
isOver();
/** A reference to a function object used to report performance events. */
std::reference_wrapper<LoadSampler const>
sample()
{
return std::cref(eventCallback_);
}
private:
void
update();
std::mutex mutex_;
std::uint64_t samples_ = 0;
std::chrono::milliseconds averageLatency_{};
std::chrono::milliseconds peakLatency_{};
std::optional<std::chrono::milliseconds> targetAverageLatency_{};
std::optional<std::chrono::milliseconds> targetPeakLatency_{};
UptimeClock::time_point lastUpdate_;
std::uint64_t mCounts;
int mLatencyEvents;
std::chrono::milliseconds mLatencyMSAvg;
std::chrono::milliseconds mLatencyMSPeak;
std::chrono::milliseconds mTargetLatencyAvg;
std::chrono::milliseconds mTargetLatencyPk;
UptimeClock::time_point mLastUpdate;
beast::Journal const j_;
LoadSampler eventCallback_;
};
} // namespace ripple
+4 -4
View File
@@ -367,15 +367,15 @@ Config::setup(
strXdgDataHome = strHome + "/.local/share";
}
CONFIG_DIR = strXdgConfigHome + "/" + std::string(systemName);
CONFIG_DIR = strXdgConfigHome + "/" + systemName();
CONFIG_FILE = CONFIG_DIR / strConfFile;
dataDir = strXdgDataHome + "/" + std::string(systemName);
dataDir = strXdgDataHome + "/" + systemName();
if (!boost::filesystem::exists(CONFIG_FILE))
{
CONFIG_DIR = "/etc/opt/" + std::string(systemName);
CONFIG_DIR = "/etc/opt/" + systemName();
CONFIG_FILE = CONFIG_DIR / strConfFile;
dataDir = "/var/opt/" + std::string(systemName);
dataDir = "/var/opt/" + systemName();
}
}
}
+73 -32
View File
@@ -23,60 +23,101 @@
namespace ripple {
Job::Job(
JobType type,
std::string name,
std::uint64_t index,
std::reference_wrapper<LoadSampler const> sample,
std::function<void()> job)
: type_(type)
, index_(index)
, queued_(clock_type::now())
, work_(std::move(job))
, loadEvent_(sample, std::move(name), false)
Job::Job() : mType(jtINVALID), mJobIndex(0)
{
}
Job::Job(Job&& other)
: type_(other.type_)
, index_(other.index_)
, queued_(other.queued_)
, work_(std::move(other.work_))
, loadEvent_(std::move(other.loadEvent_))
Job::Job(JobType type, std::uint64_t index) : mType(type), mJobIndex(index)
{
}
Job::Job(
JobType type,
std::string const& name,
std::uint64_t index,
LoadMonitor& lm,
std::function<void()> const& job)
: mType(type)
, mJobIndex(index)
, mJob(job)
, mName(name)
, m_queue_time(clock_type::now())
{
m_loadEvent = std::make_shared<LoadEvent>(std::ref(lm), name, false);
}
JobType
Job::getType() const
{
return type_;
return mType;
}
Job::clock_type::time_point const&
Job::queue_time() const
{
return queued_;
}
std::string const&
Job::description() const
{
return loadEvent_.name();
return m_queue_time;
}
void
Job::execute()
Job::doJob()
{
beast::setCurrentThreadName("Job: " + loadEvent_.name());
beast::setCurrentThreadName("doJob: " + mName);
m_loadEvent->start();
m_loadEvent->setName(mName);
loadEvent_.start();
work_();
mJob();
// Destroy the lambda, otherwise we won't include
// its duration in the time measurement
work_ = nullptr;
mJob = nullptr;
}
bool
Job::operator>(const Job& j) const
{
if (mType < j.mType)
return true;
if (mType > j.mType)
return false;
return mJobIndex > j.mJobIndex;
}
bool
Job::operator>=(const Job& j) const
{
if (mType < j.mType)
return true;
if (mType > j.mType)
return false;
return mJobIndex >= j.mJobIndex;
}
bool
Job::operator<(const Job& j) const
{
if (mType < j.mType)
return false;
if (mType > j.mType)
return true;
return mJobIndex < j.mJobIndex;
}
bool
Job::operator<=(const Job& j) const
{
if (mType < j.mType)
return false;
if (mType > j.mType)
return true;
return mJobIndex <= j.mJobIndex;
}
} // namespace ripple
+273 -183
View File
@@ -20,10 +20,7 @@
#include <xrpld/core/JobQueue.h>
#include <xrpld/perflog/PerfLog.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <mutex>
#include <utility>
namespace ripple {
@@ -34,22 +31,36 @@ JobQueue::JobQueue(
Logs& logs,
perf::PerfLog& perfLog)
: m_journal(journal)
, data_([&]<std::size_t... I>(std::index_sequence<I...>) {
return std::array<Data, sizeof...(I)>{
Data{jobTypes[I], collector, logs}...};
}(std::make_index_sequence<jobTypes.size()>()))
, m_workers(*this, "JobQueue", threadCount)
, m_lastJob(0)
, m_invalidJobData(JobTypes::instance().getInvalid(), collector, logs)
, m_processCount(0)
, m_workers(*this, &perfLog, "JobQueue", threadCount)
, perfLog_(perfLog)
, m_collector(collector)
{
JLOG(m_journal.info()) << "Using " << threadCount << " threads";
JLOG(m_journal.info()) << "Using " << threadCount << " threads";
// This is important to do before dispatching any jobs
// so that the logger knows the number of threads.
perfLog_.resizeJobs(threadCount);
hook = m_collector->make_hook([this] { collect(); });
hook = m_collector->make_hook(std::bind(&JobQueue::collect, this));
job_count = m_collector->make_gauge("job_count");
{
std::lock_guard lock(m_mutex);
for (auto const& x : JobTypes::instance())
{
JobTypeInfo const& jt = x.second;
// And create dynamic information for all jobs
auto const result(m_jobData.emplace(
std::piecewise_construct,
std::forward_as_tuple(jt.type()),
std::forward_as_tuple(jt, m_collector, logs)));
XRPL_ASSERT(
result.second == true,
"ripple::JobQueue::JobQueue : jobs added");
(void)result.second;
}
}
}
JobQueue::~JobQueue()
@@ -63,42 +74,65 @@ JobQueue::collect()
{
std::lock_guard lock(m_mutex);
job_count = m_jobSet.size();
activeThreadsGauge_ = activeThreads_;
}
bool
JobQueue::addRefCountedJob(
JobType type,
std::string const& name,
JobFunction func)
JobFunction const& func)
{
JLOG(m_journal.debug()) << "Adding job '" << name << "' (" << type << ")";
XRPL_ASSERT(
type != jtINVALID,
"ripple::JobQueue::addRefCountedJob : valid input job type");
auto iter(m_jobData.find(type));
XRPL_ASSERT(
iter != m_jobData.end(),
"ripple::JobQueue::addRefCountedJob : job type found in jobs");
if (iter == m_jobData.end())
return false;
JLOG(m_journal.debug())
<< __func__ << " : Adding job : " << name << " : " << type;
JobTypeData& data(iter->second);
// FIXME: Workaround incorrect client shutdown ordering
// do not add jobs to a queue with no threads
XRPL_ASSERT(
(type >= jtCLIENT && type <= jtCLIENT_WEBSOCKET) ||
m_workers.count() > 0,
m_workers.getNumberOfThreads() > 0,
"ripple::JobQueue::addRefCountedJob : threads available or job "
"requires no threads");
std::lock_guard lock(m_mutex);
{
std::lock_guard lock(m_mutex);
auto result =
m_jobSet.emplace(type, name, ++m_lastJob, data.load(), func);
auto const& job = *result.first;
auto& d = data_[type];
JobType const type(job.getType());
XRPL_ASSERT(
type != jtINVALID,
"ripple::JobQueue::addRefCountedJob : has valid job type");
XRPL_ASSERT(
m_jobSet.find(job) != m_jobSet.end(),
"ripple::JobQueue::addRefCountedJob : job found");
perfLog_.jobQueue(type);
auto r = m_jobSet.emplace(
type, name, ++m_lastJob, d.load.sample(), std::move(func));
assert(r.second && m_jobSet.find(*r.first) != m_jobSet.end());
perfLog_.jobQueue(type);
if (d.waiting + d.running < jobTypes[type].limit)
m_workers.addTask();
else
++d.deferred;
++d.waiting;
JobTypeData& data(getJobTypeData(type));
if (data.waiting + data.running < getJobLimit(type))
{
m_workers.addTask();
}
else
{
// defer the task until we go below the limit
++data.deferred;
}
++data.waiting;
}
return true;
}
@@ -106,100 +140,125 @@ int
JobQueue::getJobCount(JobType t) const
{
std::lock_guard lock(m_mutex);
return data_[t].waiting;
JobDataMap::const_iterator c = m_jobData.find(t);
return (c == m_jobData.end()) ? 0 : c->second.waiting;
}
int
JobQueue::getJobCountTotal(JobType t) const
{
std::lock_guard lock(m_mutex);
auto const& d = data_[t];
return d.waiting + d.running;
JobDataMap::const_iterator c = m_jobData.find(t);
return (c == m_jobData.end()) ? 0 : (c->second.waiting + c->second.running);
}
int
JobQueue::getJobCountGE(JobType t) const
{
// return the number of jobs at this priority level or greater
int ret = 0;
std::lock_guard lock(m_mutex);
for (std::size_t i = 0; i < data_.size(); ++i)
for (auto const& x : m_jobData)
{
if (static_cast<JobType>(i) >= t)
ret += data_[i].waiting;
if (x.first >= t)
ret += x.second.waiting;
}
return ret;
}
LoadEvent
JobQueue::makeLoadEvent(JobType t, std::string name)
std::unique_ptr<LoadEvent>
JobQueue::makeLoadEvent(JobType t, std::string const& name)
{
return {data_[t].load.sample(), std::move(name), true};
JobDataMap::iterator iter(m_jobData.find(t));
XRPL_ASSERT(
iter != m_jobData.end(),
"ripple::JobQueue::makeLoadEvent : valid job type input");
if (iter == m_jobData.end())
return {};
return std::make_unique<LoadEvent>(iter->second.load(), name, true);
}
void
JobQueue::addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed)
{
if (!isStopped()) [[likely]]
data_[t].load.addSamples(count, elapsed);
if (isStopped())
LogicError("JobQueue::addLoadEvents() called after JobQueue stopped");
JobDataMap::iterator iter(m_jobData.find(t));
XRPL_ASSERT(
iter != m_jobData.end(),
"ripple::JobQueue::addLoadEvents : valid job type input");
iter->second.load().addSamples(count, elapsed);
}
bool
JobQueue::isOverloaded()
{
return std::any_of(
data_.begin(), data_.end(), [](auto& d) { return d.load.isOver(); });
return std::any_of(m_jobData.begin(), m_jobData.end(), [](auto& entry) {
return entry.second.load().isOver();
});
}
Json::Value
JobQueue::getJson(int)
JobQueue::getJson(int c)
{
using namespace std::chrono_literals;
Json::Value ret(Json::objectValue);
ret["threads"] = m_workers.count();
ret["coro.suspended"] = suspendedCoroutines_.load();
ret["coro.total"] = totalCoroutines_.load();
ret["threads"] = m_workers.getNumberOfThreads();
Json::Value priorities = Json::arrayValue;
std::lock_guard lock(m_mutex);
for (std::size_t i = 0; i < data_.size(); ++i)
for (auto& x : m_jobData)
{
auto const type = static_cast<JobType>(i);
XRPL_ASSERT(
x.first != jtINVALID, "ripple::JobQueue::getJson : valid job type");
if (type == jtGENERIC)
if (x.first == jtGENERIC)
continue;
auto& d = data_[i];
auto const s = d.load.getStats();
JobTypeData& data(x.second);
if (s.count || d.waiting || d.running || (s.peakLatency != 0ms))
LoadMonitor::Stats stats(data.stats());
int waiting(data.waiting);
int running(data.running);
if ((stats.count != 0) || (waiting != 0) ||
(stats.latencyPeak != 0ms) || (running != 0))
{
Json::Value& pri = priorities.append(Json::objectValue);
pri["job_type"] = std::string(jobTypes[i].name);
pri["job_type"] = data.name();
if (s.isOverloaded)
if (stats.isOverloaded)
pri["over_target"] = true;
if (d.waiting != 0)
pri["waiting"] = d.waiting;
if (waiting != 0)
pri["waiting"] = waiting;
if (s.count != 0)
pri["per_second"] = static_cast<int>(s.count);
if (stats.count != 0)
pri["per_second"] = static_cast<int>(stats.count);
if (s.peakLatency != 0ms)
pri["peak_time"] = static_cast<int>(s.peakLatency.count());
if (stats.latencyPeak != 0ms)
pri["peak_time"] = static_cast<int>(stats.latencyPeak.count());
if (s.averageLatency != 0ms)
pri["avg_time"] = static_cast<int>(s.averageLatency.count());
if (stats.latencyAvg != 0ms)
pri["avg_time"] = static_cast<int>(stats.latencyAvg.count());
if (d.running != 0)
pri["in_progress"] = d.running;
if (running != 0)
pri["in_progress"] = running;
}
}
@@ -212,148 +271,179 @@ void
JobQueue::rendezvous()
{
std::unique_lock<std::mutex> lock(m_mutex);
cv_.wait(lock, [this] { return activeThreads_ == 0 && m_jobSet.empty(); });
cv_.wait(lock, [this] { return m_processCount == 0 && m_jobSet.empty(); });
}
JobTypeData&
JobQueue::getJobTypeData(JobType type)
{
JobDataMap::iterator c(m_jobData.find(type));
XRPL_ASSERT(
c != m_jobData.end(),
"ripple::JobQueue::getJobTypeData : valid job type input");
// NIKB: This is ugly and I hate it. We must remove jtINVALID completely
// and use something sane.
if (c == m_jobData.end())
return m_invalidJobData;
return c->second;
}
void
JobQueue::stop()
{
if (state expected = state::running; !state_.compare_exchange_strong(
expected, state::stopping, std::memory_order_acq_rel))
stopping_ = true;
using namespace std::chrono_literals;
jobCounter_.join("JobQueue", 1s, m_journal);
{
while (state_.load(std::memory_order_acquire) != state::stopped)
std::this_thread::yield();
return;
// After the JobCounter is joined, all jobs have finished executing
// (i.e. returned from `Job::doJob`) and no more are being accepted,
// but there may still be some threads between the return of
// `Job::doJob` and the return of `JobQueue::processTask`. That is why
// we must wait on the condition variable to make these assertions.
std::unique_lock<std::mutex> lock(m_mutex);
cv_.wait(
lock, [this] { return m_processCount == 0 && m_jobSet.empty(); });
XRPL_ASSERT(
m_processCount == 0,
"ripple::JobQueue::stop : all processes completed");
XRPL_ASSERT(
m_jobSet.empty(), "ripple::JobQueue::stop : all jobs completed");
XRPL_ASSERT(
nSuspend_ == 0, "ripple::JobQueue::stop : no coros suspended");
stopped_ = true;
}
jobCounter_.join("JobQueue", std::chrono::seconds(1), m_journal);
// All jobs have finished executing (i.e. returned from `Job::doJob`) and
// no more are being accepted, but there may still be some threads between
// the return of `Job::doJob` and the return of `JobQueue::processTask`.
// That is why we must wait on the condition variable to make these
// assertions.
std::unique_lock lock(m_mutex);
cv_.wait(lock, [this] { return activeThreads_ == 0 && m_jobSet.empty(); });
XRPL_ASSERT(
activeThreads_ == 0,
"ripple::JobQueue::stop : all processes completed");
XRPL_ASSERT(
m_jobSet.empty(), "ripple::JobQueue::stop : all jobs completed");
XRPL_ASSERT(
suspendedCoroutines_ == 0,
"ripple::JobQueue::stop : no coros suspended");
state_.store(state::stopped, std::memory_order_release);
}
void
JobQueue::uncaughtException(unsigned int instance, std::exception_ptr eptr)
bool
JobQueue::isStopped() const
{
try
{
if (eptr)
std::rethrow_exception(eptr);
LogicError(
beast::getCurrentThreadName() +
": Uncaught exception handler invoked with no exception_ptr");
}
catch (std::exception const& e)
{
LogicError(
beast::getCurrentThreadName() +
": Exception caught during task processing: " + e.what());
}
catch (...)
{
LogicError(
beast::getCurrentThreadName() +
": Unknown exception caught during task processing");
}
return stopped_;
}
void
JobQueue::processTask(unsigned int instance)
JobQueue::getNextJob(Job& job)
{
XRPL_ASSERT(
!m_jobSet.empty(), "ripple::JobQueue::getNextJob : non-empty jobs");
std::set<Job>::const_iterator iter;
for (iter = m_jobSet.begin(); iter != m_jobSet.end(); ++iter)
{
JobType const type = iter->getType();
XRPL_ASSERT(
type != jtINVALID, "ripple::JobQueue::getNextJob : valid job type");
JobTypeData& data(getJobTypeData(type));
XRPL_ASSERT(
data.running <= getJobLimit(type),
"ripple::JobQueue::getNextJob : maximum jobs running");
// Run this job if we're running below the limit.
if (data.running < getJobLimit(data.type()))
{
XRPL_ASSERT(
data.waiting > 0,
"ripple::JobQueue::getNextJob : positive data waiting");
--data.waiting;
++data.running;
break;
}
}
XRPL_ASSERT(
iter != m_jobSet.end(),
"ripple::JobQueue::getNextJob : found next job");
job = *iter;
m_jobSet.erase(iter);
}
void
JobQueue::finishJob(JobType type)
{
XRPL_ASSERT(
type != jtINVALID,
"ripple::JobQueue::finishJob : valid input job type");
JobTypeData& data = getJobTypeData(type);
// Queue a deferred task if possible
if (data.deferred > 0)
{
XRPL_ASSERT(
data.running + data.waiting >= getJobLimit(type),
"ripple::JobQueue::finishJob : job limit");
--data.deferred;
m_workers.addTask();
}
--data.running;
}
void
JobQueue::processTask(int instance)
{
JobType type;
{
Job job = [this] {
std::lock_guard lock(m_mutex);
assert(!m_jobSet.empty());
for (auto iter = m_jobSet.begin(); iter != m_jobSet.end(); ++iter)
{
JobType const t = iter->getType();
auto& d = data_[t];
assert(d.running <= jobTypes[t].limit);
if (d.running < jobTypes[t].limit)
{
assert(d.waiting > 0);
--d.waiting;
++d.running;
++activeThreads_;
return std::move(m_jobSet.extract(iter).value());
}
}
LogicError("Attempt to get a job when none are available");
}();
type = job.getType();
using namespace std::chrono;
auto const start_time = Job::clock_type::now();
auto const q_time = ceil<microseconds>(start_time - job.queue_time());
perfLog_.jobStart(type, q_time, start_time, instance);
JLOG(m_journal.trace())
<< "Starting: " << jobTypes[type].name
<< " (q_time=" << q_time.count() << " microseconds)";
job.execute();
auto const x_time =
ceil<microseconds>(Job::clock_type::now() - start_time);
JLOG(m_journal.trace())
<< "Finished: " << jobTypes[type].name
<< " (x_time=" << x_time.count() << " microseconds)";
if (x_time >= 10ms || q_time >= 10ms)
Job::clock_type::time_point const start_time(Job::clock_type::now());
{
data_[type].dequeue.notify(q_time);
data_[type].execute.notify(x_time);
Job job;
{
std::lock_guard lock(m_mutex);
getNextJob(job);
++m_processCount;
}
type = job.getType();
JobTypeData& data(getJobTypeData(type));
JLOG(m_journal.trace()) << "Doing " << data.name() << "job";
// The amount of time that the job was in the queue
auto const q_time =
ceil<microseconds>(start_time - job.queue_time());
perfLog_.jobStart(type, q_time, start_time, instance);
job.doJob();
// The amount of time it took to execute the job
auto const x_time =
ceil<microseconds>(Job::clock_type::now() - start_time);
if (x_time >= 10ms || q_time >= 10ms)
{
getJobTypeData(type).dequeue.notify(q_time);
getJobTypeData(type).execute.notify(x_time);
}
perfLog_.jobFinish(type, x_time, instance);
}
perfLog_.jobFinish(type, x_time, instance);
// Note that the Job object gets destroyed at this point.
}
std::lock_guard lock(m_mutex);
auto& d = data_[type];
// Queue a deferred task if possible. This doesn't mean the task will
// run next; only that one is now runnable.
if (d.deferred > 0)
{
assert(d.running + d.waiting >= jobTypes[type].limit);
--d.deferred;
m_workers.addTask();
std::lock_guard lock(m_mutex);
// Job should be destroyed before stopping
// otherwise destructors with side effects can access
// parent objects that are already destroyed.
finishJob(type);
if (--m_processCount == 0 && m_jobSet.empty())
cv_.notify_all();
}
--d.running;
// Note that when Job::~Job is called, the last reference
// to the associated LoadEvent object (in the Job) may be destroyed.
}
if (--activeThreads_ == 0 && m_jobSet.empty())
cv_.notify_all();
int
JobQueue::getJobLimit(JobType type)
{
JobTypeInfo const& j(JobTypes::instance().get(type));
XRPL_ASSERT(
j.type() != jtINVALID,
"ripple::JobQueue::getJobLimit : valid job type");
return j.limit();
}
} // namespace ripple
+27 -50
View File
@@ -18,63 +18,25 @@
//==============================================================================
#include <xrpld/core/LoadEvent.h>
#include <xrpld/core/LoadMonitor.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <iomanip>
namespace ripple {
LoadEvent::LoadEvent(
std::reference_wrapper<LoadSampler const> callback,
std::string name,
LoadMonitor& monitor,
std::string const& name,
bool shouldStart)
: name_(std::move(name))
, callback_(callback)
, mark_(std::chrono::steady_clock::now())
: monitor_(monitor)
, running_(shouldStart)
, name_(name)
, mark_{std::chrono::steady_clock::now()}
, timeWaiting_{}
, timeRunning_{}
, running_(shouldStart)
, neutered_(false)
{
}
LoadEvent::LoadEvent(LoadEvent&& other)
: name_(std::move(other.name_))
, callback_(other.callback_)
, mark_(other.mark_)
, timeWaiting_(other.timeWaiting_)
, timeRunning_(other.timeRunning_)
, running_(other.running_)
, neutered_(other.neutered_)
{
other.running_ = false;
other.neutered_ = true;
}
LoadEvent&
LoadEvent::operator=(LoadEvent&& other)
{
if (this != &other)
{
// If we are currently measuring something, report it before we
// take over the other event, so that the sample isn't lost.
if (running_)
stop();
name_ = std::move(other.name_);
callback_ = other.callback_;
running_ = other.running_;
mark_ = other.mark_;
timeWaiting_ = other.timeWaiting_;
timeRunning_ = other.timeRunning_;
neutered_ = other.neutered_;
// Leave the moved-from object in a sane but "neutered" state.
other.running_ = false;
other.neutered_ = true;
}
return *this;
}
LoadEvent::~LoadEvent()
{
if (running_)
@@ -87,11 +49,27 @@ LoadEvent::name() const
return name_;
}
std::chrono::steady_clock::duration
LoadEvent::waitTime() const
{
return timeWaiting_;
}
std::chrono::steady_clock::duration
LoadEvent::runTime() const
{
return timeRunning_;
}
void
LoadEvent::setName(std::string const& name)
{
name_ = name;
}
void
LoadEvent::start()
{
XRPL_ASSERT(!neutered_, "ripple::LoadEvent::start : is neutered");
auto const now = std::chrono::steady_clock::now();
// If we had already called start, this call will
@@ -113,8 +91,7 @@ LoadEvent::stop()
mark_ = now;
running_ = false;
if (!neutered_)
callback_(name_.c_str(), timeRunning_, timeWaiting_);
monitor_.addLoadSample(*this);
}
} // namespace ripple
+127 -86
View File
@@ -23,95 +23,104 @@
namespace ripple {
LoadMonitor::LoadMonitor(
std::optional<std::chrono::milliseconds> targetAverageLatency,
std::optional<std::chrono::milliseconds> targetPeakLatency,
beast::Journal j)
: targetAverageLatency_([&targetAverageLatency]() {
using namespace std::chrono_literals;
if (targetAverageLatency.has_value() && targetAverageLatency == 0ms)
targetAverageLatency.reset();
return targetAverageLatency;
}())
, targetPeakLatency_([&targetPeakLatency]() {
using namespace std::chrono_literals;
if (targetPeakLatency.has_value() && targetPeakLatency == 0ms)
targetPeakLatency.reset();
return targetPeakLatency;
}())
, lastUpdate_(UptimeClock::now())
, j_(j)
, eventCallback_([this](
char const* name,
std::chrono::steady_clock::duration run,
std::chrono::steady_clock::duration wait) {
using namespace std::chrono;
/*
auto const latency = [total = run + wait] {
// Don't include "jitter" as part of the latency
if (total < 5ms)
return 0ms;
TODO
----
return round<milliseconds>(total);
}();
- Use Journal for logging
if (latency > targetPeakLatency_.value_or(1s))
{
// Log at a higher severity if the latency is much greater than
// the peak latency we want to target.
auto mj = (latency > 2 * targetPeakLatency_.value_or(latency))
? j_.warn()
: j_.info();
*/
JLOG(mj) << "Job: " << name
<< " run: " << round<milliseconds>(run).count() << "ms"
<< " wait: " << round<milliseconds>(wait).count() << "ms";
}
//------------------------------------------------------------------------------
addSamples(1, latency);
})
LoadMonitor::Stats::Stats()
: count(0), latencyAvg(0), latencyPeak(0), isOverloaded(false)
{
}
//------------------------------------------------------------------------------
LoadMonitor::LoadMonitor(beast::Journal j)
: mCounts(0)
, mLatencyEvents(0)
, mLatencyMSAvg(0)
, mLatencyMSPeak(0)
, mTargetLatencyAvg(0)
, mTargetLatencyPk(0)
, mLastUpdate(UptimeClock::now())
, j_(j)
{
}
// VFALCO NOTE WHY do we need "the mutex?" This dependence on
// a hidden global, especially a synchronization primitive,
// is a flawed design.
// It's not clear exactly which data needs to be protected.
//
// call with the mutex
void
LoadMonitor::update()
{
using namespace std::chrono_literals;
auto now = UptimeClock::now();
if (now == mLastUpdate) // current
return;
if (auto const now = UptimeClock::now(); now != lastUpdate_)
// VFALCO TODO Why 8?
if ((now < mLastUpdate) || (now > (mLastUpdate + 8s)))
{
// VFALCO TODO Why 8?
if ((now < lastUpdate_) || (now > (lastUpdate_ + 8s)))
{
// way out of date
samples_ = 0;
averageLatency_ = 0ms;
peakLatency_ = 0ms;
lastUpdate_ = now;
return;
}
// do exponential decay
/*
David:
"Imagine if you add 10 to something every second. And you
also reduce it by 1/4 every second. It will "idle" at 40,
correponding to 10 counts per second."
*/
do
{
lastUpdate_ += 1s;
samples_ -= ((samples_ + 3) / 4);
averageLatency_ -= (averageLatency_ / 4);
peakLatency_ -= (peakLatency_ / 4);
} while (lastUpdate_ < now);
// way out of date
mCounts = 0;
mLatencyEvents = 0;
mLatencyMSAvg = 0ms;
mLatencyMSPeak = 0ms;
mLastUpdate = now;
return;
}
// do exponential decay
/*
David:
"Imagine if you add 10 to something every second. And you
also reduce it by 1/4 every second. It will "idle" at 40,
correponding to 10 counts per second."
*/
do
{
mLastUpdate += 1s;
mCounts -= ((mCounts + 3) / 4);
mLatencyEvents -= ((mLatencyEvents + 3) / 4);
mLatencyMSAvg -= (mLatencyMSAvg / 4);
mLatencyMSPeak -= (mLatencyMSPeak / 4);
} while (mLastUpdate < now);
}
void
LoadMonitor::addLoadSample(LoadEvent const& s)
{
using namespace std::chrono;
auto const total = s.runTime() + s.waitTime();
// Don't include "jitter" as part of the latency
auto const latency = total < 2ms ? 0ms : round<milliseconds>(total);
if (latency > 500ms)
{
auto mj = (latency > 1s) ? j_.warn() : j_.info();
JLOG(mj) << "Job: " << s.name()
<< " run: " << round<milliseconds>(s.runTime()).count() << "ms"
<< " wait: " << round<milliseconds>(s.waitTime()).count()
<< "ms";
}
addSamples(1, latency);
}
/* Add multiple samples
@param count The number of samples to add
@param latency The total number of milliseconds
@param latencyMS The total number of milliseconds
*/
void
LoadMonitor::addSamples(int count, std::chrono::milliseconds latency)
@@ -119,45 +128,77 @@ LoadMonitor::addSamples(int count, std::chrono::milliseconds latency)
std::lock_guard sl(mutex_);
update();
samples_ += count;
averageLatency_ += latency;
peakLatency_ += latency;
mCounts += count;
mLatencyEvents += count;
mLatencyMSAvg += latency;
mLatencyMSPeak += latency;
auto const latencyPeak = samples_ * latency * 4 / count;
auto const latencyPeak = mLatencyEvents * latency * 4 / count;
if (peakLatency_ < latencyPeak)
peakLatency_ = latencyPeak;
if (mLatencyMSPeak < latencyPeak)
mLatencyMSPeak = latencyPeak;
}
void
LoadMonitor::setTargetLatency(
std::chrono::milliseconds avg,
std::chrono::milliseconds pk)
{
mTargetLatencyAvg = avg;
mTargetLatencyPk = pk;
}
bool
LoadMonitor::isOverTarget(
std::chrono::milliseconds avg,
std::chrono::milliseconds peak)
{
using namespace std::chrono_literals;
return (mTargetLatencyPk > 0ms && (peak > mTargetLatencyPk)) ||
(mTargetLatencyAvg > 0ms && (avg > mTargetLatencyAvg));
}
bool
LoadMonitor::isOver()
{
return getStats().isOverloaded;
std::lock_guard sl(mutex_);
update();
if (mLatencyEvents == 0)
return 0;
return isOverTarget(
mLatencyMSAvg / (mLatencyEvents * 4),
mLatencyMSPeak / (mLatencyEvents * 4));
}
LoadMonitor::Stats
LoadMonitor::getStats()
{
using namespace std::chrono_literals;
Stats stats;
std::lock_guard sl(mutex_);
update();
Stats s;
s.count = samples_ / 4;
stats.count = mCounts / 4;
if (samples_ != 0)
if (mLatencyEvents == 0)
{
s.averageLatency = averageLatency_ / (samples_ * 4);
s.peakLatency = peakLatency_ / (samples_ * 4);
stats.latencyAvg = 0ms;
stats.latencyPeak = 0ms;
}
else
{
stats.latencyAvg = mLatencyMSAvg / (mLatencyEvents * 4);
stats.latencyPeak = mLatencyMSPeak / (mLatencyEvents * 4);
}
s.isOverloaded =
(s.averageLatency > targetAverageLatency_.value_or(s.averageLatency)) ||
(s.peakLatency > targetPeakLatency_.value_or(s.peakLatency));
stats.isOverloaded = isOverTarget(stats.latencyAvg, stats.latencyPeak);
return s;
return stats;
}
} // namespace ripple
+229 -88
View File
@@ -18,124 +18,265 @@
//==============================================================================
#include <xrpld/core/detail/Workers.h>
#include <xrpld/perflog/PerfLog.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <chrono>
#include <thread>
namespace ripple {
Workers::Workers(
Callback& callback,
std::string const& name,
unsigned int count)
: callback_(callback)
perf::PerfLog* perfLog,
std::string const& threadNames,
int numberOfThreads)
: m_callback(callback)
, perfLog_(perfLog)
, m_threadNames(threadNames)
, m_allPaused(true)
, m_semaphore(0)
, m_numberOfThreads(0)
, m_activeCount(0)
, m_pauseCount(0)
, m_runningTaskCount(0)
{
assert(count != 0);
// It is important that head and tail are equal but non-zero on
// startup: we use zero as a special value to indicate that the
// thread must terminate; if zero was a 'legal' value then we'd
// encounter the ABA problem with std::atomic::wait.
assert(head_ == tail_ && head_ != 0);
while (count--)
{
// We need to increment this outside of the thread, to avoid a
// subtle (if unlikely) race condition during shutdown.
++threads_;
std::thread th(
[this, name](unsigned int instance) {
auto const n = name + ":" + std::to_string(instance);
while (head_)
{
auto t = tail_.load();
auto h = head_.load();
assert((h == 0) || (h >= t));
if (h == t)
{
// Only pay for the thread rename when we are
// actually about to go to sleep.
beast::setCurrentThreadName(n + " [zZz]");
while (h == t)
{
// This will block until we're notified.
head_.wait(h);
t = tail_.load();
h = head_.load();
}
}
// As long as we aren't stopping and there is a task
// that's waiting, we try to do work.
if (h > t && tail_.compare_exchange_strong(t, t + 1))
{
// Restore the name, in case the previous callback
// had changed it.
beast::setCurrentThreadName(n);
// Exceptions should never bubble up to here but
// just in case, if one does catch it.
try
{
callback_.processTask(instance);
}
catch (...)
{
callback_.uncaughtException(
instance, std::current_exception());
}
}
}
// Track number of threads
--threads_;
},
count);
// We detach from this thread; it will continue to run in the
// background until we instruct it to stop.
th.detach();
}
setNumberOfThreads(numberOfThreads);
}
Workers::~Workers()
{
stop();
assert(head_.load() == 0 && threads_.load() == 0);
deleteWorkers(m_everyone);
}
int
Workers::getNumberOfThreads() const noexcept
{
return m_numberOfThreads;
}
// VFALCO NOTE if this function is called quickly to reduce then
// increase the number of threads, it could result in
// more paused threads being created than expected.
//
void
Workers::setNumberOfThreads(int numberOfThreads)
{
static int instance{0};
if (m_numberOfThreads == numberOfThreads)
return;
if (perfLog_)
perfLog_->resizeJobs(numberOfThreads);
if (numberOfThreads > m_numberOfThreads)
{
// Increasing the number of working threads
int const amount = numberOfThreads - m_numberOfThreads;
for (int i = 0; i < amount; ++i)
{
// See if we can reuse a paused worker
Worker* worker = m_paused.pop_front();
if (worker != nullptr)
{
// If we got here then the worker thread is at [1]
// This will unblock their call to wait()
//
worker->notify();
}
else
{
worker = new Worker(*this, m_threadNames, instance++);
m_everyone.push_front(worker);
}
}
}
else
{
// Decreasing the number of working threads
int const amount = m_numberOfThreads - numberOfThreads;
for (int i = 0; i < amount; ++i)
{
++m_pauseCount;
// Pausing a thread counts as one "internal task"
m_semaphore.notify();
}
}
m_numberOfThreads = numberOfThreads;
}
void
Workers::stop()
{
head_.store(0);
head_.notify_all();
setNumberOfThreads(0);
while (threads_.load() != 0)
std::this_thread::yield();
std::unique_lock<std::mutex> lk{m_mut};
m_cv.wait(lk, [this] { return m_allPaused; });
lk.unlock();
XRPL_ASSERT(
numberOfCurrentlyRunningTasks() == 0,
"ripple::Workers::stop : zero running tasks");
}
void
Workers::addTask()
{
auto h = head_.load();
m_semaphore.notify();
}
while (h != 0)
int
Workers::numberOfCurrentlyRunningTasks() const noexcept
{
return m_runningTaskCount.load();
}
void
Workers::deleteWorkers(beast::LockFreeStack<Worker>& stack)
{
for (;;)
{
if (head_.compare_exchange_strong(h, h + 1))
Worker* const worker = stack.pop_front();
if (worker != nullptr)
{
head_.notify_one();
h = 0;
// This call blocks until the thread orderly exits
delete worker;
}
else
{
break;
}
}
}
//------------------------------------------------------------------------------
Workers::Worker::Worker(
Workers& workers,
std::string const& threadName,
int const instance)
: m_workers{workers}
, threadName_{threadName}
, instance_{instance}
, wakeCount_{0}
, shouldExit_{false}
{
thread_ = std::thread{&Workers::Worker::run, this};
}
Workers::Worker::~Worker()
{
{
std::lock_guard lock{mutex_};
++wakeCount_;
shouldExit_ = true;
}
wakeup_.notify_one();
thread_.join();
}
void
Workers::Worker::notify()
{
std::lock_guard lock{mutex_};
++wakeCount_;
wakeup_.notify_one();
}
void
Workers::Worker::run()
{
bool shouldExit = true;
do
{
// Increment the count of active workers, and if
// we are the first one then reset the "all paused" event
//
if (++m_workers.m_activeCount == 1)
{
std::lock_guard lk{m_workers.m_mut};
m_workers.m_allPaused = false;
}
for (;;)
{
// Put the name back in case the callback changed it
beast::setCurrentThreadName(threadName_);
// Acquire a task or "internal task."
//
m_workers.m_semaphore.wait();
// See if there's a pause request. This
// counts as an "internal task."
//
int pauseCount = m_workers.m_pauseCount.load();
if (pauseCount > 0)
{
// Try to decrement
pauseCount = --m_workers.m_pauseCount;
if (pauseCount >= 0)
{
// We got paused
break;
}
else
{
// Undo our decrement
++m_workers.m_pauseCount;
}
}
// We couldn't pause so we must have gotten
// unblocked in order to process a task.
//
++m_workers.m_runningTaskCount;
m_workers.m_callback.processTask(instance_);
--m_workers.m_runningTaskCount;
}
// Any worker that goes into the paused list must
// guarantee that it will eventually block on its
// event object.
//
m_workers.m_paused.push_front(this);
// Decrement the count of active workers, and if we
// are the last one then signal the "all paused" event.
//
if (--m_workers.m_activeCount == 0)
{
std::lock_guard lk{m_workers.m_mut};
m_workers.m_allPaused = true;
m_workers.m_cv.notify_all();
}
// Set inactive thread name.
beast::setCurrentThreadName("(" + threadName_ + ")");
// [1] We will be here when the paused list is popped
//
// We block on our condition_variable, wakeup_, a requirement of being
// put into the paused list.
//
// wakeup_ will get signaled by either Worker::notify() or ~Worker.
{
std::unique_lock<std::mutex> lock{mutex_};
wakeup_.wait(lock, [this] { return this->wakeCount_ > 0; });
shouldExit = shouldExit_;
--wakeCount_;
}
} while (!shouldExit);
}
} // namespace ripple
+142 -73
View File
@@ -20,33 +20,61 @@
#ifndef RIPPLE_CORE_WORKERS_H_INCLUDED
#define RIPPLE_CORE_WORKERS_H_INCLUDED
#include <xrpld/core/detail/semaphore.h>
#include <xrpl/beast/core/LockFreeStack.h>
#include <atomic>
#include <condition_variable>
#include <exception>
#include <mutex>
#include <string>
#include <thread>
namespace ripple {
/** A simple thread pool.
namespace perf {
class PerfLog;
}
This class is a simple, fixed-sized thread pool. The pool only tracks
the number of outstanding tasks, and dispatches work. When the pool detects
that there is no work to be done, it puts threads to sleep.
The pool does not decide which task to run; that is handled by a callback
that the pool invokes. This makes it possible to implement the dispatch
strategy (e.g. FIFO or priority queues) that makes sense without requiring
changes in the thread pool itself.
Threads will continue to run (or sleep) until the thread pool is stopped
which can happen via an explicit call to the stop method, or automatically
by the pool's destructor.
Note that servicing existing tasks take priority over stopping and stopping
takes priority over servicing new tasks; this means that once a stop request
has been made, the worker threads will complete their current tasks (if any)
and then exit, potentially leaving work unfinished.
/**
* `Workers` is effectively a thread pool. The constructor takes a "callback"
* that has a `void processTask(int instance)` method, and a number of
* workers. It creates that many `Worker`s and then waits for calls to
* `Workers::addTask()`. It holds a semaphore that counts the number of
* pending "tasks", and a condition variable for the event when the last
* worker pauses itself.
*
* A "task" is just a call to the callback's `processTask` method.
* "Adding a task" means calling that method now, or remembering to call it in
* the future.
* This is implemented with a semaphore.
* If there are any workers waiting when a task is added, then one will be
* woken to claim the task.
* If not, then the next worker to wait on the semaphore will claim the task.
*
* Creating a `Worker` creates a thread that calls `Worker::run()`. When that
* thread enters `Worker::run`, it increments the count of active workers in
* the parent `Workers` object and then tries to claim a task, which blocks if
* there are none pending.
* It will be unblocked whenever the semaphore is notified (i.e. when the
* number of pending tasks is incremented).
* That only happens in two circumstances: (1) when
* `Workers::addTask` is called and (2) when `Workers` wants to pause some
* workers ("pause one worker" is considered one task), which happens when
* someone wants to stop the workers or shrink the threadpool. No worker
* threads are ever destroyed until `Workers` is destroyed; it merely pauses
* workers until then.
*
* When a waiting worker is woken, it checks whether `Workers` is trying to
* pause workers. If so, it changes its status from active to paused and
* blocks on
* its own condition variable. If not, then it calls `processTask` on the
* "callback" held by `Workers`.
*
* When a paused worker is woken, it checks whether it should exit. The signal
* to exit is only set in the destructor of `Worker`, which unblocks the
* paused thread and waits for it to exit. A `Worker::run` thread checks
* whether it needs to exit only when it is woken from a pause (not when it is
* woken from waiting). This is why the destructor for `Workers` pauses all
* the workers before destroying them.
*/
class Workers
{
@@ -62,54 +90,50 @@ public:
/** Perform a task.
The function is invoked precisely once for every call to the
thread pool's addTask method. It executes on one of the thread
pool's threads.
This function should process precisely one task.
The call is made on a thread owned by Workers. It is important
that you only process one task from inside your callback. Each
call to addTask will result in exactly one call to processTask.
@param instance The worker thread instance.
@throws This function should NOT throw an exception; if it does
the exception will be captured and passed to the
uncaughtException callback.
@see Workers::addTask
*/
virtual void
processTask(unsigned int instance) = 0;
/** Indicates that processTask threw an unexpected exception.
@param instance The worker thread instance.
@param eptr The exception that was thrown.
* */
virtual void
uncaughtException(unsigned int, std::exception_ptr)
{
// Default implementation does nothing
}
processTask(int instance) = 0;
};
/** Create the object.
@param callback The task selection & execution algorithm.
@param name The name for this pool (used to name threads)
@param size The number of threads (must not be 0!) for this thread pool
A number of initial threads may be optionally specified. The
default is to create one thread per CPU.
@param threadNames The name given to each created worker thread.
*/
explicit Workers(
Callback& callback,
std::string const& name,
unsigned int count);
perf::PerfLog* perfLog,
std::string const& threadNames = "Worker",
int numberOfThreads =
static_cast<int>(std::thread::hardware_concurrency()));
~Workers();
/** Retrieve the number of threads in the thread pool. */
unsigned int
count() const noexcept
{
return threads_.load();
}
/** Retrieve the desired number of threads.
This just returns the number of active threads that were requested. If
there was a recent call to setNumberOfThreads, the actual number of
active threads may be temporarily different from what was last requested.
@note This function is not thread-safe.
*/
int
getNumberOfThreads() const noexcept;
/** Set the desired number of threads.
@note This function is not thread-safe.
*/
void
setNumberOfThreads(int numberOfThreads);
/** Pause all threads and wait until they are paused.
@@ -133,34 +157,79 @@ public:
void
addTask();
/** Get the number of currently executing calls of Callback::processTask.
While this function is thread-safe, the value may not stay
accurate for very long. It's mainly for diagnostic purposes.
*/
int
numberOfCurrentlyRunningTasks() const noexcept;
//--------------------------------------------------------------------------
private:
Callback& callback_;
struct PausedTag
{
explicit PausedTag() = default;
};
/// This represents the total number of threads in the thread pool:
std::atomic<unsigned int> threads_ = 0;
/* A Worker executes tasks on its provided thread.
/** Queued task tracking
These are the states:
To minimize overhead, we track two things: total number of tasks
added, and total number of tasks dispatched. The key insight is
that both of these numbers are only ever incremented and never
decremented. Along with atomic operations, this allows us to
write lock free code.
Active: Running the task processing loop.
Idle: Active, but blocked on waiting for a task.
Paused: Blocked waiting to exit or become active.
*/
class Worker : public beast::LockFreeStack<Worker>::Node,
public beast::LockFreeStack<Worker, PausedTag>::Node
{
public:
Worker(
Workers& workers,
std::string const& threadName,
int const instance);
The choice of 64-bit unsigned integers helps to avoid overflow. A
32-bit value would overflow too fast even at low queue rates (e.g.
at a 1,000 tasks per second, the counter would overflow in about 50
days). With 64 bits, even at a truly obscene rate of 1,000,000,000
increments per second, this counter is good for over 580 years, at
which point the server is really due for a reboot.
*/
/** @{ */
/// The total number of tasks that have been queued since startup.
std::atomic<std::uint64_t> head_ = 1;
~Worker();
/// The total number of tasks that have been dispatched for processing.
std::atomic<std::uint64_t> tail_ = 1;
/** @} */
void
notify();
private:
void
run();
private:
Workers& m_workers;
std::string const threadName_;
int const instance_;
std::thread thread_;
std::mutex mutex_;
std::condition_variable wakeup_;
int wakeCount_; // how many times to un-pause
bool shouldExit_;
};
private:
static void
deleteWorkers(beast::LockFreeStack<Worker>& stack);
private:
Callback& m_callback;
perf::PerfLog* perfLog_;
std::string m_threadNames; // The name to give each thread
std::condition_variable m_cv; // signaled when all threads paused
std::mutex m_mut;
bool m_allPaused;
semaphore m_semaphore; // each pending task is 1 resource
int m_numberOfThreads; // how many we want active now
std::atomic<int> m_activeCount; // to know when all are paused
std::atomic<int> m_pauseCount; // how many threads need to pause now
std::atomic<int>
m_runningTaskCount; // how many calls to processTask() active
beast::LockFreeStack<Worker> m_everyone; // holds all created workers
beast::LockFreeStack<Worker, PausedTag>
m_paused; // holds just paused workers
};
} // namespace ripple
+83
View File
@@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_CORE_SEMAPHORE_H_INCLUDED
#define RIPPLE_CORE_SEMAPHORE_H_INCLUDED
#include <condition_variable>
#include <mutex>
namespace ripple {
template <class Mutex, class CondVar>
class basic_semaphore
{
private:
Mutex m_mutex;
CondVar m_cond;
std::size_t m_count;
public:
using size_type = std::size_t;
/** Create the semaphore, with an optional initial count.
If unspecified, the initial count is zero.
*/
explicit basic_semaphore(size_type count = 0) : m_count(count)
{
}
/** Increment the count and unblock one waiting thread. */
void
notify()
{
std::lock_guard lock{m_mutex};
++m_count;
m_cond.notify_one();
}
/** Block until notify is called. */
void
wait()
{
std::unique_lock lock{m_mutex};
while (m_count == 0)
m_cond.wait(lock);
--m_count;
}
/** Perform a non-blocking wait.
@return `true` If the wait would be satisfied.
*/
bool
try_wait()
{
std::lock_guard lock{m_mutex};
if (m_count == 0)
return false;
--m_count;
return true;
}
};
using semaphore = basic_semaphore<std::mutex, std::condition_variable>;
} // namespace ripple
#endif
+1 -1
View File
@@ -74,7 +74,7 @@ createHTTPPost(
// BuildInfo::getFullVersionString () as well?
s << "POST " << (strPath.empty() ? "/" : strPath) << " HTTP/1.0\r\n"
<< "User-Agent: " << systemName << "-json-rpc/v1\r\n"
<< "User-Agent: " << systemName() << "-json-rpc/v1\r\n"
<< "Host: " << strHost << "\r\n"
<< "Content-Type: application/json\r\n"
<< "Content-Length: " << strMsg.size() << "\r\n"
+3 -14
View File
@@ -31,23 +31,12 @@ class DummyScheduler : public Scheduler
public:
DummyScheduler() = default;
~DummyScheduler() = default;
void
scheduleTask(Task& task) override
{
// Invoke the task synchronously.
task.performScheduledTask();
}
scheduleTask(Task& task) override;
void
onFetch(FetchReport const& report) override
{
}
onFetch(FetchReport const& report) override;
void
onBatchWrite(BatchWriteReport const& report) override
{
}
onBatchWrite(BatchWriteReport const& report) override;
};
} // namespace NodeStore
@@ -0,0 +1,43 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/nodestore/DummyScheduler.h>
namespace ripple {
namespace NodeStore {
void
DummyScheduler::scheduleTask(Task& task)
{
// Invoke the task synchronously.
task.performScheduledTask();
}
void
DummyScheduler::onFetch(const FetchReport& report)
{
}
void
DummyScheduler::onBatchWrite(const BatchWriteReport& report)
{
}
} // namespace NodeStore
} // namespace ripple
+1 -1
View File
@@ -177,7 +177,7 @@ private:
std::queue<std::shared_ptr<Message>> send_queue_;
bool gracefulClose_ = false;
int large_sendq_ = 0;
std::optional<LoadEvent> load_event_;
std::unique_ptr<LoadEvent> load_event_;
// The highest sequence of each PublisherList that has
// been sent to or received from this peer.
hash_map<PublicKey, std::size_t> publisherListSequences_;
+12 -10
View File
@@ -41,7 +41,9 @@
namespace ripple {
namespace perf {
PerfLogImp::Counters::Counters(std::set<char const*> const& labels)
PerfLogImp::Counters::Counters(
std::set<char const*> const& labels,
JobTypes const& jobTypes)
{
{
// populateRpc
@@ -61,9 +63,9 @@ PerfLogImp::Counters::Counters(std::set<char const*> const& labels)
{
// populateJq
jq_.reserve(jobTypes.size());
for (auto const& jobType : jobTypes)
for (auto const& [jobType, _] : jobTypes)
{
auto const inserted = jq_.emplace(jobType.type, Jq()).second;
auto const inserted = jq_.emplace(jobType, Jq()).second;
if (!inserted)
{
// Ensure that no other function populates this entry.
@@ -146,7 +148,7 @@ PerfLogImp::Counters::countersJson() const
j[jss::running_duration_us] =
std::to_string(value.runningDuration.count());
totalJq.runningDuration += value.runningDuration;
jqobj[std::string(jobTypes[proc.first].name)] = j;
jqobj[JobTypes::name(proc.first)] = j;
}
if (totalJq.queued)
@@ -183,12 +185,12 @@ PerfLogImp::Counters::currentJson() const
for (auto const& j : jobs)
{
if (!j)
if (j.first == jtINVALID)
continue;
Json::Value jobj(Json::objectValue);
jobj[jss::job] = std::string(jobTypes[j->first].name);
jobj[jss::job] = JobTypes::name(j.first);
jobj[jss::duration_us] = std::to_string(
std::chrono::duration_cast<microseconds>(present - j->second)
std::chrono::duration_cast<microseconds>(present - j.second)
.count());
jobsArray.append(jobj);
}
@@ -412,7 +414,7 @@ PerfLogImp::jobStart(
}
std::lock_guard lock(counters_.jobsMutex_);
if (instance >= 0 && instance < counters_.jobs_.size())
counters_.jobs_[instance].emplace(type, startTime);
counters_.jobs_[instance] = {type, startTime};
}
void
@@ -432,7 +434,7 @@ PerfLogImp::jobFinish(JobType const type, microseconds dur, int instance)
}
std::lock_guard lock(counters_.jobsMutex_);
if (instance >= 0 && instance < counters_.jobs_.size())
counters_.jobs_[instance].reset();
counters_.jobs_[instance] = {jtINVALID, steady_time_point()};
}
void
@@ -440,7 +442,7 @@ PerfLogImp::resizeJobs(int const resize)
{
std::lock_guard lock(counters_.jobsMutex_);
if (resize > counters_.jobs_.size())
counters_.jobs_.resize(resize, std::nullopt);
counters_.jobs_.resize(resize, {jtINVALID, steady_time_point()});
}
void
+3 -6
View File
@@ -25,15 +25,12 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/jss.h>
#include <boost/asio/ip/host_name.hpp>
#include <condition_variable>
#include <cstdint>
#include <fstream>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <type_traits>
@@ -111,12 +108,12 @@ class PerfLogImp : public PerfLog
// keys and values are created before more threads are started.
std::unordered_map<std::string, Locked<Rpc>> rpc_;
std::unordered_map<JobType, Locked<Jq>> jq_;
std::vector<std::optional<std::pair<JobType, steady_time_point>>> jobs_;
std::vector<std::pair<JobType, steady_time_point>> jobs_;
mutable std::mutex jobsMutex_;
std::unordered_map<std::uint64_t, MethodStart> methods_;
mutable std::mutex methodsMutex_;
Counters(std::set<char const*> const& labels);
Counters(std::set<char const*> const& labels, JobTypes const& jobTypes);
Json::Value
countersJson() const;
Json::Value
@@ -127,7 +124,7 @@ class PerfLogImp : public PerfLog
Application& app_;
beast::Journal const j_;
std::function<void()> const signalStop_;
Counters counters_{ripple::RPC::getHandlerNames()};
Counters counters_{ripple::RPC::getHandlerNames(), JobTypes::instance()};
std::ofstream logFile_;
std::thread thread_;
std::mutex mutex_;
+7 -10
View File
@@ -193,7 +193,7 @@ getAccountObjects(
auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
// this is a mutable version of limit, used to seamlessly switch
// this is a mutable version of limit, used to seemlessly switch
// to iterating directory entries when nftokenpages are exhausted
uint32_t mlimit = limit;
@@ -459,7 +459,7 @@ ledgerFromRequest(T& ledger, JsonContext& context)
indexValue = legacyLedger;
}
if (!hashValue.isNull())
if (hashValue)
{
if (!hashValue.isString())
return {rpcINVALID_PARAMS, "ledgerHashNotString"};
@@ -470,9 +470,6 @@ ledgerFromRequest(T& ledger, JsonContext& context)
return getLedger(ledger, ledgerHash, context);
}
if (!indexValue.isConvertibleTo(Json::stringValue))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
auto const index = indexValue.asString();
if (index == "current" || index.empty())
@@ -484,11 +481,11 @@ ledgerFromRequest(T& ledger, JsonContext& context)
if (index == "closed")
return getLedger(ledger, LedgerShortcut::CLOSED, context);
std::uint32_t val;
if (!beast::lexicalCastChecked(val, index))
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
std::uint32_t iVal;
if (beast::lexicalCastChecked(iVal, index))
return getLedger(ledger, iVal, context);
return getLedger(ledger, val, context);
return {rpcINVALID_PARAMS, "ledgerIndexMalformed"};
}
} // namespace
@@ -704,7 +701,7 @@ getLedger(T& ledger, LedgerShortcut shortcut, Context& context)
return Status::OK;
}
// Explicit instantiation of above three functions
// Explicit instantiaion of above three functions
template Status
getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context&);
+3 -5
View File
@@ -538,7 +538,7 @@ pwaReply(
{
std::string out =
"HTTP/1.1 " + std::to_string(status) + " " + pwaReason(status) + "\r\n";
out += "Server: " + std::string(systemName) +
out += "Server: " + systemName() +
"\r\nConnection: close\r\nX-Content-Type-Options: nosniff\r\n"
"Referrer-Policy: no-referrer\r\n";
for (auto const& h : headers)
@@ -1985,10 +1985,8 @@ ServerHandler::statusResponse(http_request_type const& request) const
if (app_.serverOkay(reason))
{
msg.result(boost::beast::http::status::ok);
msg.body() = "<!DOCTYPE html><html><head><title>" +
std::string(systemName) +
" Test page for rippled</title></head><body><h1>" +
std::string(systemName) +
msg.body() = "<!DOCTYPE html><html><head><title>" + systemName() +
" Test page for rippled</title></head><body><h1>" + systemName() +
" Test</h1><p>This page shows rippled http(s) "
"connectivity is working.</p></body></html>";
}
File diff suppressed because it is too large. Load diff
-299
View File
@@ -1,299 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/json/json_errors.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/RPCErr.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <xrpl/protocol/jss.h>
#include <functional>
namespace ripple {
namespace LedgerEntryHelpers {
Unexpected<Json::Value>
missingFieldError(
Json::StaticString const field,
std::optional<std::string> err = std::nullopt)
{
Json::Value json = Json::objectValue;
auto error = RPC::missing_field_message(std::string(field.c_str()));
json[jss::error] = err.value_or("malformedRequest");
json[jss::error_code] = rpcINVALID_PARAMS;
json[jss::error_message] = std::move(error);
return Unexpected(json);
}
Unexpected<Json::Value>
invalidFieldError(
std::string const& err,
Json::StaticString const field,
std::string const& type)
{
Json::Value json = Json::objectValue;
auto error = RPC::expected_field_message(field, type);
json[jss::error] = err;
json[jss::error_code] = rpcINVALID_PARAMS;
json[jss::error_message] = std::move(error);
return Unexpected(json);
}
Unexpected<Json::Value>
malformedError(std::string const& err, std::string const& message)
{
Json::Value json = Json::objectValue;
json[jss::error] = err;
json[jss::error_code] = rpcINVALID_PARAMS;
json[jss::error_message] = message;
return Unexpected(json);
}
Expected<bool, Json::Value>
hasRequired(
Json::Value const& params,
std::initializer_list<Json::StaticString> fields,
std::optional<std::string> err = std::nullopt)
{
for (auto const field : fields)
{
if (!params.isMember(field) || params[field].isNull())
{
return missingFieldError(field, err);
}
}
return true;
}
template <class T>
std::optional<T>
parse(Json::Value const& param);
template <class T>
Expected<T, Json::Value>
required(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err,
std::string const& expectedType)
{
if (!params.isMember(fieldName) || params[fieldName].isNull())
{
return missingFieldError(fieldName);
}
if (auto obj = parse<T>(params[fieldName]))
{
return *obj;
}
return invalidFieldError(err, fieldName, expectedType);
}
template <>
std::optional<AccountID>
parse(Json::Value const& param)
{
if (!param.isString())
return std::nullopt;
auto const account = parseBase58<AccountID>(param.asString());
if (!account || account->isZero())
{
return std::nullopt;
}
return account;
}
Expected<AccountID, Json::Value>
requiredAccountID(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<AccountID>(params, fieldName, err, "AccountID");
}
std::optional<Blob>
parseHexBlob(Json::Value const& param, std::size_t maxLength)
{
if (!param.isString())
return std::nullopt;
auto const blob = strUnHex(param.asString());
if (!blob || blob->empty() || blob->size() > maxLength)
return std::nullopt;
return blob;
}
Expected<Blob, Json::Value>
requiredHexBlob(
Json::Value const& params,
Json::StaticString const fieldName,
std::size_t maxLength,
std::string const& err)
{
if (!params.isMember(fieldName) || params[fieldName].isNull())
{
return missingFieldError(fieldName);
}
if (auto blob = parseHexBlob(params[fieldName], maxLength))
{
return *blob;
}
return invalidFieldError(err, fieldName, "hex string");
}
template <>
std::optional<std::uint32_t>
parse(Json::Value const& param)
{
if (param.isUInt() || (param.isInt() && param.asInt() >= 0))
return param.asUInt();
if (param.isString())
{
std::uint32_t v;
if (beast::lexicalCastChecked(v, param.asString()))
return v;
}
return std::nullopt;
}
Expected<std::uint32_t, Json::Value>
requiredUInt32(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<std::uint32_t>(params, fieldName, err, "number");
}
template <>
std::optional<uint256>
parse(Json::Value const& param)
{
uint256 uNodeIndex;
if (!param.isString() || !uNodeIndex.parseHex(param.asString()))
{
return std::nullopt;
}
return uNodeIndex;
}
Expected<uint256, Json::Value>
requiredUInt256(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<uint256>(params, fieldName, err, "hex string");
}
template <>
std::optional<uint192>
parse(Json::Value const& param)
{
uint192 field;
if (!param.isString() || !field.parseHex(param.asString()))
{
return std::nullopt;
}
return field;
}
Expected<uint192, Json::Value>
requiredUInt192(
Json::Value const& params,
Json::StaticString const fieldName,
std::string const& err)
{
return required<uint192>(params, fieldName, err, "Hash192");
}
Expected<STXChainBridge, Json::Value>
parseBridgeFields(Json::Value const& params)
{
if (auto const value = hasRequired(
params,
{jss::LockingChainDoor,
jss::LockingChainIssue,
jss::IssuingChainDoor,
jss::IssuingChainIssue});
!value)
{
return Unexpected(value.error());
}
auto const lockingChainDoor = requiredAccountID(
params, jss::LockingChainDoor, "malformedLockingChainDoor");
if (!lockingChainDoor)
{
return Unexpected(lockingChainDoor.error());
}
auto const issuingChainDoor = requiredAccountID(
params, jss::IssuingChainDoor, "malformedIssuingChainDoor");
if (!issuingChainDoor)
{
return Unexpected(issuingChainDoor.error());
}
Issue lockingChainIssue;
try
{
lockingChainIssue = issueFromJson(params[jss::LockingChainIssue]);
}
catch (std::runtime_error const& ex)
{
return invalidFieldError(
"malformedIssue", jss::LockingChainIssue, "Issue");
}
Issue issuingChainIssue;
try
{
issuingChainIssue = issueFromJson(params[jss::IssuingChainIssue]);
}
catch (std::runtime_error const& ex)
{
return invalidFieldError(
"malformedIssue", jss::IssuingChainIssue, "Issue");
}
return STXChainBridge(
*lockingChainDoor,
lockingChainIssue,
*issuingChainDoor,
issuingChainIssue);
}
} // namespace LedgerEntryHelpers
} // namespace ripple
+1 -1
View File
@@ -436,7 +436,7 @@ private:
static_cast<uint32_t>(entry.first);
}
ret[jss::native_currency_code] = std::string(systemCurrencyCode);
ret[jss::native_currency_code] = systemCurrencyCode();
// Hook API: enough to regenerate hook/extern.h, hookapi.h keylet
// constants and error.h without the xahaud source tree.
+1 -1
View File
@@ -48,7 +48,7 @@ doServerInfo(RPC::JsonContext& context)
context.params.isMember(jss::counters) &&
context.params[jss::counters].asBool());
ret[jss::native_currency_code] = std::string(systemCurrencyCode);
ret[jss::native_currency_code] = systemCurrencyCode();
return ret;
}
+1 -1
View File
@@ -38,7 +38,7 @@ doServerState(RPC::JsonContext& context)
context.params.isMember(jss::counters) &&
context.params[jss::counters].asBool());
ret[jss::native_currency_code] = std::string(systemCurrencyCode);
ret[jss::native_currency_code] = systemCurrencyCode();
return ret;
}
+1 -1
View File
@@ -33,7 +33,7 @@ Json::Value
doStop(RPC::JsonContext& context)
{
context.app.signalStop();
return RPC::makeObjectValue(std::string(systemName) + " server stopping");
return RPC::makeObjectValue(systemName() + " server stopping");
}
} // namespace ripple
+23 -32
View File
@@ -114,15 +114,15 @@ namespace detail {
// The slab cutoffs and the number of megabytes per allocation are customized
// based on the number of objects of each size we expect to need at any point
// in time and with an eye to minimize the number of slack bytes in a block.
inline constinit slab::allocator_t<SHAMapItem,
slab::config<1000000, 128>,
slab::config<1000000, 296>,
slab::config<125000, 392>,
slab::config<125000, 520>,
slab::config<62500, 760>,
slab::config<62500, 856>,
slab::config<31250, 1048>
> slabber;
inline SlabAllocatorSet<SHAMapItem> slabber({
{ 128, megabytes(std::size_t(60)) },
{ 192, megabytes(std::size_t(46)) },
{ 272, megabytes(std::size_t(60)) },
{ 384, megabytes(std::size_t(56)) },
{ 564, megabytes(std::size_t(40)) },
{ 772, megabytes(std::size_t(46)) },
{ 1052, megabytes(std::size_t(60)) },
});
// clang-format on
} // namespace detail
@@ -130,11 +130,7 @@ inline constinit slab::allocator_t<SHAMapItem,
inline void
intrusive_ptr_add_ref(SHAMapItem const* x)
{
assert(x);
// In order to call this, we must already have an intrusive pointer
// to this item, so it's reference count should be at least 1. This
// would only happen if someone released that last reference to the
// This can only happen if someone releases the last reference to the
// item while we were trying to increment the refcount.
if (x->refcount_++ == 0)
LogicError("SHAMapItem: the reference count is 0!");
@@ -143,23 +139,20 @@ intrusive_ptr_add_ref(SHAMapItem const* x)
inline void
intrusive_ptr_release(SHAMapItem const* x)
{
assert(x);
if (--x->refcount_ == 0)
{
auto p = reinterpret_cast<std::uint8_t const*>(x);
// We need to invoke the destructor for this object before we
// release the memory.
std::destroy_at(x);
// The SHAMapItem constuctor isn't trivial (because the destructor
// for CountedObject isn't) so we can't avoid calling it here, but
// plan for a future where we might not need to.
if constexpr (!std::is_trivially_destructible_v<SHAMapItem>)
std::destroy_at(x);
// If the slabber doesn't claim this pointer, it was allocated
// manually (with the aligned operator new[] in make_shamapitem),
// so we free it manually with the matching aligned delete.
// If the slabber doens't claim this pointer, it was allocated
// manually, so we free it manually.
if (!detail::slabber.deallocate(const_cast<std::uint8_t*>(p)))
::operator delete[](
const_cast<std::uint8_t*>(p),
std::align_val_t(alignof(SHAMapItem)));
delete[] p;
}
}
@@ -175,20 +168,18 @@ make_shamapitem(uint256 const& tag, Slice data)
// If we can't grab memory from the slab allocators, we fall back to
// the standard library and try to grab a precisely-sized memory block:
if (raw == nullptr)
raw = new (std::align_val_t(alignof(SHAMapItem)))
std::uint8_t[sizeof(SHAMapItem) + data.size()];
assert(reinterpret_cast<std::uintptr_t>(raw) % alignof(SHAMapItem) == 0);
raw = new std::uint8_t[sizeof(SHAMapItem) + data.size()];
// We do not increment the reference count here on purpose: the
// constructor of SHAMapItem explicitly sets it to 1. We use the fact
// that the refcount can never be zero before incrementing as an
// invariant.
return {
new (std::assume_aligned<alignof(SHAMapItem)>(raw))
SHAMapItem{tag, data},
false};
return {new (raw) SHAMapItem{tag, data}, false};
}
static_assert(alignof(SHAMapItem) != 40);
static_assert(alignof(SHAMapItem) == 8 || alignof(SHAMapItem) == 4);
inline boost::intrusive_ptr<SHAMapItem>
make_shamapitem(SHAMapItem const& other)
{