Merge remote-tracking branch 'origin/develop' into ripple/wasmi

This commit is contained in:
Peng Wang
2026-08-09 14:34:11 -04:00
207 changed files with 16719 additions and 12061 deletions

View File

@@ -132,6 +132,7 @@ words:
- godexsoft
- gpgcheck
- gpgkey
- Hinnant
- hotwallet
- hwaddress
- hwrap
@@ -165,6 +166,7 @@ words:
- llection
- LOCALGOOD
- logwstream
- Lombrozo
- lseq
- lsmf
- ltype
@@ -201,6 +203,7 @@ words:
- nftokens
- nftpage
- nikb
- Nikolaos
- nixfmt
- nixos
- nixpkgs

View File

@@ -33,6 +33,10 @@ def get_cmake_args(build_type: str, extra_args: str) -> str:
# Every config must declare 'minimal'. Minimal configs form the reduced matrix
# built for pull requests by default; the full matrix adds the rest. Packaging
# configs declare it too, but packaging is gated in the workflow, not by it.
#
# Configs may also opt into 'benchmark' to smoke-run the benchmarks. Note that
# the flag applies to every entry a config expands into, so only set it on
# configs that expand to a single combination.
@dataclasses.dataclass
@@ -43,6 +47,7 @@ class LinuxConfig:
build_type: list[str]
arch: list[str]
minimal: bool
benchmark: bool = False # if true, smoke-run the benchmarks after testing
sanitizers: list[str] = dataclasses.field(default_factory=list)
suffix: str = ""
extra_cmake_args: str = ""
@@ -81,6 +86,7 @@ class PlatformConfig:
build_type: list[str]
minimal: bool
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
benchmark: bool = False # if true, smoke-run the benchmarks after testing
extra_cmake_args: str = ""
def __post_init__(self) -> None:
@@ -125,6 +131,7 @@ class MatrixEntry:
cmake_args: str
cmake_target: str
build_only: bool
benchmark: bool
build_type: str
architecture: Architecture
sanitizers: str
@@ -136,7 +143,8 @@ class MatrixEntry:
class PackagingEntry:
"""One entry in the generated packaging strategy matrix."""
artifact_name: str
xrpld_artifact_name: str
validator_keys_artifact_name: str
image: str
distro: str # e.g. "debian" or "rhel"; drives package-format-specific steps
@@ -193,6 +201,7 @@ def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
cmake_target="all",
build_only=False,
benchmark=cfg.benchmark,
build_type=build_type,
architecture=arch_info,
sanitizers=sanitizer,
@@ -210,14 +219,19 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
the nix-based build images, because deb/rpm tooling (debhelper, rpm-build)
is taken from the distro's archive rather than from nixpkgs. Each config
entry carries its own 'image'.
The artifact names must match what the build job uploads: one artifact per
binary, each named after the build config.
"""
entries = []
for distro, configs in linux.package_configs.items():
for cfg in configs:
for compiler, build_type in itertools.product(cfg.compiler, cfg.build_type):
config_name = f"{distro}-{compiler}-{build_type.lower()}-amd64"
entries.append(
PackagingEntry(
artifact_name=f"xrpld-{distro}-{compiler}-{build_type.lower()}-amd64",
xrpld_artifact_name=f"xrpld-{config_name}",
validator_keys_artifact_name=f"validator-keys-{config_name}",
image=cfg.image,
distro=distro,
)
@@ -245,6 +259,7 @@ def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
cmake_target="install" if is_windows else "all",
build_only=cfg.build_only,
benchmark=cfg.benchmark,
build_type=build_type,
architecture=Architecture(platform=pf.platform, runner=pf.runner),
sanitizers="",

View File

@@ -14,7 +14,8 @@
"compiler": ["clang"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": true
"minimal": true,
"benchmark": true
},
{
@@ -69,7 +70,8 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false
"minimal": false,
"extra_cmake_args": "-Dvalidator_keys=ON"
}
],
@@ -78,7 +80,8 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false
"minimal": false,
"extra_cmake_args": "-Dvalidator_keys=ON"
}
]
},

View File

@@ -82,6 +82,7 @@ jobs:
.github/scripts/strategy-matrix/**
.github/workflows/reusable-build-test-config.yml
.github/workflows/reusable-build-test.yml
.github/workflows/reusable-check-autogen.yml
.github/workflows/reusable-clang-tidy.yml
.github/workflows/reusable-package.yml
.github/workflows/reusable-strategy-matrix.yml
@@ -126,6 +127,11 @@ jobs:
outputs:
go: ${{ steps.go.outputs.go == 'true' }}
check-autogen:
needs: should-run
if: ${{ needs.should-run.outputs.go == 'true' }}
uses: ./.github/workflows/reusable-check-autogen.yml
check-levelization:
needs: should-run
if: ${{ needs.should-run.outputs.go == 'true' }}
@@ -200,6 +206,7 @@ jobs:
passed:
if: failure() || cancelled()
needs:
- check-autogen
- check-levelization
- check-rename
- clang-tidy

View File

@@ -20,6 +20,7 @@ on:
- ".github/scripts/strategy-matrix/**"
- ".github/workflows/reusable-build-test-config.yml"
- ".github/workflows/reusable-build-test.yml"
- ".github/workflows/reusable-check-autogen.yml"
- ".github/workflows/reusable-clang-tidy.yml"
- ".github/workflows/reusable-package.yml"
- ".github/workflows/reusable-strategy-matrix.yml"
@@ -67,6 +68,9 @@ defaults:
shell: bash
jobs:
check-autogen:
uses: ./.github/workflows/reusable-check-autogen.yml
clang-tidy:
uses: ./.github/workflows/reusable-clang-tidy.yml
permissions:

View File

@@ -3,6 +3,12 @@ name: Build and test configuration
on:
workflow_call:
inputs:
benchmark:
description: "Whether to smoke-run the benchmarks after testing."
required: false
type: boolean
default: false
build_only:
description: 'Whether to only build or to build and test the code ("true", "false").'
required: true
@@ -100,9 +106,10 @@ jobs:
# header files are copied into separate directories by CMake, which will
# otherwise result in cache misses.
CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime
# Determine if coverage and voidstar should be enabled.
# Determine if coverage, voidstar and validator-keys should be enabled.
COVERAGE_ENABLED: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }}
VOIDSTAR_ENABLED: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }}
VALIDATOR_KEYS_ENABLED: ${{ contains(inputs.cmake_args, '-Dvalidator_keys=ON') }}
SANITIZERS_ENABLED: ${{ inputs.sanitizers != '' }}
steps:
- name: Cleanup workspace (macOS and Windows)
@@ -170,9 +177,9 @@ jobs:
..
# Export the sanitizer options before any instrumented binary runs. The
# protocol code-gen and build steps below invoke instrumented dependency
# tools (protoc, grpc), so setting UBSAN_OPTIONS here lets the UBSan
# suppression list silence their diagnostics too, not just at test time.
# build step below invokes instrumented dependency tools (protoc, grpc),
# so setting UBSAN_OPTIONS here lets the UBSan suppression list silence
# their diagnostics too, not just at test time.
# GITHUB_WORKSPACE (not the github.workspace context) is used so the path
# resolves correctly inside the container job.
- name: Set sanitizer options
@@ -190,32 +197,6 @@ jobs:
echo "UBSAN_OPTIONS=include=${SUPP}/runtime-ubsan-options.txt:suppressions=${SUPP}/ubsan.supp" >>${GITHUB_ENV}
echo "LSAN_OPTIONS=include=${SUPP}/runtime-lsan-options.txt:suppressions=${SUPP}/lsan.supp" >>${GITHUB_ENV}
- name: Check protocol autogen files are up-to-date
working-directory: ${{ env.BUILD_DIR }}
env:
MESSAGE: |
The generated protocol wrapper classes are out of date.
This typically happens when the macro files or generator scripts
have changed but the generated files were not regenerated.
To fix this:
1. Run: cmake --build . --target setup_code_gen
2. Run: cmake --build . --target code_gen
3. Commit and push the regenerated files
run: |
set -e
cmake --build . --target setup_code_gen
cmake --build . --target code_gen
DIFF=$(git -C .. status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen)
if [ -n "${DIFF}" ]; then
echo "::error::Generated protocol files are out of date"
git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen
echo "${MESSAGE}"
exit 1
fi
- name: Build the binary
working-directory: ${{ env.BUILD_DIR }}
env:
@@ -249,6 +230,22 @@ jobs:
retention-days: 3
if-no-files-found: error
- name: Run the validator-keys tests
if: ${{ env.VALIDATOR_KEYS_ENABLED == 'true' }}
working-directory: ${{ env.BUILD_DIR }}
run: ./validator-keys --unittest
- name: Upload the validator-keys binary
if: ${{ github.event.repository.visibility == 'public' && env.VALIDATOR_KEYS_ENABLED == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: validator-keys-${{ inputs.config_name }}
path: |
${{ env.BUILD_DIR }}/validator-keys
${{ env.BUILD_DIR }}/validator-keys-LICENSE
retention-days: 3
if-no-files-found: error
- name: Upload the test binary (Linux)
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@@ -328,11 +325,14 @@ jobs:
# Smoke-run every benchmark module with a single repetition to confirm the
# benchmarks still build and execute. This is a correctness check, not a
# performance measurement, so it is skipped for instrumented builds
# (sanitizers/coverage/voidstar), where it would be slow and meaningless,
# and on Windows, where the `install` target does not build them.
# performance measurement, so there is nothing to gain from repeating it
# across configurations: it is opted into by a single config in the
# strategy matrix (see the 'benchmark' flag in the JSON files), which
# keeps it off instrumented builds (sanitizers/coverage/voidstar), where
# it would be slow and meaningless, off Debug builds, where it is much
# slower, and off Windows, where the `install` target does not build them.
- name: Run the benchmarks
if: ${{ !inputs.build_only && runner.os != 'Windows' && env.SANITIZERS_ENABLED == 'false' && env.COVERAGE_ENABLED != 'true' && env.VOIDSTAR_ENABLED != 'true' }}
if: ${{ inputs.benchmark }}
working-directory: ${{ env.BUILD_DIR }}
run: |
rc=0
@@ -387,7 +387,7 @@ jobs:
--target coverage
- name: Upload coverage report
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
disable_search: true

View File

@@ -40,6 +40,7 @@ jobs:
fail-fast: ${{ github.event_name == 'merge_group' }}
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
with:
benchmark: ${{ matrix.benchmark }}
build_only: ${{ matrix.build_only }}
build_type: ${{ matrix.build_type }}
ccache_enabled: ${{ inputs.ccache_enabled }}

View File

@@ -0,0 +1,76 @@
# This workflow checks that the generated protocol wrapper classes are
# up-to-date with the macro files and generator scripts they are produced from,
# see more info in include/xrpl/protocol_autogen/README.md.
name: Check autogen
# This workflow can only be triggered by other workflows.
on: workflow_call
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-autogen
cancel-in-progress: true
defaults:
run:
shell: bash
env:
BUILD_DIR: build/codegen
jobs:
autogen:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
# Code generation is pure Python, so the standalone project below offers
# the same targets as the main build without needing its dependencies or
# a compiler, which keeps this job down to a few seconds.
- name: Configure CMake
run: cmake -S cmake/codegen -B "${BUILD_DIR}"
- name: Install code generation dependencies
run: cmake --build "${BUILD_DIR}" --target setup_code_gen
- name: Generate code
run: cmake --build "${BUILD_DIR}" --target code_gen
- name: Check for differences
env:
MESSAGE: |
The generated protocol wrapper classes are out of date.
This typically happens when the macro files or generator scripts
have changed but the generated files were not regenerated.
Run the following from the repository root, then commit and push
the regenerated files. This needs neither the dependencies nor a
compiler. See include/xrpl/protocol_autogen/README.md for more info.
cmake -S cmake/codegen -B build/codegen
cmake --build build/codegen --target setup_code_gen
cmake --build build/codegen --target code_gen
In an already configured build directory, the 'setup_code_gen' and
'code_gen' targets do the same thing.
run: |
# Record untracked files in the index without staging their contents,
# so that classes generated for a newly added transaction or ledger
# entry type show up in the diff below rather than silently as an
# empty one.
git add --intent-to-add .
DIFF=$(git status --porcelain)
if [ -n "${DIFF}" ]; then
# Print the differences to give the contributor a hint about what to
# expect when running code generation on their own machine.
git diff
echo "${MESSAGE}"
exit 1
fi

View File

@@ -1,7 +1,7 @@
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
# Discovers which configurations to package from linux.json (configs in
# "package_configs") and fans out one job per distro. Only linux/amd64 is
# supported; the runner is hardcoded in the job below.
# Build Linux packages (DEB and RPM) from pre-built binary artifacts (xrpld and
# validator-keys). Discovers which configurations to package from linux.json
# (configs in "package_configs") and fans out one job per distro. Only
# linux/amd64 is supported; the runner is hardcoded in the job below.
name: Package
on:
@@ -45,7 +45,7 @@ jobs:
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
name: "${{ matrix.artifact_name }}"
name: "${{ matrix.xrpld_artifact_name }}"
permissions:
contents: read
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
@@ -56,14 +56,20 @@ jobs:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download pre-built binary
- name: Download pre-built xrpld binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.artifact_name }}
name: ${{ matrix.xrpld_artifact_name }}
path: ${{ env.BUILD_DIR }}
- name: Make binary executable
run: chmod +x "${BUILD_DIR}/xrpld"
- name: Download pre-built validator-keys binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.validator_keys_artifact_name }}
path: ${{ env.BUILD_DIR }}
- name: Make binaries executable
run: chmod +x "${BUILD_DIR}/xrpld" "${BUILD_DIR}/validator-keys"
- name: Build package
env:
@@ -73,7 +79,7 @@ jobs:
- name: Upload package artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.artifact_name }}-pkg
name: ${{ matrix.xrpld_artifact_name }}-pkg
path: |
${{ env.BUILD_DIR }}/debbuild/*.deb
${{ env.BUILD_DIR }}/debbuild/*.ddeb

View File

@@ -42,6 +42,7 @@ This section contains changes targeting a future version.
### Bugfixes
- `get_aggregate_price`: Duplicate entries in the `oracles` request array are now ignored. [#6586](https://github.com/XRPLF/rippled/pull/6586)
- Peer Crawler: The `port` field in `overlay.active[]` now consistently returns an integer instead of a string for outbound peers. [#6318](https://github.com/XRPLF/rippled/pull/6318)
- `ping`: The `ip` field is no longer returned as an empty string for proxied connections without a forwarded-for header. It is now omitted, consistent with the behavior for identified connections. [#6730](https://github.com/XRPLF/rippled/pull/6730)
- gRPC `GetLedgerDiff`: Fixed error message that incorrectly said "base ledger not validated" when the desired ledger was not validated. [#6730](https://github.com/XRPLF/rippled/pull/6730)

View File

@@ -42,6 +42,8 @@ Our Linux CI tooling is distro-independent and uses a Nix-based environment, so
### macOS
Many `xrpld` engineers use macOS for development.
The minimum supported version is macOS 15 (Sequoia).
CI testing is done in macOS 26 (Tahoe), but the build defaults `CMAKE_OSX_DEPLOYMENT_TARGET` to 15.
### Windows
@@ -245,7 +247,17 @@ cmake --build . --target setup_code_gen # create venv and install dependencies
cmake --build . --target code_gen # regenerate code
```
The regenerated files should be committed alongside your changes.
The same targets are also available as a standalone project, which does not
need the dependencies to be configured first:
```
cmake -S cmake/codegen -B build/codegen
cmake --build build/codegen --target setup_code_gen
cmake --build build/codegen --target code_gen
```
The regenerated files should be committed alongside your changes. CI verifies
that they are up-to-date.
## Coverage report

View File

@@ -13,6 +13,23 @@ if(DEFINED CMAKE_MODULE_PATH)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# Must be set before project() because project() consumes it when configuring the compiler and SDK.
# A user-provided -DCMAKE_OSX_DEPLOYMENT_TARGET still takes precedence.
#
# CMAKE_SYSTEM_NAME can't be used before project(), so CMAKE_HOST_SYSTEM_NAME is used instead.
#
# When CMAKE_OSX_DEPLOYMENT_TARGET is bumped to >=26.0, FastFloat dependency won't be needed anymore
if(
CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin"
AND NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET
)
set(CMAKE_OSX_DEPLOYMENT_TARGET
"15.0"
CACHE STRING
"Minimum macOS deployment version"
)
endif()
project(xrpl)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 23)
@@ -87,6 +104,7 @@ include(deps/Boost)
add_subdirectory(external/antithesis-sdk)
find_package(date REQUIRED)
find_package(ed25519 REQUIRED)
find_package(FastFloat REQUIRED)
find_package(gRPC REQUIRED)
find_package(LibArchive REQUIRED)
find_package(lz4 REQUIRED)
@@ -103,6 +121,7 @@ target_link_libraries(
xrpl_libs
INTERFACE
ed25519::ed25519
FastFloat::fast_float
lz4::lz4
mpt-crypto::mpt-crypto
OpenSSL::Crypto
@@ -143,8 +162,10 @@ endif()
include(XrplCore)
include(XrplProtocolAutogen)
include(XrplInstall)
include(XrplPackaging)
include(XrplValidatorKeys)
# Must come after XrplValidatorKeys: the 'package' target depends on the
# validator-keys target existing.
include(XrplPackaging)
if(tests)
include(CTest)

View File

@@ -488,6 +488,17 @@
# Must be a number between 100 and 1000, defaults to 250
#
#
# [max_subscriptions_per_connection]
#
# Maximum number of account, real-time account, and account-history
# subscriptions a single client connection may hold at once. Bounds the
# per-connection state torn down when the connection disconnects. Book
# subscriptions are tracked separately and are not counted here.
#
# Defaults to 100000 if not set; large enough for legitimate power users
# such as block explorers.
#
#
# [overlay]
#
# Controls settings related to the peer to peer overlay.
@@ -538,6 +549,45 @@
# only be used for local testing and debugging. Do not disable
# on mainnet.
#
# max_untrusted_count = <number>
#
# The number of manifests the server keeps for validators it does not
# list, and the number it sends and processes in a single peer protocol
# message. Once the server holds this many, a manifest for a new
# unlisted validator is rejected, so peer gossip cannot grow the cache
# without end.
#
# This option can take any value between 50 and 1000, inclusive. If
# the option is not present the server uses its built-in value.
#
# The current default (which is subject to change) is 300.
#
# max_trusted_count = <number>
#
# The number of manifests for listed validators to allow for when
# sizing peer protocol messages. Manifests for listed validators are
# never dropped, whether sending or receiving, because doing so would
# delay a validator key change reaching this server. Set this above the
# number of validators the server lists.
#
# Together the two counts above set the largest manifest message the
# server accepts: bigger messages are discarded without reading them,
# and without penalising the sender. Raising either means the server
# accepts and sends bigger messages than a peer using the defaults, and
# those peers will discard what this server sends. Lowering either below
# what peers send makes this server discard their manifest messages,
# which it does without recording anything.
#
# This option can take any value between 50 and 1000, inclusive. If
# the option is not present the server uses its built-in value.
#
# The current default (which is subject to change) is 300.
#
# NOTE: These two options (max_untrusted_count and max_trusted_count)
# are transitional. They exist to bound manifest-message size and cache
# growth during the network upgrade. They may be removed in a future
# release once the fleet has upgraded, and should not be relied upon as
# stable configuration.
#
# [transaction_queue] EXPERIMENTAL
#

View File

@@ -2,9 +2,10 @@
Patch executables to run in non-Nix environments.
The Nix toolchain links binaries against an ELF interpreter (loader)
that lives in the Nix store, so the resulting binaries don't run elsewhere.
`patch_nix_binary` adds a POST_BUILD step that resets the interpreter
to the system default loader and drops the rpath.
that lives in the Nix store, so the resulting binaries don't run elsewhere
(including once installed from the .deb package). `patch_nix_binary` resets
the interpreter to the system default loader and drops the rpath, once the
binary has been linked.
This runs by default for Nix-toolchain builds (determined by whether the compiler resolves under /nix/store/).
Those builds are where binaries get a Nix-store loader.
@@ -52,13 +53,38 @@ function(patch_nix_binary target)
if(NOT PATCH_NIX_BINARIES)
return()
endif()
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND
"${PATCHELF_COMMAND}" --set-interpreter "${DEFAULT_LOADER_PATH}"
--remove-rpath "$<TARGET_FILE:${target}>"
COMMENT "Patching ${target}: set default loader, remove rpath"
VERBATIM
set(patch_command
"${PATCHELF_COMMAND}"
--set-interpreter
"${DEFAULT_LOADER_PATH}"
--remove-rpath
"$<TARGET_FILE:${target}>"
)
set(comment "Patching ${target}: set default loader, remove rpath")
# POST_BUILD is the cheap way to do this: it runs only when the binary is
# relinked. It is also only available in the directory that defined the
# target, so for a target from elsewhere (e.g. a FetchContent subproject)
# fall back to a custom target that runs after the binary is linked. That
# one runs on every build, which is harmless because patchelf is idempotent.
get_target_property(target_source_dir ${target} SOURCE_DIR)
if("${target_source_dir}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND ${patch_command}
COMMENT "${comment}"
VERBATIM
)
else()
add_custom_target(
${target}-patch-nix
ALL
COMMAND ${patch_command}
COMMENT "${comment}"
VERBATIM
)
add_dependencies(${target}-patch-nix ${target})
endif()
endfunction()

View File

@@ -25,6 +25,19 @@ if(NOT (RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE))
return()
endif()
if(NOT TARGET xrpld)
message(STATUS "xrpld=ON is required; 'package' target not available")
return()
endif()
if(NOT TARGET validator-keys)
message(
STATUS
"validator_keys=ON is required; 'package' target not available"
)
return()
endif()
set(package_env
SRC_DIR=${CMAKE_SOURCE_DIR}
BUILD_DIR=${CMAKE_BINARY_DIR}
@@ -37,7 +50,7 @@ add_custom_target(
${CMAKE_COMMAND} -E env ${package_env}
${CMAKE_SOURCE_DIR}/package/build_pkg.sh
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS xrpld
DEPENDS xrpld validator-keys
COMMENT "Building Linux package (deb/rpm inferred from host tooling)"
VERBATIM
)

View File

@@ -2,21 +2,22 @@
Protocol Autogen - Code generation for protocol wrapper classes
#]===================================================================]
# The repository root, derived from the location of this file rather than from
# the including project, so that the targets below can also be offered on their
# own by cmake/codegen/CMakeLists.txt.
get_filename_component(XRPL_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
set(CODEGEN_VENV_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/.venv"
"${XRPL_ROOT}/.venv"
CACHE PATH
"Path to a Python virtual environment for code generation. A venv will be created here by setup_code_gen and used to run generation scripts."
)
# 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(AUTOGEN_TEST_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/src/tests/libxrpl/protocol_autogen"
)
set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/codegen")
set(MACRO_DIR "${XRPL_ROOT}/include/xrpl/protocol/detail")
set(AUTOGEN_HEADER_DIR "${XRPL_ROOT}/include/xrpl/protocol_autogen")
set(AUTOGEN_TEST_DIR "${XRPL_ROOT}/src/tests/libxrpl/protocol_autogen")
set(SCRIPTS_DIR "${XRPL_ROOT}/cmake/scripts/codegen")
# Input macro files
set(TRANSACTIONS_MACRO "${MACRO_DIR}/transactions.macro")
@@ -114,14 +115,14 @@ if(CODEGEN_VENV_DIR)
setup_code_gen
COMMAND ${Python3_EXECUTABLE} -m venv "${CODEGEN_VENV_DIR}"
COMMAND ${CODEGEN_PYTHON} -m pip install -r "${REQUIREMENTS_FILE}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
WORKING_DIRECTORY "${XRPL_ROOT}"
COMMENT "Creating venv and installing code generation dependencies..."
)
else()
add_custom_target(
setup_code_gen
COMMAND ${Python3_EXECUTABLE} -m pip install -r "${REQUIREMENTS_FILE}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
WORKING_DIRECTORY "${XRPL_ROOT}"
COMMENT "Installing code generation dependencies..."
)
endif()
@@ -139,8 +140,8 @@ add_custom_target(
-DSFIELDS_MACRO=${SFIELDS_MACRO}
-DAUTOGEN_HEADER_DIR=${AUTOGEN_HEADER_DIR}
-DAUTOGEN_TEST_DIR=${AUTOGEN_TEST_DIR} -P
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplProtocolAutogenRun.cmake"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_LIST_DIR}/XrplProtocolAutogenRun.cmake"
WORKING_DIRECTORY "${XRPL_ROOT}"
COMMENT "Running protocol code generation..."
SOURCES ${ALL_INPUT_FILES}
)

View File

@@ -5,22 +5,39 @@ option(
)
if(validator_keys)
git_branch(current_branch)
# default to tracking VK master branch unless we are on release
if(NOT (current_branch STREQUAL "release"))
set(current_branch "master")
endif()
message(STATUS "Tracking ValidatorKeys branch: ${current_branch}")
# Own the install destination below rather than relying on another module
# having pulled this in first.
include(GNUInstallDirs)
# Pinned to an exact commit, not a branch: the tool ships inside our
# packages, so the same xrpld version must always package the same
# validator-keys. Bump this deliberately.
set(validator_keys_commit "4c0fb75eec9601c711645998c904507e87e910ae")
message(STATUS "Using ValidatorKeys commit: ${validator_keys_commit}")
FetchContent_Declare(
validator_keys
GIT_REPOSITORY https://github.com/ripple/validator-keys-tool.git
GIT_TAG "${current_branch}"
GIT_TAG "${validator_keys_commit}"
)
FetchContent_MakeAvailable(validator_keys)
# The tool's own CMakeLists excludes the target from 'all' when it is built
# as a subproject. Undo that, so validator_keys=ON really does build it.
set_target_properties(
validator-keys
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
EXCLUDE_FROM_ALL OFF
EXCLUDE_FROM_DEFAULT_BUILD OFF
)
# We ship this binary, so like xrpld it must not keep the Nix store's ELF
# loader, or it cannot run on the target distro at all.
patch_nix_binary(validator-keys)
configure_file(
"${validator_keys_SOURCE_DIR}/LICENSE"
"${CMAKE_BINARY_DIR}/validator-keys-LICENSE"
COPYONLY
)
install(TARGETS validator-keys RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()

View File

@@ -0,0 +1,21 @@
#[===================================================================[
Protocol Autogen - Standalone project
Exposes the 'setup_code_gen' and 'code_gen' targets on their own, without
configuring the rest of xrpl. Code generation is pure Python, so this needs
neither the dependencies nor a compiler, which makes it usable in CI and by
contributors who only want to regenerate the protocol wrapper classes:
cmake -S cmake/codegen -B build/codegen
cmake --build build/codegen --target setup_code_gen
cmake --build build/codegen --target code_gen
The targets are identical to the ones offered by the top-level build, since
both come from cmake/XrplProtocolAutogen.cmake.
#]===================================================================]
cmake_minimum_required(VERSION 3.16)
project(xrpl_codegen LANGUAGES NONE)
include("${CMAKE_CURRENT_LIST_DIR}/../XrplProtocolAutogen.cmake")

View File

@@ -13,7 +13,7 @@
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
"openssl/3.6.3#f806de8933e3bf6f01016c6a888cee2e%1783945160.863288",
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1782392402.297166",
"mpt-crypto/0.4.0-rc4#ffdba12f2332357f0d8b0ae944cfff52%1784138702.932355",
"mpt-crypto/1.0.2#b313cef0c1a493eb970ad185b2e9bab7%1784285108.866483",
"lz4/1.10.0#982d9b673900f665a1da109e09c17cab%1782392402.164188",
"libiconv/1.17#9923bc6dc6f106646d6967e0039a5ada%1782392792.775744",
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1782392402.420732",
@@ -21,6 +21,7 @@
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979",
"grpc/1.81.1#f729f6d75992d20f9c72828e9142d62f%1783945160.094135",
"fast_float/8.2.10#f6f28d6bb22112078e7dbda611caf681%1782494504.298",
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
@@ -35,7 +36,7 @@
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1782395690.33162",
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
"m4/1.4.19#34c4bbc3eeebe98ca6edf2f52d602e7d%1777282960.259",
"m4/1.4.19#1727f439cf74e83826ec96d0b4904eee%1784541921.659",
"cmake/4.3.3#840cf00ea09777e05c2050a50a82c722%1782392418.696091",
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1782392402.624226",
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",

View File

@@ -5,6 +5,13 @@
{% if os == "Linux" %}
{% set compiler_version = detect_api.default_compiler_version(compiler, version) %}
{% endif %}
{% if os == "Macos" %}
{# Minimum macOS the dependencies target. #}
{# Without this, Conan builds each dependency against the (possibly newer) host SDK, so the #}
{# dependency objects target a newer macOS than the binary and the linker warns. #}
{# Keep at or below CMAKE_OSX_DEPLOYMENT_TARGET in CMakeLists.txt. #}
{% set min_macos_version = "15.0" %}
{% endif %}
[settings]
os={{ os }}
@@ -18,6 +25,9 @@ compiler.runtime=static
{% else %}
compiler.libcxx={{ detect_api.detect_libcxx(compiler, version, compiler_exe) }}
{% endif %}
{% if os == "Macos" %}
os.version={{ min_macos_version }}
{% endif %}
[conf]
{# The Boost recipe builds with b2, which doesn't use Conan's toolchain files. #}
@@ -41,3 +51,13 @@ tools.build:compiler_executables={'c':'{{ cc_exe }}','cpp':'{{ cxx_exe }}'}
{# More info: https://docs.conan.io/2/reference/extensions/binary_compatibility.html #}
user.package:cppstd_version=23
tools.info.package_id:confs+=["user.package:cppstd_version"]
{% if os == "Macos" %}
[buildenv]
{# os.version adds -mmacosx-version-min to compiler command lines, #}
{# but Boost.Context's b2 assembly (.S) rule ignores it, #}
{# so those objects keep the host SDK version and still warn at link time. #}
{# clang's assembler honors this env var regardless, pinning them. #}
{# Scoped to boost/* since it is the only gap. #}
boost/*:MACOSX_DEPLOYMENT_TARGET={{ min_macos_version }}
{% endif %}

View File

@@ -29,6 +29,7 @@ class Xrpl(ConanFile):
requires = [
"ed25519/2015.03",
"fast_float/8.2.10",
"grpc/1.81.1",
"libarchive/3.8.7",
"nudb/2.0.9",
@@ -139,7 +140,7 @@ class Xrpl(ConanFile):
if self.options.jemalloc:
self.requires("jemalloc/5.3.1")
self.requires("lz4/1.10.0", force=True)
self.requires("mpt-crypto/0.4.0-rc4", transitive_headers=True)
self.requires("mpt-crypto/1.0.2", transitive_headers=True)
self.requires("protobuf/6.33.5", force=True)
if self.options.rocksdb:
self.requires("rocksdb/10.5.1")
@@ -212,6 +213,7 @@ class Xrpl(ConanFile):
"boost::thread",
"date::date",
"ed25519::ed25519",
"fast_float::fast_float",
"grpc::grpc++",
"libarchive::libarchive",
"lz4::lz4",

View File

@@ -3,6 +3,78 @@
Common issues encountered when using the [Nix development shell](./nix.md), and
how to resolve them.
## `command not found: nix` after a macOS update
If a shell suddenly can't find `nix` at all:
```
$ nix develop
zsh: command not found: nix
```
then Nix is almost certainly still installed — only the shell hook that puts it
on your `PATH` is gone. Confirm that first:
```bash
ls -l /nix/var/nix/profiles/default/bin/nix
```
If that exists, the installation is fine and this is purely a `PATH` problem.
### Why it happens
The installer does not touch your dotfiles. Instead it sources a setup script
from the Nix store by editing **system-wide** rc files:
| Shell | File the installer edits |
| ----- | ------------------------------------- |
| bash | `/etc/bashrc`, `/etc/bash.bashrc` |
| zsh | `/etc/zshrc` |
| fish | `$__fish_sysconf_dir/conf.d/nix.fish` |
macOS manages `/etc/zshrc`, so an OS update can replace it with the vendor copy
and silently drop the Nix block. `/etc/bashrc` and the fish file usually survive,
which is why the breakage often shows up in zsh only. You can verify this by
diffing against the backup the installer left behind:
```bash
diff /etc/zshrc /etc/zshrc.backup-before-nix
```
If they are identical, the Nix snippet was wiped. This is upstream issue
[NixOS/nix#3616](https://github.com/NixOS/nix/issues/3616).
### Fix
To unblock the current shell:
```bash
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
```
For a permanent fix, add the snippet to your **user** rc file rather than
restoring `/etc/zshrc` — user dotfiles are not clobbered by OS updates:
```bash
cat >>~/.zshrc <<'EOF'
# Nix
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
fi
# End Nix
EOF
```
The scripts guard against double-sourcing via `__ETC_PROFILE_NIX_SOURCED`, so
this is safe even if a system-wide hook is later restored.
> [!NOTE]
> `/etc/zshrc` and `~/.zshrc` are only read by **interactive** zsh. If the
> snippet is present but `zsh -c '…'`, a script, or an IDE terminal still can't
> find `nix`, that shell is non-interactive — put the snippet in `~/.zshenv`
> instead.
## Git worktrees
If `nix develop` fails with an error like:

View File

@@ -41,6 +41,35 @@
namespace xrpl {
namespace base64 {
/**
* Returns the maximum number of characters needed to base64-encode @p nBytes bytes.
*
* @param nBytes Number of input bytes.
* @return Size of the encoded string, including padding.
*/
constexpr std::size_t
encodedSize(std::size_t const nBytes)
{
return 4 * ((nBytes + 2) / 3);
}
/**
* Returns the maximum number of bytes a base64 string of @p numChars characters
* decodes to.
*
* @param numChars Number of base64 characters.
* @return Upper bound on the number of decoded bytes.
*/
constexpr std::size_t
decodedSize(std::size_t const numChars)
{
return ((numChars / 4) * 3) + 2;
}
} // namespace base64
std::string
base64Encode(std::uint8_t const* data, std::size_t len);

View File

@@ -295,6 +295,20 @@ public:
return runner_->arg();
}
protected:
/**
* Lets a suite compose other suites (e.g. an aggregator that reruns a
* group of related suites under its own name) via `SuiteInfo::run`.
*
* @return The runner this suite is executing under.
*/
Runner&
runner() const
{
return *runner_;
}
public:
/**
* DEPRECATED
* @return `true` if the test condition indicates success(a false value)

View File

@@ -25,6 +25,7 @@ struct Sections
static constexpr auto kLedgerHistory = "ledger_history";
static constexpr auto kLedgerReplay = "ledger_replay";
static constexpr auto kLedgerTxTables = "ledger_tx_tables";
static constexpr auto kMaxSubscriptionsPerConnection = "max_subscriptions_per_connection";
static constexpr auto kMaxTransactions = "max_transactions";
static constexpr auto kNetworkId = "network_id";
static constexpr auto kNetworkQuorum = "network_quorum";
@@ -118,7 +119,9 @@ struct Keys
static constexpr auto kLogInterval = "log_interval";
static constexpr auto kMaxDivergedTime = "max_diverged_time";
static constexpr auto kMaxLedgerCountsToStore = "max_ledger_counts_to_store";
static constexpr auto kMaxTrustedCount = "max_trusted_count";
static constexpr auto kMaxUnknownTime = "max_unknown_time";
static constexpr auto kMaxUntrustedCount = "max_untrusted_count";
static constexpr auto kMaximumTxnInLedger = "maximum_txn_in_ledger";
static constexpr auto kMaximumTxnPerAccount = "maximum_txn_per_account";
static constexpr auto kMemoryLevel = "memory_level";

View File

@@ -21,6 +21,7 @@
#include <map>
#include <memory>
#include <optional>
#include <ranges>
#include <sstream>
#include <string>
#include <utility>
@@ -1579,7 +1580,13 @@ Consensus<Adaptor>::updateOurPositions(std::unique_ptr<std::stringstream> const&
JLOG(j_.info()) << ss.str();
CLOG(clog) << ss.str();
for (auto const& [t, v] : closeTimeVotes)
// Walk the votes highest-time first so that, among close times tied
// for the most votes, the earliest wins. The smaller value is the
// safer choice: without close-time consensus this round, the winner
// only updates our position for the next proposal, and a too-early
// time is bounded below by the prior ledger's close time. Only the
// tie-break changes; the bin with the most votes still wins.
for (auto const& [t, v] : std::views::reverse(closeTimeVotes))
{
JLOG(j_.debug()) << "CCTime: seq "
<< static_cast<std::uint32_t>(previousLedger_.seq()) + 1 << ": "

View File

@@ -8,7 +8,9 @@
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <map>
#include <optional>
#include <string>
namespace xrpl {
@@ -189,6 +191,75 @@ struct ConsensusCloseTimes
NetClock::time_point self;
};
/**
* Offset of the network's close time relative to ours, using a weighted median.
*
* Treats the sample set as `{self x 1}` merged with `{t x w}` for each
* `(t, w)` in `times.peers`, in time order, and returns `(median - self)`
* in whole seconds. Uses the lower weighted median: the median is the
* earliest time at which the running weight reaches half the total, so an
* even total whose halfway point falls between two bins resolves to the
* earlier bin.
*
* @param times Our own close time and the weighted close times of peers.
* @return Weighted median of all close times minus our own, in whole seconds.
*/
inline std::chrono::seconds
medianCloseOffset(ConsensusCloseTimes const& times)
{
using namespace std::chrono;
using time_point = NetClock::time_point;
std::int64_t totalWeight = 1;
for (auto const& [_, w] : times.peers)
totalWeight += w;
std::int64_t const halfWeight = (totalWeight + 1) / 2;
std::optional<time_point> median{};
std::int64_t tally = 0;
bool selfPlaced = false;
// Accumulate weight in time order; the first bin to reach halfWeight is
// the (lower) weighted median. Returns true once that bin is found.
auto step = [&](time_point t, std::int64_t w) {
XRPL_ASSERT(tally < halfWeight, "xrpl::medianCloseOffset::step : median not yet found");
tally += w;
if (tally >= halfWeight)
{
median = t;
return true;
}
return false;
};
for (auto const& [t, w] : times.peers)
{
if (!selfPlaced && times.self <= t)
{
selfPlaced = true;
if (step(times.self, 1))
break;
}
if (step(t, w))
break;
}
if (!selfPlaced && !median)
step(times.self, 1);
if (!median)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::medianCloseOffset : median not found");
median = times.self;
// LCOV_EXCL_STOP
}
return duration_cast<seconds>(
duration<std::int64_t>{median->time_since_epoch().count()} -
duration<std::int64_t>{times.self.time_since_epoch().count()});
}
/**
* Whether we have or don't have a consensus
*/

View File

@@ -2,6 +2,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
@@ -15,6 +16,7 @@
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
@@ -241,10 +243,25 @@ escrowUnlockApplyHelper<MPTIssue>(
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
// compute transfer fee, if any
auto const xferFee = amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
if (ctx.view.rules().enabled(fixCleanup3_4_0))
{
XRPL_ASSERT(
lockedRate >= kParityRate,
"xrpl::escrowUnlockApplyHelper<MPTIssue> : lockedRate is at least parity");
// MPTs are integral, so round the delivered amount down and
// charge any fractional transfer fee to the escrowed amount.
auto const delivered =
mulRatio(amount.mpt(), kParityRate.value, lockedRate.value, false);
finalAmt = STAmount(amount.asset(), delivered.value());
}
else
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
}
return unlockEscrowMPT(
ctx.view,

View File

@@ -301,14 +301,15 @@ message TMLedgerData {
}
message TMPing {
// Previously used - don't reuse.
reserved 3, 4;
enum pingType {
ptPING = 0; // we want a reply
ptPONG = 1; // this is a reply
}
required pingType type = 1;
optional uint32 seq = 2; // detect stale replies, ensure other side is reading
optional uint64 pingTime = 3; // know when we think we sent the ping
optional uint64 netTime = 4;
optional uint32 seq = 2; // detect stale replies, ensure other side is reading
}
message TMSquelch {

View File

@@ -12,6 +12,7 @@
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/UintTypes.h>
#include <array>
@@ -21,8 +22,6 @@
#include <utility>
namespace xrpl {
class SeqProxy;
/**
* Keylet computation functions.
*
@@ -123,7 +122,7 @@ trustLine(AccountID const& id, Issue const& issue) noexcept
*/
/** @{ */
Keylet
offer(AccountID const& id, std::uint32_t seq) noexcept;
offer(AccountID const& id, SeqProxy const& seq) noexcept;
inline Keylet
offer(uint256 const& key) noexcept
@@ -136,7 +135,7 @@ offer(uint256 const& key) noexcept
* The initial directory page for a specific quality
*/
Keylet
quality(Keylet const& k, std::uint64_t q) noexcept;
quality(Keylet const& k, std::uint64_t const q) noexcept;
/**
* The directory for the next lower quality
@@ -149,10 +148,7 @@ next(Keylet const& k);
*/
/** @{ */
Keylet
ticket(AccountID const& id, std::uint32_t ticketSeq);
Keylet
ticket(AccountID const& id, SeqProxy ticketSeq);
ticket(AccountID const& id, SeqProxy const& ticketSeq);
inline Keylet
ticket(uint256 const& key)
@@ -178,7 +174,7 @@ sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept;
*/
/** @{ */
Keylet
check(AccountID const& id, std::uint32_t seq) noexcept;
check(AccountID const& id, SeqProxy const& seq) noexcept;
inline Keylet
check(uint256 const& key) noexcept
@@ -225,10 +221,10 @@ ownerDir(AccountID const& id) noexcept;
*/
/** @{ */
Keylet
page(uint256 const& root, std::uint64_t index = 0) noexcept;
page(uint256 const& root, std::uint64_t const index = 0) noexcept;
inline Keylet
page(Keylet const& root, std::uint64_t index = 0) noexcept
page(Keylet const& root, std::uint64_t const index = 0) noexcept
{
XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::keylet::page : valid root type");
return page(root.key, index);
@@ -239,13 +235,13 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
* An escrow entry
*/
Keylet
escrow(AccountID const& src, std::uint32_t seq) noexcept;
escrow(AccountID const& src, SeqProxy const& seq) noexcept;
/**
* A PaymentChannel
*/
Keylet
payChannel(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
payChannel(AccountID const& src, AccountID const& dst, SeqProxy const& seq) noexcept;
/**
* NFT page keylets
@@ -276,7 +272,7 @@ nftokenPage(Keylet const& k, uint256 const& token);
* An offer from an account to buy or sell an NFT
*/
Keylet
nftokenOffer(AccountID const& owner, std::uint32_t seq);
nftokenOffer(AccountID const& owner, SeqProxy const& seq);
inline Keylet
nftokenOffer(uint256 const& offer)
@@ -316,17 +312,17 @@ bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType);
// `seq` is stored as `sfXChainClaimID` in the object
Keylet
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq);
xChainClaimID(STXChainBridge const& bridge, std::uint64_t const seq);
// `seq` is stored as `sfXChainAccountCreateCount` in the object
Keylet
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq);
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t const seq);
Keylet
did(AccountID const& account) noexcept;
Keylet
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept;
oracle(AccountID const& account, std::uint32_t const documentID) noexcept;
Keylet
credential(AccountID const& subject, AccountID const& issuer, Slice const& credType) noexcept;
@@ -337,9 +333,6 @@ credential(uint256 const& key) noexcept
return {ltCREDENTIAL, key};
}
Keylet
mptokenIssuance(std::uint32_t seq, AccountID const& issuer) noexcept;
Keylet
mptokenIssuance(MPTID const& issuanceID) noexcept;
@@ -362,7 +355,7 @@ Keylet
mptoken(uint256 const& issuanceKey, AccountID const& holder) noexcept;
Keylet
vault(AccountID const& owner, std::uint32_t seq) noexcept;
vault(AccountID const& owner, SeqProxy const& seq) noexcept;
inline Keylet
vault(uint256 const& vaultKey)
@@ -371,7 +364,7 @@ vault(uint256 const& vaultKey)
}
Keylet
loanBroker(AccountID const& owner, std::uint32_t seq) noexcept;
loanBroker(AccountID const& owner, SeqProxy const& seq) noexcept;
inline Keylet
loanBroker(uint256 const& key)
@@ -380,7 +373,7 @@ loanBroker(uint256 const& key)
}
Keylet
loan(uint256 const& loanBrokerID, std::uint32_t loanSeq) noexcept;
loan(uint256 const& loanBrokerID, SeqProxy const& loanSeq) noexcept;
inline Keylet
loan(uint256 const& key)
@@ -389,7 +382,7 @@ loan(uint256 const& key)
}
Keylet
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
permissionedDomain(AccountID const& account, SeqProxy const& seq) noexcept;
Keylet
permissionedDomain(uint256 const& domainID) noexcept;
@@ -407,12 +400,6 @@ getQualityNext(uint256 const& uBase);
std::uint64_t
getQuality(uint256 const& uBase);
uint256
getTicketIndex(AccountID const& account, std::uint32_t uSequence);
uint256
getTicketIndex(AccountID const& account, SeqProxy ticketSeq);
template <class... KeyletParams>
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
struct KeyletDesc
@@ -426,6 +413,6 @@ struct KeyletDesc
extern std::array<KeyletDesc<AccountID const&>, 6> const kDirectAccountKeylets;
MPTID
makeMptID(std::uint32_t sequence, AccountID const& account);
makeMptID(std::uint32_t const sequence, AccountID const& account);
} // namespace xrpl

View File

@@ -190,17 +190,6 @@ enum LedgerEntryType : std::uint16_t {
LSF_FLAG(lsfMPTCanClawback, 0x00000040) \
LSF_FLAG(lsfMPTCanHoldConfidentialBalance, 0x00000080)) \
\
LEDGER_OBJECT(MPTokenIssuanceMutable, \
LSF_FLAG(lsmfMPTCanEnableCanLock, 0x00000002) \
LSF_FLAG(lsmfMPTCanEnableRequireAuth, 0x00000004) \
LSF_FLAG(lsmfMPTCanEnableCanEscrow, 0x00000008) \
LSF_FLAG(lsmfMPTCanEnableCanTrade, 0x00000010) \
LSF_FLAG(lsmfMPTCanEnableCanTransfer, 0x00000020) \
LSF_FLAG(lsmfMPTCanEnableCanClawback, 0x00000040) \
LSF_FLAG(lsmfMPTCannotEnableCanHoldConfidentialBalance, 0x00000080) \
LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \
\
LEDGER_OBJECT(MPToken, \
LSF_FLAG2(lsfMPTLocked, 0x00000001) \
LSF_FLAG(lsfMPTAuthorized, 0x00000002) \
@@ -294,6 +283,17 @@ getAllLedgerFlags()
#pragma pop_macro("TO_MAP")
#pragma pop_macro("ALL_LEDGER_FLAGS")
// MPTokenIssuance ImmutableFlags (sfImmutableFlags)
inline constexpr std::uint32_t lsifMPTCanLock = 0x00000002;
inline constexpr std::uint32_t lsifMPTRequireAuth = 0x00000004;
inline constexpr std::uint32_t lsifMPTCanEscrow = 0x00000008;
inline constexpr std::uint32_t lsifMPTCanTrade = 0x00000010;
inline constexpr std::uint32_t lsifMPTCanTransfer = 0x00000020;
inline constexpr std::uint32_t lsifMPTCanClawback = 0x00000040;
inline constexpr std::uint32_t lsifMPTCanHoldConfidentialBalance = 0x00000080;
inline constexpr std::uint32_t lsifMPTMetadata = 0x00010000;
inline constexpr std::uint32_t lsifMPTTransferFee = 0x00020000;
//------------------------------------------------------------------------------
/**

View File

@@ -90,7 +90,11 @@ public:
operator=(STObject&& other);
STObject(SOTemplate const& type, SField const& name);
STObject(SOTemplate const& type, SerialIter& sit, SField const& name);
STObject(
SOTemplate const& type,
SerialIter& sit,
SField const& name,
bool requireCanonicalOrder = false);
STObject(SerialIter& sit, SField const& name, int depth = 0);
STObject(SerialIter&& sit, SField const& name);
explicit STObject(SField const& name);
@@ -123,7 +127,7 @@ public:
set(SOTemplate const&);
bool
set(SerialIter& u, int depth = 0);
set(SerialIter& u, int depth = 0, bool requireCanonicalOrder = false);
[[nodiscard]] SerializedTypeID
getSType() const override;

View File

@@ -1,6 +1,7 @@
#pragma once
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
@@ -108,6 +109,9 @@ public:
[[nodiscard]] bool
isType(Type const& pe) const;
[[nodiscard]] size_t
getHash() const;
bool
operator==(STPathElement const& t) const;
@@ -171,12 +175,23 @@ public:
reserve(size_t s);
};
template <class Hasher>
void
hash_append(Hasher& h, STPath const& p) noexcept
{
for (auto const& e : p)
{
beast::hash_append(h, e.getHash());
}
}
//------------------------------------------------------------------------------
// A set of zero or more payment paths
class STPathSet final : public STBase, public CountedObject<STPathSet>
{
std::vector<STPath> value_;
xrpl::hardened_hash_set<STPath> seenHashes_;
public:
STPathSet() = default;
@@ -205,9 +220,6 @@ public:
std::vector<STPath>::const_reference
operator[](std::vector<STPath>::size_type n) const;
std::vector<STPath>::reference
operator[](std::vector<STPath>::size_type n);
[[nodiscard]] std::vector<STPath>::const_iterator
begin() const;
@@ -227,6 +239,9 @@ public:
void
emplaceBack(Args&&... args);
[[nodiscard]] bool
contains(STPath const& path) const;
private:
STBase*
copy(std::size_t n, void* buf) const override;
@@ -515,12 +530,6 @@ STPathSet::operator[](std::vector<STPath>::size_type n) const
return value_[n];
}
inline std::vector<STPath>::reference
STPathSet::operator[](std::vector<STPath>::size_type n)
{
return value_[n];
}
inline std::vector<STPath>::const_iterator
STPathSet::begin() const
{
@@ -549,6 +558,7 @@ inline void
STPathSet::pushBack(STPath const& e)
{
value_.push_back(e);
seenHashes_.emplace(value_.back());
}
template <typename... Args>
@@ -556,6 +566,13 @@ inline void
STPathSet::emplaceBack(Args&&... args)
{
value_.emplace_back(std::forward<Args>(args)...);
seenHashes_.emplace(value_.back());
}
inline bool
STPathSet::contains(STPath const& path) const
{
return seenHashes_.contains(path);
}
} // namespace xrpl

View File

@@ -93,12 +93,6 @@ public:
[[nodiscard]] SeqProxy
getSeqProxy() const;
/**
* Returns the first non-zero value of (Sequence, TicketSequence).
*/
[[nodiscard]] std::uint32_t
getSeqValue() const;
[[nodiscard]] boost::container::flat_set<AccountID>
getMentionedAccounts() const;

View File

@@ -54,6 +54,22 @@ class STValidation final : public STObject, public CountedObject<STValidation>
NetClock::time_point seenTime_;
public:
/**
* @struct DeserializeOptions
* @brief Options controlling deserialization of a STValidation.
* @var DeserializeOptions::checkSignature
* Whether to verify the data was signed properly
*
* @var DeserializeOptions::requireCanonicalOrder
* Whether to require the fields to be in canonical order
*/
struct DeserializeOptions
{
bool checkSignature;
bool requireCanonicalOrder;
};
/**
* Construct a STValidation from a peer from serialized data.
*
@@ -64,12 +80,12 @@ public:
* that signed the validation. For manifest based
* validators, this should be the NodeID of the master
* public key.
* @param checkSignature Whether to verify the data was signed properly
* @param options Options controlling deserialization
*
* @note Throws if the object is not valid
*/
template <class LookupNodeID>
STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool checkSignature);
STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, DeserializeOptions options);
/**
* Construct, sign and trust a new STValidation issued by this node.
@@ -163,8 +179,8 @@ private:
};
template <class LookupNodeID>
STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool checkSignature)
: STObject(validationFormat(), sit, sfValidation)
STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, DeserializeOptions options)
: STObject(validationFormat(), sit, sfValidation, options.requireCanonicalOrder)
, signingPubKey_([this]() {
auto const spk = getFieldVL(sfSigningPubKey);
@@ -175,7 +191,7 @@ STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool ch
}())
, nodeID_(lookupNodeID(signingPubKey_))
{
if (checkSignature && !isValid())
if (options.checkSignature && !isValid())
{
JLOG(debugLog().error()) << "Invalid signature in validation: "
<< getJson(JsonOptions::Values::None);

View File

@@ -53,14 +53,29 @@ public:
operator=(SeqProxy const& other) = default;
/**
* Factory function to return a sequence-based SeqProxy
* Factory function to return a sequence-based SeqProxy.
* Outside of tests, this function should only be used for "secondary" transaction sequences,
* e.g. `sfOfferSequence`, or sequence fields in an existing ledger object. DO NOT use this for
* the "primary" sequence of a transaction, `sfSequence`.
*/
static constexpr SeqProxy
sequence(std::uint32_t v)
rawSequence(std::uint32_t v)
{
return SeqProxy{Type::Seq, v};
}
/**
* Factory function to return a ticket-based SeqProxy.
* Outside of tests, this function should only be used for "secondary" transaction sequences,
* e.g. `sfOfferSequence`, or sequence fields in an existing ledger object. DO NOT use this for
* the "primary" ticket sequence of a transaction, `sfTicketSequence`.
*/
static constexpr SeqProxy
rawTicket(std::uint32_t v)
{
return SeqProxy{Type::Ticket, v};
}
[[nodiscard]] constexpr std::uint32_t
value() const
{

View File

@@ -152,7 +152,14 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
\
TRANSACTION(MPTokenIssuanceSet, \
TF_FLAG(tfMPTLock, 0x00000001) \
TF_FLAG(tfMPTUnlock, 0x00000002), \
TF_FLAG(tfMPTUnlock, 0x00000002) \
TF_FLAG(tfMPTSetCanLock, 0x00000004) \
TF_FLAG(tfMPTSetRequireAuth, 0x00000008) \
TF_FLAG(tfMPTSetCanEscrow, 0x00000010) \
TF_FLAG(tfMPTSetCanTrade, 0x00000020) \
TF_FLAG(tfMPTSetCanTransfer, 0x00000040) \
TF_FLAG(tfMPTSetCanClawback, 0x00000080) \
TF_FLAG(tfMPTSetCanHoldConfidentialBalance, 0x00000100), \
MASK_ADJ(0)) \
\
TRANSACTION(NFTokenCreateOffer, \
@@ -356,38 +363,26 @@ inline constexpr FlagValue tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment);
inline constexpr FlagValue tfTrustSetPermissionMask =
~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze);
// MPTokenIssuanceCreate MutableFlags:
// Indicating specific fields or flags may be changed after issuance.
inline constexpr FlagValue tmfMPTCanEnableCanLock = lsmfMPTCanEnableCanLock;
inline constexpr FlagValue tmfMPTCanEnableRequireAuth = lsmfMPTCanEnableRequireAuth;
inline constexpr FlagValue tmfMPTCanEnableCanEscrow = lsmfMPTCanEnableCanEscrow;
inline constexpr FlagValue tmfMPTCanEnableCanTrade = lsmfMPTCanEnableCanTrade;
inline constexpr FlagValue tmfMPTCanEnableCanTransfer = lsmfMPTCanEnableCanTransfer;
inline constexpr FlagValue tmfMPTCanEnableCanClawback = lsmfMPTCanEnableCanClawback;
inline constexpr FlagValue tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata;
inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee;
inline constexpr FlagValue tmfMPTCannotEnableCanHoldConfidentialBalance =
lsmfMPTCannotEnableCanHoldConfidentialBalance;
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask =
~(tmfMPTCanEnableCanLock | tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanEscrow |
tmfMPTCanEnableCanTrade | tmfMPTCanEnableCanTransfer | tmfMPTCanEnableCanClawback |
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee |
tmfMPTCannotEnableCanHoldConfidentialBalance);
// MPTokenIssuanceCreate / MPTokenIssuanceSet ImmutableFlags:
// Defines the immutable fields and flags specific to MPTokenIssuance.
inline constexpr FlagValue tifMPTCanLock = lsifMPTCanLock;
inline constexpr FlagValue tifMPTRequireAuth = lsifMPTRequireAuth;
inline constexpr FlagValue tifMPTCanEscrow = lsifMPTCanEscrow;
inline constexpr FlagValue tifMPTCanTrade = lsifMPTCanTrade;
inline constexpr FlagValue tifMPTCanTransfer = lsifMPTCanTransfer;
inline constexpr FlagValue tifMPTCanClawback = lsifMPTCanClawback;
inline constexpr FlagValue tifMPTMetadata = lsifMPTMetadata;
inline constexpr FlagValue tifMPTTransferFee = lsifMPTTransferFee;
inline constexpr FlagValue tifMPTCanHoldConfidentialBalance = lsifMPTCanHoldConfidentialBalance;
inline constexpr FlagValue tifMPTokenIssuanceImmutableMask =
~(tifMPTCanLock | tifMPTRequireAuth | tifMPTCanEscrow | tifMPTCanTrade | tifMPTCanTransfer |
tifMPTCanClawback | tifMPTMetadata | tifMPTTransferFee | tifMPTCanHoldConfidentialBalance);
// MPTokenIssuanceSet MutableFlags:
// Enable mutable capability flags. These flags are one-way: once enabled,
// the corresponding capability cannot be disabled by MPTokenIssuanceSet.
inline constexpr FlagValue tmfMPTSetCanLock = 0x00000001;
inline constexpr FlagValue tmfMPTSetRequireAuth = 0x00000002;
inline constexpr FlagValue tmfMPTSetCanEscrow = 0x00000004;
inline constexpr FlagValue tmfMPTSetCanTrade = 0x00000008;
inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000010;
inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000020;
inline constexpr FlagValue tmfMPTSetCanHoldConfidentialBalance = 0x00000040;
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask =
~(tmfMPTSetCanLock | tmfMPTSetRequireAuth | tmfMPTSetCanEscrow | tmfMPTSetCanTrade |
tmfMPTSetCanTransfer | tmfMPTSetCanClawback | tmfMPTSetCanHoldConfidentialBalance);
// MPTokenIssuanceSet set of flags that is used to enable capabilities on an MPTokenIssuance.
// Used as `txFlags & tfMPTokenIssuanceSetEnableFlagMask` to extract the capability-enabling bits.
inline constexpr FlagValue tfMPTokenIssuanceSetEnableFlagMask = tfMPTSetCanLock |
tfMPTSetRequireAuth | tfMPTSetCanEscrow | tfMPTSetCanTrade | tfMPTSetCanTransfer |
tfMPTSetCanClawback | tfMPTSetCanHoldConfidentialBalance;
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between accounts allowed a
// TrustLine to be added to the issuer of that token without explicit permission from that issuer.

View File

@@ -59,7 +59,6 @@ XRPL_FIX (PreviousTxnID, Supported::Yes, VoteBehavior::DefaultNo
XRPL_FIX (XChainRewardRounding, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (EmptyDID, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PriceOracle, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMOverflowOffer, Supported::Yes, VoteBehavior::DefaultYes)
XRPL_FIX (FillOrKill, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DID, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(XChainBridge, Supported::Yes, VoteBehavior::DefaultNo)
@@ -100,6 +99,7 @@ XRPL_RETIRE_FIX(1578)
XRPL_RETIRE_FIX(1623)
XRPL_RETIRE_FIX(1781)
XRPL_RETIRE_FIX(AmendmentMajorityCalc)
XRPL_RETIRE_FIX(AMMOverflowOffer)
XRPL_RETIRE_FIX(CheckThreading)
XRPL_RETIRE_FIX(DisallowIncomingV1)
XRPL_RETIRE_FIX(InnerObjTemplate)

View File

@@ -404,7 +404,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfDomainID, SoeOptional},
{sfMutableFlags, SoeDefault},
{sfImmutableFlags, SoeDefault},
{sfReferenceHolding, SoeOptional},
{sfIssuerEncryptionKey, SoeOptional},
{sfAuditorEncryptionKey, SoeOptional},

View File

@@ -98,7 +98,7 @@ TYPED_SFIELD(sfVoteWeight, UINT32, 48)
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
TYPED_SFIELD(sfMutableFlags, UINT32, 53)
TYPED_SFIELD(sfImmutableFlags, UINT32, 53)
TYPED_SFIELD(sfStartDate, UINT32, 54)
TYPED_SFIELD(sfPaymentInterval, UINT32, 55)
TYPED_SFIELD(sfGracePeriod, UINT32, 56)
@@ -239,6 +239,7 @@ TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset
// int32
TYPED_SFIELD(sfLoanScale, INT32, 1)
TYPED_SFIELD(sfRemainingOwnerCountDelta, INT32, 2)
// currency amount (common)
TYPED_SFIELD(sfAmount, AMOUNT, 1)
@@ -278,6 +279,7 @@ TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
TYPED_SFIELD(sfFeeAmountDelta, AMOUNT, 34)
// variable length (common)
TYPED_SFIELD(sfPublicKey, VL, 1)

View File

@@ -705,7 +705,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
{sfMaximumAmount, SoeOptional},
{sfMPTokenMetadata, SoeOptional},
{sfDomainID, SoeOptional},
{sfMutableFlags, SoeOptional},
{sfImmutableFlags, SoeOptional},
}))
/** This transaction type destroys a MPTokensIssuance instance */
@@ -734,7 +734,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
{sfDomainID, SoeOptional},
{sfMPTokenMetadata, SoeOptional},
{sfTransferFee, SoeOptional},
{sfMutableFlags, SoeOptional},
{sfImmutableFlags, SoeOptional},
{sfIssuerEncryptionKey, SoeOptional},
{sfAuditorEncryptionKey, SoeOptional},
}))
@@ -1085,7 +1085,7 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
# include <xrpl/tx/transactors/token/ConfidentialMPTConvert.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_CONVERT, 85, ConfidentialMPTConvert,
Delegation::Delegable,
Delegation::NotDelegable,
featureConfidentialTransfer,
NoPriv,
({
@@ -1189,9 +1189,9 @@ TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
({
{sfCounterpartySponsor, SoeOptional},
{sfSponsee, SoeOptional},
{sfFeeAmount, SoeOptional},
{sfFeeAmountDelta, SoeOptional},
{sfMaxFee, SoeOptional},
{sfRemainingOwnerCount, SoeOptional},
{sfRemainingOwnerCountDelta, SoeOptional},
}))
/** This system-generated transaction type is used to update the status of the various amendments.

View File

@@ -23,6 +23,16 @@ By default, `CODEGEN_VENV_DIR` points to `.venv` in the project root. The
`setup_code_gen` target creates a venv there and installs the required packages.
The `code_gen` target then uses the venv's Python interpreter to run generation.
Generation is pure Python, so the same targets are also available as a
standalone project that needs neither the dependencies nor a compiler. This is
what CI uses, and it is handy if you only want to regenerate these files:
```bash
cmake -S cmake/codegen -B build/codegen
cmake --build build/codegen --target setup_code_gen
cmake --build build/codegen --target code_gen
```
### Python Dependencies
The code generation requires the following Python packages (installed by `setup_code_gen`):

View File

@@ -256,27 +256,27 @@ public:
}
/**
* @brief Get sfMutableFlags (SoeDefault)
* @brief Get sfImmutableFlags (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getMutableFlags() const
getImmutableFlags() const
{
if (hasMutableFlags())
return this->sle_->at(sfMutableFlags);
if (hasImmutableFlags())
return this->sle_->at(sfImmutableFlags);
return std::nullopt;
}
/**
* @brief Check if sfMutableFlags is present.
* @brief Check if sfImmutableFlags is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMutableFlags() const
hasImmutableFlags() const
{
return this->sle_->isFieldPresent(sfMutableFlags);
return this->sle_->isFieldPresent(sfImmutableFlags);
}
/**
@@ -557,13 +557,13 @@ public:
}
/**
* @brief Set sfMutableFlags (SoeDefault)
* @brief Set sfImmutableFlags (SoeDefault)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setMutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
setImmutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfMutableFlags] = value;
object_[sfImmutableFlags] = value;
return *this;
}

View File

@@ -19,7 +19,7 @@ class ConfidentialMPTConvertBuilder;
* @brief Transaction: ConfidentialMPTConvert
*
* Type: ttCONFIDENTIAL_MPT_CONVERT (85)
* Delegable: Delegation::Delegable
* Delegable: Delegation::NotDelegable
* Amendment: featureConfidentialTransfer
* Privileges: NoPriv
*

View File

@@ -178,29 +178,29 @@ public:
}
/**
* @brief Get sfMutableFlags (SoeOptional)
* @brief Get sfImmutableFlags (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getMutableFlags() const
getImmutableFlags() const
{
if (hasMutableFlags())
if (hasImmutableFlags())
{
return this->tx_->at(sfMutableFlags);
return this->tx_->at(sfImmutableFlags);
}
return std::nullopt;
}
/**
* @brief Check if sfMutableFlags is present.
* @brief Check if sfImmutableFlags is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMutableFlags() const
hasImmutableFlags() const
{
return this->tx_->isFieldPresent(sfMutableFlags);
return this->tx_->isFieldPresent(sfImmutableFlags);
}
};
@@ -302,13 +302,13 @@ public:
}
/**
* @brief Set sfMutableFlags (SoeOptional)
* @brief Set sfImmutableFlags (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceCreateBuilder&
setMutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
setImmutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfMutableFlags] = value;
object_[sfImmutableFlags] = value;
return *this;
}

View File

@@ -163,29 +163,29 @@ public:
}
/**
* @brief Get sfMutableFlags (SoeOptional)
* @brief Get sfImmutableFlags (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getMutableFlags() const
getImmutableFlags() const
{
if (hasMutableFlags())
if (hasImmutableFlags())
{
return this->tx_->at(sfMutableFlags);
return this->tx_->at(sfImmutableFlags);
}
return std::nullopt;
}
/**
* @brief Check if sfMutableFlags is present.
* @brief Check if sfImmutableFlags is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMutableFlags() const
hasImmutableFlags() const
{
return this->tx_->isFieldPresent(sfMutableFlags);
return this->tx_->isFieldPresent(sfImmutableFlags);
}
/**
@@ -341,13 +341,13 @@ public:
}
/**
* @brief Set sfMutableFlags (SoeOptional)
* @brief Set sfImmutableFlags (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceSetBuilder&
setMutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
setImmutableFlags(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfMutableFlags] = value;
object_[sfImmutableFlags] = value;
return *this;
}

View File

@@ -100,29 +100,29 @@ public:
}
/**
* @brief Get sfFeeAmount (SoeOptional)
* @brief Get sfFeeAmountDelta (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getFeeAmount() const
getFeeAmountDelta() const
{
if (hasFeeAmount())
if (hasFeeAmountDelta())
{
return this->tx_->at(sfFeeAmount);
return this->tx_->at(sfFeeAmountDelta);
}
return std::nullopt;
}
/**
* @brief Check if sfFeeAmount is present.
* @brief Check if sfFeeAmountDelta is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFeeAmount() const
hasFeeAmountDelta() const
{
return this->tx_->isFieldPresent(sfFeeAmount);
return this->tx_->isFieldPresent(sfFeeAmountDelta);
}
/**
@@ -152,29 +152,29 @@ public:
}
/**
* @brief Get sfRemainingOwnerCount (SoeOptional)
* @brief Get sfRemainingOwnerCountDelta (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getRemainingOwnerCount() const
protocol_autogen::Optional<SF_INT32::type::value_type>
getRemainingOwnerCountDelta() const
{
if (hasRemainingOwnerCount())
if (hasRemainingOwnerCountDelta())
{
return this->tx_->at(sfRemainingOwnerCount);
return this->tx_->at(sfRemainingOwnerCountDelta);
}
return std::nullopt;
}
/**
* @brief Check if sfRemainingOwnerCount is present.
* @brief Check if sfRemainingOwnerCountDelta is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasRemainingOwnerCount() const
hasRemainingOwnerCountDelta() const
{
return this->tx_->isFieldPresent(sfRemainingOwnerCount);
return this->tx_->isFieldPresent(sfRemainingOwnerCountDelta);
}
};
@@ -243,13 +243,13 @@ public:
}
/**
* @brief Set sfFeeAmount (SoeOptional)
* @brief Set sfFeeAmountDelta (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
setFeeAmountDelta(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfFeeAmount] = value;
object_[sfFeeAmountDelta] = value;
return *this;
}
@@ -265,13 +265,13 @@ public:
}
/**
* @brief Set sfRemainingOwnerCount (SoeOptional)
* @brief Set sfRemainingOwnerCountDelta (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setRemainingOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
setRemainingOwnerCountDelta(std::decay_t<typename SF_INT32::type::value_type> const& value)
{
object_[sfRemainingOwnerCount] = value;
object_[sfRemainingOwnerCountDelta] = value;
return *this;
}

View File

@@ -13,6 +13,7 @@ extern Charge const kFeeRequestNoReply; // A request that we cannot satisfy.
extern Charge const kFeeInvalidSignature; // An object whose signature we had to check that failed.
extern Charge const kFeeUselessData; // Data we have no use for.
extern Charge const kFeeInvalidData; // Data we have to verify before rejecting.
extern Charge const kFeeMalformedData; // Data that no honest peer would send.
// RPC loads
extern Charge const kFeeMalformedRpc; // An RPC request that we can immediately tell is invalid.

View File

@@ -11,6 +11,7 @@
#include <xrpl/server/Manifest.h>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
@@ -22,6 +23,39 @@ namespace xrpl {
// Operations that clients may wish to perform against the network
// Master operational handler, server sequencer, network tracker
/**
* Maximum number of subscriptions a single client connection may hold at once.
*
* Applies to the account, real-time account, and account-history subscriptions
* tracked on one InfoSub (the sets counted by totalSubscriptionCount), bounding
* the disconnect-time cleanup of those sets. Book subscriptions are tracked
* separately (OrderBookDB) and are not counted here. Generous enough for
* legitimate power users such as block explorers.
*/
constexpr std::size_t kMaxSubscriptionsPerConnection = 100'000;
/**
* Whether adding @p additional subscriptions to a connection already holding
* @p current would exceed the cap.
*
* Pure arithmetic split out so it can be unit-tested without a live
* connection. The first term avoids underflow in the subtraction.
*
* @param current Subscriptions already tracked on the connection.
* @param additional Subscriptions a request would add.
* @param cap The effective per-connection cap. Defaults to the
* built-in limit; callers may pass a configured override.
* @return true if the request must be rejected to stay within the cap.
*/
[[nodiscard]] constexpr bool
exceedsSubscriptionCap(
std::size_t current,
std::size_t additional,
std::size_t cap = kMaxSubscriptionsPerConnection)
{
return additional > cap || current > cap - additional;
}
class InfoSubRequest : public CountedObject<InfoSubRequest>
{
public:
@@ -44,12 +78,12 @@ public:
* map.
*
* @note Lifetime contract: every `InfoSub` instance MUST be destroyed
* before the backing `Source`. NetworkOPsImp shutdown drops all
* subscriber strong refs before its own teardown to satisfy this.
* before the backing `Source`. NetworkOPsImp shutdown drops all
* subscriber strong refs before its own teardown to satisfy this.
* @note Thread-safety: per-instance state is guarded by `lock_`. The
* destructor reads tracking sets without taking `lock_` because
* the strong-pointer ref-count is zero at destruction time, so
* no other thread can be calling the public mutators.
* destructor reads tracking sets without taking `lock_` because
* the strong-pointer ref-count is zero at destruction time, so
* no other thread can be calling the public mutators.
*/
class InfoSub : public CountedObject<InfoSub>
{
@@ -117,6 +151,34 @@ public:
AccountID const& account,
bool historyOnly) = 0;
/**
* Schedule the server-side teardown of a disconnecting connection's
* account subscriptions off the destructor thread.
*
* The implementation posts a low-priority JobQueue task that erases the
* entries in bounded chunks, so `~InfoSub` returns immediately instead
* of running the erase loop inline. The sets are taken by value so the
* job owns its copies and never references the destroyed `InfoSub`.
* Cleanup is keyed on `seq` (unique per connection), so deferring it
* cannot disturb a reconnected client reusing the same accounts.
*
* @param seq The disconnecting connection's unique subscription id.
* @param rtAccounts Real-time account subscriptions to remove.
* @param normalAccounts Normal account subscriptions to remove.
* @param historyAccounts Account-history subscriptions to remove.
*
* @note The implementing `Source` must outlive any job it posts. If the
* JobQueue is already stopping (process shutdown), the job is not
* enqueued; the cleanup is skipped because the server-side maps
* are about to be destroyed and no publishing can run.
*/
virtual void
scheduleAccountCleanup(
std::uint64_t seq,
hash_set<AccountID> rtAccounts,
hash_set<AccountID> normalAccounts,
hash_set<AccountID> historyAccounts) = 0;
// VFALCO TODO Document the bool return value
virtual bool
subLedger(ref ispListener, json::Value& jvResult) = 0;
@@ -153,12 +215,12 @@ public:
* @param ispListener The subscriber requesting removal.
* @param book The order book to unsubscribe from.
* @return true if the entry was present and removed, false if the
* subscriber was not subscribed to @p book.
* subscriber was not subscribed to @p book.
*
* @note Thread-safety: acquires subLock_ internally.
* @note Thread-safety: acquires bookLock_ internally.
* @note Do NOT call from ~InfoSub(). Use unsubBookInternal instead
* to avoid a redundant write-back to bookSubscriptions_ on a
* partially-destroyed object.
* to avoid a redundant write-back to bookSubscriptions_ on a
* partially-destroyed object.
*/
virtual bool
unsubBook(ref ispListener, Book const&) = 0;
@@ -173,9 +235,9 @@ public:
* @param uListener The sequence number of the subscriber being torn down.
* @param book The order book entry to remove.
* @return true if the entry was present and removed, false otherwise
* (e.g., already removed by a concurrent RPC unsubscribe).
* (e.g., already removed by a concurrent RPC unsubscribe).
*
* @note Thread-safety: acquires subLock_ internally.
* @note Thread-safety: acquires bookLock_ internally.
*/
virtual bool
unsubBookInternal(std::uint64_t uListener, Book const&) = 0;
@@ -221,8 +283,8 @@ public:
/**
* Journal used by InfoSub for diagnostics that occur after the
* owning subsystem (e.g. application-level Logs) is the only
* surviving sink — primarily destructor-time cleanup failures.
* owning subsystem (e.g. application-level Logs) is the only
* surviving sink — primarily destructor-time cleanup failures.
*/
[[nodiscard]] virtual beast::Journal const&
journal() const = 0;
@@ -243,6 +305,56 @@ public:
[[nodiscard]] std::uint64_t
getSeq() const;
/**
* Return the number of subscriptions currently tracked on this
* connection.
*
* The combined size of the per-connection account, real-time account, and
* account-history subscription sets. `doSubscribe` reads this to enforce
* the per-connection subscription cap before admitting more.
*
* @return The total tracked subscription count for this connection.
*
* @note Thread-safe: takes `lock_` for the read; read-only.
*/
[[nodiscard]] std::size_t
totalSubscriptionCount() const;
/**
* Enforce the cap and reserve a request's net-new accounts, atomically.
*
* Under one hold of `lock_`: count the net-new entries in the two sets,
* check the total against @p cap, and insert them only if it fits.
* All-or-nothing. Doing check and insert together stops two concurrent
* requests sharing an InfoSub (the admin subscribe-by-url path) from both
* passing the check before either records its accounts. The server-side
* maps are populated afterwards by subAccount, whose re-insert is a no-op.
*
* @param proposedAccounts Real-time (accounts_proposed) ids to reserve.
* @param normalAccounts Normal (accounts) ids to reserve.
* @param cap The effective per-connection cap.
* @return true if reserved; false if the request must be rejected.
* @note Thread-safe: takes `lock_`.
*/
[[nodiscard]] bool
tryReserveAccountSubscriptions(
hash_set<AccountID> const& proposedAccounts,
hash_set<AccountID> const& normalAccounts,
std::size_t cap);
/**
* Whether this connection already tracks an account-history for @p account.
*
* `doSubscribe` reads this to charge the cap for an account_history_tx_stream
* only when it is net-new, matching the account branches.
*
* @param account The account an account_history_tx_stream would add.
* @return true if @p account is already in the account-history set.
* @note Thread-safe: takes `lock_`; read-only.
*/
[[nodiscard]] bool
hasAccountHistorySubscription(AccountID const& account) const;
void
onSendEmpty();
@@ -302,7 +414,9 @@ public:
getApiVersion() const noexcept;
protected:
std::mutex lock_;
// Mutable so the read-only totalSubscriptionCount() accessor can lock it
// from a const method; locking semantics are otherwise unchanged.
mutable std::mutex lock_;
private:
Consumer consumer_;

View File

@@ -3,12 +3,14 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base64.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SecretKey.h>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
@@ -43,12 +45,15 @@ namespace xrpl {
dynamically generates the signatureless form when it needs to verify
the signature.
An instance of ManifestCache stores, for each trusted validator, (a) its
An instance of ManifestCache stores, for each known validator, (a) its
master public key, and (b) the most senior of all valid manifests it has
seen for that validator, if any. On startup, the [validator_token] config
entry (which contains the manifest for this validator) is decoded and
added to the manifest cache. Other manifests are added as "gossip"
received from xrpld peers.
received from xrpld peers, including ones for validators this node does not
trust. Manifests for untrusted validators are capped (kMaxUntrustedCount)
so peer gossip cannot grow the cache without bound; trusted validators are
not capped. Entries are never evicted, so a stored revocation is permanent.
When an ephemeral key is compromised, a new signing key pair is created,
along with a new manifest vouching for it (with a higher sequence number),
@@ -164,6 +169,100 @@ struct Manifest
std::string
to_string(Manifest const& m);
/**
* Largest a valid manifest can be, in decoded bytes.
*
* A manifest has a fixed set of fields. Each is serialized as a field header
* (1-2 bytes), an optional length prefix (1 byte for these sizes), and the
* field body. Taking every field at its largest gives the maximum below, so
* anything larger cannot be a valid manifest.
*
* Field header + length + body = bytes
* sfVersion (U16) 2 0 2 4
* sfSequence (U32) 1 0 4 5
* sfPublicKey (33) 1 1 33 35
* sfSigningPubKey (33) 1 1 33 35
* sfSignature (72) 1 1 72 74
* sfMasterSignature (72) 2 1 72 75
* sfDomain (128) 1 1 128 130
* -----
* 358
*/
constexpr std::size_t kMaxManifestBytes = 358;
/**
* Largest a valid manifest can be, in base64 characters.
*
* base64 encodes 3 bytes as 4 characters, so this is the encoded form of
* @ref kMaxManifestBytes. Callers that receive a base64 manifest should
* reject anything longer than this before decoding, to avoid allocating
* memory for an oversized input.
*/
constexpr std::size_t kMaxManifestBase64 = base64::encodedSize(kMaxManifestBytes);
/**
* Default number of untrusted manifests to store in cache and allowed
* in one Manifest message.
*
* Bounds unlisted validators two ways. In the cache, a manifest for a
* brand-new unlisted key is rejected once this many are held, so peer gossip
* cannot grow the cache without end. In a TMManifests message, this many are
* sent and processed, so a peer sending its whole cache cannot force unbounded
* work.
*
* Operators can override this with `[overlay] max_untrusted_count`. Both users
* read the configured value and fall back to this default.
*/
constexpr std::size_t kMaxUntrustedCount = 300;
/**
* Default number of trusted manifests allowed in a Manifest message.
* Not used atm while creating the message, but used to calculate the higher limit on
* received message size. Introduced to maintain consistency. Future implementation
* will use this limit.
*
* Trusted manifests are never dropped: every one this node holds is sent, and
* every one received is processed, since dropping one would delay a validator
* key rotation. This count only sizes the largest message accepted, so it must
* stay above any realistic validator list. Cap can be increased in the config
* file if messages get rejected with actual trusted manifest count crossing
* configured(or else default) value.
* Operators can override this with `[overlay] max_trusted_count`.
*/
constexpr std::size_t kMaxTrustedCount = 300;
/**
* Number of untrusted manifests to store in cache and allowed
* in one Manifest message..
*
* Returns the operator's override when one is configured, otherwise
* @ref kMaxUntrustedCount. Config stores an override rather than the default
* itself because the core module cannot depend on this module.
*
* @param configured The value from `[overlay] max_untrusted_count`, or
* `std::nullopt` when the operator did not set it.
*/
constexpr std::size_t
untrustedManifestCount(std::optional<std::size_t> const& configured)
{
return configured.value_or(kMaxUntrustedCount);
}
/**
* Number of trusted manifests allowed in a Manifest message.
*
* Not a cap on how many are sent or processed; see @ref kMaxTrustedCount.
* but used to calculate the higher limit on received message size.
*
* @param configured The value from `[overlay] max_trusted_count`, or
* `std::nullopt` when the operator did not set it.
*/
constexpr std::size_t
trustedManifestCount(std::optional<std::size_t> const& configured)
{
return configured.value_or(kMaxTrustedCount);
}
/**
* Constructs Manifest from serialized string
*
@@ -172,7 +271,7 @@ to_string(Manifest const& m);
* @return `std::nullopt` if string is invalid
*
* @note This does not verify manifest signatures.
* `Manifest::verify` should be called after constructing manifest.
* `Manifest::verify` should be called after constructing manifest.
*/
/** @{ */
std::optional<Manifest>
@@ -225,30 +324,17 @@ loadValidatorToken(
beast::Journal journal = beast::Journal(beast::Journal::getNullSink()));
enum class ManifestDisposition {
/**
* Manifest is valid
*/
Accepted = 0,
Accepted = 0, ///< Manifest is valid
/**
* Sequence is too old
*/
Stale,
Stale, ///< Sequence is too old
/**
* The master key is not acceptable to us
*/
BadMasterKey,
BadMasterKey, ///< The master key is not acceptable to us
/**
* The ephemeral key is not acceptable to us
*/
BadEphemeralKey,
BadEphemeralKey, ///< The ephemeral key is not acceptable to us
/**
* Timely, but invalid signature
*/
Invalid
Invalid, ///< Timely, but invalid signature
UntrustedCapacity ///< Unlisted and limit reached
};
inline std::string
@@ -266,11 +352,25 @@ to_string(ManifestDisposition m)
return "badEphemeralKey";
case ManifestDisposition::Invalid:
return "invalid";
case ManifestDisposition::UntrustedCapacity:
return "untrustedCapacity";
default:
return "unknown";
}
}
/**
* Whether a manifest counts against the 'untrusted' cache cap.
*
* Passed to `ManifestCache::applyManifest` with no default, so every caller
* must choose. `Capped` is the safe, flood-resistant value; only listed or
* configured keys should use `Uncapped`.
*/
enum class ManifestRateLimitCapPolicy : std::uint8_t {
Capped, ///< Subject to the untrusted cap (unlisted peer gossip)
Uncapped ///< Bypasses the cap (listed/trusted or config manifests)
};
class DatabaseCon;
/**
@@ -294,8 +394,51 @@ private:
std::atomic<std::uint32_t> seq_{0};
/**
* Master keys of cached manifests for validators this node does not list.
*
* One entry per capped key in `map_`; its size enforces the cap below.
* A key is added when first cached under `Capped` and removed when it
* becomes listed (see `promoteToTrusted`) or an `Uncapped` update arrives,
* never re-added on de-listing. Uncapped keys are not tracked here.
*/
hash_set<PublicKey> untrustedKeys_;
/**
* Maximum number of untrusted master keys kept in the cache.
*
* Once reached, a manifest for a brand-new unlisted key is rejected. Set
* from the config, defaulting to @ref kMaxUntrustedCount.
*/
std::size_t const maxUntrustedCount_;
/**
* Running count of manifests rejected because the untrusted cap was full.
*
* Drives throttled logging (see `kUntrustedRejectCount`). Atomic because
* `applyManifest` may run concurrently.
*/
std::atomic<std::uint64_t> untrustedRejectCount_{0};
/**
* Number of cap rejections between summary warnings.
*
* @see untrustedRejectCount_
*/
static constexpr std::uint64_t kUntrustedRejectCount = 10000;
public:
explicit ManifestCache(beast::Journal j = beast::Journal(beast::Journal::getNullSink())) : j_(j)
/**
* @param j Journal for logging.
*
* @param maxUntrustedCount Untrusted master keys to keep. Pass the
* configured value; defaults to @ref kMaxUntrustedCount. Taken as a
* parameter because this module cannot depend on the config.
*/
explicit ManifestCache(
beast::Journal j = beast::Journal(beast::Journal::getNullSink()),
std::size_t maxUntrustedCount = kMaxUntrustedCount)
: j_(j), maxUntrustedCount_(maxUntrustedCount)
{
}
@@ -378,17 +521,44 @@ public:
/**
* Add manifest to cache.
*
* A brand-new unlisted key is rejected once the untrusted cap is full;
* updates to a cached key and `Uncapped` manifests bypass the cap. The
* caller decides `cap` before calling so the cache lock is not held while
* consulting the validator list, which would risk a lock-ordering deadlock.
*
* @param m Manifest to add
*
* @return `ManifestDisposition::accepted` if successful, or
* `stale` or `invalid` otherwise
* @param cap `Uncapped` skips the untrusted cap; use it for keys that are
* listed, configured, or loaded from the DB. Note `Uncapped` does not
* assert the key is currently trusted (a DB entry may predate a
* de-listing). Callers must state this explicitly so a manifest is
* never left uncapped by omission.
*
* @return `Accepted` if stored, `Stale` if superseded, `Invalid`/
* `BadEphemeralKey` if malformed, or `UntrustedCapacity` if the
* untrusted cap is full.
*
* @par Thread Safety
*
* May be called concurrently
*/
ManifestDisposition
applyManifest(Manifest m);
applyManifest(Manifest m, ManifestRateLimitCapPolicy cap);
/**
* Stop counting a master key against the untrusted cap.
*
* Called when a cached untrusted key becomes listed, freeing its slot.
* Idempotent and a no-op for keys that were never counted.
*
* @param pk Master public key that is now listed/trusted
*
* @par Thread Safety
*
* May be called concurrently
*/
void
promoteToTrusted(PublicKey const& pk);
/**
* Populate manifest cache with manifests in database and config.

View File

@@ -6,7 +6,6 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/tx/transactors/dex/AMMContext.h>
#include <cstdint>
@@ -124,17 +123,12 @@ private:
generateFibSeqOffer(TAmounts<TIn, TOut> const& balances) const;
/**
* Generate max offer.
* If `fixAMMOverflowOffer` is active, the offer is generated as:
* Generate max offer. The offer is generated as:
* takerGets = 99% * balances.out takerPays = swapOut(takerGets).
* Return nullopt if takerGets is 0 or takerGets == balances.out.
*
* If `fixAMMOverflowOffer` is not active, the offer is generated as:
* takerPays = max input amount;
* takerGets = swapIn(takerPays).
*/
[[nodiscard]] std::optional<AMMOffer<TIn, TOut>>
maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const;
maxOffer(TAmounts<TIn, TOut> const& balances) const;
};
} // namespace xrpl

View File

@@ -2,6 +2,8 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -16,7 +18,7 @@ namespace xrpl {
class SponsorshipSet : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Custom;
explicit SponsorshipSet(ApplyContext& ctx) : Transactor(ctx)
{
@@ -47,6 +49,15 @@ public:
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
private:
TER
createSponsorship(
Keylet const& sponsorshipKeylet,
AccountID const& sponsorID,
AccountID const& sponseeID,
SLE::ref sponsorAccSle,
SLE::ref reserveSponsorAccSle);
};
} // namespace xrpl

View File

@@ -32,7 +32,7 @@ struct MPTCreateArgs
std::optional<std::uint16_t> transferFee = std::nullopt;
std::optional<Slice> const& metadata{};
std::optional<uint256> domainId = std::nullopt;
std::optional<std::uint32_t> mutableFlags = std::nullopt;
std::optional<std::uint32_t> immutableFlags = std::nullopt;
// Set only by callers that issue an MPT representing a wrapped asset
// (e.g. VaultCreate's share token). The keylet must point to an
// existing MPToken or RippleState owned by `account`. Surfaces on

View File

@@ -3,12 +3,15 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
#include <array>
#include <cstdint>
namespace xrpl {
@@ -22,6 +25,37 @@ public:
{
}
// Maps each MPTokenIssuanceSet set flag(e.g., tfMPTSetCanLock), to the issuance's
// corresponding immutable flag (e.g., lsifMPTCanLock) and the target ledger flag (e.g.,
// lsfMPTCanLock).
struct FlagMapping
{
std::uint32_t setFlag;
std::uint32_t immutableFlag;
std::uint32_t ledgerFlag;
};
static constexpr std::array<FlagMapping, 7> flagMapping = {
{{.setFlag = tfMPTSetCanLock, .immutableFlag = lsifMPTCanLock, .ledgerFlag = lsfMPTCanLock},
{.setFlag = tfMPTSetRequireAuth,
.immutableFlag = lsifMPTRequireAuth,
.ledgerFlag = lsfMPTRequireAuth},
{.setFlag = tfMPTSetCanEscrow,
.immutableFlag = lsifMPTCanEscrow,
.ledgerFlag = lsfMPTCanEscrow},
{.setFlag = tfMPTSetCanTrade,
.immutableFlag = lsifMPTCanTrade,
.ledgerFlag = lsfMPTCanTrade},
{.setFlag = tfMPTSetCanTransfer,
.immutableFlag = lsifMPTCanTransfer,
.ledgerFlag = lsfMPTCanTransfer},
{.setFlag = tfMPTSetCanClawback,
.immutableFlag = lsifMPTCanClawback,
.ledgerFlag = lsfMPTCanClawback},
{.setFlag = tfMPTSetCanHoldConfidentialBalance,
.immutableFlag = lsifMPTCanHoldConfidentialBalance,
.ledgerFlag = lsfMPTCanHoldConfidentialBalance}}};
static bool
checkExtraFeatures(PreflightContext const& ctx);

View File

@@ -1,6 +1,8 @@
# Linux Packaging
This directory contains all files needed to build RPM and Debian packages for `xrpld`.
This directory contains all files needed to build RPM and Debian packages for
`xrpld`. The packages also ship the `validator-keys` tool, so packaging requires
a build configured with `-Dvalidator_keys=ON`.
## Directory layout
@@ -46,17 +48,28 @@ To print the full packaging matrix (artifact names and images) for the current
Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
`reusable-package.yml`. That workflow generates its own packaging matrix from
`package_configs` in `linux.json` (via `generate.py --packaging`) and fans out
one job per distro. Each job downloads the pre-built `xrpld` binary artifact and
runs in that distro's container, so the package format follows from the
container's package manager. The packaging script derives the package version
from the downloaded binary's `xrpld --version` output; no CMake configure or
build step is needed inside the packaging job.
one job per distro. Each job downloads the pre-built `xrpld` and `validator-keys`
binary artifacts and runs in that distro's container, so the package format
follows from the container's package manager. The packaging script derives the
package version from the downloaded binary's `xrpld --version` output; no CMake
configure or build step is needed inside the packaging job.
The binaries come from the `debian` and `rhel` build configurations in
`linux.json`'s `configs` section, which pass `-Dvalidator_keys=ON` so that the
build job produces `validator-keys` next to `xrpld` and uploads it as the
`validator-keys-<config name>` artifact. The packaging entry for a distro names
both artifacts (`xrpld_artifact_name` and `validator_keys_artifact_name`), so a
packaged configuration must keep `-Dvalidator_keys=ON`.
`validator-keys` is fetched from an exact commit pinned in
[`cmake/XrplValidatorKeys.cmake`](../cmake/XrplValidatorKeys.cmake), so a given
`xrpld` version always packages the same tool; bump that commit deliberately.
### Locally (mirrors CI)
With an `xrpld` binary already built at `build/xrpld`, run the packaging step
inside the same container CI uses. The image tag is derived from `linux.json`
so you don't need to hardcode a SHA.
With `xrpld` and `validator-keys` binaries already built at `build/xrpld` and
`build/validator-keys`, run the packaging step inside the same container CI uses.
The image tag is derived from `linux.json` so you don't need to hardcode a SHA.
```bash
# From the repo root. Each distro's container image is the `image` field of its
@@ -87,6 +100,7 @@ needed, but the host toolchain replaces the pinned CI image:
```bash
cmake \
-Dxrpld=ON \
-Dvalidator_keys=ON \
-Dpkg_release=1 \
-Dtests=OFF \
..
@@ -95,9 +109,11 @@ cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL
```
The `cmake/XrplPackaging.cmake` module defines the `package` target only if at
least one of `rpmbuild` / `dpkg-buildpackage` is present; `build_pkg.sh` then
infers the package format from the host's package manager. The packaging script
installs to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of
least one of `rpmbuild` / `dpkg-buildpackage` is present and both the `xrpld` and
`validator-keys` targets exist (`-Dxrpld=ON -Dvalidator_keys=ON`); the target
builds both binaries before packaging. `build_pkg.sh` then infers the package
format from the host's package manager. The packaging script installs to
FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of
`CMAKE_INSTALL_PREFIX`.
The package version is not a CMake input on this path: `build_pkg.sh` derives it
@@ -156,13 +172,17 @@ CMake/CI integration. The CI workflow and the CMake `package` target both invoke
and lets the script use defaults for the rest.
It resolves `SRC_DIR` and `BUILD_DIR` to absolute paths, then calls
`stage_common()` to copy the binary, config files, and shared support files
into the staging area, and invokes the platform build tool.
`stage_common()` to copy the `xrpld` and `validator-keys` binaries, config files,
and shared support files into the staging area, and invokes the platform build
tool. Both binaries must be present in `BUILD_DIR` and must run in the packaging
environment; a missing or non-runnable one fails early. That runtime check is
what catches a binary still linked against the Nix store's ELF loader (see
`patch_nix_binary` in `cmake/PatchNixBinary.cmake`).
### RPM
1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
2. Copies `xrpld.spec` and all shared source files (binary, configs, service files) into `SOURCES/`.
2. Copies `xrpld.spec` and all shared source files (binaries, configs, service files) into `SOURCES/`.
3. Runs `rpmbuild -bb`, passing the normalized package metadata version as the
`pkg_version` RPM macro and `PKG_RELEASE` as the `pkg_release` RPM macro.
The spec uses manual `install` commands to place files, disables `dwz`, and
@@ -182,7 +202,8 @@ service restart.
### DEB
1. Creates a staging source tree at `debbuild/source/` inside the build directory.
2. Stages the binary, configs, `README.md`, and `LICENSE.md`.
2. Stages the binaries, configs, `README.md`, `LICENSE.md`, and
`validator-keys-LICENSE`.
3. Copies `package/debian/` control files into `debbuild/source/debian/`.
4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`,

View File

@@ -1,7 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
# Build an RPM or Debian package from a pre-built xrpld binary.
# Build an RPM or Debian package from the pre-built xrpld and validator-keys
# binaries.
#
# Flags override env vars; env vars override defaults.
@@ -11,7 +12,9 @@ Usage: build_pkg.sh [options]
Options (each can also be set via the env var shown):
--src-dir DIR repo root [SRC_DIR; default: ${PWD}]
--build-dir DIR directory holding xrpld [BUILD_DIR; default: ${PWD}/build]
--build-dir DIR directory holding the
xrpld and validator-keys
binaries [BUILD_DIR; default: ${PWD}/build]
--pkg-release N package release iteration [PKG_RELEASE; default: 1]
--source-date-epoch SECS reproducibility timestamp [SOURCE_DATE_EPOCH; latest git ctime; fallback: current time]
-h, --help show this help and exit
@@ -69,15 +72,44 @@ SRC_DIR="$(cd "${SRC_DIR:-${PWD}}" && pwd)"
BUILD_DIR="${BUILD_DIR:-${PWD}/build}"
if [[ ! -d "${BUILD_DIR}" ]]; then
echo "build_pkg.sh: build directory not found: ${BUILD_DIR}" >&2
echo "Build xrpld before packaging, or set BUILD_DIR to the directory containing xrpld." >&2
echo "Build the binaries before packaging, or set BUILD_DIR to the directory containing them." >&2
exit 1
fi
BUILD_DIR="$(cd "${BUILD_DIR}" && pwd)"
xrpld_binary="${BUILD_DIR}/xrpld"
if [[ ! -x "${xrpld_binary}" ]]; then
echo "build_pkg.sh: expected executable xrpld binary at ${xrpld_binary}." >&2
echo "Build xrpld before packaging, or set BUILD_DIR to the directory containing xrpld." >&2
validator_keys_binary="${BUILD_DIR}/validator-keys"
# Report both binaries at once: they share a single BUILD_DIR, so telling the
# reader to point it at one of them in isolation is advice they cannot follow.
missing=()
[[ -x "${xrpld_binary}" ]] || missing+=(xrpld)
[[ -x "${validator_keys_binary}" ]] || missing+=(validator-keys)
if [[ ${#missing[@]} -gt 0 ]]; then
echo "build_pkg.sh: missing or not executable in ${BUILD_DIR}: ${missing[*]}" >&2
echo "Both binaries come from a single CMake build directory configured with" >&2
echo "-Dxrpld=ON -Dvalidator_keys=ON. Build them, then point BUILD_DIR at that" >&2
echo "directory." >&2
exit 1
fi
# Shipping validator-keys means shipping its notice, so treat it as required
# rather than letting a package go out without the attribution.
validator_keys_license="${BUILD_DIR}/validator-keys-LICENSE"
if [[ ! -f "${validator_keys_license}" ]]; then
echo "build_pkg.sh: missing ${validator_keys_license}." >&2
echo "cmake/XrplValidatorKeys.cmake copies it out of the fetched" >&2
echo "validator-keys-tool source, so reconfigure with -Dvalidator_keys=ON." >&2
exit 1
fi
# The binary must also *run* here. Packaging happens in a vanilla distro
# container, so this is what catches a binary still pointing at the Nix store's
# ELF loader (see patch_nix_binary in cmake/PatchNixBinary.cmake); xrpld is
# covered implicitly by the version query below.
if ! "${validator_keys_binary}" --version >/dev/null; then
echo "build_pkg.sh: ${validator_keys_binary} exists but does not run here." >&2
exit 1
fi
@@ -150,7 +182,9 @@ stage_common() {
local dest="$1"
mkdir -p "${dest}"
cp "${BUILD_DIR}/xrpld" "${dest}/xrpld"
cp "${xrpld_binary}" "${dest}/xrpld"
cp "${validator_keys_binary}" "${dest}/validator-keys"
cp "${validator_keys_license}" "${dest}/validator-keys-LICENSE"
cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg"
cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt"
cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md"

View File

@@ -18,6 +18,8 @@ Depends:
${shlibs:Depends},
${misc:Depends}
Description: XRP Ledger daemon
Reference implementation of the XRP Ledger protocol.
Participates in the peer-to-peer network, processes transactions,
and maintains a local ledger copy.
xrpld is the reference implementation of the XRP Ledger protocol. It
participates in the peer-to-peer XRP Ledger network, processes
transactions, and maintains the ledger database.
This package also includes the validator-keys tool for validator key
management.

View File

@@ -4,6 +4,25 @@ Source: https://github.com/XRPLF/rippled
Files: *
Copyright: 2011-present, the XRP Ledger developers
License: ISC
Files: validator-keys
Copyright: 2016, Ripple Labs Inc.
2011, Arthur Britto, David Schwartz, Jed McCaleb, Vinnie Falco, Bob Way,
Eric Lombrozo, Nikolaos D. Bougalis, Howard Hinnant
2013, Raw Material Software Ltd.
2003-2011, Christopher M. Kohlhoff
2009-2010, Satoshi Nakamoto
2011, The Bitcoin developers
2003-2005, Tom Wu
License: ISC
Comment: Built from https://github.com/ripple/validator-keys-tool at the commit
pinned in cmake/XrplValidatorKeys.cmake. Besides ISC-licensed code it
incorporates work under the Boost Software License 1.0 (ASIO), the MIT/X11
license (Bitcoin) and Tom Wu's license, whose terms require its notice to be
retained intact. The complete upstream notice is therefore shipped verbatim as
/usr/share/doc/xrpld/validator-keys-LICENSE.
License: ISC
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above

View File

@@ -18,6 +18,7 @@ override_dh_installsysusers:
override_dh_install:
install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld
install -D -m 0755 validator-keys debian/xrpld/usr/bin/validator-keys
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt

View File

@@ -1 +1,2 @@
README.md
validator-keys-LICENSE

View File

@@ -32,6 +32,8 @@ BuildRequires: systemd-rpm-macros
xrpld is the reference implementation of the XRP Ledger protocol. It
participates in the peer-to-peer XRP Ledger network, processes
transactions, and maintains the ledger database.
This package also includes the validator-keys tool for validator key
management.
%prep
:
@@ -41,6 +43,7 @@ transactions, and maintains the ledger database.
%install
install -Dm0755 %{_sourcedir}/xrpld %{buildroot}%{_bindir}/%{name}
install -Dm0755 %{_sourcedir}/validator-keys %{buildroot}%{_bindir}/validator-keys
install -Dm0644 %{_sourcedir}/xrpld.cfg %{buildroot}%{_sysconfdir}/%{name}/xrpld.cfg
install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{name}/validators.txt
@@ -59,6 +62,8 @@ install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/lo
# Docs
install -Dm0644 %{_sourcedir}/LICENSE.md %{buildroot}%{_docdir}/%{name}/LICENSE.md
install -Dm0644 %{_sourcedir}/README.md %{buildroot}%{_docdir}/%{name}/README.md
# Upstream notice for the bundled validator-keys tool.
install -Dm0644 %{_sourcedir}/validator-keys-LICENSE %{buildroot}%{_docdir}/%{name}/validator-keys-LICENSE
# Legacy compatibility for pre-FHS package layouts.
# TODO: remove after rippled fully deprecated.
@@ -80,11 +85,13 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
%files
%license %{_docdir}/%{name}/LICENSE.md
%license %{_docdir}/%{name}/validator-keys-LICENSE
%doc %{_docdir}/%{name}/README.md
%dir %{_sysconfdir}/%{name}
%{_bindir}/%{name}
%{_bindir}/validator-keys
%config(noreplace) %{_sysconfdir}/%{name}/xrpld.cfg
%config(noreplace) %{_sysconfdir}/%{name}/validators.txt

View File

@@ -102,6 +102,10 @@ undefined:nudb
# Snappy compression library intentional overflows
unsigned-integer-overflow:snappy.cc
# fast_float parses floats with a SWAR trick (parse_eight_digits_unrolled) that
# multiplies eight packed digits modulo 2^64; the wraparound is by design.
unsigned-integer-overflow:fast_float
# Abseil intentional overflows in hashing, RNG and time arithmetic.
# Matched at library scope (like boost above): the wraparound is by design
# across many absl files (hash mixing, raw_hash_set probing, duration math,
@@ -194,7 +198,7 @@ unsigned-integer-overflow:tests/libxrpl/basics/RangeSet.cpp
unsigned-integer-overflow:test/app/Batch_test.cpp
unsigned-integer-overflow:test/app/ConfidentialTransfer_test.cpp
unsigned-integer-overflow:test/app/Invariants_test.cpp
unsigned-integer-overflow:test/app/Loan_test.cpp
unsigned-integer-overflow:test/app/lending/LoanSecurity_test.cpp
unsigned-integer-overflow:test/app/NFToken_test.cpp
unsigned-integer-overflow:test/app/OfferMPT_test.cpp
unsigned-integer-overflow:test/app/Offer_test.cpp

View File

@@ -76,24 +76,6 @@ getInverse()
return &kTab[0];
}
/**
* Returns max chars needed to encode a base64 string
*/
constexpr std::size_t
encodedSize(std::size_t n)
{
return 4 * ((n + 2) / 3);
}
/**
* Returns max bytes needed to decode a base64 string
*/
constexpr std::size_t
decodedSize(std::size_t n)
{
return ((n / 4) * 3) + 2;
}
/**
* Encode a series of octets as a padded, base64 string.
*

View File

@@ -3,9 +3,11 @@
#include <xrpl/basics/contract.h>
#include <xrpl/json/json_value.h>
#include <fast_float/fast_float.h> // IWYU pragma: keep
#include <fast_float/parse_number.h>
#include <algorithm>
#include <cctype>
#include <charconv>
#include <cstdint>
#include <cstdio>
#include <istream>
@@ -605,8 +607,14 @@ Reader::decodeNumber(Token& token)
bool
Reader::decodeDouble(Token& token)
{
// Sanity check to avoid buffer overflow exploits.
if (token.end < token.start)
{
return addError("Unable to parse token length", token);
}
double value = 0;
auto const [ptr, ec] = std::from_chars(token.start, token.end, value);
auto const [ptr, ec] = fast_float::from_chars(token.start, token.end, value);
// Reject anything from_chars could not turn into a finite double:
// - ec != std::errc{}: no valid conversion, or an out-of-range magnitude

View File

@@ -925,7 +925,7 @@ tokenOfferCreateApply(
priorBalance < accountReserve(view, acct, j, {.ownerCountDelta = 1}))
return tecINSUFFICIENT_RESERVE;
auto const offerID = keylet::nftokenOffer(acctID, seqProxy.value());
auto const offerID = keylet::nftokenOffer(acctID, seqProxy);
// Create the offer:
{

View File

@@ -23,7 +23,7 @@ namespace {
//------------------------------------------------------------------------------
// clang-format off
// NOLINTNEXTLINE(readability-identifier-naming)
char const* const versionString = "3.3.0-rc1"
char const* const versionString = "3.4.0-b0"
// clang-format on
;

View File

@@ -180,26 +180,13 @@ getQuality(uint256 const& uBase)
return boost::endian::load_big_u64(uBase.end() - 8);
}
uint256
getTicketIndex(AccountID const& account, std::uint32_t ticketSeq)
{
return indexHash(LedgerNameSpace::Ticket, account, ticketSeq);
}
uint256
getTicketIndex(AccountID const& account, SeqProxy ticketSeq)
{
XRPL_ASSERT(ticketSeq.isTicket(), "xrpl::getTicketIndex : valid input");
return getTicketIndex(account, ticketSeq.value());
}
MPTID
makeMptID(std::uint32_t sequence, AccountID const& account)
makeMptID(std::uint32_t const sequence, AccountID const& account)
{
MPTID u;
sequence = boost::endian::native_to_big(sequence);
memcpy(u.data(), &sequence, sizeof(sequence));
memcpy(u.data() + sizeof(sequence), account.data(), sizeof(account));
auto const bigEndianSequence = boost::endian::native_to_big(sequence);
memcpy(u.data(), &bigEndianSequence, sizeof(bigEndianSequence));
memcpy(u.data() + sizeof(bigEndianSequence), account.data(), sizeof(account));
return u;
}
@@ -286,13 +273,13 @@ trustLine(AccountID const& id0, AccountID const& id1, Currency const& currency)
}
Keylet
offer(AccountID const& id, std::uint32_t seq) noexcept
offer(AccountID const& id, SeqProxy const& seq) noexcept
{
return {ltOFFER, indexHash(LedgerNameSpace::Offer, id, seq)};
return {ltOFFER, indexHash(LedgerNameSpace::Offer, id, seq.value())};
}
Keylet
quality(Keylet const& k, std::uint64_t q) noexcept
quality(Keylet const& k, std::uint64_t const q) noexcept
{
XRPL_ASSERT(k.type == ltDIR_NODE, "xrpl::keylet::quality : valid input type");
@@ -320,22 +307,17 @@ next(Keylet const& k)
}
Keylet
ticket(AccountID const& id, std::uint32_t ticketSeq)
ticket(AccountID const& id, SeqProxy const& seq)
{
return {ltTICKET, getTicketIndex(id, ticketSeq)};
}
Keylet
ticket(AccountID const& id, SeqProxy ticketSeq)
{
return {ltTICKET, getTicketIndex(id, ticketSeq)};
XRPL_ASSERT(seq.isTicket(), "xrpl::keylet::ticket : valid input");
return {ltTICKET, indexHash(LedgerNameSpace::Ticket, id, seq.value())};
}
// This function is presently static, since it's never accessed from anywhere
// else. If we ever support multiple pages of signer lists, this would be the
// keylet used to locate them.
static Keylet
signerList(AccountID const& account, std::uint32_t page) noexcept
signerList(AccountID const& account, std::uint32_t const page) noexcept
{
return {ltSIGNER_LIST, indexHash(LedgerNameSpace::SignerList, account, page)};
}
@@ -353,9 +335,9 @@ sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept
}
Keylet
check(AccountID const& id, std::uint32_t seq) noexcept
check(AccountID const& id, SeqProxy const& seq) noexcept
{
return {ltCHECK, indexHash(LedgerNameSpace::Check, id, seq)};
return {ltCHECK, indexHash(LedgerNameSpace::Check, id, seq.value())};
}
Keylet
@@ -394,7 +376,7 @@ ownerDir(AccountID const& id) noexcept
}
Keylet
page(uint256 const& key, std::uint64_t index) noexcept
page(uint256 const& key, std::uint64_t const index) noexcept
{
if (index == 0)
return {ltDIR_NODE, key};
@@ -403,15 +385,15 @@ page(uint256 const& key, std::uint64_t index) noexcept
}
Keylet
escrow(AccountID const& src, std::uint32_t seq) noexcept
escrow(AccountID const& src, SeqProxy const& seq) noexcept
{
return {ltESCROW, indexHash(LedgerNameSpace::Escrow, src, seq)};
return {ltESCROW, indexHash(LedgerNameSpace::Escrow, src, seq.value())};
}
Keylet
payChannel(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept
payChannel(AccountID const& src, AccountID const& dst, SeqProxy const& seq) noexcept
{
return {ltPAYCHAN, indexHash(LedgerNameSpace::XRPPaymentChannel, src, dst, seq)};
return {ltPAYCHAN, indexHash(LedgerNameSpace::XRPPaymentChannel, src, dst, seq.value())};
}
Keylet
@@ -438,9 +420,9 @@ nftokenPage(Keylet const& k, uint256 const& token)
}
Keylet
nftokenOffer(AccountID const& owner, std::uint32_t seq)
nftokenOffer(AccountID const& owner, SeqProxy const& seq)
{
return {ltNFTOKEN_OFFER, indexHash(LedgerNameSpace::NftokenOffer, owner, seq)};
return {ltNFTOKEN_OFFER, indexHash(LedgerNameSpace::NftokenOffer, owner, seq.value())};
}
Keylet
@@ -512,7 +494,7 @@ bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType)
}
Keylet
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq)
xChainClaimID(STXChainBridge const& bridge, std::uint64_t const seq)
{
return {
ltXCHAIN_OWNED_CLAIM_ID,
@@ -526,7 +508,7 @@ xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq)
}
Keylet
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq)
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t const seq)
{
return {
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
@@ -546,17 +528,11 @@ did(AccountID const& account) noexcept
}
Keylet
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept
oracle(AccountID const& account, std::uint32_t const documentID) noexcept
{
return {ltORACLE, indexHash(LedgerNameSpace::Oracle, account, documentID)};
}
Keylet
mptokenIssuance(std::uint32_t seq, AccountID const& issuer) noexcept
{
return mptokenIssuance(makeMptID(seq, issuer));
}
Keylet
mptokenIssuance(MPTID const& issuanceID) noexcept
{
@@ -582,27 +558,29 @@ credential(AccountID const& subject, AccountID const& issuer, Slice const& credT
}
Keylet
vault(AccountID const& owner, std::uint32_t seq) noexcept
vault(AccountID const& owner, SeqProxy const& seq) noexcept
{
return vault(indexHash(LedgerNameSpace::Vault, owner, seq));
return vault(indexHash(LedgerNameSpace::Vault, owner, seq.value()));
}
Keylet
loanBroker(AccountID const& owner, std::uint32_t seq) noexcept
loanBroker(AccountID const& owner, SeqProxy const& seq) noexcept
{
return loanBroker(indexHash(LedgerNameSpace::LoanBroker, owner, seq));
return loanBroker(indexHash(LedgerNameSpace::LoanBroker, owner, seq.value()));
}
Keylet
loan(uint256 const& loanBrokerID, std::uint32_t loanSeq) noexcept
loan(uint256 const& loanBrokerID, SeqProxy const& loanSeq) noexcept
{
return loan(indexHash(LedgerNameSpace::Loan, loanBrokerID, loanSeq));
return loan(indexHash(LedgerNameSpace::Loan, loanBrokerID, loanSeq.value()));
}
Keylet
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept
permissionedDomain(AccountID const& account, SeqProxy const& seq) noexcept
{
return {ltPERMISSIONED_DOMAIN, indexHash(LedgerNameSpace::PermissionedDomain, account, seq)};
return {
ltPERMISSIONED_DOMAIN,
indexHash(LedgerNameSpace::PermissionedDomain, account, seq.value())};
}
Keylet

View File

@@ -11,6 +11,8 @@
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/UintTypes.h>
#include <boost/endian/conversion.hpp>
#include <cstddef>
#include <cstdint>
#include <cstring>
@@ -45,6 +47,10 @@ STIssue::STIssue(SerialIter& sit, SField const& name) : STBase{name}
{
MPTID mptID;
std::uint32_t sequence = sit.get32();
// MPTID stores the sequence in canonical big-endian bytes. STIssue
// ledger bytes are the legacy LE-host encoding, so convert the
// native get32() value to LE bytes before copying into the MPTID.
sequence = boost::endian::native_to_little(sequence);
static_assert(MPTID::size() == sizeof(sequence) + sizeof(currencyOrAccount));
memcpy(mptID.data(), &sequence, sizeof(sequence));
memcpy(
@@ -100,6 +106,10 @@ STIssue::add(Serializer& s) const
s.addBitString(noAccount());
std::uint32_t sequence = 0;
memcpy(&sequence, issue.getMptID().data(), sizeof(sequence));
// The MPTID bytes are canonical big-endian. Interpret those bytes
// as the legacy LE-host value so add32() writes the preserved
// STIssue wire bytes on every host endian.
sequence = boost::endian::little_to_native(sequence);
s.add32(sequence);
});
}

View File

@@ -56,10 +56,15 @@ STObject::STObject(SOTemplate const& type, SField const& name) : STBase(name)
set(type);
}
STObject::STObject(SOTemplate const& type, SerialIter& sit, SField const& name) : STBase(name)
STObject::STObject(
SOTemplate const& type,
SerialIter& sit,
SField const& name,
bool requireCanonicalOrder)
: STBase(name)
{
v_.reserve(type.size());
set(sit);
set(sit, 0, requireCanonicalOrder);
applyTemplate(type); // May throw
}
@@ -208,12 +213,13 @@ STObject::applyTemplateFromSField(SField const& sField)
// return true = terminated with end-of-object
bool
STObject::set(SerialIter& sit, int depth)
STObject::set(SerialIter& sit, int depth, bool requireCanonicalOrder)
{
bool reachedEndOfObject = false;
v_.clear();
std::optional<int> prevFieldCode;
// Consume data in the pipe until we run out or reach the end
while (!sit.empty())
{
@@ -238,7 +244,6 @@ STObject::set(SerialIter& sit, int depth)
}
auto const& fn = SField::getField(type, field);
if (fn.isInvalid())
{
JLOG(debugLog().error())
@@ -246,6 +251,13 @@ STObject::set(SerialIter& sit, int depth)
Throw<std::runtime_error>("Unknown field");
}
if (requireCanonicalOrder && prevFieldCode.has_value() && fn.fieldCodeMem <= *prevFieldCode)
{
JLOG(debugLog().error()) << "Fields in object are not in canonical order";
Throw<std::runtime_error>("Fields in object are not in canonical order");
}
prevFieldCode = fn.fieldCodeMem;
// Unflatten the field
v_.emplace_back(sit, fn, depth + 1);

View File

@@ -51,6 +51,12 @@ STPathElement::getHash(STPathElement const& element)
return (hashAccount ^ hashCurrency ^ hashIssuer);
}
[[nodiscard]] size_t
STPathElement::getHash() const
{
return STPathElement::getHash(*this);
}
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
{
std::vector<STPathElement> path;
@@ -126,21 +132,15 @@ STPathSet::move(std::size_t n, void* buf)
bool
STPathSet::assembleAdd(STPath const& base, STPathElement const& tail)
{ // assemble base+tail and add it to the set if it's not a duplicate
value_.push_back(base);
STPath combined = base;
combined.pushBack(tail);
auto it = value_.rbegin();
STPath& newPath = *it;
newPath.pushBack(tail);
while (++it != value_.rend())
if (!seenHashes_.insert(combined).second)
{
if (*it == newPath)
{
value_.pop_back();
return false;
}
return false;
}
value_.push_back(std::move(combined));
return true;
}

View File

@@ -200,22 +200,16 @@ STTx::getSeqProxy() const
{
std::uint32_t const seq{getFieldU32(sfSequence)};
if (seq != 0)
return SeqProxy::sequence(seq);
return SeqProxy::rawSequence(seq);
std::optional<std::uint32_t> const ticketSeq{operator[](~sfTicketSequence)};
std::optional<std::uint32_t> const ticketSeq{at(~sfTicketSequence)};
if (!ticketSeq)
{
// No TicketSequence specified. Return the Sequence, whatever it is.
return SeqProxy::sequence(seq);
return SeqProxy::rawSequence(seq);
}
return SeqProxy{SeqProxy::Type::Ticket, *ticketSeq};
}
std::uint32_t
STTx::getSeqValue() const
{
return getSeqProxy().value();
return SeqProxy::rawTicket(*ticketSeq);
}
void
@@ -459,7 +453,7 @@ STTx::checkBatchSingleSign(STObject const& batchSigner, std::vector<uint256> con
{
XRPL_ASSERT(getTxnType() == ttBATCH, "STTx::checkBatchSingleSign : batch transaction");
Serializer msg;
serializeBatch(msg, getAccountID(sfAccount), getSeqValue(), getFlags(), txIds);
serializeBatch(msg, getAccountID(sfAccount), getSeqProxy().value(), getFlags(), txIds);
finishMultiSigningData(batchSigner.getAccountID(sfAccount), msg);
return singleSignHelper(batchSigner, msg.slice());
}
@@ -553,7 +547,7 @@ STTx::checkBatchMultiSign(
// with the stuff that stays constant from signature to signature.
auto const batchSignerAccount = batchSigner.getAccountID(sfAccount);
Serializer dataStart;
serializeBatch(dataStart, getAccountID(sfAccount), getSeqValue(), getFlags(), txIds);
serializeBatch(dataStart, getAccountID(sfAccount), getSeqProxy().value(), getFlags(), txIds);
dataStart.addBitString(batchSignerAccount);
return multiSignHelper(
batchSigner,
@@ -812,16 +806,19 @@ invalidMPTAmountInTx(STObject const& tx)
static bool
isBatchRawTransactionOkay(STTx const& tx, std::string& reason)
{
if (!tx.isFieldPresent(sfRawTransactions))
XRPL_ASSERT(
tx.getTxnType() == ttBATCH || !tx.isFieldPresent(sfRawTransactions),
"xrpl::isBatchRawTransactionOkay : raw transactions only on batch");
if (tx.getTxnType() != ttBATCH)
return true;
// sfRawTransactions only appears on a Batch. passesLocalChecks runs on
// unverified user and peer input, so reject (rather than assert) a non-batch
// transaction that carries it.
if (tx.getTxnType() != ttBATCH)
if (!tx.isFieldPresent(sfRawTransactions))
{
reason = "Only Batch transactions may contain raw transactions.";
// LCOV_EXCL_START
reason = "Batch transactions must contain raw transactions.";
return false;
// LCOV_EXCL_STOP
}
if (tx.isFieldPresent(sfBatchSigners) &&

View File

@@ -9,6 +9,7 @@ Charge const kFeeRequestNoReply(10, "unsatisfiable request");
Charge const kFeeInvalidSignature(2000, "invalid signature");
Charge const kFeeUselessData(150, "useless data");
Charge const kFeeInvalidData(400, "invalid data");
Charge const kFeeMalformedData(2000, "malformed data");
Charge const kFeeMalformedRpc(100, "malformed RPC");
Charge const kFeeReferenceRpc(20, "reference RPC");

View File

@@ -7,10 +7,12 @@
#include <xrpl/protocol/Book.h>
#include <xrpl/resource/Consumer.h>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <memory>
#include <mutex>
#include <utility>
namespace xrpl {
@@ -64,6 +66,9 @@ InfoSub::InfoSub(Source& source, Consumer consumer)
InfoSub::~InfoSub()
{
// Stream unsubscribes are O(1): each erases this connection's single seq_
// from one stream map, so they are cheap enough to run inline on the
// disconnect thread.
// Each Source teardown call below acquires a server-side lock and
// can throw. Wrap each independent call so partial failure does not
// skip the remaining teardown steps.
@@ -79,29 +84,48 @@ InfoSub::~InfoSub()
safeUnsub(seq_, [&] { source_.unsubPeerStatus(seq_); }, j);
safeUnsub(seq_, [&] { source_.unsubConsensus(seq_); }, j);
// Use the internal unsubscribe so that it won't call
// back to us and modify its own parameter
if (!realTimeSubscriptions_.empty())
{
safeUnsub(
seq_, [&] { source_.unsubAccountInternal(seq_, realTimeSubscriptions_, true); }, j);
}
if (!normalSubscriptions_.empty())
{
safeUnsub(
seq_, [&] { source_.unsubAccountInternal(seq_, normalSubscriptions_, false); }, j);
}
for (auto const& account : accountHistorySubscriptions_)
{
safeUnsub(seq_, [&] { source_.unsubAccountHistoryInternal(seq_, account, false); }, j);
}
// Book subscriptions are torn down inline here, keyed on seq_, rather than
// through the chunked account cleanup below. The book set is not capped, so
// it can be large; but each unsubBookInternal takes bookLock_ for a single
// O(1) erase and releases it, so even a large set never holds a lock across
// the whole loop - a competing book publish can interleave between erases.
// The disconnect thread still does O(N) brief acquisitions. Use the internal
// variant so it does not write back to bookSubscriptions_ on this
// partially-destroyed object.
for (auto const& book : bookSubscriptions_)
{
safeUnsub(seq_, [&] { source_.unsubBookInternal(seq_, book); }, j);
}
// Hand the account sets off (by move) to the Source for a chunked,
// off-thread teardown keyed on seq_, instead of erasing them inline here.
// This keeps the destructor from holding the account lock across a large
// erase loop. The job never references this object, which is being
// destroyed.
//
// Moving the sets without holding lock_ is safe: the destructor runs only
// when the last shared_ptr to this InfoSub is released, so by the
// shared_ptr contract no other thread holds a reference. Subscription maps
// store weak_ptrs, so a concurrent publisher must weak_ptr::lock() first;
// that succeeds only while a strong reference exists, which cannot overlap
// with destruction. No other thread can observe the moved-from sets.
//
// Wrapped like the steps above: scheduleAccountCleanup enqueues a JobQueue
// task, which allocates and locks and so can throw. A throw out of this
// noexcept destructor would terminate the process. Skipping the cleanup on
// throw is harmless: the account/rt maps hold weak_ptrs that the next
// publish prunes once this InfoSub is gone, and any history paging job
// self-terminates when its weak sink can no longer be locked.
safeUnsub(
seq_,
[&] {
source_.scheduleAccountCleanup(
seq_,
std::move(realTimeSubscriptions_),
std::move(normalSubscriptions_),
std::move(accountHistorySubscriptions_));
},
j);
}
resource::Consumer&
@@ -121,6 +145,53 @@ InfoSub::onSendEmpty()
{
}
std::size_t
InfoSub::totalSubscriptionCount() const
{
// Hold lock_ for the whole read so the three sets cannot be mutated
// mid-count by a concurrent (un)subscribe on this connection.
std::scoped_lock const sl(lock_);
// Combined tally the per-connection cap is enforced against.
return normalSubscriptions_.size() + realTimeSubscriptions_.size() +
accountHistorySubscriptions_.size();
}
bool
InfoSub::tryReserveAccountSubscriptions(
hash_set<AccountID> const& proposedAccounts,
hash_set<AccountID> const& normalAccounts,
std::size_t cap)
{
// One lock hold covers the count, the check and the insert.
std::scoped_lock const sl(lock_);
// Entries not already tracked; re-subscribing held accounts is not charged.
auto const countNew = [](hash_set<AccountID> const& requested,
hash_set<AccountID> const& existing) {
std::size_t fresh = 0;
for (auto const& account : requested)
{
if (!existing.contains(account))
++fresh;
}
return fresh;
};
std::size_t const additional = countNew(proposedAccounts, realTimeSubscriptions_) +
countNew(normalAccounts, normalSubscriptions_);
std::size_t const current = normalSubscriptions_.size() + realTimeSubscriptions_.size() +
accountHistorySubscriptions_.size();
if (exceedsSubscriptionCap(current, additional, cap))
return false;
realTimeSubscriptions_.insert(proposedAccounts.begin(), proposedAccounts.end());
normalSubscriptions_.insert(normalAccounts.begin(), normalAccounts.end());
return true;
}
void
InfoSub::insertSubAccountInfo(AccountID const& account, bool rt)
{
@@ -165,6 +236,13 @@ InfoSub::deleteSubAccountHistory(AccountID const& account)
accountHistorySubscriptions_.erase(account);
}
bool
InfoSub::hasAccountHistorySubscription(AccountID const& account) const
{
std::scoped_lock const sl(lock_);
return accountHistorySubscriptions_.contains(account);
}
void
InfoSub::insertBookSubscription(Book const& book)
{

View File

@@ -62,6 +62,11 @@ deserializeManifest(Slice s, beast::Journal journal)
if (s.empty())
return std::nullopt;
// A valid manifest has a fixed maximum size, so reject anything larger
// before parsing it.
if (s.size() > kMaxManifestBytes)
return std::nullopt;
static SOTemplate const kManifestFormat{
// A manifest must include:
// - the master public key
@@ -377,16 +382,20 @@ ManifestCache::revoked(PublicKey const& pk) const
}
ManifestDisposition
ManifestCache::applyManifest(Manifest m)
ManifestCache::applyManifest(Manifest m, ManifestRateLimitCapPolicy const cap)
{
bool const uncapped = cap == ManifestRateLimitCapPolicy::Uncapped;
// The signature is checked only on the first `prewriteCheck` run (under the
// read lock). It is expensive, so `checkSignature` is cleared the first
// time it is read; the second run (under the write lock) skips it.
bool checkSignature = true;
// Check the manifest against the conditions that do not require a
// `unique_lock` (write lock) on the `mutex_`. Since the signature can be
// relatively expensive, the `checkSignature` parameter determines if the
// signature should be checked. Since `prewriteCheck` is run twice (see
// comment below), `checkSignature` only needs to be set to true on the
// first run.
auto prewriteCheck = [this, &m](auto const& iter, bool checkSignature, auto const& lock)
-> std::optional<ManifestDisposition> {
// `unique_lock` (write lock) on the `mutex_`.
auto prewriteCheck = [this, &m, &checkSignature](
auto const& iter,
auto const& lock) -> std::optional<ManifestDisposition> {
XRPL_ASSERT(lock.owns_lock(), "xrpl::ManifestCache::applyManifest::prewriteCheck : locked");
(void)lock; // not used. parameter is present to ensure the mutex is
// locked when the lambda is called.
@@ -401,11 +410,15 @@ ManifestCache::applyManifest(Manifest m)
return ManifestDisposition::Stale;
}
if (checkSignature && !m.verify())
if (checkSignature)
{
if (auto stream = j_.warn())
logMftAct(stream, "Invalid", m.masterKey, m.sequence);
return ManifestDisposition::Invalid;
checkSignature = false;
if (!m.verify())
{
if (auto stream = j_.warn())
logMftAct(stream, "Invalid", m.masterKey, m.sequence);
return ManifestDisposition::Invalid;
}
}
// If the master key associated with a manifest is or might be
@@ -465,14 +478,51 @@ ManifestCache::applyManifest(Manifest m)
return std::nullopt;
};
// Reject a brand-new manifest for an unlisted key once the untrusted cap
// is full. Updates to a cached key and uncapped manifests always pass.
// Called under both the read and write lock, since the cap can be reached
// between the two. The lock param enforces that.
auto atUntrustedCap = [this, &m, uncapped](auto const& iter, auto const& lock) {
XRPL_ASSERT(
lock.owns_lock(), "xrpl::ManifestCache::applyManifest::atUntrustedCap : locked");
(void)lock; // not used. parameter is present to ensure the mutex is
// locked when the lambda is called.
if (iter == map_.end() && !uncapped && untrustedKeys_.size() >= maxUntrustedCount_)
{
// Log each rejection at debug, but warn only once per interval so a
// flood does not fill the log.
if (auto stream = j_.debug())
logMftAct(stream, "UntrustedCapacity", m.masterKey, m.sequence);
if (auto const n = untrustedRejectCount_.fetch_add(1) + 1;
n % kUntrustedRejectCount == 0)
{
JLOG(j_.warn()) << "Untrusted manifest cap reached; " << n
<< " manifests rejected so far";
}
return true;
}
return false;
};
{
std::shared_lock const sl{mutex_};
if (auto d = prewriteCheck(map_.find(m.masterKey), /*checkSig*/ true, sl))
auto const iter = map_.find(m.masterKey);
if (atUntrustedCap(iter, sl))
return ManifestDisposition::UntrustedCapacity;
if (auto d = prewriteCheck(iter, sl); d.has_value())
return *d;
}
std::unique_lock const sl{mutex_};
auto const iter = map_.find(m.masterKey);
// Re-check the cap under the write lock: the cache may have grown while the
// read lock above was released.
if (atUntrustedCap(iter, sl))
return ManifestDisposition::UntrustedCapacity;
// Since we released the previously held read lock, it's possible that the
// collections have been written to. This means we need to run
// `prewriteCheck` again. This re-does work, but `prewriteCheck` is
@@ -482,7 +532,7 @@ ManifestCache::applyManifest(Manifest m)
// doesn't need to happen again (signature checks are somewhat expensive).
// Note: It's a mistake to use an upgradable lock. This is a recipe for
// deadlock.
if (auto d = prewriteCheck(iter, /*checkSig*/ false, sl))
if (auto d = prewriteCheck(iter, sl); d.has_value())
return *d;
bool const revoked = m.revoked();
@@ -501,6 +551,12 @@ ManifestCache::applyManifest(Manifest m)
}
auto masterKey = m.masterKey;
// Count this key against the untrusted cap. Uncapped keys (listed,
// configured, or DB-loaded) are not tracked.
if (!uncapped)
untrustedKeys_.insert(masterKey);
map_.emplace(std::move(masterKey), std::move(m));
// Something has changed. Keep track of it.
@@ -514,6 +570,11 @@ ManifestCache::applyManifest(Manifest m)
if (auto stream = j_.info())
logMftAct(stream, "AcceptedUpdate", m.masterKey, m.sequence, iter->second.sequence);
// If this key was counted against the cap but now arrives uncapped, free
// its slot without waiting for promoteToTrusted.
if (uncapped)
untrustedKeys_.erase(m.masterKey);
signingToMasterKeys_.erase(
*iter->second.signingKey); // NOLINT(bugprone-unchecked-optional-access) prewriteCheck
// ensures old manifest is not revoked
@@ -521,8 +582,8 @@ ManifestCache::applyManifest(Manifest m)
if (!revoked)
{
signingToMasterKeys_.emplace(
*m.signingKey, m.masterKey); // NOLINT(bugprone-unchecked-optional-access) non-revoked
// manifest always has signingKey
*m.signingKey, m.masterKey); // NOLINT(bugprone-unchecked-optional-access)
// non-revoked manifest always has signingKey
}
iter->second = std::move(m);
@@ -533,6 +594,16 @@ ManifestCache::applyManifest(Manifest m)
return ManifestDisposition::Accepted;
}
void
ManifestCache::promoteToTrusted(PublicKey const& pk)
{
// Frees the key's untrusted slot; a no-op (and idempotent) if the key was
// never counted. Not re-added on de-listing, so list/de-list cannot grow
// the count.
std::unique_lock const sl{mutex_};
untrustedKeys_.erase(pk);
}
void
ManifestCache::load(DatabaseCon& dbCon, std::string const& dbTable)
{
@@ -563,7 +634,8 @@ ManifestCache::load(
JLOG(j_.warn()) << "Configured manifest revokes public key";
}
if (applyManifest(std::move(*mo)) == ManifestDisposition::Invalid)
if (applyManifest(std::move(*mo), ManifestRateLimitCapPolicy::Uncapped) ==
ManifestDisposition::Invalid)
{
JLOG(j_.error()) << "Manifest in config was rejected";
return false;
@@ -585,7 +657,9 @@ ManifestCache::load(
auto mo = deserializeManifest(base64Decode(revocationStr));
if (!mo || !mo->revoked() || applyManifest(std::move(*mo)) == ManifestDisposition::Invalid)
if (!mo || !mo->revoked() ||
applyManifest(std::move(*mo), ManifestRateLimitCapPolicy::Uncapped) ==
ManifestDisposition::Invalid)
{
JLOG(j_.error()) << "Invalid validator key revocation in config";
return false;

View File

@@ -29,6 +29,7 @@
#include <soci/use.h>
#include <array>
#include <cstddef>
#include <functional>
#include <memory>
#include <string>
@@ -77,7 +78,9 @@ getManifests(
continue;
}
cache.applyManifest(std::move(*mo));
// Only trusted manifests are persisted (see saveManifests), so
// anything loaded from the DB bypasses the untrusted cap.
cache.applyManifest(std::move(*mo), ManifestRateLimitCapPolicy::Uncapped);
}
else
{
@@ -107,19 +110,27 @@ saveManifests(
{
soci::transaction tr(session);
session << "DELETE FROM " << dbTable;
// Count skipped untrusted manifests and log one summary afterwards, since
// the cache can hold many and per-entry logging would flood at shutdown.
std::size_t skipped = 0;
for (auto const& v : map)
{
// Save all revocation manifests,
// but only save trusted non-revocation manifests.
if (!v.second.revoked() && !isTrusted(v.second.masterKey))
// Persist only trusted keys. Untrusted gossip is left out so a flood
// cannot survive a restart on disk.
if (!isTrusted(v.second.masterKey))
{
JLOG(j.info()) << "Untrusted manifest in cache not saved to db";
++skipped;
continue;
}
saveManifest(session, dbTable, v.second.serialized);
}
tr.commit();
if (skipped != 0)
{
JLOG(j.info()) << skipped << " untrusted manifest(s) in cache not saved to db";
}
}
void

View File

@@ -709,7 +709,7 @@ Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j
}
SeqProxy const tSeqProx = tx.getSeqProxy();
SeqProxy const aSeq = SeqProxy::sequence((*sle)[sfSequence]);
SeqProxy const aSeq = SeqProxy::rawSequence((*sle)[sfSequence]);
if (tSeqProx.isSeq())
{
@@ -791,16 +791,17 @@ TER
Transactor::consumeSeqProxy(SLE::pointer const& sleAccount)
{
XRPL_ASSERT(sleAccount, "xrpl::Transactor::consumeSeqProxy : non-null account");
SeqProxy const seqProx = ctx_.tx.getSeqProxy();
if (seqProx.isSeq())
SeqProxy const seqProxy = ctx_.tx.getSeqProxy();
if (seqProxy.isSeq())
{
// Note that if this transaction is a TicketCreate, then
// the transaction will modify the account root sfSequence
// yet again.
sleAccount->setFieldU32(sfSequence, seqProx.value() + 1);
sleAccount->setFieldU32(sfSequence, seqProxy.value() + 1);
return tesSUCCESS;
}
return ticketDelete(view(), accountID_, getTicketIndex(accountID_, seqProx), j_);
auto const keylet = keylet::ticket(accountID_, seqProxy);
return ticketDelete(view(), accountID_, keylet.key, j_);
}
// Remove a single Ticket from the ledger.

View File

@@ -251,7 +251,7 @@ TxConsequences::TxConsequences(NotTEC pfResult)
: isBlocker_(false)
, fee_(beast::kZero)
, potentialSpend_(beast::kZero)
, seqProx_(SeqProxy::sequence(0))
, seqProx_(SeqProxy::rawSequence(0))
, sequencesConsumed_(0)
{
XRPL_ASSERT(

View File

@@ -465,7 +465,7 @@ ValidVault::finalize(
if (afterVault.assetsAvailable < kZero)
{
JLOG(j.fatal()) << "Invariant failed: assets available must be positive";
JLOG(j.fatal()) << "Invariant failed: assets available must not be negative";
result = false;
}
@@ -491,13 +491,13 @@ ValidVault::finalize(
if (afterVault.assetsTotal < kZero)
{
JLOG(j.fatal()) << "Invariant failed: assets outstanding must be positive";
JLOG(j.fatal()) << "Invariant failed: assets outstanding must not be negative";
result = false;
}
if (afterVault.assetsMaximum < kZero)
{
JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
JLOG(j.fatal()) << "Invariant failed: assets maximum must not be negative";
result = false;
}

View File

@@ -133,17 +133,8 @@ maxOut(T const& out, Asset const& asset)
template <typename TIn, typename TOut>
std::optional<AMMOffer<TIn, TOut>>
AMMLiquidity<TIn, TOut>::maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const
AMMLiquidity<TIn, TOut>::maxOffer(TAmounts<TIn, TOut> const& balances) const
{
if (!rules.enabled(fixAMMOverflowOffer))
{
return AMMOffer<TIn, TOut>(
*this,
{maxAmount<TIn>(), swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
balances,
Quality{balances});
}
auto const out = maxOut<TOut>(balances.out, assetOut());
if (out <= TOut{0} || out >= balances.out)
return std::nullopt;
@@ -206,7 +197,7 @@ AMMLiquidity<TIn, TOut>::getOffer(ReadView const& view, std::optional<Quality> c
// changed in BookStep per either deliver amount limit, or
// sendmax, or available output or input funds. Might return
// nullopt if the pool is small.
return maxOffer(balances, view.rules());
return maxOffer(balances);
}
if (auto const amounts =
changeSpotPriceQuality(balances, *clobQuality, tradingFee_, view.rules(), j_))
@@ -215,7 +206,7 @@ AMMLiquidity<TIn, TOut>::getOffer(ReadView const& view, std::optional<Quality> c
}
if (view.rules().enabled(fixAMMv1_2))
{
if (auto const maxAMMOffer = maxOffer(balances, view.rules());
if (auto const maxAMMOffer = maxOffer(balances);
maxAMMOffer && Quality{maxAMMOffer->amount()} > *clobQuality)
return maxAMMOffer;
}
@@ -223,10 +214,6 @@ AMMLiquidity<TIn, TOut>::getOffer(ReadView const& view, std::optional<Quality> c
catch (std::overflow_error const& e)
{
JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
if (!view.rules().enabled(fixAMMOverflowOffer))
{
return maxOffer(balances, view.rules());
}
return std::nullopt;
}

View File

@@ -134,11 +134,13 @@ AMMOffer<TIn, TOut>::checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::
{
if (consumed.in > amounts_.in || consumed.out > amounts_.out)
{
// LCOV_EXCL_START
JLOG(j.error()) << "AMMOffer::checkInvariant failed: consumed " << to_string(consumed.in)
<< " " << to_string(consumed.out) << " amounts " << to_string(amounts_.in)
<< " " << to_string(amounts_.out);
return false;
// LCOV_EXCL_STOP
}
Number const product = balances_.in * balances_.out;
@@ -149,6 +151,7 @@ AMMOffer<TIn, TOut>::checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::
if (newProduct >= product || withinRelativeDistance(product, newProduct, Number{1, -7}))
return true;
// LCOV_EXCL_START
JLOG(j.error()) << "AMMOffer::checkInvariant failed: balances " << to_string(balances_.in)
<< " " << to_string(balances_.out) << " new balances "
<< to_string(newBalances.in) << " " << to_string(newBalances.out)
@@ -156,6 +159,7 @@ AMMOffer<TIn, TOut>::checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::
<< (product != Number{0} ? to_string((product - newProduct) / product)
: "undefined");
return false;
// LCOV_EXCL_STOP
}
template class AMMOffer<IOUAmount, IOUAmount>;

View File

@@ -865,12 +865,9 @@ BookStep<TIn, TOut, TDerived>::consumeOffer(
{
if (!offer.checkInvariant(ofrAmt, j_))
{
// purposely written as separate if statements so we get logging even
// when the amendment isn't active.
if (sb.rules().enabled(fixAMMOverflowOffer))
{
Throw<FlowException>(tecINVARIANT_FAILED, "AMM pool product invariant failed.");
}
// LCOV_EXCL_START
Throw<FlowException>(tecINVARIANT_FAILED, "AMM pool product invariant failed.");
// LCOV_EXCL_STOP
}
// The offer owner gets the ofrAmt. The difference between ofrAmt and

View File

@@ -241,6 +241,8 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
if (!ctx.tx.isFieldPresent(sfCredentialIDs))
{
// Check whether the destination account requires deposit authorization.
// This also checks if destination is a pseudo-account, since pseudo-accounts have the
// lsfDepositAuth flag set by default
if (sleDst->isFlag(lsfDepositAuth))
{
if (!ctx.view.exists(keylet::depositPreauth(dst, account)))

View File

@@ -19,8 +19,9 @@
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -376,18 +377,29 @@ CheckCash::doApply()
else
{
// Note that for DeliverMin we don't know exactly how much
// currency we want flow to deliver. We can't ask for the
// maximum possible currency because there might be a gateway
// transfer rate to account for. Since the transfer rate cannot
// exceed 200%, we use 1/2 maxValue as our limit.
// currency we want flow to deliver. For IOUs, use a value
// higher than any real delivery as the request. MPTs are
// bounded integral amounts, so use the maximum output the check
// can actually deliver without exceeding SendMax.
auto const maxDeliverMin = [&]() {
return optDeliverMin->asset().visit(
[&](Issue const&) {
return STAmount(
optDeliverMin->asset(), STAmount::kMaxValue / 2, STAmount::kMaxOffset);
},
[&](MPTIssue const&) {
return STAmount(optDeliverMin->asset(), kMaxMpTokenAmount / 2);
[&](MPTIssue const& issue) {
MPTAmount maxDeliver = sendMax.mpt();
auto const& issuer = issue.getIssuer();
if (srcId != issuer && accountID_ != issuer)
{
auto const rate = transferRate(psb, issue.getMptID());
// Request at most floor(SendMax / rate). The endpoint reverse pass
// will quote ceil(output * rate), so this keeps the input
// representable and within SendMax.
maxDeliver =
mulRatio(maxDeliver, QUALITY_ONE, rate.value, /*roundUp*/ false);
}
return STAmount(maxDeliver, issue);
});
};
STAmount const flowDeliver{

View File

@@ -25,7 +25,6 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
#include <memory>
#include <optional>
@@ -201,14 +200,14 @@ CheckCreate::doApply()
return ret;
// Note that we use the value from the sequence or ticket as the
// Check sequence. For more explanation see comments in SeqProxy.h.
std::uint32_t const seq = ctx_.tx.getSeqValue();
auto const seq = ctx_.tx.getSeqProxy();
Keylet const checkKeylet = keylet::check(accountID_, seq);
auto sleCheck = std::make_shared<SLE>(checkKeylet);
sleCheck->setAccountID(sfAccount, accountID_);
AccountID const dstAccountId = ctx_.tx[sfDestination];
sleCheck->setAccountID(sfDestination, dstAccountId);
sleCheck->setFieldU32(sfSequence, seq);
sleCheck->setFieldU32(sfSequence, seq.value());
sleCheck->setFieldAmount(sfSendMax, ctx_.tx[sfSendMax]);
if (auto const srcTag = ctx_.tx[~sfSourceTag])
sleCheck->setFieldU32(sfSourceTag, *srcTag);

View File

@@ -84,7 +84,9 @@ CredentialCreate::preclaim(PreclaimContext const& ctx)
auto const credType(ctx.tx[sfCredentialType]);
auto const subject = ctx.tx[sfSubject];
if (!ctx.view.exists(keylet::account(subject)))
auto const subjectSle = ctx.view.read(keylet::account(subject));
if (!subjectSle)
{
JLOG(ctx.j.trace()) << "Subject doesn't exist.";
return tecNO_TARGET;
@@ -96,6 +98,12 @@ CredentialCreate::preclaim(PreclaimContext const& ctx)
return tecDUPLICATE;
}
if (ctx.view.rules().enabled(fixCleanup3_3_0) && isPseudoAccount(subjectSle))
{
JLOG(ctx.j.trace()) << "Subject is a pseudo-account.";
return tecPSEUDO_ACCOUNT;
}
return tesSUCCESS;
}

View File

@@ -57,7 +57,7 @@ DelegateSet::preclaim(PreclaimContext const& ctx)
return tecNO_TARGET;
if (isPseudoAccount(sleAuthorize))
return tecNO_PERMISSION;
return tecPSEUDO_ACCOUNT;
// Deleting the delegate object is invalid if it doesnt exist.
if (ctx.tx.getFieldArray(sfPermissions).empty() &&

View File

@@ -28,6 +28,7 @@
#include <cstdint>
#include <exception>
#include <optional>
#include <stdexcept>
#include <utility>
namespace xrpl {
@@ -437,11 +438,10 @@ AMMDeposit::applyGuts(Sandbox& sb)
auto const subTxType = ctx_.tx.getFlags() & tfDepositSubTx;
auto const [result, newLPTokenBalance] = [&,
&amountBalance = amountBalance,
&amount2Balance = amount2Balance,
&lptAMMBalance =
lptAMMBalance]() -> std::pair<TER, STAmount> {
auto dispatchToDeposit = [&,
&amountBalance = amountBalance,
&amount2Balance = amount2Balance,
&lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
if (subTxType & tfTwoAsset)
{
return equalDepositLimit(
@@ -493,6 +493,28 @@ AMMDeposit::applyGuts(Sandbox& sb)
JLOG(j_.error()) << "AMM Deposit: invalid options.";
return std::make_pair(tecINTERNAL, STAmount{});
// LCOV_EXCL_STOP
};
auto const [result, newLPTokenBalance] = [&]() -> std::pair<TER, STAmount> {
try
{
return dispatchToDeposit();
}
catch (std::runtime_error const& e)
{
REACHABLE("xrpl::AMMDeposit::applyGuts : deposit amount out of range reached");
// A deposit whose solved amount exceeds the integral asset's range
// throws while converting to STAmount: past int64max
// Number::operator rep() throws std::overflow_error; above the asset
// maximum STAmount::canonicalize throws std::runtime_error. Fail
// cleanly with a tec rather than letting it escape doApply as
// tefEXCEPTION. Any other exception is left to propagate.
// Gated by fixCleanup3_4_0 to preserve the legacy result pre-amendment.
if (!sb.rules().enabled(fixCleanup3_4_0))
throw; // LCOV_EXCL_LINE - preserve legacy tefEXCEPTION
JLOG(j_.error()) << "AMMDeposit: deposit amount out of range " << e.what();
return std::make_pair(tecAMM_FAILED, STAmount{});
}
}();
if (isTesSuccess(result))

View File

@@ -34,6 +34,7 @@
#include <cstdint>
#include <exception>
#include <optional>
#include <stdexcept>
#include <tuple>
#include <utility>
@@ -374,11 +375,10 @@ AMMWithdraw::applyGuts(Sandbox& sb)
auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
auto const [result, newLPTokenBalance] = [&,
&amountBalance = amountBalance,
&amount2Balance = amount2Balance,
&lptAMMBalance =
lptAMMBalance]() -> std::pair<TER, STAmount> {
auto dispatchToWithdraw = [&,
&amountBalance = amountBalance,
&amount2Balance = amount2Balance,
&lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
if (subTxType & tfTwoAsset)
{
return equalWithdrawLimit(
@@ -432,6 +432,29 @@ AMMWithdraw::applyGuts(Sandbox& sb)
JLOG(j_.error()) << "AMM Withdraw: invalid options.";
return std::make_pair(tecINTERNAL, STAmount{});
// LCOV_EXCL_STOP
};
auto const [result, newLPTokenBalance] = [&]() -> std::pair<TER, STAmount> {
try
{
return dispatchToWithdraw();
}
catch (std::runtime_error const& e)
{
// Defense in-depth for amount overflow/out-of-range: the withdrawal
// counterpart of the AMMDeposit guard. Unlike deposit, no known
// withdraw path can throw here - preclaim bounds the requested
// amounts by the pool balances, and the only historical throw
// (denom == 0 in singleWithdrawEPrice) is guarded under
// fixCleanup3_3_0. Gated by fixCleanup3_4_0 to preserve the
// legacy tefEXCEPTION pre-amendment.
if (!sb.rules().enabled(fixCleanup3_4_0))
throw;
// LCOV_EXCL_START
JLOG(j_.error()) << "AMMWithdraw: amount out of range " << e.what();
return std::make_pair(tecAMM_FAILED, STAmount{});
// LCOV_EXCL_STOP
}
}();
if (!isTesSuccess(result))

View File

@@ -6,6 +6,7 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
@@ -57,7 +58,8 @@ OfferCancel::doApply()
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
if (auto sleOffer = view().peek(keylet::offer(accountID_, offerSequence)))
auto const seqProxy = SeqProxy::rawSequence(offerSequence);
if (auto sleOffer = view().peek(keylet::offer(accountID_, seqProxy)))
{
JLOG(j_.debug()) << "Trying to cancel offer #" << offerSequence;
return offerDelete(view(), sleOffer, ctx_.registry.get().getJournal("View"));

View File

@@ -34,6 +34,7 @@
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STPathSet.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/UintTypes.h>
@@ -634,7 +635,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
// Note that we use the value from the sequence or ticket as the
// offer sequence. For more explanation see comments in SeqProxy.h.
auto const offerSequence = ctx_.tx.getSeqValue();
auto const offerSequence = ctx_.tx.getSeqProxy();
// This is the original rate of the offer, and is the rate at which
// it will be placed, even if crossing offers change the amounts that
@@ -648,7 +649,8 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
// Process a cancellation request that's passed along with an offer.
if (cancelSequence)
{
auto const sleCancel = sb.peek(keylet::offer(accountID_, *cancelSequence));
auto const seqProxy = SeqProxy::rawSequence(*cancelSequence);
auto const sleCancel = sb.peek(keylet::offer(accountID_, seqProxy));
// It's not an error to not find the offer to cancel: it might have
// been consumed or removed. If it is found, however, it's an error
@@ -933,7 +935,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
auto sleOffer = std::make_shared<SLE>(offerIndex);
sleOffer->setAccountID(sfAccount, accountID_);
sleOffer->setFieldU32(sfSequence, offerSequence);
sleOffer->setFieldU32(sfSequence, offerSequence.value());
sleOffer->setFieldH256(sfBookDirectory, dir.key);
sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
sleOffer->setFieldAmount(sfTakerGets, saTakerGets);

View File

@@ -19,6 +19,7 @@
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
@@ -92,7 +93,8 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
{
if (ctx.view.rules().enabled(featureTokenEscrow))
{
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy);
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
@@ -117,7 +119,8 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
TER
EscrowCancel::doApply()
{
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
auto const k = keylet::escrow(ctx_.tx[sfOwner], seqProxy);
auto const slep = ctx_.view().peek(k);
if (!slep)
{

View File

@@ -476,7 +476,7 @@ EscrowCreate::doApply()
// Create escrow in ledger. Note that we use the value from the
// sequence or ticket. For more explanation see comments in SeqProxy.h.
Keylet const escrowKeylet = keylet::escrow(accountID_, ctx_.tx.getSeqValue());
Keylet const escrowKeylet = keylet::escrow(accountID_, ctx_.tx.getSeqProxy());
auto const slep = std::make_shared<SLE>(escrowKeylet);
(*slep)[sfAmount] = amount;
(*slep)[sfAccount] = accountID_;
@@ -489,7 +489,7 @@ EscrowCreate::doApply()
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
{
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value();
}
if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount))

View File

@@ -26,6 +26,7 @@
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
@@ -203,7 +204,8 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
if (ctx.view.rules().enabled(featureTokenEscrow))
{
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy);
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
@@ -228,7 +230,8 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
TER
EscrowFinish::doApply()
{
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
auto const k = keylet::escrow(ctx_.tx[sfOwner], seqProxy);
auto const slep = ctx_.view().peek(k);
if (!slep)
{

View File

@@ -218,7 +218,7 @@ LoanBrokerSet::doApply()
}
auto const vaultPseudoID = sleVault->at(sfAccount);
auto const vaultAsset = sleVault->at(sfAsset);
auto const sequence = tx.getSeqValue();
auto const sequence = tx.getSeqProxy();
auto owner = view.peek(keylet::account(accountID_));
if (!owner)
@@ -253,7 +253,7 @@ LoanBrokerSet::doApply()
return ter;
// Initialize data fields:
broker->at(sfSequence) = sequence;
broker->at(sfSequence) = sequence.value();
broker->at(sfVaultID) = vaultID;
broker->at(sfOwner) = accountID_;
broker->at(sfAccount) = pseudoId;

View File

@@ -813,7 +813,7 @@ LoanPay::doApply()
XRPL_ASSERT_PARTS(
vaultBalanceAfter >= beast::kZero && brokerBalanceAfter >= beast::kZero,
"xrpl::LoanPay::doApply",
"positive vault and broker balances");
"non-negative vault and broker balances");
XRPL_ASSERT_PARTS(
vaultBalanceAfter >= vaultBalanceBefore,
"xrpl::LoanPay::doApply",

View File

@@ -22,6 +22,7 @@
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTakesAsset.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/Units.h>
@@ -594,7 +595,8 @@ LoanSet::doApply()
auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
// Create the loan
auto loan = std::make_shared<SLE>(keylet::loan(brokerID, *loanSequenceProxy));
auto loan =
std::make_shared<SLE>(keylet::loan(brokerID, SeqProxy::rawSequence(*loanSequenceProxy)));
// Prevent copy/paste errors
auto setLoanField = [&loan, &tx](auto const& field, std::uint32_t const defValue = 0) {

View File

@@ -103,9 +103,16 @@ DepositPreauth::preclaim(PreclaimContext const& ctx)
{
// Verify that the Authorize account is present in the ledger.
AccountID const auth{ctx.tx[sfAuthorize]};
if (!ctx.view.exists(keylet::account(auth)))
auto const sleAuth = ctx.view.read(keylet::account(auth));
if (!sleAuth)
return tecNO_TARGET;
if (ctx.view.rules().enabled(fixCleanup3_3_0) && isPseudoAccount(sleAuth))
{
JLOG(ctx.j.debug()) << "Authorized account is a pseudo-account.";
return tecPSEUDO_ACCOUNT;
}
// Verify that the Preauth entry they asked to add is not already
// in the ledger.
if (ctx.view.exists(keylet::depositPreauth(account, auth)))

View File

@@ -169,7 +169,7 @@ PaymentChannelCreate::doApply()
//
// Note that we use the value from the sequence or ticket as the
// payChan sequence. For more explanation see comments in SeqProxy.h.
Keylet const payChanKeylet = keylet::payChannel(account, dst, ctx_.tx.getSeqValue());
Keylet const payChanKeylet = keylet::payChannel(account, dst, ctx_.tx.getSeqProxy());
auto const slep = std::make_shared<SLE>(payChanKeylet);
// Funds held in this channel
@@ -185,7 +185,7 @@ PaymentChannelCreate::doApply()
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
{
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value();
}
ctx_.view().insert(slep);

View File

@@ -13,6 +13,7 @@
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
@@ -114,12 +115,13 @@ PermissionedDomainSet::doApply()
return tecINSUFFICIENT_RESERVE;
bool const fixEnabled = view().rules().enabled(fixCleanup3_1_3);
auto const seq = fixEnabled ? ctx_.tx.getSeqValue() : ctx_.tx.getFieldU32(sfSequence);
auto const seq = fixEnabled ? ctx_.tx.getSeqProxy()
: SeqProxy::rawSequence(ctx_.tx.getFieldU32(sfSequence));
Keylet const pdKeylet = keylet::permissionedDomain(accountID_, seq);
auto slePd = std::make_shared<SLE>(pdKeylet);
slePd->setAccountID(sfOwner, accountID_);
slePd->setFieldU32(sfSequence, seq);
slePd->setFieldU32(sfSequence, seq.value());
slePd->peekFieldArray(sfAcceptedCredentials) = std::move(sortedLE);
auto const page =
view().dirInsert(keylet::ownerDir(accountID_), pdKeylet, describeOwnerDir(accountID_));

View File

@@ -3,13 +3,16 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
@@ -17,36 +20,62 @@
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/Transactor.h>
#include <algorithm>
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
namespace xrpl {
// Compute the resulting RemainingOwnerCount using signed 64-bit arithmetic to
// avoid unsigned wraparound. A missing SLE (object creation) or absent field
// counts as zero. Callers handle the out-of-range results: a negative value is
// clamped to zero (field absent) and overflow is rejected in preclaim.
static std::int64_t
totalRemainingOwnerCount(
SLE::const_ref sponsorshipSle,
std::optional<std::int32_t> const& remainingOwnerCountDelta)
{
std::uint32_t const currentCount =
sponsorshipSle ? (*sponsorshipSle)[~sfRemainingOwnerCount].value_or(0u) : 0u;
return static_cast<std::int64_t>(currentCount) + remainingOwnerCountDelta.value_or(0);
}
static bool
hasSponsorshipBudget(
SLE::const_ref sponsorshipSle,
std::optional<STAmount> const& feeAmount,
std::optional<std::uint32_t> const& remainingOwnerCount)
std::optional<STAmount> const& feeAmountDelta,
std::optional<std::int32_t> const& remainingOwnerCountDelta)
{
// A field the transaction omits keeps whatever the existing object holds,
// sfFeeAmountDelta and sfRemainingOwnerCountDelta must be non-negative when creating a new
// Sponsorship object.
if (!sponsorshipSle)
{
if (feeAmountDelta.has_value() && *feeAmountDelta <= beast::kZero)
return false;
if (remainingOwnerCountDelta.has_value() && *remainingOwnerCountDelta <= 0)
return false;
}
// If the transaction omits a field, it keeps whatever the existing object holds,
// so fall back to the current SLE value when the tx does not set it.
bool const hasFeeAmount = feeAmount
? *feeAmount > beast::kZero
: sponsorshipSle && (*sponsorshipSle)[~sfFeeAmount].value_or(STAmount{0}) > beast::kZero;
STAmount const currentFee =
sponsorshipSle ? (*sponsorshipSle)[~sfFeeAmount].value_or(STAmount{0}) : STAmount{0};
STAmount const newFee = currentFee + feeAmountDelta.value_or(STAmount{0});
bool const hasRemainingOwnerCount = remainingOwnerCount
? *remainingOwnerCount > 0
: sponsorshipSle && (*sponsorshipSle)[~sfRemainingOwnerCount].value_or(0) > 0;
std::int64_t const newCount =
totalRemainingOwnerCount(sponsorshipSle, remainingOwnerCountDelta);
return hasFeeAmount || hasRemainingOwnerCount;
return newFee > beast::kZero || newCount > 0;
}
TxConsequences
SponsorshipSet::makeTxConsequences(PreflightContext const& ctx)
{
auto const feeAmount = ctx.tx[~sfFeeAmount];
return TxConsequences{ctx.tx, feeAmount.has_value() ? feeAmount->xrp() : beast::kZero};
auto const feeAmount = ctx.tx[~sfFeeAmountDelta];
auto const feeAmountDelta = std::max(STAmount{0}, feeAmount.value_or(STAmount{0}));
return TxConsequences{ctx.tx, feeAmountDelta.xrp()};
}
std::uint32_t
@@ -90,8 +119,8 @@ SponsorshipSet::preflight(PreflightContext const& ctx)
return temINVALID_FLAG;
// Transactions deleting `Sponsorship` cannot include modification fields.
if (ctx.tx.isFieldPresent(sfFeeAmount) || ctx.tx.isFieldPresent(sfRemainingOwnerCount) ||
ctx.tx.isFieldPresent(sfMaxFee))
if (ctx.tx.isFieldPresent(sfFeeAmountDelta) ||
ctx.tx.isFieldPresent(sfRemainingOwnerCountDelta) || ctx.tx.isFieldPresent(sfMaxFee))
return temMALFORMED;
}
else
@@ -101,27 +130,26 @@ SponsorshipSet::preflight(PreflightContext const& ctx)
if (account != sponsorID)
return temMALFORMED;
// FeeAmount and MaxFee must be non-negative XRP amounts when present.
auto const checkOptionalAmountField = [&](SField const& field) -> NotTEC {
if (!ctx.tx.isFieldPresent(field))
return tesSUCCESS;
// FeeAmountDelta must be a non-zero XRP amount when present.
if (auto const feeAmt = ctx.tx[~sfFeeAmountDelta];
feeAmt && (!isXRP(*feeAmt) || *feeAmt == beast::kZero))
return temBAD_AMOUNT;
auto const amount = ctx.tx.getFieldAmount(field);
// MaxFee must be a non-negative XRP amount when present.
if (auto const maxFee = ctx.tx[~sfMaxFee];
maxFee && (!isXRP(*maxFee) || *maxFee < beast::kZero))
return temBAD_AMOUNT;
if (!isXRP(amount))
return temBAD_AMOUNT;
// RemainingOwnerCountDelta must be a non-zero integer when present.
if (auto const remainingOwnerCountDelta = ctx.tx[~sfRemainingOwnerCountDelta];
remainingOwnerCountDelta && *remainingOwnerCountDelta == 0)
return temINVALID;
if (amount.xrp() < beast::kZero)
return temBAD_AMOUNT;
return tesSUCCESS;
};
if (auto const ret = checkOptionalAmountField(sfFeeAmount); !isTesSuccess(ret))
return ret;
if (auto const ret = checkOptionalAmountField(sfMaxFee); !isTesSuccess(ret))
return ret;
// nothing specified in the tx
if (!ctx.tx.isFieldPresent(sfRemainingOwnerCountDelta) &&
!ctx.tx.isFieldPresent(sfFeeAmountDelta) && !ctx.tx.isFieldPresent(sfMaxFee) &&
((ctx.tx.getFlags() & tfUniversalMask) == 0))
return temREDUNDANT;
}
return tesSUCCESS;
@@ -146,7 +174,7 @@ SponsorshipSet::preclaim(PreclaimContext const& ctx)
// Pseudo-accounts cannot participate in sponsorship.
if (isPseudoAccount(sponsorAccSle) || isPseudoAccount(sponseeSle))
return tecNO_PERMISSION;
return tecPSEUDO_ACCOUNT;
auto const sponsorshipSle = ctx.view.read(keylet::sponsorship(sponsorID, sponseeID));
@@ -154,12 +182,21 @@ SponsorshipSet::preclaim(PreclaimContext const& ctx)
if (ctx.tx.isFlag(tfDeleteObject) && !sponsorshipSle)
return tecNO_ENTRY;
// Reject creating or updating a Sponsorship that would be left with no
// budget (neither a positive FeeAmount nor a positive RemainingOwnerCount).
// Such an object is unusable yet still consumes the sponsor's reserve.
if (!ctx.tx.isFlag(tfDeleteObject) &&
!hasSponsorshipBudget(sponsorshipSle, ctx.tx[~sfFeeAmount], ctx.tx[~sfRemainingOwnerCount]))
return tecNO_PERMISSION;
if (!ctx.tx.isFlag(tfDeleteObject))
{
// Reject if applying the delta would overflow uint32_t. A negative delta
// that underflows is clamped to zero (field absent) rather than erroring.
if (totalRemainingOwnerCount(sponsorshipSle, ctx.tx[~sfRemainingOwnerCountDelta]) >
static_cast<std::int64_t>(std::numeric_limits<std::uint32_t>::max()))
return tecLIMIT_EXCEEDED;
// Reject creating or updating a Sponsorship that would be left with no
// budget (neither a positive FeeAmount nor a positive RemainingOwnerCount).
// Such an object is unusable yet still consumes the sponsor's reserve.
if (!hasSponsorshipBudget(
sponsorshipSle, ctx.tx[~sfFeeAmountDelta], ctx.tx[~sfRemainingOwnerCountDelta]))
return tecNO_PERMISSION;
}
return tesSUCCESS;
}
@@ -208,6 +245,91 @@ deleteSponsorship(ApplyView& view, SLE::ref sle, beast::Journal j)
return tesSUCCESS;
}
TER
SponsorshipSet::createSponsorship(
Keylet const& sponsorshipKeylet,
AccountID const& sponsorID,
AccountID const& sponseeID,
SLE::ref sponsorAccSle,
SLE::ref reserveSponsorAccSle)
{
auto const feeAmountDelta = ctx_.tx[~sfFeeAmountDelta];
auto const maxFee = ctx_.tx[~sfMaxFee];
auto const remainingOwnerCountDelta = ctx_.tx[~sfRemainingOwnerCountDelta];
bool const hasPositiveFeeAmount = feeAmountDelta.has_value() && *feeAmountDelta > beast::kZero;
// Create a new Sponsorship object between the sponsor and sponsee.
auto newSle = std::make_shared<SLE>(sponsorshipKeylet);
STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance];
// sfFeeAmountDelta must be positive if the sponsorship object doesn't exist. This is
// checked in preclaim.
XRPL_ASSERT(
!feeAmountDelta.has_value() || *feeAmountDelta > beast::kZero,
"xrpl::SponsorshipSet::doApply : new sponsorship has positive fee amount");
(*newSle)[sfOwner] = sponsorID;
(*newSle)[sfSponsee] = sponseeID;
if (feeAmountDelta && feeAmountDelta->xrp() > sponsorBalanceAfterFee.xrp())
return tecUNFUNDED;
if (hasPositiveFeeAmount)
sponsorBalanceAfterFee -= *feeAmountDelta;
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(),
sponsorAccSle,
sponsorBalanceAfterFee.xrp(),
reserveSponsorAccSle,
{.ownerCountDelta = 1},
ctx_.journal,
tecUNFUNDED);
!isTesSuccess(ret))
{
return ret;
}
if (hasPositiveFeeAmount)
{
// New object: FeeAmount starts absent, so deduct and record the full amount
(*newSle)[sfFeeAmount] = *feeAmountDelta;
(*sponsorAccSle)[sfBalance] -= *feeAmountDelta;
}
if (maxFee && *maxFee > beast::kZero)
(*newSle)[sfMaxFee] = *maxFee;
if (remainingOwnerCountDelta && *remainingOwnerCountDelta > 0)
(*newSle)[sfRemainingOwnerCount] = *remainingOwnerCountDelta;
std::uint32_t flags = 0;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
flags |= lsfSponsorshipRequireSignForFee;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
flags |= lsfSponsorshipRequireSignForReserve;
(*newSle)[sfFlags] = flags;
auto const sponsorPage = view().dirInsert(
keylet::ownerDir(sponsorID), sponsorshipKeylet, describeOwnerDir(sponsorID));
if (!sponsorPage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfOwnerNode] = *sponsorPage;
auto const sponseePage = view().dirInsert(
keylet::ownerDir(sponseeID), sponsorshipKeylet, describeOwnerDir(sponseeID));
if (!sponseePage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfSponseeNode] = *sponseePage;
// NOLINTNEXTLINE(readability-suspicious-call-argument)
increaseOwnerCount(view(), sponsorAccSle, reserveSponsorAccSle, 1, ctx_.journal);
addSponsorToLedgerEntry(newSle, reserveSponsorAccSle);
ctx_.view().insert(newSle);
return tesSUCCESS;
}
TER
SponsorshipSet::doApply()
{
@@ -224,8 +346,8 @@ SponsorshipSet::doApply()
if (!ctx_.view().exists(keylet::account(sponseeID)))
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sponsorKeylet = keylet::sponsorship(sponsorID, sponseeID);
auto const sponsorshipSle = ctx_.view().peek(sponsorKeylet);
auto const sponsorshipKeylet = keylet::sponsorship(sponsorID, sponseeID);
auto const sponsorshipSle = ctx_.view().peek(sponsorshipKeylet);
if (ctx_.tx.isFlag(tfDeleteObject))
{
@@ -235,11 +357,9 @@ SponsorshipSet::doApply()
return deleteSponsorship(ctx_.view(), sponsorshipSle, ctx_.journal);
}
auto const feeAmount = ctx_.tx[~sfFeeAmount];
auto const feeAmountDelta = ctx_.tx[~sfFeeAmountDelta];
auto const maxFee = ctx_.tx[~sfMaxFee];
auto const remainingOwnerCount = ctx_.tx[~sfRemainingOwnerCount];
bool const hasPositiveFeeAmount = feeAmount.has_value() && *feeAmount > beast::kZero;
auto const remainingOwnerCountDelta = ctx_.tx[~sfRemainingOwnerCountDelta];
auto reserveSponsorAccSle = getTxReserveSponsor(ctx_.getApplyViewContext());
if (!reserveSponsorAccSle)
@@ -247,24 +367,33 @@ SponsorshipSet::doApply()
if (!sponsorshipSle)
{
// Create a new Sponsorship object between the sponsor and sponsee.
auto newSle = std::make_shared<SLE>(sponsorKeylet);
return createSponsorship(
sponsorshipKeylet, sponsorID, sponseeID, sponsorAccSle, *reserveSponsorAccSle);
}
(*newSle)[sfOwner] = sponsorID;
(*newSle)[sfSponsee] = sponseeID;
if (feeAmount && (*feeAmount).xrp() > (*sponsorAccSle)[sfBalance])
// Update the existing Sponsorship object.
if (feeAmountDelta)
{
auto actualDelta = feeAmountDelta.value();
auto const currentFee = (*sponsorshipSle)[~sfFeeAmount].valueOr(XRPAmount{0});
// Clamp negative delta to avoid underflow.
if (actualDelta < beast::kZero && -actualDelta > currentFee)
actualDelta = -currentFee;
// Reject if the sponsor cannot afford the (positive) delta.
if (actualDelta > beast::kZero && actualDelta > (*sponsorAccSle)[sfBalance])
return tecUNFUNDED;
STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance];
if (hasPositiveFeeAmount)
sponsorBalanceAfterFee -= *feeAmount;
// Move the FeeAmount delta between the sponsor balance and Sponsorship
// object.
(*sponsorAccSle)[sfBalance] -= actualDelta;
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(),
sponsorAccSle,
sponsorBalanceAfterFee.xrp(),
(*sponsorAccSle)[sfBalance]->xrp(),
*reserveSponsorAccSle,
{.ownerCountDelta = 1},
{},
ctx_.journal,
tecUNFUNDED);
!isTesSuccess(ret))
@@ -272,87 +401,19 @@ SponsorshipSet::doApply()
return ret;
}
if (hasPositiveFeeAmount)
STAmount const newFee = currentFee + actualDelta;
// checked in preclaim
XRPL_ASSERT(
newFee >= beast::kZero, "xrpl::SponsorshipSet::doApply : new fee is non-negative");
if (newFee == beast::kZero)
{
// New object: FeeAmount starts absent, so deduct and record the full amount
(*newSle)[sfFeeAmount] = *feeAmount;
(*sponsorAccSle)[sfBalance] -= *feeAmount;
sponsorshipSle->makeFieldAbsent(sfFeeAmount);
}
if (maxFee && *maxFee > beast::kZero)
(*newSle)[sfMaxFee] = *maxFee;
if (remainingOwnerCount && *remainingOwnerCount > 0)
(*newSle)[sfRemainingOwnerCount] = *remainingOwnerCount;
std::uint32_t flags = 0;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
flags |= lsfSponsorshipRequireSignForFee;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
flags |= lsfSponsorshipRequireSignForReserve;
(*newSle)[sfFlags] = flags;
auto const sponsorPage = view().dirInsert(
keylet::ownerDir(sponsorID), sponsorKeylet, describeOwnerDir(sponsorID));
if (!sponsorPage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfOwnerNode] = *sponsorPage;
auto const sponseePage = view().dirInsert(
keylet::ownerDir(sponseeID), sponsorKeylet, describeOwnerDir(sponseeID));
if (!sponseePage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfSponseeNode] = *sponseePage;
// NOLINTNEXTLINE(readability-suspicious-call-argument)
increaseOwnerCount(view(), sponsorAccSle, *reserveSponsorAccSle, 1, ctx_.journal);
addSponsorToLedgerEntry(newSle, *reserveSponsorAccSle);
ctx_.view().insert(newSle);
return tesSUCCESS;
}
// Update the existing Sponsorship object.
if (feeAmount)
{
auto const currentFeeAmount = (*sponsorshipSle)[~sfFeeAmount].valueOr(XRPAmount{0});
auto const feeAmountDelta = XRPAmount(*feeAmount - currentFeeAmount);
if (feeAmountDelta > beast::kZero && feeAmountDelta > (*sponsorAccSle)[sfBalance])
return tecUNFUNDED;
// Move the FeeAmount delta between the sponsor balance and Sponsorship
// object.
if (feeAmountDelta != beast::kZero)
else
{
STAmount sponsorBalanceAfterFee = (*sponsorAccSle)[sfBalance];
sponsorBalanceAfterFee -= feeAmountDelta;
if (auto const ret = checkReserve(
ctx_.getApplyViewContext(),
sponsorAccSle,
sponsorBalanceAfterFee.xrp(),
*reserveSponsorAccSle,
{},
ctx_.journal,
tecUNFUNDED);
!isTesSuccess(ret))
{
return ret;
}
(*sponsorAccSle)[sfBalance] -= feeAmountDelta;
if (*feeAmount == beast::kZero)
{
(*sponsorshipSle).makeFieldAbsent(sfFeeAmount);
}
else
{
(*sponsorshipSle).setFieldAmount(sfFeeAmount, *feeAmount);
}
ctx_.view().update(sponsorAccSle);
(*sponsorshipSle)[sfFeeAmount] = newFee;
}
ctx_.view().update(sponsorAccSle);
}
if (maxFee)
@@ -367,15 +428,21 @@ SponsorshipSet::doApply()
}
}
if (remainingOwnerCount)
if (remainingOwnerCountDelta)
{
if (*remainingOwnerCount == 0)
std::int64_t const newCount =
totalRemainingOwnerCount(sponsorshipSle, remainingOwnerCountDelta);
// Overflow is rejected in preclaim; underflow clamps to zero (field absent).
XRPL_ASSERT(
newCount <= static_cast<std::int64_t>(std::numeric_limits<std::uint32_t>::max()),
"xrpl::SponsorshipSet::doApply : RemainingOwnerCount does not overflow");
if (newCount <= 0)
{
sponsorshipSle->makeFieldAbsent(sfRemainingOwnerCount);
}
else
{
sponsorshipSle->at(sfRemainingOwnerCount) = *remainingOwnerCount;
sponsorshipSle->at(sfRemainingOwnerCount) = static_cast<std::uint32_t>(newCount);
}
}

Some files were not shown because too many files have changed in this diff Show More