Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,7 @@ DerivedData

# clangd cache
/.cache

# Auto-generated protocol wrapper classes (generated at CMake configure time)
/include/xrpl/protocol_autogen/transactions/
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be a good idea to check those files in

/include/xrpl/protocol_autogen/ledger_objects/
13 changes: 11 additions & 2 deletions cmake/XrplCore.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -140,6 +148,7 @@ target_link_modules(
net
nodestore
protocol
protocol_autogen
rdb
resource
server
Expand Down
1 change: 1 addition & 0 deletions cmake/XrplInstall.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
132 changes: 132 additions & 0 deletions cmake/XrplProtocolAutogen.cmake
Original file line number Diff line number Diff line change
@@ -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")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to have: may be worth making it a build option


# 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")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if it builds if there's no network connection

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 ()
133 changes: 133 additions & 0 deletions include/xrpl/protocol_autogen/LedgerEntryBase.h
Original file line number Diff line number Diff line change
@@ -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
75 changes: 75 additions & 0 deletions include/xrpl/protocol_autogen/LedgerEntryBuilderBase.h
Original file line number Diff line number Diff line change
@@ -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
Loading