Compare commits

..

2 Commits

Author SHA1 Message Date
JCW
78d8f89267 Updated the template
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2026-02-26 18:31:40 +00:00
JCW
34c49ff166 Add code generator
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2026-02-26 16:36:44 +00:00
36 changed files with 2080 additions and 866 deletions

4
.gitignore vendored
View File

@@ -80,3 +80,7 @@ DerivedData
# clangd cache
/.cache
# Auto-generated protocol wrapper classes (generated at CMake configure time)
/include/xrpl/protocol_autogen/transactions/
/include/xrpl/protocol_autogen/ledger_objects/

View File

@@ -74,15 +74,23 @@ add_module(xrpl protocol)
target_link_libraries(xrpl.libxrpl.protocol PUBLIC xrpl.libxrpl.crypto xrpl.libxrpl.json)
# Level 05
add_module(xrpl protocol_autogen)
target_link_libraries(xrpl.libxrpl.protocol_autogen PUBLIC xrpl.libxrpl.protocol)
# Set up code generation for protocol_autogen module
include(XrplProtocolAutogen)
setup_protocol_autogen()
# Level 06
add_module(xrpl core)
target_link_libraries(xrpl.libxrpl.core PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json
xrpl.libxrpl.protocol)
# Level 06
# Level 07
add_module(xrpl resource)
target_link_libraries(xrpl.libxrpl.resource PUBLIC xrpl.libxrpl.protocol)
# Level 07
# Level 08
add_module(xrpl net)
target_link_libraries(xrpl.libxrpl.net PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json
xrpl.libxrpl.protocol xrpl.libxrpl.resource)
@@ -140,6 +148,7 @@ target_link_modules(
net
nodestore
protocol
protocol_autogen
rdb
resource
server

View File

@@ -29,6 +29,7 @@ install(TARGETS common
xrpl.libxrpl.net
xrpl.libxrpl.nodestore
xrpl.libxrpl.protocol
xrpl.libxrpl.protocol_autogen
xrpl.libxrpl.resource
xrpl.libxrpl.server
xrpl.libxrpl.shamap

View File

@@ -0,0 +1,132 @@
#[===================================================================[
Protocol Autogen - Code generation for protocol wrapper classes
#]===================================================================]
# Function to set up code generation for protocol_autogen module
# This runs at configure time to generate C++ wrapper classes from macro files
function (setup_protocol_autogen)
# Directory paths
set(MACRO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol/detail")
set(AUTOGEN_HEADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol_autogen")
set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/scripts")
# Input macro files
set(TRANSACTIONS_MACRO "${MACRO_DIR}/transactions.macro")
set(LEDGER_ENTRIES_MACRO "${MACRO_DIR}/ledger_entries.macro")
set(SFIELDS_MACRO "${MACRO_DIR}/sfields.macro")
# Python scripts
set(GENERATE_TX_SCRIPT "${SCRIPTS_DIR}/generate_tx_classes.py")
set(GENERATE_LEDGER_SCRIPT "${SCRIPTS_DIR}/generate_ledger_classes.py")
set(REQUIREMENTS_FILE "${SCRIPTS_DIR}/requirements.txt")
# Create output directories
file(MAKE_DIRECTORY "${AUTOGEN_HEADER_DIR}/transactions")
file(MAKE_DIRECTORY "${AUTOGEN_HEADER_DIR}/ledger_objects")
# Find Python3 - check if already found by Conan or find it ourselves
if (NOT Python3_EXECUTABLE)
find_package(Python3 COMPONENTS Interpreter QUIET)
endif ()
if (NOT Python3_EXECUTABLE)
# Try finding python3 executable directly
find_program(Python3_EXECUTABLE NAMES python3 python)
endif ()
if (NOT Python3_EXECUTABLE)
message(FATAL_ERROR "Python3 not found. Code generation cannot proceed.")
return()
endif ()
message(STATUS "Using Python3 for code generation: ${Python3_EXECUTABLE}")
# Set up Python virtual environment for code generation
set(VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/codegen_venv")
# Determine the Python executable path in the venv
if (WIN32)
set(VENV_PYTHON "${VENV_DIR}/Scripts/python.exe")
set(VENV_PIP "${VENV_DIR}/Scripts/pip.exe")
else ()
set(VENV_PYTHON "${VENV_DIR}/bin/python")
set(VENV_PIP "${VENV_DIR}/bin/pip")
endif ()
# Check if venv needs to be created or updated
set(VENV_NEEDS_UPDATE FALSE)
if (NOT EXISTS "${VENV_PYTHON}")
set(VENV_NEEDS_UPDATE TRUE)
message(STATUS "Creating Python virtual environment for code generation...")
elseif ("${REQUIREMENTS_FILE}" IS_NEWER_THAN "${VENV_DIR}/.requirements_installed")
set(VENV_NEEDS_UPDATE TRUE)
message(STATUS "Updating Python virtual environment (requirements changed)...")
endif ()
# Create/update virtual environment if needed
if (VENV_NEEDS_UPDATE)
message(STATUS "Setting up Python virtual environment at ${VENV_DIR}")
execute_process(COMMAND ${Python3_EXECUTABLE} -m venv "${VENV_DIR}"
RESULT_VARIABLE VENV_RESULT ERROR_VARIABLE VENV_ERROR)
if (NOT VENV_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to create virtual environment: ${VENV_ERROR}")
endif ()
message(STATUS "Installing Python dependencies...")
execute_process(COMMAND ${VENV_PIP} install --upgrade pip RESULT_VARIABLE PIP_UPGRADE_RESULT
OUTPUT_QUIET ERROR_VARIABLE PIP_UPGRADE_ERROR)
if (NOT PIP_UPGRADE_RESULT EQUAL 0)
message(WARNING "Failed to upgrade pip: ${PIP_UPGRADE_ERROR}")
endif ()
execute_process(COMMAND ${VENV_PIP} install -r "${REQUIREMENTS_FILE}"
RESULT_VARIABLE PIP_INSTALL_RESULT ERROR_VARIABLE PIP_INSTALL_ERROR)
if (NOT PIP_INSTALL_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to install Python dependencies: ${PIP_INSTALL_ERROR}")
endif ()
# Mark requirements as installed
file(TOUCH "${VENV_DIR}/.requirements_installed")
message(STATUS "Python virtual environment ready")
endif ()
# Generate transaction classes at configure time
message(STATUS "Generating transaction classes from transactions.macro...")
execute_process(COMMAND ${VENV_PYTHON} "${GENERATE_TX_SCRIPT}" "${TRANSACTIONS_MACRO}"
--header-dir "${AUTOGEN_HEADER_DIR}/transactions" --sfields-macro
"${SFIELDS_MACRO}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE TX_GEN_RESULT
OUTPUT_VARIABLE TX_GEN_OUTPUT
ERROR_VARIABLE TX_GEN_ERROR)
if (NOT TX_GEN_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to generate transaction classes:\n${TX_GEN_ERROR}")
else ()
message(STATUS "Transaction classes generated successfully")
endif ()
# Generate ledger entry classes at configure time
message(STATUS "Generating ledger entry classes from ledger_entries.macro...")
execute_process(COMMAND ${VENV_PYTHON} "${GENERATE_LEDGER_SCRIPT}" "${LEDGER_ENTRIES_MACRO}"
--header-dir "${AUTOGEN_HEADER_DIR}/ledger_objects" --sfields-macro
"${SFIELDS_MACRO}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE LEDGER_GEN_RESULT
OUTPUT_VARIABLE LEDGER_GEN_OUTPUT
ERROR_VARIABLE LEDGER_GEN_ERROR)
if (NOT LEDGER_GEN_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to generate ledger entry classes:\n${LEDGER_GEN_ERROR}")
else ()
message(STATUS "Ledger entry classes generated successfully")
endif ()
# Add the generated header directory to the module's include path
target_include_directories(
xrpl.libxrpl.protocol_autogen PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
# Install generated headers
install(DIRECTORY "${AUTOGEN_HEADER_DIR}/"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/xrpl/protocol_autogen" FILES_MATCHING
PATTERN "*.h")
endfunction ()

View File

@@ -1,139 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 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_CANPROCESS_H_INCLUDED
#define RIPPLE_BASICS_CANPROCESS_H_INCLUDED
#include <functional>
#include <mutex>
#include <set>
/** RAII class to check if an Item is already being processed on another thread,
* as indicated by it's presence in a Collection.
*
* If the Item is not in the Collection, it will be added under lock in the
* ctor, and removed under lock in the dtor. The object will be considered
* "usable" and evaluate to `true`.
*
* If the Item is in the Collection, no changes will be made to the collection,
* and the CanProcess object will be considered "unusable".
*
* It's up to the caller to decide what "usable" and "unusable" mean. (e.g.
* Process or skip a block of code, or set a flag.)
*
* The current use is to avoid lock contention that would be involved in
* processing something associated with the Item.
*
* Examples:
*
* void IncomingLedgers::acquireAsync(LedgerHash const& hash, ...)
* {
* if (CanProcess check{acquiresMutex_, pendingAcquires_, hash})
* {
* acquire(hash, ...);
* }
* }
*
* bool
* NetworkOPsImp::recvValidation(
* std::shared_ptr<STValidation> const& val,
* std::string const& source)
* {
* CanProcess check(
* validationsMutex_, pendingValidations_, val->getLedgerHash());
* BypassAccept bypassAccept =
* check ? BypassAccept::no : BypassAccept::yes;
* handleNewValidation(app_, val, source, bypassAccept, m_journal);
* }
*
*/
class CanProcess
{
public:
template <class Mutex, class Collection, class Item>
CanProcess(Mutex& mtx, Collection& collection, Item const& item)
: cleanup_(insert(mtx, collection, item))
{
}
~CanProcess()
{
if (cleanup_)
cleanup_();
}
CanProcess(CanProcess const&) = delete;
CanProcess&
operator=(CanProcess const&) = delete;
explicit
operator bool() const
{
return static_cast<bool>(cleanup_);
}
private:
template <bool useIterator, class Mutex, class Collection, class Item>
std::function<void()>
doInsert(Mutex& mtx, Collection& collection, Item const& item)
{
std::unique_lock<Mutex> lock(mtx);
// TODO: Use structured binding once LLVM 16 is the minimum supported
// version. See also: https://github.com/llvm/llvm-project/issues/48582
// https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c
auto const insertResult = collection.insert(item);
auto const it = insertResult.first;
if (!insertResult.second)
return {};
if constexpr (useIterator)
return [&, it]() {
std::unique_lock<Mutex> lock(mtx);
collection.erase(it);
};
else
return [&]() {
std::unique_lock<Mutex> lock(mtx);
collection.erase(item);
};
}
// Generic insert() function doesn't use iterators because they may get
// invalidated
template <class Mutex, class Collection, class Item>
std::function<void()>
insert(Mutex& mtx, Collection& collection, Item const& item)
{
return doInsert<false>(mtx, collection, item);
}
// Specialize insert() for std::set, which does not invalidate iterators for
// insert and erase
template <class Mutex, class Item>
std::function<void()>
insert(Mutex& mtx, std::set<Item>& collection, Item const& item)
{
return doInsert<true>(mtx, collection, item);
}
// If set, then the item is "usable"
std::function<void()> cleanup_;
};
#endif

View File

@@ -1,73 +0,0 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <chrono>
#include <cstdint>
#include <string_view>
namespace xrpl {
// cSpell:ignore ptmalloc
// -----------------------------------------------------------------------------
// Allocator interaction note:
// - This facility invokes glibc's malloc_trim(0) on Linux/glibc to request that
// ptmalloc return free heap pages to the OS.
// - If an alternative allocator (e.g. jemalloc or tcmalloc) is linked or
// preloaded (LD_PRELOAD), calling glibc's malloc_trim typically has no effect
// on the *active* heap. The call is harmless but may not reclaim memory
// because those allocators manage their own arenas.
// - Only glibc sbrk/arena space is eligible for trimming; large mmap-backed
// allocations are usually returned to the OS on free regardless of trimming.
// - Call at known reclamation points (e.g., after cache sweeps / online delete)
// and consider rate limiting to avoid churn.
// -----------------------------------------------------------------------------
struct MallocTrimReport
{
bool supported{false};
int trimResult{-1};
std::int64_t rssBeforeKB{-1};
std::int64_t rssAfterKB{-1};
std::chrono::microseconds durationUs{-1};
std::int64_t minfltDelta{-1};
std::int64_t majfltDelta{-1};
[[nodiscard]] std::int64_t
deltaKB() const noexcept
{
if (rssBeforeKB < 0 || rssAfterKB < 0)
return 0;
return rssAfterKB - rssBeforeKB;
}
};
/**
* @brief Attempt to return freed memory to the operating system.
*
* On Linux with glibc malloc, this issues ::malloc_trim(0), which may release
* free space from ptmalloc arenas back to the kernel. On other platforms, or if
* a different allocator is in use, this function is a no-op and the report will
* indicate that trimming is unsupported or had no effect.
*
* @param tag Identifier for logging/debugging purposes.
* @param journal Journal for diagnostic logging.
* @return Report containing before/after metrics and the trim result.
*
* @note If an alternative allocator (jemalloc/tcmalloc) is linked or preloaded,
* calling glibc's malloc_trim may have no effect on the active heap. The
* call is harmless but typically does not reclaim memory under those
* allocators.
*
* @note Only memory served from glibc's sbrk/arena heaps is eligible for trim.
* Large allocations satisfied via mmap are usually returned on free
* independently of trimming.
*
* @note Intended for use after operations that free significant memory (e.g.,
* cache sweeps, ledger cleanup, online delete). Consider rate limiting.
*/
MallocTrimReport
mallocTrim(std::string_view tag, beast::Journal journal);
} // namespace xrpl

View File

@@ -199,7 +199,7 @@ public:
/** Add a suppression peer and get message's relay status.
* Return pair:
* element 1: true if the key is added.
* element 1: true if the peer is added.
* element 2: optional is seated to the relay time point or
* is unseated if has not relayed yet. */
std::pair<bool, std::optional<Stopwatch::time_point>>

View File

@@ -35,8 +35,6 @@ struct LedgerHeader
// If validated is false, it means "not yet validated."
// Once validated is true, it will never be set false at a later time.
// NOTE: If you are accessing this directly, you are probably doing it
// wrong. Use LedgerMaster::isValidated().
// VFALCO TODO Make this not mutable
bool mutable validated = false;
bool accepted = false;

View File

@@ -15,10 +15,9 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
@@ -32,7 +31,7 @@ XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Batch, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
// Check flags in Credential transactions

View File

@@ -0,0 +1,133 @@
#pragma once
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <optional>
#include <string>
namespace xrpl::ledger_entries {
/**
* @brief Base class for type-safe ledger entry wrappers.
*
* This class provides common functionality for all ledger entry types,
* including access to common fields (sfLedgerIndex, sfLedgerEntryType, sfFlags).
*
* This is an immutable wrapper around SLE (Serialized Ledger Entry).
* Use the corresponding Builder classes to construct new ledger entries.
*/
class LedgerEntryBase
{
public:
/**
* @brief Construct a ledger entry wrapper from an existing SLE object.
* @param sle The underlying serialized ledger entry to wrap
*/
explicit LedgerEntryBase(SLE const& sle) : sle_(sle)
{
}
/**
* @brief Get the ledger entry type.
* @return The type of this ledger entry
*/
[[nodiscard]]
LedgerEntryType
getType() const
{
return sle_.getType();
}
/**
* @brief Get the key (index) of this ledger entry.
*
* The key uniquely identifies this ledger entry in the ledger state.
* @return A constant reference to the 256-bit key
*/
[[nodiscard]]
uint256 const&
getKey() const
{
return sle_.key();
}
// Common field getters (from LedgerFormats.cpp commonFields)
/**
* @brief Get the ledger index (sfLedgerIndex).
*
* This field is OPTIONAL and represents the index of the ledger entry.
* @return The ledger index if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint256>
getLedgerIndex() const
{
if (sle_.isFieldPresent(sfLedgerIndex))
{
return sle_.at(sfLedgerIndex);
}
return std::nullopt;
}
/**
* @brief Check if the ledger entry has a ledger index.
* @return true if sfLedgerIndex is present, false otherwise
*/
[[nodiscard]]
bool
hasLedgerIndex() const
{
return sle_.isFieldPresent(sfLedgerIndex);
}
/**
* @brief Get the ledger entry type field (sfLedgerEntryType).
*
* This field is REQUIRED for all ledger entries and indicates the type
* of the ledger entry (e.g., AccountRoot, RippleState, Offer, etc.).
* @return The ledger entry type as a 16-bit unsigned integer
*/
[[nodiscard]]
uint16_t
getLedgerEntryType() const
{
return sle_.at(sfLedgerEntryType);
}
/**
* @brief Get the flags field (sfFlags).
*
* This field is REQUIRED for all ledger entries and contains
* type-specific flags that modify the behavior of the ledger entry.
* @return The flags value as a 32-bit unsigned integer
*/
[[nodiscard]]
std::uint32_t
getFlags() const
{
return sle_.at(sfFlags);
}
/**
* @brief Get the underlying SLE object.
*
* Provides direct access to the wrapped serialized ledger entry object
* for cases where the type-safe accessors are insufficient.
* @return A constant reference to the underlying SLE object
*/
[[nodiscard]]
SLE const&
getSle() const
{
return sle_;
}
protected:
/** @brief The underlying serialized ledger entry being wrapped. */
SLE const& sle_;
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,75 @@
#pragma once
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
namespace xrpl::ledger_entries {
/**
* Base class for all ledger entry builders.
* Provides common field setters that are available for all ledger entry types.
*/
template <typename Derived>
class LedgerEntryBuilderBase
{
public:
/**
* Set the ledger index.
* @param value Ledger index
* @return Reference to the derived builder for method chaining.
*/
Derived&
setLedgerIndex(uint256 const& value)
{
object_[sfLedgerIndex] = value;
return static_cast<Derived&>(*this);
}
/**
* Set the flags.
* @param value Flags value
* @return Reference to the derived builder for method chaining.
*/
Derived&
setFlags(uint32_t value)
{
object_.setFieldU32(sfFlags, value);
return static_cast<Derived&>(*this);
}
/**
* @brief Factory method to create a new instance of the derived builder.
*
* Creates a default-constructed builder instance. It is recommended to use
* this factory method instead of directly constructing the derived type to
* avoid creating unnecessary temporary objects.
* @return A new instance of the derived builder type
*/
static Derived
create()
{
return Derived{};
}
/**
* @brief Factory method to create an instance of the derived builder from an existing SLE.
*
* Creates a builder instance initialized with data from an existing serialized
* ledger entry. It is recommended to use this factory method instead of directly
* constructing the derived type to avoid creating unnecessary temporary objects.
* @param sle The existing serialized ledger entry to initialize from
* @return A new instance of the derived builder type initialized with the SLE data
*/
static Derived
create(SLE const& sle)
{
return Derived{sle};
}
protected:
STObject object_{sfLedgerEntry};
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,64 @@
<!-- cspell:words pyparsing -->
# Protocol Autogen
This directory contains auto-generated C++ wrapper classes for XRP Ledger protocol types.
## Generated Files
The files in this directory are automatically generated at **CMake configure time** from macro definition files:
- **Transaction classes** (in `transactions/`): Generated from `include/xrpl/protocol/detail/transactions.macro` by `scripts/generate_tx_classes.py`
- **Ledger entry classes** (in `ledger_objects/`): Generated from `include/xrpl/protocol/detail/ledger_entries.macro` by `scripts/generate_ledger_classes.py`
## Generation Process
The generation happens automatically when you **configure** the project (not during build). When you run CMake, the system:
1. Creates a Python virtual environment in the build directory (`codegen_venv`)
2. Installs Python dependencies from `scripts/requirements.txt` into the venv (only if needed)
3. Runs the Python generation scripts using the venv Python interpreter
4. Parses the macro files to extract type definitions
5. Generates type-safe C++ wrapper classes using Jinja2 templates
6. Places the generated headers in this directory
### When Regeneration Happens
The code is regenerated when:
- You run CMake configure for the first time
- The Python virtual environment doesn't exist
- `scripts/requirements.txt` has been modified
To force regeneration, delete the build directory and reconfigure.
### Python Dependencies
The code generation requires the following Python packages (automatically installed):
- `pcpp` - C preprocessor for Python
- `pyparsing` - Parser combinator library
- `Jinja2` - Template engine
These are isolated in a virtual environment and won't affect your system Python installation.
## Version Control
The generated `.h` files are **not checked into version control** - they are listed in `.gitignore`.
This means:
- Every developer needs Python 3 installed to configure the project
- CI/CD systems must run CMake configure to generate the files
- Generated files are always fresh and match the current macro definitions
## Modifying Generated Code
**Do not manually edit files in this directory.** Any changes will be overwritten the next time CMake configure runs.
To modify the generated classes:
- Edit the macro files in `include/xrpl/protocol/detail/`
- Edit the Jinja2 templates in `scripts/templates/`
- Edit the generation scripts in `scripts/`
- Update Python dependencies in `scripts/requirements.txt`
- Run CMake configure to regenerate

View File

@@ -0,0 +1,442 @@
#pragma once
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFormats.h>
#include <optional>
#include <string>
namespace xrpl::transactions {
/**
* @brief Base class for all transaction wrapper types.
*
* Provides type-safe read-only accessors for common transaction fields.
* This is an immutable wrapper around STTx. Use the corresponding Builder classes
* to construct new transactions.
*/
class TransactionBase
{
public:
/**
* @brief Construct a transaction wrapper from an existing STTx object.
* @param tx The underlying transaction object to wrap
*/
explicit TransactionBase(STTx const& tx) : tx_(tx)
{
}
/**
* @brief Validate the transaction using passesLocalChecks.
* @param reason Output parameter for validation failure reason
* @return true if validation passes, false otherwise
*/
[[nodiscard]]
bool
validate(std::string& reason) const
{
return passesLocalChecks(tx_, reason);
}
/**
* @brief Get the transaction type.
* @return The type of this transaction
*/
[[nodiscard]]
xrpl::TxType
getTransactionType() const
{
return tx_.getTxnType();
}
/**
* @brief Get the account initiating the transaction (sfAccount).
*
* This field is REQUIRED for all transactions.
* @return The account ID of the transaction sender
*/
[[nodiscard]]
AccountID
getAccount() const
{
return tx_.at(sfAccount);
}
/**
* @brief Get the sequence number of the transaction (sfSequence).
*
* This field is REQUIRED for all transactions.
* @return The sequence number
*/
[[nodiscard]]
std::uint32_t
getSequence() const
{
return tx_.at(sfSequence);
}
/**
* @brief Get the transaction fee (sfFee).
*
* This field is REQUIRED for all transactions.
* @return The fee amount
*/
[[nodiscard]]
STAmount
getFee() const
{
return tx_.at(sfFee);
}
/**
* @brief Get the signing public key (sfSigningPubKey).
*
* This field is REQUIRED for all transactions.
* @return The public key used for signing as a blob
*/
[[nodiscard]]
Blob
getSigningPubKey() const
{
return tx_.getFieldVL(sfSigningPubKey);
}
/**
* @brief Get the transaction flags (sfFlags).
*
* This field is OPTIONAL.
* @return The flags value if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getFlags() const
{
if (tx_.isFieldPresent(sfFlags))
return tx_.at(sfFlags);
return std::nullopt;
}
/**
* @brief Check if the transaction has flags set.
* @return true if sfFlags is present, false otherwise
*/
[[nodiscard]]
bool
hasFlags() const
{
return tx_.isFieldPresent(sfFlags);
}
/**
* @brief Get the source tag (sfSourceTag).
*
* This field is OPTIONAL and used to identify the source of a payment.
* @return The source tag value if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getSourceTag() const
{
if (tx_.isFieldPresent(sfSourceTag))
return tx_.at(sfSourceTag);
return std::nullopt;
}
/**
* @brief Check if the transaction has a source tag.
* @return true if sfSourceTag is present, false otherwise
*/
[[nodiscard]]
bool
hasSourceTag() const
{
return tx_.isFieldPresent(sfSourceTag);
}
/**
* @brief Get the previous transaction ID (sfPreviousTxnID).
*
* This field is OPTIONAL and used for transaction chaining.
* @return The previous transaction ID if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint256>
getPreviousTxnID() const
{
if (tx_.isFieldPresent(sfPreviousTxnID))
return tx_.at(sfPreviousTxnID);
return std::nullopt;
}
/**
* @brief Check if the transaction has a previous transaction ID.
* @return true if sfPreviousTxnID is present, false otherwise
*/
[[nodiscard]]
bool
hasPreviousTxnID() const
{
return tx_.isFieldPresent(sfPreviousTxnID);
}
/**
* @brief Get the last ledger sequence (sfLastLedgerSequence).
*
* This field is OPTIONAL and specifies the latest ledger sequence
* in which this transaction can be included.
* @return The last ledger sequence if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getLastLedgerSequence() const
{
if (tx_.isFieldPresent(sfLastLedgerSequence))
return tx_.at(sfLastLedgerSequence);
return std::nullopt;
}
/**
* @brief Check if the transaction has a last ledger sequence.
* @return true if sfLastLedgerSequence is present, false otherwise
*/
[[nodiscard]]
bool
hasLastLedgerSequence() const
{
return tx_.isFieldPresent(sfLastLedgerSequence);
}
/**
* @brief Get the account transaction ID (sfAccountTxnID).
*
* This field is OPTIONAL and used to track transaction sequences.
* @return The account transaction ID if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint256>
getAccountTxnID() const
{
if (tx_.isFieldPresent(sfAccountTxnID))
return tx_.at(sfAccountTxnID);
return std::nullopt;
}
/**
* @brief Check if the transaction has an account transaction ID.
* @return true if sfAccountTxnID is present, false otherwise
*/
[[nodiscard]]
bool
hasAccountTxnID() const
{
return tx_.isFieldPresent(sfAccountTxnID);
}
/**
* @brief Get the operation limit (sfOperationLimit).
*
* This field is OPTIONAL and limits the number of operations in a transaction.
* @return The operation limit if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getOperationLimit() const
{
if (tx_.isFieldPresent(sfOperationLimit))
return tx_.at(sfOperationLimit);
return std::nullopt;
}
/**
* @brief Check if the transaction has an operation limit.
* @return true if sfOperationLimit is present, false otherwise
*/
[[nodiscard]]
bool
hasOperationLimit() const
{
return tx_.isFieldPresent(sfOperationLimit);
}
/**
* @brief Get the memos array (sfMemos).
*
* This field is OPTIONAL and contains arbitrary data attached to the transaction.
* @note This is an untyped field (STArray).
* @return A reference wrapper to the memos array if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getMemos() const
{
if (tx_.isFieldPresent(sfMemos))
return tx_.getFieldArray(sfMemos);
return std::nullopt;
}
/**
* @brief Check if the transaction has memos.
* @return true if sfMemos is present, false otherwise
*/
[[nodiscard]]
bool
hasMemos() const
{
return tx_.isFieldPresent(sfMemos);
}
/**
* @brief Get the ticket sequence (sfTicketSequence).
*
* This field is OPTIONAL and used when consuming a ticket instead of a sequence number.
* @return The ticket sequence if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getTicketSequence() const
{
if (tx_.isFieldPresent(sfTicketSequence))
return tx_.at(sfTicketSequence);
return std::nullopt;
}
/**
* @brief Check if the transaction has a ticket sequence.
* @return true if sfTicketSequence is present, false otherwise
*/
[[nodiscard]]
bool
hasTicketSequence() const
{
return tx_.isFieldPresent(sfTicketSequence);
}
/**
* @brief Get the transaction signature (sfTxnSignature).
*
* This field is OPTIONAL and contains the signature for single-signed transactions.
* @return The transaction signature as a blob if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<Blob>
getTxnSignature() const
{
if (tx_.isFieldPresent(sfTxnSignature))
return tx_.getFieldVL(sfTxnSignature);
return std::nullopt;
}
/**
* @brief Check if the transaction has a transaction signature.
* @return true if sfTxnSignature is present, false otherwise
*/
[[nodiscard]]
bool
hasTxnSignature() const
{
return tx_.isFieldPresent(sfTxnSignature);
}
/**
* @brief Get the signers array (sfSigners).
*
* This field is OPTIONAL and contains the list of signers for multi-signed transactions.
* @note This is an untyped field (STArray).
* @return A reference wrapper to the signers array if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<std::reference_wrapper<STArray const>>
getSigners() const
{
if (tx_.isFieldPresent(sfSigners))
return tx_.getFieldArray(sfSigners);
return std::nullopt;
}
/**
* @brief Check if the transaction has signers.
* @return true if sfSigners is present, false otherwise
*/
[[nodiscard]]
bool
hasSigners() const
{
return tx_.isFieldPresent(sfSigners);
}
/**
* @brief Get the network ID (sfNetworkID).
*
* This field is OPTIONAL and identifies the network this transaction is intended for.
* @return The network ID if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<uint32_t>
getNetworkID() const
{
if (tx_.isFieldPresent(sfNetworkID))
return tx_.at(sfNetworkID);
return std::nullopt;
}
/**
* @brief Check if the transaction has a network ID.
* @return true if sfNetworkID is present, false otherwise
*/
[[nodiscard]]
bool
hasNetworkID() const
{
return tx_.isFieldPresent(sfNetworkID);
}
/**
* @brief Get the delegate account (sfDelegate).
*
* This field is OPTIONAL and specifies a delegate account for the transaction.
* @return The delegate account ID if present, std::nullopt otherwise
*/
[[nodiscard]]
std::optional<AccountID>
getDelegate() const
{
if (tx_.isFieldPresent(sfDelegate))
return tx_.at(sfDelegate);
return std::nullopt;
}
/**
* @brief Check if the transaction has a delegate account.
* @return true if sfDelegate is present, false otherwise
*/
[[nodiscard]]
bool
hasDelegate() const
{
return tx_.isFieldPresent(sfDelegate);
}
/**
* @brief Get the underlying STTx object.
*
* Provides direct access to the wrapped transaction object for cases
* where the type-safe accessors are insufficient.
* @return A constant reference to the underlying STTx object
*/
[[nodiscard]]
STTx const&
getSTTx() const
{
return tx_;
}
protected:
/** @brief The underlying transaction object being wrapped. */
STTx const& tx_;
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,128 @@
#pragma once
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/jss.h>
namespace xrpl::transactions {
/**
* Base class for all transaction builders.
* Provides common field setters that are available for all transaction types.
*/
template <typename Derived>
class TransactionBuilderBase
{
public:
/**
* Set the account that is sending the transaction.
* @param value Account address (typically as a string)
* @return Reference to the derived builder for method chaining.
*/
Derived&
setAccount(AccountID const& value)
{
set(object_, sfAccount, value);
return static_cast<Derived&>(*this);
}
/**
* Set the transaction fee.
* @param value Fee in drops (typically as a string or number)
* @return Reference to the derived builder for method chaining.
*/
Derived&
setFee(STAmount const& value)
{
set(object_, sfFee, value);
return static_cast<Derived&>(*this);
}
/**
* Set the sequence number.
* @param value Sequence number
* @return Reference to the derived builder for method chaining.
*/
Derived&
setSequence(std::uint32_t const& value)
{
set(object_, sfSequence, value);
return static_cast<Derived&>(*this);
}
/**
* Set the signing public key.
* @param value Public key (typically as a hex string)
* @return Reference to the derived builder for method chaining.
*/
Derived&
setSigningPubKey(Blob const& value)
{
set(object_, sfSigningPubKey, value);
return static_cast<Derived&>(*this);
}
/**
* Set transaction flags.
* @param value Flags value
* @return Reference to the derived builder for method chaining.
*/
Derived&
setFlags(std::uint32_t const& value)
{
set(object_, sfFlags, value);
return static_cast<Derived&>(*this);
}
/**
* Set the source tag.
* @param value Source tag
* @return Reference to the derived builder for method chaining.
*/
Derived&
setSourceTag(std::uint32_t const& value)
{
set(object_, sfSourceTag, value);
return static_cast<Derived&>(*this);
}
/**
* Set the last ledger sequence.
* @param value Last ledger sequence number
* @return Reference to the derived builder for method chaining.
*/
Derived&
setLastLedgerSequence(std::uint32_t const& value)
{
set(object_, sfLastLedgerSequence, value);
return static_cast<Derived&>(*this);
}
/**
* Set the account transaction ID.
* @param value Account transaction ID (typically as a hex string)
* @return Reference to the derived builder for method chaining.
*/
Derived&
setAccountTxnID(STUInt256 const& value)
{
set(object_, sfAccountTxnID, value);
return static_cast<Derived&>(*this);
}
/**
* @brief Factory method to create an instance of the derived builder.
*
* @return A new instance of the derived builder type
*/
static Derived
create()
{
return Derived{};
}
protected:
STObject object_{sfTransaction};
};
} // namespace xrpl::transactions

View File

@@ -185,7 +185,7 @@ public:
virtual bool
isFull() = 0;
virtual void
setMode(OperatingMode om, char const* reason) = 0;
setMode(OperatingMode om) = 0;
virtual bool
isBlocked() = 0;
virtual bool

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""
Generate C++ wrapper classes for XRP Ledger entry types from ledger_entries.macro.
This script parses the ledger_entries.macro file and generates type-safe wrapper
classes for each ledger entry type, similar to the transaction wrapper classes.
Uses pcpp to preprocess the macro file and pyparsing to parse the DSL.
"""
# cspell:words sfields
import io
import argparse
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
import pyparsing as pp
# Import common utilities
from macro_parser_common import CppCleaner, parse_sfields_macro, parse_field_list
def create_ledger_entry_parser():
"""Create a pyparsing parser for LEDGER_ENTRY macros.
This parser extracts the full LEDGER_ENTRY macro call and parses its arguments
using pyparsing's nesting-aware delimited list parsing.
"""
# Match the exact words
ledger_entry = pp.Keyword("LEDGER_ENTRY") | pp.Keyword("LEDGER_ENTRY_DUPLICATE")
# Define nested structures so pyparsing protects them
nested_braces = pp.original_text_for(pp.nested_expr("{", "}"))
nested_parens = pp.original_text_for(pp.nested_expr("(", ")"))
# Define standard text (anything that isn't a comma, parens, or braces)
plain_text = pp.Word(pp.printables + " \t\n", exclude_chars=",{}()")
# A single argument is any combination of the above
single_arg = pp.Combine(pp.OneOrMore(nested_braces | nested_parens | plain_text))
single_arg.set_parse_action(lambda t: t[0].strip())
# The arguments are a delimited list
args_list = pp.DelimitedList(single_arg)
# The full macro: LEDGER_ENTRY(args) or LEDGER_ENTRY_DUPLICATE(args)
macro_parser = (
ledger_entry + pp.Suppress("(") + pp.Group(args_list)("args") + pp.Suppress(")")
)
return macro_parser
def parse_ledger_entry_args(args_list):
"""Parse the arguments of a LEDGER_ENTRY macro call.
Args:
args_list: A list of parsed arguments from pyparsing, e.g.,
['ltACCOUNT_ROOT', '0x0061', 'AccountRoot', 'account', '({...})']
Returns:
A dict with parsed ledger entry information.
"""
if len(args_list) < 5:
raise ValueError(
f"Expected at least 5 parts in LEDGER_ENTRY, got {len(args_list)}: {args_list}"
)
tag = args_list[0]
value = args_list[1]
name = args_list[2]
rpc_name = args_list[3]
fields_str = args_list[4]
# Parse fields: ({field1, field2, ...})
fields = parse_field_list(fields_str)
return {
"tag": tag,
"value": value,
"name": name,
"rpc_name": rpc_name,
"fields": fields,
}
def parse_macro_file(file_path):
"""Parse the ledger_entries.macro file and return a list of ledger entry definitions.
Uses pcpp to preprocess the file and pyparsing to parse the LEDGER_ENTRY macros.
"""
with open(file_path, "r") as f:
c_code = f.read()
# Step 1: Clean the C++ code using pcpp
cleaner = CppCleaner("LEDGER_ENTRY_INCLUDE")
cleaner.parse(c_code)
out = io.StringIO()
cleaner.write(out)
clean_text = out.getvalue()
# Step 2: Parse the clean text using pyparsing
parser = create_ledger_entry_parser()
entries = []
for match, _, _ in parser.scan_string(clean_text):
# Extract the macro name and arguments
raw_args = match.args
# Parse the arguments
entry_data = parse_ledger_entry_args(raw_args)
entries.append(entry_data)
return entries
def generate_cpp_class(entry_info, header_dir, jinja_env, field_types):
"""Generate C++ header file for a ledger entry type."""
# Enrich field information with type data
for field in entry_info["fields"]:
field_name = field["name"]
if field_name in field_types:
field["typed"] = field_types[field_name]["typed"]
field["stiSuffix"] = field_types[field_name]["stiSuffix"]
field["typeData"] = field_types[field_name]["typeData"]
else:
# Unknown field - assume typed for safety
field["typed"] = True
field["stiSuffix"] = None
field["typeData"] = None
template = jinja_env.get_template("LedgerEntry.h.jinja2")
# Render the template
header_content = template.render(
name=entry_info["name"],
tag=entry_info["tag"],
value=entry_info["value"],
rpc_name=entry_info["rpc_name"],
fields=entry_info["fields"],
)
# Write header file
header_path = Path(header_dir) / f"{entry_info['name']}.h"
with open(header_path, "w") as f:
f.write(header_content)
print(f"Generated {header_path}")
def main():
parser = argparse.ArgumentParser(
description="Generate C++ ledger entry classes from ledger_entries.macro"
)
parser.add_argument("macro_path", help="Path to ledger_entries.macro")
parser.add_argument(
"--header-dir",
help="Output directory for header files",
default="include/xrpl/protocol/ledger_objects",
)
parser.add_argument(
"--sfields-macro",
help="Path to sfields.macro (default: auto-detect from macro_path)",
)
args = parser.parse_args()
# Auto-detect sfields.macro path if not provided
if args.sfields_macro:
sfields_path = Path(args.sfields_macro)
else:
# Assume sfields.macro is in the same directory as ledger_entries.macro
macro_path = Path(args.macro_path)
sfields_path = macro_path.parent / "sfields.macro"
# Parse sfields.macro to get field type information
print(f"Parsing {sfields_path}...")
field_types = parse_sfields_macro(sfields_path)
print(
f"Found {len(field_types)} field definitions ({sum(1 for f in field_types.values() if f['typed'])} typed, {sum(1 for f in field_types.values() if not f['typed'])} untyped)\n"
)
# Parse the file
entries = parse_macro_file(args.macro_path)
print(f"Found {len(entries)} ledger entries\n")
for entry in entries:
print(f"Ledger Entry: {entry['name']}")
print(f" Tag: {entry['tag']}")
print(f" Value: {entry['value']}")
print(f" RPC Name: {entry['rpc_name']}")
print(f" Fields: {len(entry['fields'])}")
for field in entry["fields"]:
mpt_info = f" ({field['mpt_support']})" if "mpt_support" in field else ""
print(f" - {field['name']}: {field['requirement']}{mpt_info}")
print()
# Set up Jinja2 environment
script_dir = Path(__file__).parent
template_dir = script_dir / "templates"
jinja_env = Environment(loader=FileSystemLoader(str(template_dir)))
# Generate C++ classes
header_dir = Path(args.header_dir)
header_dir.mkdir(parents=True, exist_ok=True)
for entry in entries:
generate_cpp_class(entry, header_dir, jinja_env, field_types)
print(f"\nGenerated {len(entries)} ledger entry classes")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,226 @@
#!/usr/bin/env python3
"""
Parse transactions.macro file to extract transaction information
and generate C++ classes for each transaction type.
Uses pcpp to preprocess the macro file and pyparsing to parse the DSL.
"""
# cspell:words sfields
import io
import argparse
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
import pyparsing as pp
# Import common utilities
from macro_parser_common import CppCleaner, parse_sfields_macro, parse_field_list
def create_transaction_parser():
"""Create a pyparsing parser for TRANSACTION macros.
This parser extracts the full TRANSACTION macro call and parses its arguments
using pyparsing's nesting-aware delimited list parsing.
"""
# Define nested structures so pyparsing protects them
nested_braces = pp.original_text_for(pp.nested_expr("{", "}"))
nested_parens = pp.original_text_for(pp.nested_expr("(", ")"))
# Define standard text (anything that isn't a comma, parens, or braces)
plain_text = pp.Word(pp.printables + " \t\n", exclude_chars=",{}()")
# A single argument is any combination of the above
single_arg = pp.Combine(pp.OneOrMore(nested_braces | nested_parens | plain_text))
single_arg.set_parse_action(lambda t: t[0].strip())
# The arguments are a delimited list
args_list = pp.DelimitedList(single_arg)
# The full macro: TRANSACTION(args)
macro_parser = (
pp.Keyword("TRANSACTION")
+ pp.Suppress("(")
+ pp.Group(args_list)("args")
+ pp.Suppress(")")
)
return macro_parser
def parse_transaction_args(args_list):
"""Parse the arguments of a TRANSACTION macro call.
Args:
args_list: A list of parsed arguments from pyparsing, e.g.,
['ttPAYMENT', '0', 'Payment', 'Delegation::delegable',
'uint256{}', 'createAcct', '({...})']
Returns:
A dict with parsed transaction information.
"""
if len(args_list) < 7:
raise ValueError(
f"Expected at least 7 parts in TRANSACTION, got {len(args_list)}: {args_list}"
)
tag = args_list[0]
value = args_list[1]
name = args_list[2]
delegable = args_list[3]
amendments = args_list[4]
privileges = args_list[5]
fields_str = args_list[6]
# Parse fields: ({field1, field2, ...})
fields = parse_field_list(fields_str)
return {
"tag": tag,
"value": value,
"name": name,
"delegable": delegable,
"amendments": amendments,
"privileges": privileges,
"fields": fields,
}
def parse_macro_file(filepath):
"""Parse the transactions.macro file.
Uses pcpp to preprocess the file and pyparsing to parse the TRANSACTION macros.
"""
with open(filepath, "r") as f:
c_code = f.read()
# Step 1: Clean the C++ code using pcpp
cleaner = CppCleaner("TRANSACTION_INCLUDE")
cleaner.parse(c_code)
out = io.StringIO()
cleaner.write(out)
clean_text = out.getvalue()
# Step 2: Parse the clean text using pyparsing
parser = create_transaction_parser()
transactions = []
for match, _, _ in parser.scan_string(clean_text):
# Extract the macro name and arguments
raw_args = match.args
# Parse the arguments
tx_data = parse_transaction_args(raw_args)
transactions.append(tx_data)
return transactions
def generate_cpp_class(tx_info, header_dir, jinja_env, field_types):
"""Generate a header-only template class for a transaction using Jinja2 templates."""
class_name = tx_info["name"]
# Enrich field information with type data
for field in tx_info["fields"]:
field_name = field["name"]
if field_name in field_types:
field["typed"] = field_types[field_name]["typed"]
field["stiSuffix"] = field_types[field_name]["stiSuffix"]
field["typeData"] = field_types[field_name]["typeData"]
else:
# Unknown field - assume typed for safety
field["typed"] = True
field["stiSuffix"] = None
field["typeData"] = None
# Load template
header_template = jinja_env.get_template("Transaction.h.jinja2")
# Render header
header_content = header_template.render(tx_info)
header_path = header_dir / f"{class_name}.h"
with open(header_path, "w") as f:
f.write(header_content)
return header_path
# TransactionBase is a static file in the repository at:
# - include/xrpl/protocol/TransactionBase.h
# - src/libxrpl/protocol/TransactionBase.cpp
# It is NOT generated by this script.
def main():
parser = argparse.ArgumentParser(
description="Generate C++ transaction classes from transactions.macro"
)
parser.add_argument("macro_path", help="Path to transactions.macro")
parser.add_argument(
"--header-dir",
help="Output directory for header files",
default="include/xrpl/protocol/transactions",
)
parser.add_argument(
"--sfields-macro",
help="Path to sfields.macro (default: auto-detect from macro_path)",
)
args = parser.parse_args()
# Auto-detect sfields.macro path if not provided
if args.sfields_macro:
sfields_path = Path(args.sfields_macro)
else:
# Assume sfields.macro is in the same directory as transactions.macro
macro_path = Path(args.macro_path)
sfields_path = macro_path.parent / "sfields.macro"
# Parse sfields.macro to get field type information
print(f"Parsing {sfields_path}...")
field_types = parse_sfields_macro(sfields_path)
print(
f"Found {len(field_types)} field definitions ({sum(1 for f in field_types.values() if f['typed'])} typed, {sum(1 for f in field_types.values() if not f['typed'])} untyped)\n"
)
# Parse the file
transactions = parse_macro_file(args.macro_path)
print(f"Found {len(transactions)} transactions\n")
for tx in transactions:
print(f"Transaction: {tx['name']}")
print(f" Tag: {tx['tag']}")
print(f" Value: {tx['value']}")
print(f" Fields: {len(tx['fields'])}")
for field in tx["fields"]:
print(f" - {field['name']}: {field['requirement']}")
print()
# Set up output directory
header_dir = Path(args.header_dir)
header_dir.mkdir(parents=True, exist_ok=True)
print(f"\nGenerating header-only template classes...")
print(f" Headers: {header_dir}\n")
# Set up Jinja2 environment
script_dir = Path(__file__).parent
template_dir = script_dir / "templates"
jinja_env = Environment(loader=FileSystemLoader(template_dir))
generated_files = []
for tx_info in transactions:
header_path = generate_cpp_class(tx_info, header_dir, jinja_env, field_types)
generated_files.append(header_path)
print(f" Generated: {tx_info['name']}.h")
print(
f"\n✓ Successfully generated {len(transactions)} transaction classes ({len(generated_files)} header files)"
)
print(f" Headers: {header_dir.absolute()}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""
Common utilities for parsing XRP Ledger macro files.
This module provides shared functionality for parsing transactions.macro
and ledger_entries.macro files using pcpp and pyparsing.
"""
# cspell:words sfields
import re
import pyparsing as pp
from pcpp import Preprocessor
class CppCleaner(Preprocessor):
"""C preprocessor that removes C++ noise while preserving macro calls."""
def __init__(self, macro_include_name):
"""
Initialize the preprocessor.
Args:
macro_include_name: The name of the include flag to set to 0
(e.g., "TRANSACTION_INCLUDE" or "LEDGER_ENTRY_INCLUDE")
"""
super(CppCleaner, self).__init__()
# Define flags so #if blocks evaluate correctly
# We set the include flag to 0 so includes are skipped
self.define(f"{macro_include_name} 0")
# Suppress line directives
self.line_directive = None
def on_error(self, file, line, msg):
# Ignore #error directives
pass
def on_include_not_found(
self, is_malformed, is_system_include, curdir, includepath
):
# Ignore missing headers
pass
def parse_sfields_macro(sfields_path):
"""
Parse sfields.macro to determine which fields are typed vs untyped.
Returns a dict mapping field names to their type information:
{
'sfMemos': {'typed': False, 'stiSuffix': 'ARRAY', 'typeData': {...}},
'sfAmount': {'typed': True, 'stiSuffix': 'AMOUNT', 'typeData': {...}},
...
}
"""
# Mapping from STI suffix to C++ type for untyped fields
UNTYPED_TYPE_MAP = {
"ARRAY": {
"getter_method": "getFieldArray",
"setter_method": "setFieldArray",
"setter_use_brackets": False,
"setter_type": "STArray const&",
"return_type": "STArray const&",
"return_type_optional": "std::optional<std::reference_wrapper<STArray const>>",
},
"OBJECT": {
"getter_method": "getFieldObject",
"setter_method": "setFieldObject",
"setter_use_brackets": False,
"setter_type": "STObject const&",
"return_type": "STObject",
"return_type_optional": "std::optional<STObject>",
},
"PATHSET": {
"getter_method": "getFieldPathSet",
"setter_method": "setFieldPathSet",
"setter_use_brackets": False,
"setter_type": "STPathSet const&",
"return_type": "STPathSet const&",
"return_type_optional": "std::optional<std::reference_wrapper<STPathSet const>>",
},
}
field_info = {}
with open(sfields_path, "r") as f:
content = f.read()
# Parse TYPED_SFIELD entries
# Format: TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...)
typed_pattern = r"TYPED_SFIELD\s*\(\s*(\w+)\s*,\s*(\w+)\s*,"
for match in re.finditer(typed_pattern, content):
field_name = match.group(1)
sti_suffix = match.group(2)
field_info[field_name] = {
"typed": True,
"stiSuffix": sti_suffix,
"typeData": {
"getter_method": "at",
"setter_method": "",
"setter_use_brackets": True,
"setter_type": f"SF_{sti_suffix}::type::value_type const&",
"return_type": f"SF_{sti_suffix}::type::value_type",
"return_type_optional": f"std::optional<SF_{sti_suffix}::type::value_type>",
},
}
# Parse UNTYPED_SFIELD entries
# Format: UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...)
untyped_pattern = r"UNTYPED_SFIELD\s*\(\s*(\w+)\s*,\s*(\w+)\s*,"
for match in re.finditer(untyped_pattern, content):
field_name = match.group(1)
sti_suffix = match.group(2)
type_data = UNTYPED_TYPE_MAP.get(
sti_suffix, UNTYPED_TYPE_MAP.get("OBJECT")
) # Default to OBJECT
field_info[field_name] = {
"typed": False,
"stiSuffix": sti_suffix,
"typeData": type_data,
}
return field_info
def create_field_list_parser():
"""Create a pyparsing parser for field lists like '({...})'."""
# A field identifier (e.g., sfDestination, soeREQUIRED, soeMPTSupported)
field_identifier = pp.Word(pp.alphas + "_", pp.alphanums + "_")
# A single field definition: {sfName, soeREQUIRED, ...}
# Allow optional trailing comma inside the braces
field_def = (
pp.Suppress("{")
+ pp.Group(pp.DelimitedList(field_identifier) + pp.Optional(pp.Suppress(",")))(
"parts"
)
+ pp.Suppress("}")
)
# The field list: ({field1, field2, ...}) or ({}) for empty lists
# Allow optional trailing comma after the last field definition
field_list = (
pp.Suppress("(")
+ pp.Suppress("{")
+ pp.Group(
pp.Optional(pp.DelimitedList(field_def) + pp.Optional(pp.Suppress(",")))
)("fields")
+ pp.Suppress("}")
+ pp.Suppress(")")
)
return field_list
def parse_field_list(fields_str):
"""Parse a field list string like '({...})' using pyparsing.
Args:
fields_str: A string like '({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported}
})'
Returns:
A list of field dicts with 'name', 'requirement', 'flags', and 'supports_mpt'.
"""
parser = create_field_list_parser()
try:
result = parser.parse_string(fields_str, parse_all=True)
fields = []
for field_parts in result.fields:
if len(field_parts) < 2:
continue
field_name = field_parts[0]
requirement = field_parts[1]
flags = list(field_parts[2:]) if len(field_parts) > 2 else []
supports_mpt = "soeMPTSupported" in flags
fields.append(
{
"name": field_name,
"requirement": requirement,
"flags": flags,
"supports_mpt": supports_mpt,
}
)
return fields
except pp.ParseException as e:
raise ValueError(f"Failed to parse field list: {e}")

14
scripts/requirements.txt Normal file
View File

@@ -0,0 +1,14 @@
# Python dependencies for XRP Ledger code generation scripts
#
# These packages are required to run the code generation scripts that
# parse macro files and generate C++ wrapper classes.
# cspell:words pyparsing
# C preprocessor for Python - used to preprocess macro files
pcpp>=1.30
# Parser combinator library - used to parse the macro DSL
pyparsing>=3.0.0
# Template engine - used to generate C++ code from templates
Jinja2>=3.0.0

View File

@@ -0,0 +1,176 @@
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
// Forward declaration
class {{ name }}Builder;
/**
* Ledger Entry: {{ name }}
* Type: {{ tag }} ({{ value }})
* RPC Name: {{ rpc_name }}
*
* Immutable wrapper around SLE providing type-safe field access.
* Use {{ name }}Builder to construct new ledger entries.
*/
class {{ name }} : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = {{ tag }};
/**
* Construct a {{ name }} ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit {{ name }}(SLE const& sle)
: LedgerEntryBase(sle)
{
// Verify ledger entry type
if (sle.getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for {{ name }}");
}
}
// Ledger entry-specific field getters
{%- for field in fields %}
{%- if field.typed %}
/**
* Get {{ field.name }} ({{ field.requirement }})
{%- if field.mpt_support %}
* MPT Support: {{ field.mpt_support }}
{%- endif %}
*/
{%- if field.requirement == 'soeREQUIRED' %}
[[nodiscard]]
{{ field.typeData.return_type }}
get{{ field.name[2:] }}() const
{
return this->sle_.{{ field.typeData.getter_method }}({{ field.name }});
}
{%- else %}
[[nodiscard]]
{{ field.typeData.return_type_optional }}
get{{ field.name[2:] }}() const
{
if (has{{ field.name[2:] }}())
return this->sle_.{{ field.typeData.getter_method }}({{ field.name }});
return std::nullopt;
}
[[nodiscard]]
bool
has{{ field.name[2:] }}() const
{
return this->sle_.isFieldPresent({{ field.name }});
}
{%- endif %}
{%- else %}
/**
* Get {{ field.name }} ({{ field.requirement }})
{%- if field.mpt_support %}
* MPT Support: {{ field.mpt_support }}
{%- endif %}
* Note: This is an untyped field ({{ field.cppType }}).
*/
{%- if field.requirement == 'soeREQUIRED' %}
[[nodiscard]]
{{ field.typeData.return_type }}
get{{ field.name[2:] }}() const
{
return this->sle_.{{ field.typeData.getter_method }}({{ field.name }});
}
{%- else %}
[[nodiscard]]
{{ field.typeData.return_type_optional }}
get{{ field.name[2:] }}() const
{
if (this->sle_.isFieldPresent({{ field.name }}))
return this->sle_.{{ field.typeData.getter_method }}({{ field.name }});
return std::nullopt;
}
[[nodiscard]]
bool
has{{ field.name[2:] }}() const
{
return this->sle_.isFieldPresent({{ field.name }});
}
{%- endif %}
{%- endif %}
{%- endfor %}
};
/**
* Builder for {{ name }} ledger entries.
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses Json::Value internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class {{ name }}Builder : public LedgerEntryBuilderBase<{{ name }}Builder>
{
public:
{{ name }}Builder()
{
// Initialize with ledger entry type
object_[sfLedgerEntryType] = {{ tag }};
}
{{ name }}Builder(SLE const& sle)
{
if (object_[sfLedgerEntryType] != {{ tag }})
{
throw std::runtime_error("Invalid ledger entry type for {{ name }}");
}
object_ = sle;
}
// Ledger entry-specific field setters
{%- for field in fields %}
/**
* Set {{ field.name }} ({{ field.requirement }})
{%- if field.mpt_support %}
* MPT Support: {{ field.mpt_support }}
{%- endif %}
* @return Reference to this builder for method chaining.
*/
{{ name }}Builder&
set{{ field.name[2:] }}({{ field.typeData.setter_type }} value)
{
{%- if field.stiSuffix == 'ISSUE' %}
object_[{{ field.name }}] = STIssue({{ field.name }}, value);
{%- elif field.typeData.setter_use_brackets %}
object_[{{ field.name }}] = value;
{%- else %}
object_.{{ field.typeData.setter_method }}({{ field.name }}, value);
{%- endif %}
return *this;
}
{%- endfor %}
/**
* Build and return the completed {{ name }} wrapper.
* @return The constructed ledger entry wrapper.
* @throws std::runtime_error if the JSON cannot be parsed into a valid ledger entry.
*/
{{ name }}
build(uint256 const& index)
{
return {{ name }}{SLE(object_, index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -0,0 +1,179 @@
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
// Forward declaration
class {{ name }}Builder;
/**
* Transaction: {{ name }}
* Type: {{ tag }} ({{ value }})
* Delegable: {{ delegable }}
* Amendment: {{ amendments }}
* Privileges: {{ privileges }}
*
* Immutable wrapper around STTx providing type-safe field access.
* Use {{ name }}Builder to construct new transactions.
*/
class {{ name }} : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = {{ tag }};
/**
* Construct a {{ name }} transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit {{ name }}(STTx const& tx)
: TransactionBase(tx)
{
// Verify transaction type
if (tx.getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for {{ name }}");
}
}
// Transaction-specific field getters
{%- for field in fields %}
{%- if field.typed %}
/**
* Get {{ field.name }} ({{ field.requirement }})
{%- if field.supports_mpt %}
* Note: This field supports MPT (Multi-Purpose Token) amounts.
{%- endif %}
*/
{%- if field.requirement == 'soeREQUIRED' %}
[[nodiscard]]
{{ field.typeData.return_type }}
get{{ field.name[2:] }}() const
{
return this->tx_.{{ field.typeData.getter_method }}({{ field.name }});
}
{%- else %}
[[nodiscard]]
{{ field.typeData.return_type_optional }}
get{{ field.name[2:] }}() const
{
if (has{{ field.name[2:] }}())
{
return this->tx_.{{ field.typeData.getter_method }}({{ field.name }});
}
return std::nullopt;
}
[[nodiscard]]
bool
has{{ field.name[2:] }}() const
{
return this->tx_.isFieldPresent({{ field.name }});
}
{%- endif %}
{%- else %}
/**
* Get {{ field.name }} ({{ field.requirement }})
{%- if field.supports_mpt %}
* Note: This field supports MPT (Multi-Purpose Token) amounts.
{%- endif %}
* Note: This is an untyped field
*/
{%- if field.requirement == 'soeREQUIRED' %}
[[nodiscard]]
{{ field.typeData.return_type }}
get{{ field.name[2:] }}() const
{
return this->tx_.{{ field.typeData.getter_method }}({{ field.name }});
}
{%- else %}
[[nodiscard]]
{{ field.typeData.return_type_optional }}
get{{ field.name[2:] }}() const
{
if (this->tx_.isFieldPresent({{ field.name }}))
return this->tx_.{{ field.typeData.getter_method }}({{ field.name }});
return std::nullopt;
}
[[nodiscard]]
bool
has{{ field.name[2:] }}() const
{
return this->tx_.isFieldPresent({{ field.name }});
}
{%- endif %}
{%- endif %}
{%- endfor %}
};
/**
* Builder for {{ name }} transactions.
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class {{ name }}Builder : public TransactionBuilderBase<{{ name }}Builder>
{
public:
{{ name }}Builder()
{
// Initialize with transaction type
object_[sfTransactionType] = {{ tag }};
}
{{ name }}Builder(STTx const& tx)
{
if (tx.getTxnType() != {{ tag }})
{
throw std::runtime_error("Invalid transaction type for {{ name }}Builder");
}
object_ = tx;
}
// Transaction-specific field setters
{%- for field in fields %}
/**
* Set {{ field.name }} ({{ field.requirement }})
{%- if field.supports_mpt %}
* Note: This field supports MPT (Multi-Purpose Token) amounts.
{%- endif %}
* @return Reference to this builder for method chaining.
*/
{{ name }}Builder&
set{{ field.name[2:] }}({{ field.typeData.setter_type }} value)
{
{%- if field.stiSuffix == 'ISSUE' %}
object_[{{ field.name }}] = STIssue({{ field.name }}, value);
{%- elif field.typeData.setter_use_brackets %}
object_[{{ field.name }}] = value;
{%- else %}
object_.{{ field.typeData.setter_method }}({{ field.name }}, value);
{%- endif %}
return *this;
}
{%- endfor %}
/**
* Build and return the completed {{ name }} wrapper.
* @return The constructed transaction wrapper.
* @throws std::runtime_error if the JSON cannot be parsed into a valid transaction.
*/
{{ name }}
build()
{
return {{ name }}(STTx(std::move(object_)));
}
};
} // namespace xrpl::transactions

View File

@@ -1,157 +0,0 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/MallocTrim.h>
#include <boost/predef.h>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <sstream>
#if defined(__GLIBC__) && BOOST_OS_LINUX
#include <sys/resource.h>
#include <malloc.h>
#include <unistd.h>
// Require RUSAGE_THREAD for thread-scoped page fault tracking
#ifndef RUSAGE_THREAD
#error "MallocTrim rusage instrumentation requires RUSAGE_THREAD on Linux/glibc"
#endif
namespace {
bool
getRusageThread(struct rusage& ru)
{
return ::getrusage(RUSAGE_THREAD, &ru) == 0; // LCOV_EXCL_LINE
}
} // namespace
#endif
namespace xrpl {
namespace detail {
// cSpell:ignore statm
#if defined(__GLIBC__) && BOOST_OS_LINUX
inline int
mallocTrimWithPad(std::size_t padBytes)
{
return ::malloc_trim(padBytes);
}
long
parseStatmRSSkB(std::string const& statm)
{
// /proc/self/statm format: size resident shared text lib data dt
// We want the second field (resident) which is in pages
std::istringstream iss(statm);
long size, resident;
if (!(iss >> size >> resident))
return -1;
// Convert pages to KB
long const pageSize = ::sysconf(_SC_PAGESIZE);
if (pageSize <= 0)
return -1;
return (resident * pageSize) / 1024;
}
#endif // __GLIBC__ && BOOST_OS_LINUX
} // namespace detail
MallocTrimReport
mallocTrim(std::string_view tag, beast::Journal journal)
{
// LCOV_EXCL_START
MallocTrimReport report;
#if !(defined(__GLIBC__) && BOOST_OS_LINUX)
JLOG(journal.debug()) << "malloc_trim not supported on this platform (tag=" << tag << ")";
#else
// Keep glibc malloc_trim padding at 0 (default): 12h Mainnet tests across 0/256KB/1MB/16MB
// showed no clear, consistent benefit from custom padding—0 provided the best overall balance
// of RSS reduction and trim-latency stability without adding a tuning surface.
constexpr std::size_t TRIM_PAD = 0;
report.supported = true;
if (journal.debug())
{
auto readFile = [](std::string const& path) -> std::string {
std::ifstream ifs(path, std::ios::in | std::ios::binary);
if (!ifs.is_open())
return {};
// /proc files are often not seekable; read as a stream.
std::ostringstream oss;
oss << ifs.rdbuf();
return oss.str();
};
std::string const tagStr{tag};
std::string const statmPath = "/proc/self/statm";
auto const statmBefore = readFile(statmPath);
long const rssBeforeKB = detail::parseStatmRSSkB(statmBefore);
struct rusage ru0{};
bool const have_ru0 = getRusageThread(ru0);
auto const t0 = std::chrono::steady_clock::now();
report.trimResult = detail::mallocTrimWithPad(TRIM_PAD);
auto const t1 = std::chrono::steady_clock::now();
struct rusage ru1{};
bool const have_ru1 = getRusageThread(ru1);
auto const statmAfter = readFile(statmPath);
long const rssAfterKB = detail::parseStatmRSSkB(statmAfter);
// Populate report fields
report.rssBeforeKB = rssBeforeKB;
report.rssAfterKB = rssAfterKB;
report.durationUs = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0);
if (have_ru0 && have_ru1)
{
report.minfltDelta = ru1.ru_minflt - ru0.ru_minflt;
report.majfltDelta = ru1.ru_majflt - ru0.ru_majflt;
}
std::int64_t const deltaKB = (rssBeforeKB < 0 || rssAfterKB < 0)
? 0
: (static_cast<std::int64_t>(rssAfterKB) - static_cast<std::int64_t>(rssBeforeKB));
JLOG(journal.debug()) << "malloc_trim tag=" << tagStr << " result=" << report.trimResult
<< " pad=" << TRIM_PAD << " bytes"
<< " rss_before=" << rssBeforeKB << "kB"
<< " rss_after=" << rssAfterKB << "kB"
<< " delta=" << deltaKB << "kB"
<< " duration_us=" << report.durationUs.count()
<< " minflt_delta=" << report.minfltDelta
<< " majflt_delta=" << report.majfltDelta;
}
else
{
report.trimResult = detail::mallocTrimWithPad(TRIM_PAD);
}
#endif
return report;
// LCOV_EXCL_STOP
}
} // namespace xrpl

View File

@@ -0,0 +1 @@
// This file is a placeholder to ensure the protocol_autogen module can be built.

View File

@@ -85,12 +85,7 @@ public:
}
virtual void
acquireAsync(
JobType type,
std::string const& name,
uint256 const& hash,
std::uint32_t seq,
InboundLedger::Reason reason) override
acquireAsync(uint256 const& hash, std::uint32_t seq, InboundLedger::Reason reason) override
{
}

View File

@@ -1,165 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2016 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/CanProcess.h>
#include <xrpl/beast/unit_test.h>
#include <memory>
namespace ripple {
namespace test {
struct CanProcess_test : beast::unit_test::suite
{
template <class Mutex, class Collection, class Item>
void
test(
std::string const& name,
Mutex& mtx,
Collection& collection,
std::vector<Item> const& items)
{
testcase(name);
if (!BEAST_EXPECT(!items.empty()))
return;
if (!BEAST_EXPECT(collection.empty()))
return;
// CanProcess objects can't be copied or moved. To make that easier,
// store shared_ptrs
std::vector<std::shared_ptr<CanProcess>> trackers;
// Fill up the vector with two CanProcess for each Item. The first
// inserts the item into the collection and is "good". The second does
// not and is "bad".
for (int i = 0; i < items.size(); ++i)
{
{
auto const& good =
trackers.emplace_back(std::make_shared<CanProcess>(mtx, collection, items[i]));
BEAST_EXPECT(*good);
}
BEAST_EXPECT(trackers.size() == (2 * i) + 1);
BEAST_EXPECT(collection.size() == i + 1);
{
auto const& bad =
trackers.emplace_back(std::make_shared<CanProcess>(mtx, collection, items[i]));
BEAST_EXPECT(!*bad);
}
BEAST_EXPECT(trackers.size() == 2 * (i + 1));
BEAST_EXPECT(collection.size() == i + 1);
}
BEAST_EXPECT(collection.size() == items.size());
// Now remove the items from the vector<CanProcess> two at a time, and
// try to get another CanProcess for that item.
for (int i = 0; i < items.size(); ++i)
{
// Remove the "bad" one in the second position
// This will have no effect on the collection
{
auto const iter = trackers.begin() + 1;
BEAST_EXPECT(!**iter);
trackers.erase(iter);
}
BEAST_EXPECT(trackers.size() == (2 * items.size()) - 1);
BEAST_EXPECT(collection.size() == items.size());
{
// Append a new "bad" one
auto const& bad =
trackers.emplace_back(std::make_shared<CanProcess>(mtx, collection, items[i]));
BEAST_EXPECT(!*bad);
}
BEAST_EXPECT(trackers.size() == 2 * items.size());
BEAST_EXPECT(collection.size() == items.size());
// Remove the "good" one from the front
{
auto const iter = trackers.begin();
BEAST_EXPECT(**iter);
trackers.erase(iter);
}
BEAST_EXPECT(trackers.size() == (2 * items.size()) - 1);
BEAST_EXPECT(collection.size() == items.size() - 1);
{
// Append a new "good" one
auto const& good =
trackers.emplace_back(std::make_shared<CanProcess>(mtx, collection, items[i]));
BEAST_EXPECT(*good);
}
BEAST_EXPECT(trackers.size() == 2 * items.size());
BEAST_EXPECT(collection.size() == items.size());
}
// Now remove them all two at a time
for (int i = items.size() - 1; i >= 0; --i)
{
// Remove the "bad" one from the front
{
auto const iter = trackers.begin();
BEAST_EXPECT(!**iter);
trackers.erase(iter);
}
BEAST_EXPECT(trackers.size() == (2 * i) + 1);
BEAST_EXPECT(collection.size() == i + 1);
// Remove the "good" one now in front
{
auto const iter = trackers.begin();
BEAST_EXPECT(**iter);
trackers.erase(iter);
}
BEAST_EXPECT(trackers.size() == 2 * i);
BEAST_EXPECT(collection.size() == i);
}
BEAST_EXPECT(trackers.empty());
BEAST_EXPECT(collection.empty());
}
void
run() override
{
{
std::mutex m;
std::set<int> collection;
std::vector<int> const items{1, 2, 3, 4, 5};
test("set of int", m, collection, items);
}
{
std::mutex m;
std::set<std::string> collection;
std::vector<std::string> const items{"one", "two", "three", "four", "five"};
test("set of string", m, collection, items);
}
{
std::mutex m;
std::unordered_set<char> collection;
std::vector<char> const items{'1', '2', '3', '4', '5'};
test("unorderd_set of char", m, collection, items);
}
{
std::mutex m;
std::unordered_set<std::uint64_t> collection;
std::vector<std::uint64_t> const items{100u, 1000u, 150u, 4u, 0u};
test("unordered_set of uint64_t", m, collection, items);
}
}
};
BEAST_DEFINE_TESTSUITE(CanProcess, ripple_basics, ripple);
} // namespace test
} // namespace ripple

View File

@@ -1,209 +0,0 @@
#include <xrpl/basics/MallocTrim.h>
#include <boost/predef.h>
#include <gtest/gtest.h>
using namespace xrpl;
// cSpell:ignore statm
#if defined(__GLIBC__) && BOOST_OS_LINUX
namespace xrpl::detail {
long
parseStatmRSSkB(std::string const& statm);
} // namespace xrpl::detail
#endif
TEST(MallocTrimReport, structure)
{
// Test default construction
MallocTrimReport report;
EXPECT_EQ(report.supported, false);
EXPECT_EQ(report.trimResult, -1);
EXPECT_EQ(report.rssBeforeKB, -1);
EXPECT_EQ(report.rssAfterKB, -1);
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
EXPECT_EQ(report.minfltDelta, -1);
EXPECT_EQ(report.majfltDelta, -1);
EXPECT_EQ(report.deltaKB(), 0);
// Test deltaKB calculation - memory freed
report.rssBeforeKB = 1000;
report.rssAfterKB = 800;
EXPECT_EQ(report.deltaKB(), -200);
// Test deltaKB calculation - memory increased
report.rssBeforeKB = 500;
report.rssAfterKB = 600;
EXPECT_EQ(report.deltaKB(), 100);
// Test deltaKB calculation - no change
report.rssBeforeKB = 1234;
report.rssAfterKB = 1234;
EXPECT_EQ(report.deltaKB(), 0);
}
#if defined(__GLIBC__) && BOOST_OS_LINUX
TEST(parseStatmRSSkB, standard_format)
{
using xrpl::detail::parseStatmRSSkB;
// Test standard format: size resident shared text lib data dt
// Assuming 4KB page size: resident=1000 pages = 4000 KB
{
std::string statm = "25365 1000 2377 0 0 5623 0";
long result = parseStatmRSSkB(statm);
// Note: actual result depends on system page size
// On most systems it's 4KB, so 1000 pages = 4000 KB
EXPECT_GT(result, 0);
}
// Test with newline
{
std::string statm = "12345 2000 1234 0 0 3456 0\n";
long result = parseStatmRSSkB(statm);
EXPECT_GT(result, 0);
}
// Test with tabs
{
std::string statm = "12345\t2000\t1234\t0\t0\t3456\t0";
long result = parseStatmRSSkB(statm);
EXPECT_GT(result, 0);
}
// Test zero resident pages
{
std::string statm = "25365 0 2377 0 0 5623 0";
long result = parseStatmRSSkB(statm);
EXPECT_EQ(result, 0);
}
// Test with extra whitespace
{
std::string statm = " 25365 1000 2377 ";
long result = parseStatmRSSkB(statm);
EXPECT_GT(result, 0);
}
// Test empty string
{
std::string statm = "";
long result = parseStatmRSSkB(statm);
EXPECT_EQ(result, -1);
}
// Test malformed data (only one field)
{
std::string statm = "25365";
long result = parseStatmRSSkB(statm);
EXPECT_EQ(result, -1);
}
// Test malformed data (non-numeric)
{
std::string statm = "abc def ghi";
long result = parseStatmRSSkB(statm);
EXPECT_EQ(result, -1);
}
// Test malformed data (second field non-numeric)
{
std::string statm = "25365 abc 2377";
long result = parseStatmRSSkB(statm);
EXPECT_EQ(result, -1);
}
}
#endif
TEST(mallocTrim, without_debug_logging)
{
beast::Journal journal{beast::Journal::getNullSink()};
MallocTrimReport report = mallocTrim("without_debug", journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
EXPECT_EQ(report.minfltDelta, -1);
EXPECT_EQ(report.majfltDelta, -1);
#else
EXPECT_EQ(report.supported, false);
EXPECT_EQ(report.trimResult, -1);
EXPECT_EQ(report.rssBeforeKB, -1);
EXPECT_EQ(report.rssAfterKB, -1);
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
EXPECT_EQ(report.minfltDelta, -1);
EXPECT_EQ(report.majfltDelta, -1);
#endif
}
TEST(mallocTrim, empty_tag)
{
beast::Journal journal{beast::Journal::getNullSink()};
MallocTrimReport report = mallocTrim("", journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
#else
EXPECT_EQ(report.supported, false);
#endif
}
TEST(mallocTrim, with_debug_logging)
{
struct DebugSink : public beast::Journal::Sink
{
DebugSink() : Sink(beast::severities::kDebug, false)
{
}
void
write(beast::severities::Severity, std::string const&) override
{
}
void
writeAlways(beast::severities::Severity, std::string const&) override
{
}
};
DebugSink sink;
beast::Journal journal{sink};
MallocTrimReport report = mallocTrim("debug_test", journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
EXPECT_GE(report.durationUs.count(), 0);
EXPECT_GE(report.minfltDelta, 0);
EXPECT_GE(report.majfltDelta, 0);
#else
EXPECT_EQ(report.supported, false);
EXPECT_EQ(report.trimResult, -1);
EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1});
EXPECT_EQ(report.minfltDelta, -1);
EXPECT_EQ(report.majfltDelta, -1);
#endif
}
TEST(mallocTrim, repeated_calls)
{
beast::Journal journal{beast::Journal::getNullSink()};
// Call malloc_trim multiple times to ensure it's safe
for (int i = 0; i < 5; ++i)
{
MallocTrimReport report = mallocTrim("iteration_" + std::to_string(i), journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
#else
EXPECT_EQ(report.supported, false);
#endif
}
}

View File

@@ -107,8 +107,10 @@ RCLConsensus::Adaptor::acquireLedger(LedgerHash const& hash)
// Tell the ledger acquire system that we need the consensus ledger
acquiringLedger_ = hash;
app_.getInboundLedgers().acquireAsync(
jtADVANCE, "GetConsL1", hash, 0, InboundLedger::Reason::CONSENSUS);
app_.getJobQueue().addJob(jtADVANCE, "GetConsL1", [id = hash, &app = app_, this]() {
JLOG(j_.debug()) << "JOB advanceLedger getConsensusLedger1 started";
app.getInboundLedgers().acquireAsync(id, 0, InboundLedger::Reason::CONSENSUS);
});
}
return std::nullopt;
}
@@ -983,7 +985,7 @@ void
RCLConsensus::Adaptor::updateOperatingMode(std::size_t const positions) const
{
if (!positions && app_.getOPs().isFull())
app_.getOPs().setMode(OperatingMode::CONNECTED, "updateOperatingMode: no positions");
app_.getOPs().setMode(OperatingMode::CONNECTED);
}
void

View File

@@ -117,8 +117,12 @@ RCLValidationsAdaptor::acquire(LedgerHash const& hash)
{
JLOG(j_.warn()) << "Need validated ledger for preferred ledger analysis " << hash;
app_.getInboundLedgers().acquireAsync(
jtADVANCE, "GetConsL2", hash, 0, InboundLedger::Reason::CONSENSUS);
Application* pApp = &app_;
app_.getJobQueue().addJob(jtADVANCE, "GetConsL2", [pApp, hash, this]() {
JLOG(j_.debug()) << "JOB advanceLedger getConsensusLedger2 started";
pApp->getInboundLedgers().acquireAsync(hash, 0, InboundLedger::Reason::CONSENSUS);
});
return std::nullopt;
}

View File

@@ -26,12 +26,7 @@ public:
// Queue. TODO review whether all callers of acquire() can use this
// instead. Inbound ledger acquisition is asynchronous anyway.
virtual void
acquireAsync(
JobType type,
std::string const& name,
uint256 const& hash,
std::uint32_t seq,
InboundLedger::Reason reason) = 0;
acquireAsync(uint256 const& hash, std::uint32_t seq, InboundLedger::Reason reason) = 0;
virtual std::shared_ptr<InboundLedger>
find(LedgerHash const& hash) = 0;

View File

@@ -353,14 +353,7 @@ InboundLedger::onTimer(bool wasProgress, ScopedLockType&)
if (!wasProgress)
{
if (checkLocal())
{
// Done. Something else (probably consensus) built the ledger
// locally while waiting for data (or possibly before requesting)
XRPL_ASSERT(isDone(), "ripple::InboundLedger::onTimer : done");
JLOG(journal_.info()) << "Finished while waiting " << hash_;
return;
}
checkLocal();
mByHash = true;

View File

@@ -2,9 +2,9 @@
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/main/Application.h>
#include <xrpl/basics/CanProcess.h>
#include <xrpl/basics/DecayingSample.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/scope.h>
#include <xrpl/beast/container/aged_map.h>
#include <xrpl/core/JobQueue.h>
#include <xrpl/core/PerfLog.h>
@@ -59,15 +59,12 @@ public:
(reason != InboundLedger::Reason::CONSENSUS))
return {};
std::stringstream ss;
bool isNew = true;
std::shared_ptr<InboundLedger> inbound;
{
ScopedLockType sl(mLock);
if (stopping_)
{
JLOG(j_.debug()) << "Abort(stopping): " << ss.str();
return {};
}
@@ -86,61 +83,47 @@ public:
++mCounter;
}
}
ss << " IsNew: " << (isNew ? "true" : "false");
if (inbound->isFailed())
{
JLOG(j_.debug()) << "Abort(failed): " << ss.str();
return {};
}
if (!isNew)
inbound->update(seq);
if (!inbound->isComplete())
{
JLOG(j_.debug()) << "InProgress: " << ss.str();
return {};
}
JLOG(j_.debug()) << "Complete: " << ss.str();
return inbound->getLedger();
};
using namespace std::chrono_literals;
return perf::measureDurationAndLog(doAcquire, "InboundLedgersImp::acquire", 500ms, j_);
std::shared_ptr<Ledger const> ledger =
perf::measureDurationAndLog(doAcquire, "InboundLedgersImp::acquire", 500ms, j_);
return ledger;
}
void
acquireAsync(
JobType type,
std::string const& name,
uint256 const& hash,
std::uint32_t seq,
InboundLedger::Reason reason) override
acquireAsync(uint256 const& hash, std::uint32_t seq, InboundLedger::Reason reason) override
{
if (auto check = std::make_shared<CanProcess const>(acquiresMutex_, pendingAcquires_, hash);
*check)
std::unique_lock lock(acquiresMutex_);
try
{
app_.getJobQueue().addJob(type, name, [check, name, hash, seq, reason, this]() {
JLOG(j_.debug()) << "JOB acquireAsync " << name << " started ";
try
{
acquire(hash, seq, reason);
}
catch (std::exception const& e)
{
JLOG(j_.warn()) << "Exception thrown for acquiring new "
"inbound ledger "
<< hash << ": " << e.what();
}
catch (...)
{
JLOG(j_.warn()) << "Unknown exception thrown for acquiring new "
"inbound ledger "
<< hash;
}
});
if (pendingAcquires_.contains(hash))
return;
pendingAcquires_.insert(hash);
scope_unlock unlock(lock);
acquire(hash, seq, reason);
}
catch (std::exception const& e)
{
JLOG(j_.warn()) << "Exception thrown for acquiring new inbound ledger " << hash << ": "
<< e.what();
}
catch (...)
{
JLOG(j_.warn()) << "Unknown exception thrown for acquiring new inbound ledger " << hash;
}
pendingAcquires_.erase(hash);
}
std::shared_ptr<InboundLedger>

View File

@@ -907,9 +907,8 @@ LedgerMaster::checkAccept(std::shared_ptr<Ledger const> const& ledger)
return;
}
JLOG(m_journal.info()) << "Advancing accepted ledger to " << ledger->header().seq << " ("
<< to_short_string(ledger->header().hash) << ") with >= " << minVal
<< " validations";
JLOG(m_journal.info()) << "Advancing accepted ledger to " << ledger->header().seq
<< " with >= " << minVal << " validations";
ledger->setValidated();
ledger->setFull();

View File

@@ -13,8 +13,7 @@ TimeoutCounter::TimeoutCounter(
QueueJobParameter&& jobParameter,
beast::Journal journal)
: app_(app)
, sink_(journal, to_short_string(hash) + " ")
, journal_(sink_)
, journal_(journal)
, hash_(hash)
, timeouts_(0)
, complete_(false)
@@ -34,7 +33,6 @@ TimeoutCounter::setTimer(ScopedLockType& sl)
{
if (isDone())
return;
JLOG(journal_.debug()) << "Setting timer for " << timerInterval_.count() << "ms";
timer_.expires_after(timerInterval_);
timer_.async_wait([wptr = pmDowncast()](boost::system::error_code const& ec) {
if (ec == boost::asio::error::operation_aborted)
@@ -42,10 +40,6 @@ TimeoutCounter::setTimer(ScopedLockType& sl)
if (auto ptr = wptr.lock())
{
JLOG(ptr->journal_.debug())
<< "timer: ec: " << ec
<< " (operation_aborted: " << boost::asio::error::operation_aborted << " - "
<< (ec == boost::asio::error::operation_aborted ? "aborted" : "other") << ")";
ScopedLockType sl(ptr->mtx_);
ptr->queueJob(sl);
}

View File

@@ -3,7 +3,6 @@
#include <xrpld/app/main/Application.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/core/Job.h>
#include <boost/asio/basic_waitable_timer.hpp>
@@ -104,7 +103,6 @@ protected:
// Used in this class for access to boost::asio::io_context and
// xrpl::Overlay. Used in subtypes for the kitchen sink.
Application& app_;
beast::WrappedSink sink_;
beast::Journal journal_;
mutable std::recursive_mutex mtx_;

View File

@@ -31,7 +31,6 @@
#include <xrpld/shamap/NodeFamily.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/MallocTrim.h>
#include <xrpl/basics/ResolverAsio.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/asio/io_latency_probe.h>
@@ -1054,8 +1053,6 @@ public:
<< "; size after: " << cachedSLEs_.size();
}
mallocTrim("doSweep", m_journal);
// Set timer to do another sweep later.
setSweepTimer();
}

View File

@@ -30,10 +30,10 @@
#include <xrpld/rpc/MPTokenIssuanceID.h>
#include <xrpld/rpc/ServerHandler.h>
#include <xrpl/basics/CanProcess.h>
#include <xrpl/basics/UptimeClock.h>
#include <xrpl/basics/mulDiv.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/basics/scope.h>
#include <xrpl/beast/utility/rngfill.h>
#include <xrpl/core/HashRouter.h>
#include <xrpl/core/NetworkIDService.h>
@@ -396,7 +396,7 @@ public:
isFull() override;
void
setMode(OperatingMode om, char const* reason) override;
setMode(OperatingMode om) override;
bool
isBlocked() override;
@@ -841,7 +841,7 @@ NetworkOPsImp::strOperatingMode(bool const admin /* = false */) const
inline void
NetworkOPsImp::setStandAlone()
{
setMode(OperatingMode::FULL, "setStandAlone");
setMode(OperatingMode::FULL);
}
inline void
@@ -984,7 +984,7 @@ NetworkOPsImp::processHeartbeatTimer()
{
if (mMode != OperatingMode::DISCONNECTED)
{
setMode(OperatingMode::DISCONNECTED, "Heartbeat: insufficient peers");
setMode(OperatingMode::DISCONNECTED);
std::stringstream ss;
ss << "Node count (" << numPeers << ") has fallen "
<< "below required minimum (" << minPeerCount_ << ").";
@@ -1008,7 +1008,7 @@ NetworkOPsImp::processHeartbeatTimer()
if (mMode == OperatingMode::DISCONNECTED)
{
setMode(OperatingMode::CONNECTED, "Heartbeat: sufficient peers");
setMode(OperatingMode::CONNECTED);
JLOG(m_journal.info()) << "Node count (" << numPeers << ") is sufficient.";
CLOG(clog.ss()) << "setting mode to CONNECTED based on " << numPeers << " peers. ";
}
@@ -1018,9 +1018,9 @@ NetworkOPsImp::processHeartbeatTimer()
auto origMode = mMode.load();
CLOG(clog.ss()) << "mode: " << strOperatingMode(origMode, true);
if (mMode == OperatingMode::SYNCING)
setMode(OperatingMode::SYNCING, "Heartbeat: check syncing");
setMode(OperatingMode::SYNCING);
else if (mMode == OperatingMode::CONNECTED)
setMode(OperatingMode::CONNECTED, "Heartbeat: check connected");
setMode(OperatingMode::CONNECTED);
auto newMode = mMode.load();
if (origMode != newMode)
{
@@ -1710,7 +1710,7 @@ void
NetworkOPsImp::setAmendmentBlocked()
{
amendmentBlocked_ = true;
setMode(OperatingMode::CONNECTED, "setAmendmentBlocked");
setMode(OperatingMode::CONNECTED);
}
inline bool
@@ -1741,7 +1741,7 @@ void
NetworkOPsImp::setUNLBlocked()
{
unlBlocked_ = true;
setMode(OperatingMode::CONNECTED, "setUNLBlocked");
setMode(OperatingMode::CONNECTED);
}
inline void
@@ -1837,7 +1837,7 @@ NetworkOPsImp::checkLastClosedLedger(Overlay::PeerSequence const& peerList, uint
if ((mMode == OperatingMode::TRACKING) || (mMode == OperatingMode::FULL))
{
setMode(OperatingMode::CONNECTED, "check LCL: not on consensus ledger");
setMode(OperatingMode::CONNECTED);
}
if (consensus)
@@ -1922,8 +1922,8 @@ NetworkOPsImp::beginConsensus(
// this shouldn't happen unless we jump ledgers
if (mMode == OperatingMode::FULL)
{
JLOG(m_journal.warn()) << "beginConsensus Don't have LCL, going to tracking";
setMode(OperatingMode::TRACKING, "beginConsensus: No LCL");
JLOG(m_journal.warn()) << "Don't have LCL, going to tracking";
setMode(OperatingMode::TRACKING);
CLOG(clog) << "beginConsensus Don't have LCL, going to tracking. ";
}
@@ -2052,7 +2052,7 @@ NetworkOPsImp::endConsensus(std::unique_ptr<std::stringstream> const& clog)
// validations we have for LCL. If the ledger is good enough, go to
// TRACKING - TODO
if (!needNetworkLedger_)
setMode(OperatingMode::TRACKING, "endConsensus: check tracking");
setMode(OperatingMode::TRACKING);
}
if (((mMode == OperatingMode::CONNECTED) || (mMode == OperatingMode::TRACKING)) &&
@@ -2065,7 +2065,7 @@ NetworkOPsImp::endConsensus(std::unique_ptr<std::stringstream> const& clog)
if (registry_.timeKeeper().now() <
(current->header().parentCloseTime + 2 * current->header().closeTimeResolution))
{
setMode(OperatingMode::FULL, "endConsensus: check full");
setMode(OperatingMode::FULL);
}
}
@@ -2077,7 +2077,7 @@ NetworkOPsImp::consensusViewChange()
{
if ((mMode == OperatingMode::FULL) || (mMode == OperatingMode::TRACKING))
{
setMode(OperatingMode::CONNECTED, "consensusViewChange");
setMode(OperatingMode::CONNECTED);
}
}
@@ -2379,7 +2379,7 @@ NetworkOPsImp::pubPeerStatus(std::function<Json::Value(void)> const& func)
}
void
NetworkOPsImp::setMode(OperatingMode om, char const* reason)
NetworkOPsImp::setMode(OperatingMode om)
{
using namespace std::chrono_literals;
if (om == OperatingMode::CONNECTED)
@@ -2399,12 +2399,11 @@ NetworkOPsImp::setMode(OperatingMode om, char const* reason)
if (mMode == om)
return;
auto const sink = om < mMode ? m_journal.warn() : m_journal.info();
mMode = om;
accounting_.mode(om);
JLOG(sink) << "STATE->" << strOperatingMode() << " - " << reason;
JLOG(m_journal.info()) << "STATE->" << strOperatingMode();
pubServer();
}
@@ -2413,24 +2412,32 @@ NetworkOPsImp::recvValidation(std::shared_ptr<STValidation> const& val, std::str
{
JLOG(m_journal.trace()) << "recvValidation " << val->getLedgerHash() << " from " << source;
std::unique_lock lock(validationsMutex_);
BypassAccept bypassAccept = BypassAccept::no;
try
{
CanProcess const check(validationsMutex_, pendingValidations_, val->getLedgerHash());
try
{
BypassAccept bypassAccept = check ? BypassAccept::no : BypassAccept::yes;
handleNewValidation(registry_.app(), val, source, bypassAccept, m_journal);
}
catch (std::exception const& e)
{
JLOG(m_journal.warn()) << "Exception thrown for handling new validation "
<< val->getLedgerHash() << ": " << e.what();
}
catch (...)
{
JLOG(m_journal.warn())
<< "Unknown exception thrown for handling new validation " << val->getLedgerHash();
}
if (pendingValidations_.contains(val->getLedgerHash()))
bypassAccept = BypassAccept::yes;
else
pendingValidations_.insert(val->getLedgerHash());
scope_unlock unlock(lock);
handleNewValidation(registry_.app(), val, source, bypassAccept, m_journal);
}
catch (std::exception const& e)
{
JLOG(m_journal.warn()) << "Exception thrown for handling new validation "
<< val->getLedgerHash() << ": " << e.what();
}
catch (...)
{
JLOG(m_journal.warn()) << "Unknown exception thrown for handling new validation "
<< val->getLedgerHash();
}
if (bypassAccept == BypassAccept::no)
{
pendingValidations_.erase(val->getLedgerHash());
}
lock.unlock();
pubValidation(val);