mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-17 20:20:22 +00:00
Compare commits
1 Commits
develop
...
mvadari/le
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
695df0deac |
@@ -262,8 +262,6 @@ words:
|
||||
- Rohrs
|
||||
- roundings
|
||||
- rustc
|
||||
- rustfmt
|
||||
- rustup
|
||||
- sahyadri
|
||||
- Satoshi
|
||||
- scons
|
||||
@@ -307,7 +305,6 @@ words:
|
||||
- takerpays
|
||||
- ters
|
||||
- TMEndpointv2
|
||||
- toolchain
|
||||
- tparam
|
||||
- trixie
|
||||
- tx
|
||||
|
||||
@@ -11,9 +11,6 @@ endfunction()
|
||||
function(create_symbolic_link target link)
|
||||
endfunction()
|
||||
|
||||
function(xrpl_add_benchmark name)
|
||||
endfunction()
|
||||
|
||||
macro(exclude_from_default target_)
|
||||
endmacro()
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
benchmarks.libxrpl > xrpl.basics
|
||||
benchmarks.libxrpl > xrpl.config
|
||||
benchmarks.libxrpl > xrpl.nodestore
|
||||
libxrpl.basics > xrpl.basics
|
||||
libxrpl.conditions > xrpl.basics
|
||||
libxrpl.conditions > xrpl.conditions
|
||||
@@ -15,9 +12,14 @@ libxrpl.json > xrpl.json
|
||||
libxrpl.ledger > xrpl.basics
|
||||
libxrpl.ledger > xrpl.json
|
||||
libxrpl.ledger > xrpl.ledger
|
||||
libxrpl.ledger > xrpl.ledger_entries
|
||||
libxrpl.ledger > xrpl.nodestore
|
||||
libxrpl.ledger > xrpl.protocol
|
||||
libxrpl.ledger > xrpl.shamap
|
||||
libxrpl.ledger_entries > xrpl.basics
|
||||
libxrpl.ledger_entries > xrpl.ledger
|
||||
libxrpl.ledger_entries > xrpl.ledger_entries
|
||||
libxrpl.ledger_entries > xrpl.protocol
|
||||
libxrpl.net > xrpl.basics
|
||||
libxrpl.net > xrpl.net
|
||||
libxrpl.nodestore > xrpl.basics
|
||||
@@ -53,6 +55,7 @@ libxrpl.tx > xrpl.conditions
|
||||
libxrpl.tx > xrpl.core
|
||||
libxrpl.tx > xrpl.json
|
||||
libxrpl.tx > xrpl.ledger
|
||||
libxrpl.tx > xrpl.ledger_entries
|
||||
libxrpl.tx > xrpl.protocol
|
||||
libxrpl.tx > xrpl.server
|
||||
libxrpl.tx > xrpl.tx
|
||||
@@ -68,6 +71,7 @@ test.app > xrpld.overlay
|
||||
test.app > xrpld.rpc
|
||||
test.app > xrpl.json
|
||||
test.app > xrpl.ledger
|
||||
test.app > xrpl.ledger_entries
|
||||
test.app > xrpl.nodestore
|
||||
test.app > xrpl.protocol
|
||||
test.app > xrpl.resource
|
||||
@@ -118,6 +122,7 @@ test.jtx > xrpld.core
|
||||
test.jtx > xrpld.rpc
|
||||
test.jtx > xrpl.json
|
||||
test.jtx > xrpl.ledger
|
||||
test.jtx > xrpl.ledger_entries
|
||||
test.jtx > xrpl.net
|
||||
test.jtx > xrpl.protocol
|
||||
test.jtx > xrpl.resource
|
||||
@@ -130,6 +135,7 @@ test.ledger > xrpld.app
|
||||
test.ledger > xrpld.core
|
||||
test.ledger > xrpl.json
|
||||
test.ledger > xrpl.ledger
|
||||
test.ledger > xrpl.ledger_entries
|
||||
test.ledger > xrpl.protocol
|
||||
test.nodestore > test.jtx
|
||||
test.nodestore > test.unit_test
|
||||
@@ -175,6 +181,7 @@ test.rpc > xrpld.overlay
|
||||
test.rpc > xrpld.rpc
|
||||
test.rpc > xrpl.json
|
||||
test.rpc > xrpl.ledger
|
||||
test.rpc > xrpl.ledger_entries
|
||||
test.rpc > xrpl.protocol
|
||||
test.rpc > xrpl.resource
|
||||
test.rpc > xrpl.server
|
||||
@@ -195,6 +202,7 @@ tests.libxrpl > xrpl.config
|
||||
tests.libxrpl > xrpl.core
|
||||
tests.libxrpl > xrpl.json
|
||||
tests.libxrpl > xrpl.ledger
|
||||
tests.libxrpl > xrpl.ledger_entries
|
||||
tests.libxrpl > xrpl.net
|
||||
tests.libxrpl > xrpl.nodestore
|
||||
tests.libxrpl > xrpl.protocol
|
||||
@@ -215,6 +223,9 @@ xrpl.ledger > xrpl.json
|
||||
xrpl.ledger > xrpl.nodestore
|
||||
xrpl.ledger > xrpl.protocol
|
||||
xrpl.ledger > xrpl.shamap
|
||||
xrpl.ledger_entries > xrpl.basics
|
||||
xrpl.ledger_entries > xrpl.ledger
|
||||
xrpl.ledger_entries > xrpl.protocol
|
||||
xrpl.net > xrpl.basics
|
||||
xrpl.nodestore > xrpl.basics
|
||||
xrpl.nodestore > xrpl.config
|
||||
@@ -242,6 +253,7 @@ xrpl.shamap > xrpl.protocol
|
||||
xrpl.tx > xrpl.basics
|
||||
xrpl.tx > xrpl.core
|
||||
xrpl.tx > xrpl.ledger
|
||||
xrpl.tx > xrpl.ledger_entries
|
||||
xrpl.tx > xrpl.protocol
|
||||
xrpld.app > test.unit_test
|
||||
xrpld.app > xrpl.basics
|
||||
@@ -251,6 +263,7 @@ xrpld.app > xrpld.consensus
|
||||
xrpld.app > xrpld.core
|
||||
xrpld.app > xrpl.json
|
||||
xrpld.app > xrpl.ledger
|
||||
xrpld.app > xrpl.ledger_entries
|
||||
xrpld.app > xrpl.net
|
||||
xrpld.app > xrpl.nodestore
|
||||
xrpld.app > xrpl.protocol
|
||||
@@ -301,6 +314,7 @@ xrpld.rpc > xrpl.core
|
||||
xrpld.rpc > xrpld.core
|
||||
xrpld.rpc > xrpl.json
|
||||
xrpld.rpc > xrpl.ledger
|
||||
xrpld.rpc > xrpl.ledger_entries
|
||||
xrpld.rpc > xrpl.net
|
||||
xrpld.rpc > xrpl.nodestore
|
||||
xrpld.rpc > xrpl.protocol
|
||||
|
||||
2
.github/scripts/strategy-matrix/linux.json
vendored
2
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-2e25435",
|
||||
"image_tag": "sha-e29b523",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
|
||||
37
.github/workflows/on-pr.yml
vendored
37
.github/workflows/on-pr.yml
vendored
@@ -25,11 +25,18 @@ on:
|
||||
- unlabeled
|
||||
|
||||
concurrency:
|
||||
# A single per-ref group with cancel-in-progress means any newer run (a push
|
||||
# or a label change) supersedes the in-progress one for that ref. Keeping
|
||||
# exactly one authoritative run per ref ensures a fast do-nothing run can never
|
||||
# mask a real build's checks.
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
# Use a per-ref group so a newer run (a push, or a change to a label below)
|
||||
# supersedes the in-progress one for that ref. Label events we don't act on get
|
||||
# their own unique group (per run id) instead, keeping them out of the shared
|
||||
# group so real builds keep running. Keep this list in sync with `should-run`.
|
||||
group: >-
|
||||
${{ github.workflow }}-${{ github.ref }}${{
|
||||
((github.event.action == 'labeled' || github.event.action == 'unlabeled')
|
||||
&& github.event.label.name != 'Ready to merge'
|
||||
&& github.event.label.name != 'DraftRunCI'
|
||||
&& github.event.label.name != 'Full CI build')
|
||||
&& format('-{0}', github.run_id) || ''
|
||||
}}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
@@ -37,17 +44,21 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
# This job determines whether the rest of the workflow should run at all,
|
||||
# based on the current set of labels: it runs when the PR is not a draft
|
||||
# (which should also cover merge-group) or has the 'DraftRunCI' or
|
||||
# 'Full CI build' label. Whether a build then happens, and whether it is the
|
||||
# minimal or full matrix, is decided further below and in the strategy matrix.
|
||||
# This job determines whether the rest of the workflow should run. It runs
|
||||
# when the PR is not a draft (which should also cover merge-group) or has the
|
||||
# 'DraftRunCI' or 'Full CI build' label. For label events it only runs when the
|
||||
# label added or removed is one we act on ('Ready to merge', 'DraftRunCI' or
|
||||
# 'Full CI build'), so unrelated label changes do not trigger a redundant run.
|
||||
should-run:
|
||||
if: >-
|
||||
${{
|
||||
!github.event.pull_request.draft
|
||||
|| contains(github.event.pull_request.labels.*.name, 'DraftRunCI')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'Full CI build')
|
||||
((github.event.action != 'labeled' && github.event.action != 'unlabeled')
|
||||
|| github.event.label.name == 'Ready to merge'
|
||||
|| github.event.label.name == 'DraftRunCI'
|
||||
|| github.event.label.name == 'Full CI build')
|
||||
&& (!github.event.pull_request.draft
|
||||
|| contains(github.event.pull_request.labels.*.name, 'DraftRunCI')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'Full CI build'))
|
||||
}}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
4
.github/workflows/publish-docs.yml
vendored
4
.github/workflows/publish-docs.yml
vendored
@@ -41,13 +41,13 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-2e25435
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@ad188deb3dae79dc39816e16ddfdad1e06c6fab2
|
||||
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
19
.github/workflows/reusable-build-test-config.yml
vendored
19
.github/workflows/reusable-build-test-config.yml
vendored
@@ -113,7 +113,7 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@ad188deb3dae79dc39816e16ddfdad1e06c6fab2
|
||||
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
@@ -324,23 +324,6 @@ jobs:
|
||||
|
||||
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
|
||||
# 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.
|
||||
- name: Run the benchmarks
|
||||
if: ${{ !inputs.build_only && runner.os != 'Windows' && env.SANITIZERS_ENABLED == 'false' && env.COVERAGE_ENABLED != 'true' && env.VOIDSTAR_ENABLED != 'true' }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
rc=0
|
||||
while IFS= read -r bench; do
|
||||
echo "::group::${bench}"
|
||||
"./${bench}" --benchmark_repetitions=1 || rc=1
|
||||
echo "::endgroup::"
|
||||
done < <(find src/benchmarks -type f -perm -u+x -name 'xrpl.bench.*')
|
||||
exit "${rc}"
|
||||
|
||||
- name: Show test failure summary
|
||||
if: ${{ failure() && !inputs.build_only }}
|
||||
env:
|
||||
|
||||
4
.github/workflows/reusable-clang-tidy.yml
vendored
4
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-2e25435"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-e29b523"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@ad188deb3dae79dc39816e16ddfdad1e06c6fab2
|
||||
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
2
.github/workflows/reusable-upload-recipe.yml
vendored
2
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,7 +40,7 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-2e25435
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
|
||||
2
.github/workflows/upload-conan-deps.yml
vendored
2
.github/workflows/upload-conan-deps.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@ad188deb3dae79dc39816e16ddfdad1e06c6fab2
|
||||
uses: XRPLF/actions/prepare-runner@64ec3cf3b152b4444638f470bbd6df7a7a30c81c
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
@@ -131,10 +131,6 @@ else()
|
||||
endif()
|
||||
target_link_libraries(xrpl_libs INTERFACE ${nudb})
|
||||
|
||||
if(benchmark)
|
||||
find_package(benchmark REQUIRED)
|
||||
endif()
|
||||
|
||||
if(coverage)
|
||||
include(XrplCov)
|
||||
endif()
|
||||
@@ -149,7 +145,3 @@ if(tests)
|
||||
include(CTest)
|
||||
add_subdirectory(src/tests/libxrpl)
|
||||
endif()
|
||||
|
||||
if(benchmark)
|
||||
add_subdirectory(src/benchmarks/libxrpl)
|
||||
endif()
|
||||
|
||||
@@ -83,7 +83,6 @@ If you create new source files, they must be organized as follows:
|
||||
`src/libxrpl`.
|
||||
- All other non-test files must go under `src/xrpld`.
|
||||
- All test source files must go under `src/test`.
|
||||
- All benchmark source files must go under `src/benchmarks`.
|
||||
|
||||
The source must be formatted according to the style guide below. The easiest
|
||||
way to satisfy this is to install the [`pre-commit`](#pre-commit-hooks) hooks,
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
include(isolate_headers)
|
||||
|
||||
# Define a benchmark executable for the module `name`.
|
||||
#
|
||||
# This follows the same general pattern as other build helpers in this repo
|
||||
# (e.g. `add_module`): create a target and isolate headers, but here the target
|
||||
# is a benchmark executable and no `add_test(...)` is registered.
|
||||
#
|
||||
# `isolate_headers` exposes only `${CMAKE_CURRENT_SOURCE_DIR}/${name}` on the
|
||||
# include path, rooted at `src`, so a benchmark's own headers are reached as
|
||||
# `<benchmarks/.../${name}/...>` and nothing else in the tree leaks in.
|
||||
function(xrpl_add_benchmark name)
|
||||
set(target ${PROJECT_NAME}.bench.${name})
|
||||
|
||||
file(
|
||||
GLOB_RECURSE sources
|
||||
CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp"
|
||||
)
|
||||
add_executable(${target} ${ARGN} ${sources})
|
||||
|
||||
# Benchmark sources register cases through Google Benchmark's static
|
||||
# registrars (anonymous-namespace lambdas). Merging several such files into
|
||||
# one unity translation unit collides those internal-linkage entities, so
|
||||
# keep benchmarks out of the unity build - mirroring xrpl.libpb in
|
||||
# XrplCore.cmake. Each file compiles fine on its own.
|
||||
set_target_properties(${target} PROPERTIES UNITY_BUILD OFF)
|
||||
|
||||
isolate_headers(
|
||||
${target}
|
||||
"${CMAKE_SOURCE_DIR}/src"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}"
|
||||
PRIVATE
|
||||
)
|
||||
endfunction()
|
||||
@@ -30,8 +30,6 @@ if(tests)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(benchmark "Build benchmarks" ON)
|
||||
|
||||
# Enabled by default so every header is compiled on its own as the main file of
|
||||
# its own compile_commands.json entry - this is what lets clang-tidy (and clangd
|
||||
# and IDEs) analyse a header's own includes directly. The per-header objects are
|
||||
|
||||
11
conan.lock
11
conan.lock
@@ -10,23 +10,22 @@
|
||||
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1782392413.075713",
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1782392402.431897",
|
||||
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
|
||||
"openssl/3.6.3#f806de8933e3bf6f01016c6a888cee2e%1783945160.863288",
|
||||
"openssl/3.6.3#1163d4ddc603907084d08a6a0c6e580f%1782307150.583886",
|
||||
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1782392402.297166",
|
||||
"mpt-crypto/0.4.0-rc4#ffdba12f2332357f0d8b0ae944cfff52%1784138702.932355",
|
||||
"mpt-crypto/0.4.0-rc2#a580f2f9ad0e795de696aa62d54fb9af%1782425834.488828",
|
||||
"lz4/1.10.0#982d9b673900f665a1da109e09c17cab%1782392402.164188",
|
||||
"libiconv/1.17#9923bc6dc6f106646d6967e0039a5ada%1782392792.775744",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1782392402.420732",
|
||||
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1782392403.066892",
|
||||
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
|
||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979",
|
||||
"grpc/1.81.1#f729f6d75992d20f9c72828e9142d62f%1783945160.094135",
|
||||
"grpc/1.81.1#5217e6ef0544c42b46f4af35d5e7f649%1782307148.845616",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1782392402.296732",
|
||||
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1782392419.475605",
|
||||
"benchmark/1.9.5#b885dc73ad67b40a55d45684d1c88ad1%1782736613.864841",
|
||||
"abseil/20250127.0#9ef01c1451a8340f9022e46238c0fbb6%1783945159.651047"
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1782307147.395833"
|
||||
],
|
||||
"build_requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
|
||||
@@ -39,7 +38,7 @@
|
||||
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1782392402.624226",
|
||||
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
|
||||
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
|
||||
"abseil/20250127.0#9ef01c1451a8340f9022e46238c0fbb6%1783945159.651047"
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1782307147.395833"
|
||||
],
|
||||
"python_requires": [],
|
||||
"overrides": {
|
||||
|
||||
@@ -15,7 +15,6 @@ class Xrpl(ConanFile):
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
options = {
|
||||
"assertions": [True, False],
|
||||
"benchmark": [True, False],
|
||||
"coverage": [True, False],
|
||||
"fPIC": [True, False],
|
||||
"jemalloc": [True, False],
|
||||
@@ -47,7 +46,6 @@ class Xrpl(ConanFile):
|
||||
|
||||
default_options = {
|
||||
"assertions": False,
|
||||
"benchmark": True,
|
||||
"coverage": False,
|
||||
"fPIC": True,
|
||||
"jemalloc": False,
|
||||
@@ -131,14 +129,12 @@ class Xrpl(ConanFile):
|
||||
self.options["boost"].without_cobalt = True
|
||||
|
||||
def requirements(self):
|
||||
if self.options.benchmark:
|
||||
self.requires("benchmark/1.9.5")
|
||||
self.requires("boost/1.91.0", force=True, transitive_headers=True)
|
||||
self.requires("date/3.0.4", transitive_headers=True)
|
||||
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/0.4.0-rc2", transitive_headers=True)
|
||||
self.requires("protobuf/6.33.5", force=True)
|
||||
if self.options.rocksdb:
|
||||
self.requires("rocksdb/10.5.1")
|
||||
@@ -166,7 +162,6 @@ class Xrpl(ConanFile):
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
tc.variables["tests"] = self.options.tests
|
||||
tc.variables["benchmark"] = self.options.benchmark
|
||||
tc.variables["assert"] = self.options.assertions
|
||||
tc.variables["coverage"] = self.options.coverage
|
||||
tc.variables["jemalloc"] = self.options.jemalloc
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
@@ -3,11 +3,11 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -263,11 +263,10 @@ constructLoanState(
|
||||
Number const& principalOutstanding,
|
||||
Number const& managementFeeOutstanding);
|
||||
|
||||
// Overload of constructLoanState() that reads the three tracked fields
|
||||
// directly from a Loan ledger object, which always holds rounded values,
|
||||
// rather than taking them as separate Number arguments.
|
||||
// Constructs a valid LoanState object from a Loan object, which always has
|
||||
// rounded values
|
||||
LoanState
|
||||
constructLoanState(SLE::const_ref loan);
|
||||
constructRoundedLoanState(SLE::const_ref loan);
|
||||
|
||||
Number
|
||||
computeManagementFee(
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -229,6 +229,13 @@ public:
|
||||
[[nodiscard]] AccountID
|
||||
getAccountID(SField const& field) const;
|
||||
|
||||
/**
|
||||
* The account responsible for the authorization: the delegate when
|
||||
* sfDelegate is present, otherwise the account.
|
||||
*/
|
||||
[[nodiscard]] AccountID
|
||||
getInitiator() const;
|
||||
|
||||
[[nodiscard]] Blob
|
||||
getFieldVL(SField const& field) const;
|
||||
[[nodiscard]] STAmount const&
|
||||
|
||||
@@ -142,26 +142,9 @@ public:
|
||||
TxnSql status,
|
||||
std::string const& escapedMetaData) const;
|
||||
|
||||
/**
|
||||
* The IDs of the inner transactions of a Batch.
|
||||
*/
|
||||
[[nodiscard]] std::vector<uint256>
|
||||
[[nodiscard]] std::vector<uint256> const&
|
||||
getBatchTransactionIDs() const;
|
||||
|
||||
/**
|
||||
* The inner transactions of a Batch, built and validated at construction.
|
||||
* Always seated for Batch STTx instances (construction throws if oversized).
|
||||
*/
|
||||
[[nodiscard]] std::vector<std::shared_ptr<STTx const>> const&
|
||||
getBatchTransactions() const;
|
||||
|
||||
/**
|
||||
* The account responsible for the authorization: the delegate when
|
||||
* sfDelegate is present, otherwise the account.
|
||||
*/
|
||||
[[nodiscard]] AccountID
|
||||
getInitiator() const;
|
||||
|
||||
[[nodiscard]] AccountID
|
||||
getFeePayerID() const;
|
||||
|
||||
@@ -183,16 +166,13 @@ private:
|
||||
checkMultiSign(Rules const& rules, STObject const& sigObject) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkBatchSingleSign(STObject const& batchSigner, std::vector<uint256> const& txIds) const;
|
||||
checkBatchSingleSign(STObject const& batchSigner) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkBatchMultiSign(
|
||||
STObject const& batchSigner,
|
||||
Rules const& rules,
|
||||
std::vector<uint256> const& txIds) const;
|
||||
checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const;
|
||||
|
||||
void
|
||||
buildBatchTxns();
|
||||
buildBatchTxnIds();
|
||||
|
||||
STBase*
|
||||
copy(std::size_t n, void* buf) const override;
|
||||
@@ -200,11 +180,11 @@ private:
|
||||
move(std::size_t n, void* buf) override;
|
||||
|
||||
friend class detail::STVar;
|
||||
std::optional<std::vector<std::shared_ptr<STTx const>>> batchTxns_;
|
||||
std::optional<std::vector<uint256>> batchTxnIds_;
|
||||
};
|
||||
|
||||
bool
|
||||
passesLocalChecks(STTx const& tx, std::string&);
|
||||
passesLocalChecks(STObject const& st, std::string&);
|
||||
|
||||
/**
|
||||
* Sterilize a transaction.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/OfferHelpers.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -40,9 +39,6 @@ public:
|
||||
static NotTEC
|
||||
checkSign(PreclaimContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
@@ -80,10 +76,6 @@ private:
|
||||
// only be reached through Batch::checkSign.
|
||||
static NotTEC
|
||||
checkBatchSign(PreclaimContext const& ctx);
|
||||
|
||||
// nullopt on overflow or oversized signer arrays.
|
||||
static std::optional<XRPAmount>
|
||||
calculateBaseFeeImpl(ReadView const& view, STTx const& tx);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# Rust toolchain pin for rustup-based CI runners and local development.
|
||||
# rustup reads this file and installs the pinned toolchain (see the
|
||||
# prepare-runner action in XRPLF/actions, which runs `rustup toolchain install`).
|
||||
# NOTE: the Nix CI image and development shell ignore this file; its rustc comes from flake.lock.
|
||||
[toolchain]
|
||||
channel = "1.95"
|
||||
components = ["rustfmt", "clippy"]
|
||||
profile = "minimal"
|
||||
@@ -165,7 +165,7 @@ unsigned-integer-overflow:basics/Number.h
|
||||
# NFTokenMint checks tokenSeq + 1u == 0u; AmendmentTable does (seq - 1) / 256.
|
||||
unsigned-integer-overflow:basics/base_uint.h
|
||||
unsigned-integer-overflow:ledger/ApplyView.cpp
|
||||
unsigned-integer-overflow:ledger/helpers/AccountRootHelpers.cpp
|
||||
unsigned-integer-overflow:ledger_entries/AccountRootHelpers.cpp
|
||||
unsigned-integer-overflow:tx/transactors/nft/NFTokenMint.cpp
|
||||
unsigned-integer-overflow:app/misc/detail/AmendmentTable.cpp
|
||||
|
||||
@@ -180,7 +180,7 @@ unsigned-integer-overflow:json/json_value.cpp
|
||||
unsigned-integer-overflow:app/misc/NetworkOPs.cpp
|
||||
unsigned-integer-overflow:rpc/detail/Role.cpp
|
||||
unsigned-integer-overflow:tx/transactors/oracle/OracleSet.cpp
|
||||
unsigned-integer-overflow:ledger/helpers/MPTokenHelpers.cpp
|
||||
unsigned-integer-overflow:ledger_entries/MPTokenHelpers.cpp
|
||||
unsigned-integer-overflow:crypto/RFC1751.cpp
|
||||
unsigned-integer-overflow:tx/paths/detail/StrandFlow.h
|
||||
unsigned-integer-overflow:protocol/STObject.h
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
include(XrplAddBenchmark)
|
||||
|
||||
# Benchmark requirements.
|
||||
find_package(benchmark REQUIRED)
|
||||
|
||||
# Custom target for all benchmarks defined in this file.
|
||||
add_custom_target(xrpl.benchmarks)
|
||||
|
||||
# Common library dependencies for every benchmark module. `benchmark_main`
|
||||
# supplies a `main()` that parses the standard Google Benchmark CLI flags
|
||||
# (`--benchmark_filter`, `--benchmark_format`, ...), so no per-module main.cpp
|
||||
# is needed.
|
||||
add_library(xrpl.imports.bench INTERFACE)
|
||||
target_link_libraries(
|
||||
xrpl.imports.bench
|
||||
INTERFACE benchmark::benchmark_main xrpl.libxrpl
|
||||
)
|
||||
|
||||
# One benchmark executable for each module.
|
||||
xrpl_add_benchmark(nodestore)
|
||||
target_link_libraries(xrpl.bench.nodestore PRIVATE xrpl.imports.bench)
|
||||
add_dependencies(xrpl.benchmarks xrpl.bench.nodestore)
|
||||
@@ -1,329 +0,0 @@
|
||||
#include <xrpl/nodestore/Backend.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/nodestore/Types.h>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <benchmarks/libxrpl/nodestore/NodeStoreBench.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t kPoolSizes[] = {1000, 10000, 100000};
|
||||
constexpr int kThreadCounts[] = {1, 4, 8};
|
||||
constexpr std::size_t kBatchSize = 256;
|
||||
|
||||
constexpr std::string_view kNamePrefix = "BM_Backend_";
|
||||
constexpr std::string_view kNameSeparator = "/";
|
||||
|
||||
struct RunState
|
||||
{
|
||||
std::unique_ptr<BackendHarness> harness;
|
||||
Batch present; // prefix-1 objects, eligible to be stored
|
||||
Batch recent; // prefix-1 objects in the "future" key space
|
||||
std::vector<uint256> missing; // prefix-2 keys that are never stored
|
||||
std::vector<std::size_t> shuffle; // [0, poolSize) permutation for random-like access
|
||||
std::size_t avgPayload = 0; // mean getData().size() over `present`
|
||||
|
||||
void
|
||||
release()
|
||||
{
|
||||
harness.reset();
|
||||
Batch{}.swap(present);
|
||||
Batch{}.swap(recent);
|
||||
std::vector<uint256>{}.swap(missing);
|
||||
std::vector<std::size_t>{}.swap(shuffle);
|
||||
}
|
||||
};
|
||||
|
||||
struct SetupContext
|
||||
{
|
||||
RunState& rs;
|
||||
Backend& backend;
|
||||
std::size_t poolSize;
|
||||
};
|
||||
|
||||
struct IterateContext
|
||||
{
|
||||
RunState& rs;
|
||||
Backend& backend;
|
||||
std::size_t index;
|
||||
std::size_t poolSize;
|
||||
};
|
||||
|
||||
struct Workload
|
||||
{
|
||||
std::string_view name;
|
||||
std::function<void(SetupContext const&)> setup;
|
||||
std::function<void(IterateContext const&)> iterate;
|
||||
bool reportBytes = false; // SetBytesProcessed from rs.avgPayload
|
||||
bool clobber = true; // ClobberMemory after the loop (false for pure stores)
|
||||
bool pinToPool = false; // pin iterations to one pool sweep instead of autotuning
|
||||
};
|
||||
|
||||
// One store() per iteration. Iterations are pinned to one pool sweep (per
|
||||
// thread) so the index never wraps past the pool - otherwise NuDB::doInsert
|
||||
// swallows key_exists and the workload degenerates into duplicate-detection
|
||||
// no-ops.
|
||||
Workload const kInsert{
|
||||
.name = "Insert",
|
||||
.setup =
|
||||
[](SetupContext const& ctx) {
|
||||
ctx.rs.present = makePool(1, ctx.poolSize);
|
||||
ctx.rs.avgPayload = averagePayload(ctx.rs.present);
|
||||
},
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, backend, index, poolSize] = ctx;
|
||||
backend.store(rs.present[index % poolSize]);
|
||||
},
|
||||
.reportBytes = true,
|
||||
.clobber = false,
|
||||
.pinToPool = true,
|
||||
};
|
||||
|
||||
// One fetch() of a present key (a hit) per iteration.
|
||||
Workload const kFetch{
|
||||
.name = "Fetch",
|
||||
.setup =
|
||||
[](SetupContext const& ctx) {
|
||||
ctx.rs.present = makePool(1, ctx.poolSize);
|
||||
ctx.rs.avgPayload = averagePayload(ctx.rs.present);
|
||||
prepopulate(ctx.backend, ctx.rs.present);
|
||||
},
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, backend, index, poolSize] = ctx;
|
||||
std::shared_ptr<NodeObject> result;
|
||||
backend.fetch(rs.present[index % poolSize]->getHash(), &result);
|
||||
benchmark::DoNotOptimize(result);
|
||||
},
|
||||
.reportBytes = true,
|
||||
};
|
||||
|
||||
// One fetch() of a never-stored key (a miss); the backend is left empty.
|
||||
Workload const kMissing{
|
||||
.name = "Missing",
|
||||
.setup = [](SetupContext const& ctx) { ctx.rs.missing = makeMissingKeys(ctx.poolSize); },
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, backend, index, poolSize] = ctx;
|
||||
std::shared_ptr<NodeObject> result;
|
||||
backend.fetch(rs.missing[index % poolSize], &result);
|
||||
benchmark::DoNotOptimize(result);
|
||||
},
|
||||
};
|
||||
|
||||
// 80% hits / 20% misses. The fetch index comes from a shuffle table so access
|
||||
// is random-like without per-iteration RNG cost; sequential `index % poolSize`
|
||||
// would be artificially cache-friendly to RocksDB's block cache.
|
||||
Workload const kMixed{
|
||||
.name = "Mixed",
|
||||
.setup =
|
||||
[](SetupContext const& ctx) {
|
||||
ctx.rs.present = makePool(1, ctx.poolSize);
|
||||
ctx.rs.missing = makeMissingKeys(ctx.poolSize);
|
||||
ctx.rs.shuffle = makeShuffle(ctx.poolSize, /*seed=*/1);
|
||||
prepopulate(ctx.backend, ctx.rs.present);
|
||||
},
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, backend, index, poolSize] = ctx;
|
||||
std::shared_ptr<NodeObject> result;
|
||||
auto const pick = rs.shuffle[index % poolSize];
|
||||
if (index % 5 == 0)
|
||||
{
|
||||
backend.fetch(rs.missing[pick], &result);
|
||||
}
|
||||
else
|
||||
{
|
||||
backend.fetch(rs.present[pick]->getHash(), &result);
|
||||
}
|
||||
benchmark::DoNotOptimize(result);
|
||||
},
|
||||
};
|
||||
|
||||
// An xrpld-like cycle: a hit, a maybe-miss recent fetch, and a store. The
|
||||
// recent fetch uses the shuffle table (not `slot`) so it doesn't fetch the item
|
||||
// it's about to store this iteration - which would give an all-miss-then-hit
|
||||
// step instead of a smooth ramp. The store walks sequentially so each recent
|
||||
// object is stored once.
|
||||
Workload const kWork{
|
||||
.name = "Work",
|
||||
.setup =
|
||||
[](SetupContext const& ctx) {
|
||||
ctx.rs.present = makePool(1, ctx.poolSize);
|
||||
ctx.rs.recent = makePool(1, ctx.poolSize, ctx.poolSize);
|
||||
ctx.rs.shuffle = makeShuffle(ctx.poolSize, /*seed=*/2);
|
||||
prepopulate(ctx.backend, ctx.rs.present);
|
||||
},
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, backend, index, poolSize] = ctx;
|
||||
auto const slot = index % poolSize;
|
||||
auto const pick = rs.shuffle[slot];
|
||||
|
||||
std::shared_ptr<NodeObject> historical;
|
||||
backend.fetch(rs.present[pick]->getHash(), &historical);
|
||||
benchmark::DoNotOptimize(historical);
|
||||
|
||||
std::shared_ptr<NodeObject> recent;
|
||||
backend.fetch(rs.recent[pick]->getHash(), &recent);
|
||||
benchmark::DoNotOptimize(recent);
|
||||
|
||||
backend.store(rs.recent[slot]);
|
||||
},
|
||||
.clobber = true,
|
||||
.pinToPool = true,
|
||||
};
|
||||
|
||||
auto
|
||||
makeRunner(Workload w, std::string cfg, std::shared_ptr<RunState> rs)
|
||||
{
|
||||
return [w = std::move(w), cfg = std::move(cfg), rs = std::move(rs)](benchmark::State& state) {
|
||||
auto const poolSize = static_cast<std::size_t>(state.range(0));
|
||||
if (state.thread_index() == 0)
|
||||
{
|
||||
rs->harness = std::make_unique<BackendHarness>(cfg);
|
||||
w.setup(
|
||||
SetupContext{.rs = *rs, .backend = *rs->harness->backend, .poolSize = poolSize});
|
||||
}
|
||||
|
||||
std::size_t index = state.thread_index();
|
||||
for (auto _ : state)
|
||||
{
|
||||
w.iterate(
|
||||
IterateContext{
|
||||
.rs = *rs,
|
||||
.backend = *rs->harness->backend,
|
||||
.index = index,
|
||||
.poolSize = poolSize});
|
||||
index += state.threads();
|
||||
}
|
||||
|
||||
if (w.clobber)
|
||||
benchmark::ClobberMemory();
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
if (w.reportBytes)
|
||||
state.SetBytesProcessed(static_cast<std::int64_t>(state.iterations() * rs->avgPayload));
|
||||
|
||||
if (state.thread_index() == 0)
|
||||
rs->release();
|
||||
};
|
||||
}
|
||||
|
||||
// Register workload `w` against backend `bc`, choosing the registration shape
|
||||
// from `w.pinToPool`.
|
||||
void
|
||||
registerWorkload(BackendConfig const& bc, Workload const& w)
|
||||
{
|
||||
std::string const cfg = bc.config;
|
||||
std::string name{kNamePrefix};
|
||||
name += w.name;
|
||||
name += kNameSeparator;
|
||||
name += bc.name;
|
||||
|
||||
if (!w.pinToPool)
|
||||
{
|
||||
auto rs = std::make_shared<RunState>();
|
||||
auto* b = benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs));
|
||||
b->RangeMultiplier(10)->Range(kPoolSizes[0], kPoolSizes[std::size(kPoolSizes) - 1]);
|
||||
b->Threads(1)->Threads(4)->Threads(8)->UseRealTime();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto const poolSize : kPoolSizes)
|
||||
{
|
||||
for (auto const threads : kThreadCounts)
|
||||
{
|
||||
if (poolSize % static_cast<std::size_t>(threads) != 0)
|
||||
continue;
|
||||
|
||||
auto rs = std::make_shared<RunState>();
|
||||
benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs))
|
||||
->Arg(poolSize)
|
||||
->Iterations(poolSize / static_cast<std::size_t>(threads))
|
||||
->Threads(threads)
|
||||
->UseRealTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// One storeBatch() of kBatchSize objects per iteration. Single-threaded:
|
||||
// Backend::storeBatch must not run concurrently with itself or store().
|
||||
// Iterations are pinned to the batch count so the index never wraps into
|
||||
// key_exists no-ops. Kept separate from Workload: batch slicing and the
|
||||
// per-batch item/byte accounting don't fit the thread-axis mold.
|
||||
void
|
||||
registerStoreBatch(BackendConfig const& bc)
|
||||
{
|
||||
std::string const cfg = bc.config;
|
||||
std::string name{kNamePrefix};
|
||||
name += "StoreBatch";
|
||||
name += kNameSeparator;
|
||||
name += bc.name;
|
||||
for (auto const poolSize : kPoolSizes)
|
||||
{
|
||||
auto const numBatches = poolSize / kBatchSize;
|
||||
if (numBatches == 0)
|
||||
continue;
|
||||
|
||||
auto rs = std::make_shared<RunState>();
|
||||
benchmark::RegisterBenchmark(
|
||||
name,
|
||||
[rs, cfg](benchmark::State& state) {
|
||||
auto const poolSize = static_cast<std::size_t>(state.range(0));
|
||||
rs->harness = std::make_unique<BackendHarness>(cfg);
|
||||
rs->present = makePool(1, poolSize);
|
||||
rs->avgPayload = averagePayload(rs->present);
|
||||
std::vector<Batch> const batches = sliceBatches(rs->present, kBatchSize);
|
||||
if (batches.empty())
|
||||
{
|
||||
state.SkipWithError("pool smaller than one batch");
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t index = 0;
|
||||
for (auto _ : state)
|
||||
{
|
||||
rs->harness->backend->storeBatch(batches[index % batches.size()]);
|
||||
++index;
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(static_cast<std::int64_t>(state.iterations() * kBatchSize));
|
||||
state.SetBytesProcessed(
|
||||
static_cast<std::int64_t>(state.iterations() * kBatchSize * rs->avgPayload));
|
||||
rs->release();
|
||||
})
|
||||
->Arg(poolSize)
|
||||
->Iterations(numBatches);
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool const kRegistered = [] {
|
||||
auto const workloads = std::to_array({&kInsert, &kFetch, &kMissing, &kMixed, &kWork});
|
||||
for (auto const& bc : backendConfigs())
|
||||
{
|
||||
for (auto const* w : workloads)
|
||||
registerWorkload(bc, *w);
|
||||
|
||||
registerStoreBatch(bc);
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
|
||||
} // namespace
|
||||
} // namespace xrpl::NodeStore
|
||||
@@ -1,243 +0,0 @@
|
||||
#include <xrpl/nodestore/Database.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/nodestore/Types.h>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <benchmarks/libxrpl/nodestore/NodeStoreBench.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
namespace {
|
||||
|
||||
// Number of distinct objects pre-generated per run.
|
||||
constexpr std::size_t kDefaultPoolSize = 100000;
|
||||
|
||||
// Async read threads the Database spawns. Unused by the synchronous fetch path
|
||||
// these benchmarks take; kept fixed so runs are comparable.
|
||||
constexpr int kReadThreads = 4;
|
||||
|
||||
constexpr std::string_view kNamePrefix = "BM_Database_";
|
||||
constexpr std::string_view kNameSeparator = "/";
|
||||
|
||||
struct RunState
|
||||
{
|
||||
std::unique_ptr<DatabaseHarness> harness;
|
||||
Batch present; // prefix-1 objects, eligible to be stored
|
||||
Batch recent; // prefix-1 objects in the "future" key space
|
||||
std::vector<uint256> missing; // prefix-2 keys that are never stored
|
||||
std::vector<std::size_t> shuffle; // [0, poolSize) permutation for random-like access
|
||||
std::size_t avgPayload = 0; // mean getData().size() over `present`
|
||||
};
|
||||
|
||||
struct SetupContext
|
||||
{
|
||||
RunState& rs;
|
||||
Database& db;
|
||||
std::size_t poolSize;
|
||||
};
|
||||
|
||||
struct IterateContext
|
||||
{
|
||||
RunState& rs;
|
||||
Database& db;
|
||||
std::uint32_t seq;
|
||||
std::size_t index;
|
||||
std::size_t poolSize;
|
||||
};
|
||||
|
||||
struct Workload
|
||||
{
|
||||
std::string_view name;
|
||||
std::function<void(SetupContext const&)> setup;
|
||||
std::function<void(IterateContext const&)> iterate;
|
||||
bool reportBytes = false;
|
||||
bool pinIterations = false;
|
||||
};
|
||||
|
||||
void
|
||||
prepopulate(Database& db, Batch const& objects)
|
||||
{
|
||||
auto const seq = db.earliestLedgerSeq();
|
||||
for (auto const& obj : objects)
|
||||
{
|
||||
Blob data(obj->getData());
|
||||
db.store(obj->getType(), std::move(data), obj->getHash(), seq);
|
||||
}
|
||||
db.sync();
|
||||
}
|
||||
|
||||
// One store() per iteration; a fresh Blob copy is handed over each time.
|
||||
Workload const kStore{
|
||||
.name = "Store",
|
||||
.setup =
|
||||
[](SetupContext const& ctx) {
|
||||
ctx.rs.present = makePool(1, ctx.poolSize);
|
||||
ctx.rs.avgPayload = averagePayload(ctx.rs.present);
|
||||
},
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, db, seq, index, poolSize] = ctx;
|
||||
auto const& obj = rs.present[index % poolSize];
|
||||
Blob data(obj->getData());
|
||||
db.store(obj->getType(), std::move(data), obj->getHash(), seq);
|
||||
},
|
||||
.reportBytes = true,
|
||||
.pinIterations = true,
|
||||
};
|
||||
|
||||
// One fetchNodeObject() of a stored key (a hit) per iteration.
|
||||
Workload const kFetch{
|
||||
.name = "Fetch",
|
||||
.setup =
|
||||
[](SetupContext const& ctx) {
|
||||
ctx.rs.present = makePool(1, ctx.poolSize);
|
||||
ctx.rs.avgPayload = averagePayload(ctx.rs.present);
|
||||
prepopulate(ctx.db, ctx.rs.present);
|
||||
},
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, db, seq, index, poolSize] = ctx;
|
||||
auto obj = db.fetchNodeObject(rs.present[index % poolSize]->getHash(), seq);
|
||||
benchmark::DoNotOptimize(obj);
|
||||
},
|
||||
.reportBytes = true,
|
||||
};
|
||||
|
||||
// One fetchNodeObject() of a never-stored key (a miss) per iteration.
|
||||
Workload const kMissing{
|
||||
.name = "Missing",
|
||||
.setup = [](SetupContext const& ctx) { ctx.rs.missing = makeMissingKeys(ctx.poolSize); },
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, db, seq, index, poolSize] = ctx;
|
||||
auto obj = db.fetchNodeObject(rs.missing[index % poolSize], seq);
|
||||
benchmark::DoNotOptimize(obj);
|
||||
},
|
||||
};
|
||||
|
||||
// 80% hits / 20% misses. The fetch index comes from a shuffle table so access
|
||||
// is random-like without per-iteration RNG cost; sequential `index % poolSize`
|
||||
// would be artificially cache-friendly.
|
||||
Workload const kMixed{
|
||||
.name = "Mixed",
|
||||
.setup =
|
||||
[](SetupContext const& ctx) {
|
||||
ctx.rs.present = makePool(1, ctx.poolSize);
|
||||
ctx.rs.missing = makeMissingKeys(ctx.poolSize);
|
||||
ctx.rs.shuffle = makeShuffle(ctx.poolSize, /*seed=*/1);
|
||||
prepopulate(ctx.db, ctx.rs.present);
|
||||
},
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, db, seq, index, poolSize] = ctx;
|
||||
auto const pick = rs.shuffle[index % poolSize];
|
||||
std::shared_ptr<NodeObject> obj;
|
||||
if (index % 5 == 0)
|
||||
{
|
||||
obj = db.fetchNodeObject(rs.missing[pick], seq);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = db.fetchNodeObject(rs.present[pick]->getHash(), seq);
|
||||
}
|
||||
benchmark::DoNotOptimize(obj);
|
||||
},
|
||||
};
|
||||
|
||||
// An xrpld-like cycle: a hit, a maybe-miss recent fetch, and a store. The
|
||||
// recent fetch uses the shuffle table (not `slot`) so it doesn't fetch the item
|
||||
// it's about to store this iteration - which would give an all-miss-then-hit
|
||||
// step instead of a smooth ramp. The store walks sequentially so each recent
|
||||
// object is stored once.
|
||||
Workload const kWork{
|
||||
.name = "Work",
|
||||
.setup =
|
||||
[](SetupContext const& ctx) {
|
||||
ctx.rs.present = makePool(1, ctx.poolSize);
|
||||
ctx.rs.recent = makePool(1, ctx.poolSize, ctx.poolSize);
|
||||
ctx.rs.shuffle = makeShuffle(ctx.poolSize, /*seed=*/2);
|
||||
prepopulate(ctx.db, ctx.rs.present);
|
||||
},
|
||||
.iterate =
|
||||
[](IterateContext const& ctx) {
|
||||
auto& [rs, db, seq, index, poolSize] = ctx;
|
||||
auto const slot = index % poolSize;
|
||||
auto const pick = rs.shuffle[slot];
|
||||
|
||||
auto historical = db.fetchNodeObject(rs.present[pick]->getHash(), seq);
|
||||
benchmark::DoNotOptimize(historical);
|
||||
|
||||
auto recent = db.fetchNodeObject(rs.recent[pick]->getHash(), seq);
|
||||
benchmark::DoNotOptimize(recent);
|
||||
|
||||
auto const& obj = rs.recent[slot];
|
||||
Blob data(obj->getData());
|
||||
db.store(obj->getType(), std::move(data), obj->getHash(), seq);
|
||||
},
|
||||
.pinIterations = true,
|
||||
};
|
||||
|
||||
void
|
||||
registerWorkload(BackendConfig const& bc, Workload const& w)
|
||||
{
|
||||
auto rs = std::make_shared<RunState>();
|
||||
std::string const cfg = bc.config;
|
||||
std::string name{kNamePrefix};
|
||||
name += w.name;
|
||||
name += kNameSeparator;
|
||||
name += bc.name;
|
||||
auto* b = benchmark::RegisterBenchmark(name, [rs, cfg, w](benchmark::State& state) {
|
||||
auto const poolSize = static_cast<std::size_t>(state.range(0));
|
||||
rs->harness = std::make_unique<DatabaseHarness>(cfg, kReadThreads);
|
||||
auto& db = *rs->harness->db;
|
||||
w.setup(SetupContext{.rs = *rs, .db = db, .poolSize = poolSize});
|
||||
auto const seq = db.earliestLedgerSeq();
|
||||
|
||||
std::size_t index = 0;
|
||||
for (auto _ : state)
|
||||
{
|
||||
w.iterate(
|
||||
IterateContext{
|
||||
.rs = *rs, .db = db, .seq = seq, .index = index, .poolSize = poolSize});
|
||||
++index;
|
||||
}
|
||||
benchmark::ClobberMemory();
|
||||
|
||||
state.SetItemsProcessed(state.iterations());
|
||||
if (w.reportBytes)
|
||||
{
|
||||
state.SetBytesProcessed(static_cast<std::int64_t>(state.iterations() * rs->avgPayload));
|
||||
}
|
||||
rs->harness.reset();
|
||||
});
|
||||
|
||||
b->Arg(kDefaultPoolSize);
|
||||
|
||||
if (w.pinIterations)
|
||||
b->Iterations(kDefaultPoolSize);
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool const kRegistered = [] {
|
||||
auto const workloads = std::to_array({&kStore, &kFetch, &kMissing, &kMixed, &kWork});
|
||||
for (auto const& bc : backendConfigs())
|
||||
{
|
||||
for (auto const* w : workloads)
|
||||
registerWorkload(bc, *w);
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
|
||||
} // namespace
|
||||
} // namespace xrpl::NodeStore
|
||||
@@ -1,318 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/temp_dir.h>
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
#include <xrpl/config/BasicConfig.h>
|
||||
#include <xrpl/nodestore/Backend.h>
|
||||
#include <xrpl/nodestore/Database.h>
|
||||
#include <xrpl/nodestore/DummyScheduler.h>
|
||||
#include <xrpl/nodestore/Manager.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/nodestore/Scheduler.h>
|
||||
#include <xrpl/nodestore/Types.h>
|
||||
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Shared helpers for the NodeStore benchmarks.
|
||||
//
|
||||
namespace xrpl::NodeStore {
|
||||
|
||||
// Fill `bytes` of memory at `buffer` with random bits drawn from `g`.
|
||||
template <class Generator>
|
||||
inline void
|
||||
rngcpy(void* buffer, std::size_t bytes, Generator& g)
|
||||
{
|
||||
using result_type = typename Generator::result_type;
|
||||
while (bytes >= sizeof(result_type))
|
||||
{
|
||||
auto const v = g();
|
||||
std::memcpy(buffer, &v, sizeof(v));
|
||||
buffer = reinterpret_cast<std::uint8_t*>(buffer) + sizeof(v);
|
||||
bytes -= sizeof(v);
|
||||
}
|
||||
|
||||
if (bytes > 0)
|
||||
{
|
||||
auto const v = g();
|
||||
std::memcpy(buffer, &v, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deterministic generator of a reproducible sequence of random NodeObjects.
|
||||
*
|
||||
* Indexing is stable: `obj(n)` and `key(n)` always return the same value for a
|
||||
* given `n`, regardless of call order, because the engine is reseeded from `n`
|
||||
* on every call.
|
||||
*
|
||||
* Using different prefixes guarantees the two key spaces are disjoint for the fetch-miss
|
||||
* workloads.
|
||||
*/
|
||||
class Sequence
|
||||
{
|
||||
private:
|
||||
static constexpr auto kMinSize = 250;
|
||||
static constexpr auto kMaxSize = 1250;
|
||||
|
||||
beast::xor_shift_engine gen_;
|
||||
std::uint8_t prefix_;
|
||||
std::discrete_distribution<std::uint32_t> dType_;
|
||||
std::uniform_int_distribution<std::uint32_t> dSize_;
|
||||
|
||||
public:
|
||||
explicit Sequence(std::uint8_t prefix)
|
||||
: prefix_(prefix)
|
||||
// uniform distribution over hotLEDGER - hotTRANSACTION_NODE
|
||||
// but exclude hotTRANSACTION = 2 (removed)
|
||||
, dType_({1, 1, 0, 1, 1})
|
||||
, dSize_(kMinSize, kMaxSize)
|
||||
{
|
||||
}
|
||||
|
||||
// Returns the n-th key. Used to generate keys that are never stored.
|
||||
// The layout mirrors obj()'s: prefix at byte 0, RNG over the rest, so the
|
||||
// two key spaces stay disjoint by construction (not by coincidence).
|
||||
uint256
|
||||
key(std::size_t n)
|
||||
{
|
||||
gen_.seed(n + 1);
|
||||
uint256 result;
|
||||
auto const data = static_cast<std::uint8_t*>(&*result.begin());
|
||||
*data = prefix_;
|
||||
rngcpy(data + 1, result.size() - 1, gen_);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the n-th complete NodeObject.
|
||||
std::shared_ptr<NodeObject>
|
||||
obj(std::size_t n)
|
||||
{
|
||||
gen_.seed(n + 1);
|
||||
uint256 key;
|
||||
auto const data = static_cast<std::uint8_t*>(&*key.begin());
|
||||
*data = prefix_;
|
||||
rngcpy(data + 1, key.size() - 1, gen_);
|
||||
Blob value(dSize_(gen_));
|
||||
rngcpy(&value[0], value.size(), gen_);
|
||||
return NodeObject::createObject(
|
||||
safeCast<NodeObjectType>(dType_(gen_)), std::move(value), key);
|
||||
}
|
||||
|
||||
// Fills `b` with `size` consecutive NodeObjects starting at index `n`.
|
||||
void
|
||||
batch(std::size_t n, Batch& b, std::size_t size)
|
||||
{
|
||||
b.clear();
|
||||
b.reserve(size);
|
||||
while ((size--) != 0u)
|
||||
b.push_back(obj(n++));
|
||||
}
|
||||
};
|
||||
|
||||
// Parse a comma-separated "key=value,key=value" string into a config Section.
|
||||
inline Section
|
||||
parseConfig(std::string const& s)
|
||||
{
|
||||
Section section;
|
||||
std::vector<std::string> values;
|
||||
boost::split(values, s, boost::algorithm::is_any_of(","));
|
||||
section.append(values);
|
||||
return section;
|
||||
}
|
||||
|
||||
// Pre-generate `count` distinct objects from key space `prefix`, starting at
|
||||
// sequence index `start`.
|
||||
inline Batch
|
||||
makePool(std::uint8_t prefix, std::size_t count, std::size_t start = 0)
|
||||
{
|
||||
Sequence seq(prefix);
|
||||
Batch pool;
|
||||
pool.reserve(count);
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
pool.push_back(seq.obj(start + i));
|
||||
return pool;
|
||||
}
|
||||
|
||||
// Pre-generate `count` keys disjoint from every `makePool(...)` object, for
|
||||
// measuring fetches that miss.
|
||||
inline std::vector<uint256>
|
||||
makeMissingKeys(std::size_t count)
|
||||
{
|
||||
Sequence seq(2);
|
||||
std::vector<uint256> keys;
|
||||
keys.reserve(count);
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
keys.push_back(seq.key(i));
|
||||
return keys;
|
||||
}
|
||||
|
||||
// Mean payload size across a pool, used for SetBytesProcessed throughput.
|
||||
inline std::size_t
|
||||
averagePayload(Batch const& pool)
|
||||
{
|
||||
if (pool.empty())
|
||||
return 0;
|
||||
std::size_t total = 0;
|
||||
for (auto const& obj : pool)
|
||||
total += obj->getData().size();
|
||||
return total / pool.size();
|
||||
}
|
||||
|
||||
// Store every object and flush, so a following fetch exercises the real read
|
||||
// path rather than an in-memory write buffer.
|
||||
//
|
||||
// We chunk the write at kBatchWriteLimitSize because Types.h documents that as
|
||||
// the maximum allowed batch size. NuDB happens to tolerate larger batches
|
||||
// today, but the benchmark should not rely on that.
|
||||
//
|
||||
// sync() is a no-op for both NuDB and RocksDB at the moment (NuDB has a small
|
||||
// internal burst buffer that the timed loop will warm up). That is a contract
|
||||
// hint, not a guarantee; if either backend ever grows a real flush we get it
|
||||
// here for free.
|
||||
inline void
|
||||
prepopulate(Backend& backend, Batch const& objects)
|
||||
{
|
||||
for (std::size_t i = 0; i < objects.size(); i += kBatchWriteLimitSize)
|
||||
{
|
||||
auto const end = std::min(i + kBatchWriteLimitSize, objects.size());
|
||||
backend.storeBatch(Batch(objects.begin() + i, objects.begin() + end));
|
||||
}
|
||||
backend.sync();
|
||||
}
|
||||
|
||||
// A deterministic permutation of [0, size). Lets the timed loop visit the
|
||||
// pre-generated pool in a random-like order with zero RNG cost per iteration -
|
||||
// the Timing_test workloads it replaces used uniform_int_distribution per
|
||||
// fetch, and a shuffle table reproduces that access pattern without paying for
|
||||
// the distribution inside the timed region.
|
||||
inline std::vector<std::size_t>
|
||||
makeShuffle(std::size_t size, std::uint64_t seed)
|
||||
{
|
||||
std::vector<std::size_t> v(size);
|
||||
std::iota(v.begin(), v.end(), std::size_t{0});
|
||||
beast::xor_shift_engine gen(seed);
|
||||
std::shuffle(v.begin(), v.end(), gen);
|
||||
return v;
|
||||
}
|
||||
|
||||
// Partition a pool into fixed-size batches. Any trailing remainder shorter than
|
||||
// `batchSize` is dropped, so every returned batch has exactly `batchSize`.
|
||||
inline std::vector<Batch>
|
||||
sliceBatches(Batch const& pool, std::size_t batchSize)
|
||||
{
|
||||
std::vector<Batch> batches;
|
||||
if (batchSize == 0)
|
||||
return batches;
|
||||
batches.reserve(pool.size() / batchSize);
|
||||
for (std::size_t i = 0; i + batchSize <= pool.size(); i += batchSize)
|
||||
batches.emplace_back(pool.begin() + i, pool.begin() + i + batchSize);
|
||||
return batches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RAII owner of a NodeStore Backend opened on a private temporary directory.
|
||||
*
|
||||
* Member declaration order matters: `tempDir` is declared first so it is
|
||||
* destroyed last, after the backend has closed and released its files.
|
||||
*/
|
||||
struct BackendHarness
|
||||
{
|
||||
beast::TempDir tempDir;
|
||||
DummyScheduler scheduler;
|
||||
beast::Journal journal{beast::Journal::getNullSink()};
|
||||
std::unique_ptr<Backend> backend;
|
||||
|
||||
explicit BackendHarness(std::string const& configString)
|
||||
{
|
||||
Section config = parseConfig(configString);
|
||||
// A private, unique path per harness, so concurrent or repeated runs
|
||||
// never share on-disk state.
|
||||
config.set("path", tempDir.path());
|
||||
backend =
|
||||
Manager::instance().makeBackend(config, megabytes(std::size_t{4}), scheduler, journal);
|
||||
backend->setDeletePath();
|
||||
backend->open();
|
||||
}
|
||||
|
||||
~BackendHarness()
|
||||
{
|
||||
if (backend)
|
||||
backend->close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* RAII owner of a NodeStore Database - the application-facing wrapper around a
|
||||
* Backend, which adds fetch/store accounting and the async read-thread pool.
|
||||
*/
|
||||
struct DatabaseHarness
|
||||
{
|
||||
beast::TempDir tempDir;
|
||||
DummyScheduler scheduler;
|
||||
beast::Journal journal{beast::Journal::getNullSink()};
|
||||
std::unique_ptr<Database> db;
|
||||
|
||||
DatabaseHarness(std::string const& configString, int readThreads)
|
||||
{
|
||||
Section config = parseConfig(configString);
|
||||
config.set("path", tempDir.path());
|
||||
db = Manager::instance().makeDatabase(
|
||||
megabytes(std::size_t{4}), scheduler, readThreads, config, journal);
|
||||
}
|
||||
|
||||
~DatabaseHarness()
|
||||
{
|
||||
if (db)
|
||||
db->stop();
|
||||
}
|
||||
};
|
||||
|
||||
// A NodeStore backend to benchmark, named for the --benchmark_filter CLI flag.
|
||||
struct BackendConfig
|
||||
{
|
||||
char const* name; // short label, e.g. "nudb"
|
||||
char const* config; // parseConfig() string, e.g. "type=nudb"
|
||||
};
|
||||
|
||||
// The backends every workload is registered against.
|
||||
//
|
||||
// The in-memory backend is intentionally excluded. It keeps its table in a
|
||||
// process-global map keyed by path, with no removal API, so building a fresh
|
||||
// backend per run - as a microbenchmark must - would leak the whole dataset on
|
||||
// every run. Timing_test, the suite this benchmark replaces, excluded it for
|
||||
// the same reason. NuDB and RocksDB are the production backends worth timing.
|
||||
//
|
||||
// RocksDB is included only when it was compiled in (xrpl.libxrpl carries
|
||||
// XRPL_ROCKSDB_AVAILABLE transitively).
|
||||
inline std::vector<BackendConfig> const&
|
||||
backendConfigs()
|
||||
{
|
||||
static std::vector<BackendConfig> const kConfigs = {
|
||||
{.name = "nudb", .config = "type=nudb"},
|
||||
#if XRPL_ROCKSDB_AVAILABLE
|
||||
{.name = "rocksdb",
|
||||
.config = "type=rocksdb,open_files=2000,filter_bits=12,cache_mb=256,"
|
||||
"file_size_mb=8,file_size_mult=2"},
|
||||
#endif
|
||||
};
|
||||
return kConfigs;
|
||||
}
|
||||
|
||||
} // namespace xrpl::NodeStore
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
@@ -11,8 +11,8 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger_entries/LendingHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
@@ -312,25 +312,6 @@ computeInterestAndFeeParts(
|
||||
return std::make_pair(interest - fee, fee);
|
||||
}
|
||||
|
||||
/* Rounds a raw (unrounded) interest amount to the loan's scale, then splits
|
||||
* the rounded amount into net interest (to the vault) and management fee (to
|
||||
* the broker).
|
||||
*
|
||||
* This is the common "round then split" step shared by late payment, full
|
||||
* payment, and overpayment interest calculations.
|
||||
*/
|
||||
std::pair<Number, Number>
|
||||
roundAndSplitInterest(
|
||||
Asset const& asset,
|
||||
Number const& rawInterest,
|
||||
TenthBips16 managementFeeRate,
|
||||
std::int32_t loanScale,
|
||||
Number::RoundingMode mode = Number::getround())
|
||||
{
|
||||
auto const interest = roundToAsset(asset, rawInterest, loanScale, mode);
|
||||
return computeInterestAndFeeParts(asset, interest, managementFeeRate, loanScale);
|
||||
}
|
||||
|
||||
/* Calculates penalty interest accrued on overdue payments.
|
||||
* Returns 0 if payment is not late.
|
||||
*
|
||||
@@ -406,18 +387,22 @@ loanAccruedInterest(
|
||||
*
|
||||
* This is the core function that updates the Loan ledger object fields based on
|
||||
* a computed payment.
|
||||
*/
|
||||
LoanPaymentParts
|
||||
doPayment(ExtendedPaymentComponents const& payment, SLE::ref loan)
|
||||
{
|
||||
auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
|
||||
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
|
||||
auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
|
||||
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
|
||||
auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate);
|
||||
auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
|
||||
std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
|
||||
|
||||
* The function is templated to work with both direct Number/uint32_t values
|
||||
* (for testing/simulation) and ValueProxy types (for actual ledger updates).
|
||||
*/
|
||||
template <class NumberProxy, class UInt32Proxy, class UInt32OptionalProxy>
|
||||
LoanPaymentParts
|
||||
doPayment(
|
||||
ExtendedPaymentComponents const& payment,
|
||||
NumberProxy& totalValueOutstandingProxy,
|
||||
NumberProxy& principalOutstandingProxy,
|
||||
NumberProxy& managementFeeOutstandingProxy,
|
||||
UInt32Proxy& paymentRemainingProxy,
|
||||
UInt32Proxy& prevPaymentDateProxy,
|
||||
UInt32OptionalProxy& nextDueDateProxy,
|
||||
std::uint32_t paymentInterval)
|
||||
{
|
||||
XRPL_ASSERT_PARTS(nextDueDateProxy, "xrpl::detail::doPayment", "Next due date proxy set");
|
||||
|
||||
if (payment.specialCase == PaymentSpecialCase::Final)
|
||||
@@ -485,12 +470,16 @@ doPayment(ExtendedPaymentComponents const& payment, SLE::ref loan)
|
||||
|
||||
// Principal can never exceed total value (principal is part of total value)
|
||||
XRPL_ASSERT_PARTS(
|
||||
// Use an explicit cast because the template parameter can be
|
||||
// ValueProxy<Number> or Number
|
||||
static_cast<Number>(principalOutstandingProxy) <=
|
||||
static_cast<Number>(totalValueOutstandingProxy),
|
||||
"xrpl::detail::doPayment",
|
||||
"principal does not exceed total");
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
// Use an explicit cast because the template parameter can be
|
||||
// ValueProxy<Number> or Number
|
||||
static_cast<Number>(managementFeeOutstandingProxy) >= beast::kZero,
|
||||
"xrpl::detail::doPayment",
|
||||
"fee outstanding stays valid");
|
||||
@@ -728,23 +717,22 @@ tryOverpayment(
|
||||
* overpayment would leave the loan in an invalid state, we can reject it
|
||||
* gracefully without corrupting the ledger data.
|
||||
*/
|
||||
template <class NumberProxy>
|
||||
std::expected<LoanPaymentParts, TER>
|
||||
doOverpayment(
|
||||
Rules const& rules,
|
||||
Asset const& asset,
|
||||
std::int32_t loanScale,
|
||||
ExtendedPaymentComponents const& overpaymentComponents,
|
||||
SLE::ref loan,
|
||||
NumberProxy& totalValueOutstandingProxy,
|
||||
NumberProxy& principalOutstandingProxy,
|
||||
NumberProxy& managementFeeOutstandingProxy,
|
||||
NumberProxy& periodicPaymentProxy,
|
||||
Number const& periodicRate,
|
||||
std::uint32_t const paymentRemaining,
|
||||
TenthBips16 const managementFeeRate,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
|
||||
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
|
||||
auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
|
||||
auto periodicPaymentProxy = loan->at(sfPeriodicPayment);
|
||||
auto const paymentsRemaining = loan->at(sfPaymentRemaining);
|
||||
|
||||
auto const loanState = constructLoanState(
|
||||
totalValueOutstandingProxy, principalOutstandingProxy, managementFeeOutstandingProxy);
|
||||
auto const periodicPayment = periodicPaymentProxy;
|
||||
@@ -756,7 +744,7 @@ doOverpayment(
|
||||
<< ", interestPart: " << overpaymentComponents.trackedInterestPart()
|
||||
<< ", untrackedInterest: " << overpaymentComponents.untrackedInterest
|
||||
<< ", totalDue: " << overpaymentComponents.totalDue
|
||||
<< ", payments remaining :" << paymentsRemaining;
|
||||
<< ", payments remaining :" << paymentRemaining;
|
||||
|
||||
// Attempt to re-amortize the loan with the overpayment applied.
|
||||
// This modifies the temporary copies, leaving the proxies unchanged.
|
||||
@@ -768,7 +756,7 @@ doOverpayment(
|
||||
loanState,
|
||||
periodicPayment,
|
||||
periodicRate,
|
||||
paymentsRemaining,
|
||||
paymentRemaining,
|
||||
managementFeeRate,
|
||||
j);
|
||||
if (!ret)
|
||||
@@ -876,15 +864,16 @@ std::expected<ExtendedPaymentComponents, TER>
|
||||
computeLatePayment(
|
||||
Asset const& asset,
|
||||
ApplyView const& view,
|
||||
SLE::const_ref loan,
|
||||
Number const& principalOutstanding,
|
||||
std::int32_t nextDueDate,
|
||||
ExtendedPaymentComponents const& periodic,
|
||||
TenthBips32 lateInterestRate,
|
||||
std::int32_t loanScale,
|
||||
Number const& latePaymentFee,
|
||||
STAmount const& amount,
|
||||
TenthBips16 managementFeeRate,
|
||||
beast::Journal j)
|
||||
{
|
||||
std::int32_t const nextDueDate = loan->at(sfNextPaymentDueDate);
|
||||
std::int32_t const loanScale = loan->at(sfLoanScale);
|
||||
|
||||
// Check if the due date has passed. If not, reject the payment as
|
||||
// being too soon
|
||||
if (!hasExpired(view, nextDueDate))
|
||||
@@ -892,15 +881,15 @@ computeLatePayment(
|
||||
|
||||
// Calculate the penalty interest based on how long the payment is overdue.
|
||||
auto const latePaymentInterest = loanLatePaymentInterest(
|
||||
loan->at(sfPrincipalOutstanding),
|
||||
TenthBips32{loan->at(sfLateInterestRate)},
|
||||
view.parentCloseTime(),
|
||||
nextDueDate);
|
||||
principalOutstanding, lateInterestRate, view.parentCloseTime(), nextDueDate);
|
||||
|
||||
// Round the late interest and split it between the vault (net interest)
|
||||
// and the broker (management fee portion).
|
||||
auto const [roundedLateInterest, roundedLateManagementFee] =
|
||||
roundAndSplitInterest(asset, latePaymentInterest, managementFeeRate, loanScale);
|
||||
// and the broker (management fee portion). This lambda ensures we
|
||||
// round before splitting to maintain precision.
|
||||
auto const [roundedLateInterest, roundedLateManagementFee] = [&]() {
|
||||
auto const interest = roundToAsset(asset, latePaymentInterest, loanScale);
|
||||
return computeInterestAndFeeParts(asset, interest, managementFeeRate, loanScale);
|
||||
}();
|
||||
|
||||
XRPL_ASSERT(roundedLateInterest >= 0, "xrpl::detail::computeLatePayment : valid late interest");
|
||||
XRPL_ASSERT_PARTS(
|
||||
@@ -919,7 +908,7 @@ computeLatePayment(
|
||||
// 1. Regular service fee (from periodic.untrackedManagementFee)
|
||||
// 2. Late payment fee (fixed penalty)
|
||||
// 3. Management fee portion of late interest
|
||||
periodic.untrackedManagementFee + loan->at(sfLatePaymentFee) + roundedLateManagementFee,
|
||||
periodic.untrackedManagementFee + latePaymentFee + roundedLateManagementFee,
|
||||
|
||||
// Untracked interest includes:
|
||||
// 1. Any untracked interest from the regular payment (usually 0)
|
||||
@@ -969,15 +958,22 @@ std::expected<ExtendedPaymentComponents, TER>
|
||||
computeFullPayment(
|
||||
Asset const& asset,
|
||||
ApplyView& view,
|
||||
SLE::const_ref loan,
|
||||
Number const& principalOutstanding,
|
||||
Number const& managementFeeOutstanding,
|
||||
Number const& periodicPayment,
|
||||
std::uint32_t paymentRemaining,
|
||||
std::uint32_t prevPaymentDate,
|
||||
std::uint32_t const startDate,
|
||||
std::uint32_t const paymentInterval,
|
||||
TenthBips32 const closeInterestRate,
|
||||
std::int32_t loanScale,
|
||||
Number const& totalInterestOutstanding,
|
||||
Number const& periodicRate,
|
||||
Number const& closePaymentFee,
|
||||
STAmount const& amount,
|
||||
TenthBips16 managementFeeRate,
|
||||
beast::Journal j)
|
||||
{
|
||||
std::uint32_t const paymentRemaining = loan->at(sfPaymentRemaining);
|
||||
std::int32_t const loanScale = loan->at(sfLoanScale);
|
||||
|
||||
// Full payment must be made before the final scheduled payment.
|
||||
if (paymentRemaining <= 1)
|
||||
{
|
||||
@@ -990,7 +986,7 @@ computeFullPayment(
|
||||
// This theoretical (unrounded) value is used to compute interest and
|
||||
// penalties accurately.
|
||||
Number const theoreticalPrincipalOutstanding = loanPrincipalFromPeriodicPayment(
|
||||
view.rules(), loan->at(sfPeriodicPayment), periodicRate, paymentRemaining);
|
||||
view.rules(), periodicPayment, periodicRate, paymentRemaining);
|
||||
|
||||
// Full payment interest includes both accrued interest (time since last
|
||||
// payment) and prepayment penalty (for closing early).
|
||||
@@ -998,21 +994,18 @@ computeFullPayment(
|
||||
theoreticalPrincipalOutstanding,
|
||||
periodicRate,
|
||||
view.parentCloseTime(),
|
||||
loan->at(sfPaymentInterval),
|
||||
loan->at(sfPreviousPaymentDueDate),
|
||||
loan->at(sfStartDate),
|
||||
TenthBips32{loan->at(sfCloseInterestRate)});
|
||||
paymentInterval,
|
||||
prevPaymentDate,
|
||||
startDate,
|
||||
closeInterestRate);
|
||||
|
||||
// Split the full payment interest into net interest (to vault) and management fee (to broker),
|
||||
// applying proper rounding.
|
||||
auto const [roundedFullInterest, roundedFullManagementFee] = roundAndSplitInterest(
|
||||
asset, fullPaymentInterest, managementFeeRate, loanScale, Number::RoundingMode::Downward);
|
||||
|
||||
LoanState const loanState = constructLoanState(loan);
|
||||
Number const principalOutstanding = loanState.principalOutstanding;
|
||||
Number const managementFeeOutstanding = loanState.managementFeeDue;
|
||||
Number const totalInterestOutstanding = loanState.interestDue;
|
||||
Number const closePaymentFee = roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
|
||||
// Split the full payment interest into net interest (to vault) and
|
||||
// management fee (to broker), applying proper rounding.
|
||||
auto const [roundedFullInterest, roundedFullManagementFee] = [&]() {
|
||||
auto const interest =
|
||||
roundToAsset(asset, fullPaymentInterest, loanScale, Number::RoundingMode::Downward);
|
||||
return computeInterestAndFeeParts(asset, interest, managementFeeRate, loanScale);
|
||||
}();
|
||||
|
||||
ExtendedPaymentComponents const full{
|
||||
PaymentComponents{
|
||||
@@ -1053,7 +1046,8 @@ computeFullPayment(
|
||||
"xrpl::detail::computeFullPayment",
|
||||
"total due is rounded");
|
||||
|
||||
JLOG(j.trace()) << "computeFullPayment result: periodicRate: " << periodicRate
|
||||
JLOG(j.trace()) << "computeFullPayment result: periodicPayment: " << periodicPayment
|
||||
<< ", periodicRate: " << periodicRate
|
||||
<< ", paymentRemaining: " << paymentRemaining
|
||||
<< ", theoreticalPrincipalOutstanding: " << theoreticalPrincipalOutstanding
|
||||
<< ", fullPaymentInterest: " << fullPaymentInterest
|
||||
@@ -1304,34 +1298,6 @@ computePaymentComponents(
|
||||
};
|
||||
}
|
||||
|
||||
/* Thin overload of computePaymentComponents() that unwraps the tracked
|
||||
* fields directly from the Loan ledger object. `periodicRate` is derived
|
||||
* rather than stored, and `managementFeeRate` comes from the LoanBroker, not
|
||||
* the Loan, so both remain explicit parameters. Kept separate from the
|
||||
* value-based overload above, which is exercised directly by unit tests
|
||||
* against simulated (non-ledger) loan states.
|
||||
*/
|
||||
PaymentComponents
|
||||
computePaymentComponents(
|
||||
Rules const& rules,
|
||||
Asset const& asset,
|
||||
SLE::ref loan,
|
||||
Number const& periodicRate,
|
||||
TenthBips16 managementFeeRate)
|
||||
{
|
||||
return computePaymentComponents(
|
||||
rules,
|
||||
asset,
|
||||
loan->at(sfLoanScale),
|
||||
loan->at(sfTotalValueOutstanding),
|
||||
loan->at(sfPrincipalOutstanding),
|
||||
loan->at(sfManagementFeeOutstanding),
|
||||
loan->at(sfPeriodicPayment),
|
||||
periodicRate,
|
||||
loan->at(sfPaymentRemaining),
|
||||
managementFeeRate);
|
||||
}
|
||||
|
||||
/* Computes payment components for an overpayment scenario.
|
||||
*
|
||||
* An overpayment occurs when a borrower pays more than the scheduled periodic
|
||||
@@ -1376,12 +1342,11 @@ computeOverpaymentComponents(
|
||||
// This interest doesn't follow the normal amortization schedule - it's
|
||||
// a one-time charge for paying early.
|
||||
// Equation (20) and (21) from XLS-66 spec, Section A-2 Equation Glossary
|
||||
auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] =
|
||||
roundAndSplitInterest(
|
||||
asset,
|
||||
tenthBipsOfValue(overpayment, overpaymentInterestRate),
|
||||
managementFeeRate,
|
||||
loanScale);
|
||||
auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] = [&]() {
|
||||
auto const interest =
|
||||
roundToAsset(asset, tenthBipsOfValue(overpayment, overpaymentInterestRate), loanScale);
|
||||
return detail::computeInterestAndFeeParts(asset, interest, managementFeeRate, loanScale);
|
||||
}();
|
||||
|
||||
auto const result = detail::ExtendedPaymentComponents{
|
||||
// Build the payment components, after fees and penalty
|
||||
@@ -1408,265 +1373,6 @@ computeOverpaymentComponents(
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Derives the two rate values every make*Payment() helper needs: the
|
||||
* broker's management fee rate, and the loan's periodic (per-payment-period)
|
||||
* interest rate.
|
||||
*/
|
||||
std::pair<TenthBips16, Number>
|
||||
loanRatesFor(SLE::const_ref loan, SLE::const_ref brokerSle)
|
||||
{
|
||||
TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
||||
TenthBips32 const interestRate{loan->at(sfInterestRate)};
|
||||
Number const periodicRate = loanPeriodicRate(interestRate, loan->at(sfPaymentInterval));
|
||||
XRPL_ASSERT(interestRate == 0 || periodicRate > 0, "xrpl::detail::loanRatesFor : valid rate");
|
||||
return {managementFeeRate, periodicRate};
|
||||
}
|
||||
|
||||
/* Handles a full (early payoff) payment. Implements the "full payment"
|
||||
* branch of the make_payment function from the XLS-66 spec, Section
|
||||
* 3.2.4.4.
|
||||
*/
|
||||
std::expected<LoanPaymentParts, TER>
|
||||
makeFullPayment(
|
||||
Asset const& asset,
|
||||
ApplyView& view,
|
||||
SLE::ref loan,
|
||||
SLE::const_ref brokerSle,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const [managementFeeRate, periodicRate] = loanRatesFor(loan, brokerSle);
|
||||
|
||||
auto const fullPaymentComponents =
|
||||
computeFullPayment(asset, view, loan, periodicRate, amount, managementFeeRate, j);
|
||||
|
||||
// computeFullPayment only ever fails with a genuine error TER (never
|
||||
// tesSUCCESS), so there is no separate "no-op" outcome to handle here.
|
||||
if (fullPaymentComponents.has_value())
|
||||
return doPayment(*fullPaymentComponents, loan);
|
||||
return std::unexpected(fullPaymentComponents.error());
|
||||
}
|
||||
|
||||
/* Handles a late payment (past due date, with the late-payment flag set).
|
||||
* Implements the "late payment" branch of the make_payment function from
|
||||
* the XLS-66 spec, Section 3.2.4.4.
|
||||
*/
|
||||
std::expected<LoanPaymentParts, TER>
|
||||
makeLatePayment(
|
||||
Asset const& asset,
|
||||
ApplyView const& view,
|
||||
SLE::ref loan,
|
||||
SLE::const_ref brokerSle,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const [managementFeeRate, periodicRate] = loanRatesFor(loan, brokerSle);
|
||||
|
||||
Number const serviceFee = loan->at(sfLoanServiceFee);
|
||||
ExtendedPaymentComponents const periodic{
|
||||
computePaymentComponents(view.rules(), asset, loan, periodicRate, managementFeeRate),
|
||||
serviceFee};
|
||||
XRPL_ASSERT_PARTS(
|
||||
periodic.trackedPrincipalDelta >= 0,
|
||||
"xrpl::detail::makeLatePayment",
|
||||
"regular payment valid principal");
|
||||
|
||||
auto const latePaymentComponents =
|
||||
computeLatePayment(asset, view, loan, periodic, amount, managementFeeRate, j);
|
||||
|
||||
// computeLatePayment only ever fails with a genuine error TER (never
|
||||
// tesSUCCESS), so there is no separate "no-op" outcome to handle here.
|
||||
if (latePaymentComponents.has_value())
|
||||
return doPayment(*latePaymentComponents, loan);
|
||||
return std::unexpected(latePaymentComponents.error());
|
||||
}
|
||||
|
||||
/* Handles regular scheduled payments, including an optional overpayment tail.
|
||||
* Implements the "regular" and "overpayment" branches of the make_payment
|
||||
* function from the XLS-66 spec, Section 3.2.4.4.
|
||||
*/
|
||||
std::expected<LoanPaymentParts, TER>
|
||||
makeRegularPayment(
|
||||
Asset const& asset,
|
||||
ApplyView const& view,
|
||||
SLE::ref loan,
|
||||
SLE::const_ref brokerSle,
|
||||
STAmount const& amount,
|
||||
LoanPaymentType const paymentType,
|
||||
beast::Journal j)
|
||||
{
|
||||
using namespace Lending;
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
paymentType == LoanPaymentType::Regular || paymentType == LoanPaymentType::Overpayment,
|
||||
"xrpl::detail::makeRegularPayment",
|
||||
"regular payment type");
|
||||
|
||||
auto const [managementFeeRate, periodicRate] = loanRatesFor(loan, brokerSle);
|
||||
|
||||
std::int32_t const loanScale = loan->at(sfLoanScale);
|
||||
Number const serviceFee = loan->at(sfLoanServiceFee);
|
||||
|
||||
ExtendedPaymentComponents periodic{
|
||||
computePaymentComponents(view.rules(), asset, loan, periodicRate, managementFeeRate),
|
||||
serviceFee};
|
||||
XRPL_ASSERT_PARTS(
|
||||
periodic.trackedPrincipalDelta >= 0,
|
||||
"xrpl::detail::makeRegularPayment",
|
||||
"regular payment valid principal");
|
||||
|
||||
// Keep a running total of the actual parts paid
|
||||
LoanPaymentParts totalParts;
|
||||
Number totalPaid = kNumZero;
|
||||
std::size_t numPayments = 0;
|
||||
|
||||
// Cached here (rather than re-looking up loan->at(sfPaymentRemaining) at each use) since it's
|
||||
// read multiple times below. It's a write-through proxy, so it still reflects doPayment's
|
||||
// mutations each iteration.
|
||||
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
|
||||
|
||||
while ((amount >= (totalPaid + periodic.totalDue)) && paymentRemainingProxy > 0 &&
|
||||
numPayments < kLoanMaximumPaymentsPerTransaction)
|
||||
{
|
||||
// Try to make more payments
|
||||
XRPL_ASSERT_PARTS(
|
||||
periodic.trackedPrincipalDelta >= 0,
|
||||
"xrpl::detail::makeRegularPayment",
|
||||
"payment pays non-negative principal");
|
||||
|
||||
totalPaid += periodic.totalDue;
|
||||
totalParts += doPayment(periodic, loan);
|
||||
++numPayments;
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
(periodic.specialCase == PaymentSpecialCase::Final) == (paymentRemainingProxy == 0),
|
||||
"xrpl::detail::makeRegularPayment",
|
||||
"final payment is the final payment");
|
||||
|
||||
// Don't compute the next payment if this was the last payment
|
||||
if (periodic.specialCase == PaymentSpecialCase::Final)
|
||||
break;
|
||||
|
||||
periodic = ExtendedPaymentComponents{
|
||||
computePaymentComponents(view.rules(), asset, loan, periodicRate, managementFeeRate),
|
||||
serviceFee};
|
||||
}
|
||||
|
||||
if (numPayments == 0)
|
||||
{
|
||||
JLOG(j.warn()) << "Regular loan payment amount is insufficient. Due: " << periodic.totalDue
|
||||
<< ", paid: " << amount;
|
||||
return std::unexpected(tecINSUFFICIENT_PAYMENT);
|
||||
}
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
totalParts.principalPaid + totalParts.interestPaid + totalParts.feePaid == totalPaid,
|
||||
"xrpl::detail::makeRegularPayment",
|
||||
"payment parts add up");
|
||||
XRPL_ASSERT_PARTS(
|
||||
totalParts.valueChange == 0, "xrpl::detail::makeRegularPayment", "no value change");
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// overpayment handling
|
||||
//
|
||||
// If the "fixCleanup3_1_3" amendment is enabled, truncate "amount",
|
||||
// at the loan scale. If the raw value is used, the overpayment
|
||||
// amount could be meaningless dust. Trying to process such a small
|
||||
// amount will, at best, waste time when all the result values round
|
||||
// to zero. At worst, it can cause logical errors with tiny amounts
|
||||
// of interest that don't add up correctly.
|
||||
auto const roundedAmount = view.rules().enabled(fixCleanup3_1_3)
|
||||
? roundToAsset(asset, amount, loanScale, Number::RoundingMode::TowardsZero)
|
||||
: amount;
|
||||
|
||||
bool const overpaymentSupported =
|
||||
paymentType == LoanPaymentType::Overpayment && loan->isFlag(lsfLoanOverpayment);
|
||||
|
||||
bool const overpaymentAllowed = //
|
||||
paymentRemainingProxy > 0 && //
|
||||
totalPaid < roundedAmount && //
|
||||
numPayments < kLoanMaximumPaymentsPerTransaction;
|
||||
|
||||
if (overpaymentSupported && overpaymentAllowed)
|
||||
{
|
||||
TenthBips32 const overpaymentInterestRate{loan->at(sfOverpaymentInterestRate)};
|
||||
TenthBips32 const overpaymentFeeRate{loan->at(sfOverpaymentFee)};
|
||||
|
||||
// It shouldn't be possible for the overpayment to be greater than
|
||||
// totalValueOutstanding, because that would have been processed as
|
||||
// another normal payment. But cap it just in case.
|
||||
Number const overpaymentRaw =
|
||||
std::min(roundedAmount - totalPaid, *loan->at(sfTotalValueOutstanding));
|
||||
|
||||
bool const fixEnabled = view.rules().enabled(fixCleanup3_2_0);
|
||||
Number const overpayment = fixEnabled
|
||||
? roundToAsset(asset, overpaymentRaw, loanScale, Number::RoundingMode::Downward)
|
||||
: overpaymentRaw;
|
||||
|
||||
// Post-amendment, the rounded overpayment can be zero; pre-amendment
|
||||
// it's always positive given the surrounding guards.
|
||||
if (!fixEnabled || overpayment > 0)
|
||||
{
|
||||
ExtendedPaymentComponents const overpaymentComponents = computeOverpaymentComponents(
|
||||
view.rules(),
|
||||
asset,
|
||||
loanScale,
|
||||
overpayment,
|
||||
overpaymentInterestRate,
|
||||
overpaymentFeeRate,
|
||||
managementFeeRate);
|
||||
|
||||
// Don't process an overpayment if the whole amount (or more!)
|
||||
// gets eaten by fees and interest.
|
||||
if (overpaymentComponents.trackedPrincipalDelta > 0)
|
||||
{
|
||||
XRPL_ASSERT_PARTS(
|
||||
overpaymentComponents.untrackedInterest >= beast::kZero,
|
||||
"xrpl::detail::makeRegularPayment",
|
||||
"overpayment penalty did not reduce value of loan");
|
||||
if (auto const overResult = doOverpayment(
|
||||
view.rules(),
|
||||
asset,
|
||||
loanScale,
|
||||
overpaymentComponents,
|
||||
loan,
|
||||
periodicRate,
|
||||
managementFeeRate,
|
||||
j))
|
||||
{
|
||||
totalParts += *overResult;
|
||||
}
|
||||
else if (overResult.error())
|
||||
{
|
||||
// error() will be the TER returned if a payment is not
|
||||
// made. It will only evaluate to true if it's unsuccessful.
|
||||
// Otherwise, tesSUCCESS means nothing was done, so
|
||||
// continue.
|
||||
return std::unexpected(overResult.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the final results are rounded, to double-check that the
|
||||
// intermediate steps were rounded.
|
||||
XRPL_ASSERT(
|
||||
isRounded(asset, totalParts.principalPaid, loanScale) &&
|
||||
totalParts.principalPaid >= beast::kZero,
|
||||
"xrpl::detail::makeRegularPayment : total principal paid is valid");
|
||||
XRPL_ASSERT(
|
||||
isRounded(asset, totalParts.interestPaid, loanScale) &&
|
||||
totalParts.interestPaid >= beast::kZero,
|
||||
"xrpl::detail::makeRegularPayment : total interest paid is valid");
|
||||
XRPL_ASSERT(
|
||||
isRounded(asset, totalParts.valueChange, loanScale),
|
||||
"xrpl::detail::makeRegularPayment : loan value change is valid");
|
||||
XRPL_ASSERT(
|
||||
isRounded(asset, totalParts.feePaid, loanScale) && totalParts.feePaid >= beast::kZero,
|
||||
"xrpl::detail::makeRegularPayment : fee paid is valid");
|
||||
return totalParts;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
detail::LoanStateDeltas
|
||||
@@ -1926,10 +1632,8 @@ constructLoanState(
|
||||
}
|
||||
|
||||
LoanState
|
||||
constructLoanState(SLE::const_ref loan)
|
||||
constructRoundedLoanState(SLE::const_ref loan)
|
||||
{
|
||||
XRPL_ASSERT(loan && loan->getType() == ltLOAN, "xrpl::constructLoanState : valid loan SLE");
|
||||
|
||||
return constructLoanState(
|
||||
loan->at(sfTotalValueOutstanding),
|
||||
loan->at(sfPrincipalOutstanding),
|
||||
@@ -2086,7 +1790,12 @@ loanMakePayment(
|
||||
LoanPaymentType const paymentType,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (loan->at(sfPaymentRemaining) == 0 || loan->at(sfPrincipalOutstanding) == 0)
|
||||
using namespace Lending;
|
||||
|
||||
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
|
||||
auto paymentRemainingProxy = loan->at(sfPaymentRemaining);
|
||||
|
||||
if (paymentRemainingProxy == 0 || principalOutstandingProxy == 0)
|
||||
{
|
||||
// Loan complete this is already checked in LoanPay::preclaim()
|
||||
// LCOV_EXCL_START
|
||||
@@ -2095,6 +1804,9 @@ loanMakePayment(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
|
||||
auto managementFeeOutstandingProxy = loan->at(sfManagementFeeOutstanding);
|
||||
|
||||
// Next payment due date must be set unless the loan is complete
|
||||
auto nextDueDateProxy = loan->at(sfNextPaymentDueDate);
|
||||
if (*nextDueDateProxy == 0)
|
||||
@@ -2103,8 +1815,26 @@ loanMakePayment(
|
||||
return std::unexpected(tecINTERNAL);
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
*loan->at(sfTotalValueOutstanding) > 0, "xrpl::loanMakePayment : valid total value");
|
||||
std::int32_t const loanScale = loan->at(sfLoanScale);
|
||||
|
||||
TenthBips32 const interestRate{loan->at(sfInterestRate)};
|
||||
|
||||
Number const serviceFee = loan->at(sfLoanServiceFee);
|
||||
TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
||||
|
||||
Number const periodicPayment = loan->at(sfPeriodicPayment);
|
||||
|
||||
auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate);
|
||||
std::uint32_t const startDate = loan->at(sfStartDate);
|
||||
|
||||
std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
|
||||
|
||||
// Compute the periodic rate that will be used for calculations
|
||||
// throughout
|
||||
Number const periodicRate = loanPeriodicRate(interestRate, paymentInterval);
|
||||
XRPL_ASSERT(interestRate == 0 || periodicRate > 0, "xrpl::loanMakePayment : valid rate");
|
||||
|
||||
XRPL_ASSERT(*totalValueOutstandingProxy > 0, "xrpl::loanMakePayment : valid total value");
|
||||
|
||||
view.update(loan);
|
||||
|
||||
@@ -2114,29 +1844,311 @@ loanMakePayment(
|
||||
{
|
||||
// If the payment is late, and the late flag was not set, it's not
|
||||
// valid
|
||||
JLOG(j.warn()) << "Loan payment is overdue. Use the tfLoanLatePayment transaction flag to "
|
||||
"make a late payment. Loan was created on "
|
||||
<< loan->at(sfStartDate) << ", prev payment due date is "
|
||||
<< loan->at(sfPreviousPaymentDueDate) << ", next payment due date is "
|
||||
<< nextDueDateProxy << ", ledger time is "
|
||||
JLOG(j.warn()) << "Loan payment is overdue. Use the tfLoanLatePayment "
|
||||
"transaction "
|
||||
"flag to make a late payment. Loan was created on "
|
||||
<< startDate << ", prev payment due date is " << prevPaymentDateProxy
|
||||
<< ", next payment due date is " << nextDueDateProxy << ", ledger time is "
|
||||
<< view.parentCloseTime().time_since_epoch().count();
|
||||
return std::unexpected(tecEXPIRED);
|
||||
}
|
||||
|
||||
switch (paymentType)
|
||||
// -------------------------------------------------------------
|
||||
// full payment handling
|
||||
if (paymentType == LoanPaymentType::Full)
|
||||
{
|
||||
case LoanPaymentType::Full:
|
||||
return detail::makeFullPayment(asset, view, loan, brokerSle, amount, j);
|
||||
case LoanPaymentType::Late:
|
||||
return detail::makeLatePayment(asset, view, loan, brokerSle, amount, j);
|
||||
case LoanPaymentType::Regular:
|
||||
case LoanPaymentType::Overpayment:
|
||||
return detail::makeRegularPayment(asset, view, loan, brokerSle, amount, paymentType, j);
|
||||
TenthBips32 const closeInterestRate{loan->at(sfCloseInterestRate)};
|
||||
Number const closePaymentFee = roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
|
||||
|
||||
LoanState const roundedLoanState = constructLoanState(
|
||||
totalValueOutstandingProxy, principalOutstandingProxy, managementFeeOutstandingProxy);
|
||||
|
||||
auto const fullPaymentComponents = detail::computeFullPayment(
|
||||
asset,
|
||||
view,
|
||||
principalOutstandingProxy,
|
||||
managementFeeOutstandingProxy,
|
||||
periodicPayment,
|
||||
paymentRemainingProxy,
|
||||
prevPaymentDateProxy,
|
||||
startDate,
|
||||
paymentInterval,
|
||||
closeInterestRate,
|
||||
loanScale,
|
||||
roundedLoanState.interestDue,
|
||||
periodicRate,
|
||||
closePaymentFee,
|
||||
amount,
|
||||
managementFeeRate,
|
||||
j);
|
||||
|
||||
if (fullPaymentComponents.has_value())
|
||||
{
|
||||
return doPayment(
|
||||
*fullPaymentComponents,
|
||||
totalValueOutstandingProxy,
|
||||
principalOutstandingProxy,
|
||||
managementFeeOutstandingProxy,
|
||||
paymentRemainingProxy,
|
||||
prevPaymentDateProxy,
|
||||
nextDueDateProxy,
|
||||
paymentInterval);
|
||||
}
|
||||
|
||||
if (fullPaymentComponents.error())
|
||||
{
|
||||
// error() will be the TER returned if a payment is not made. It
|
||||
// will only evaluate to true if it's unsuccessful. Otherwise,
|
||||
// tesSUCCESS means nothing was done, so continue.
|
||||
return std::unexpected(fullPaymentComponents.error());
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::loanMakePayment : invalid full payment result");
|
||||
JLOG(j.error()) << "Full payment computation failed unexpectedly.";
|
||||
return std::unexpected(tecINTERNAL);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::loanMakePayment : invalid payment type");
|
||||
return std::unexpected(tecINTERNAL);
|
||||
// LCOV_EXCL_STOP
|
||||
// -------------------------------------------------------------
|
||||
// compute the periodic payment info that will be needed whether the
|
||||
// payment is late or regular
|
||||
detail::ExtendedPaymentComponents periodic{
|
||||
detail::computePaymentComponents(
|
||||
view.rules(),
|
||||
asset,
|
||||
loanScale,
|
||||
totalValueOutstandingProxy,
|
||||
principalOutstandingProxy,
|
||||
managementFeeOutstandingProxy,
|
||||
periodicPayment,
|
||||
periodicRate,
|
||||
paymentRemainingProxy,
|
||||
managementFeeRate),
|
||||
serviceFee};
|
||||
XRPL_ASSERT_PARTS(
|
||||
periodic.trackedPrincipalDelta >= 0,
|
||||
"xrpl::loanMakePayment",
|
||||
"regular payment valid principal");
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// late payment handling
|
||||
if (paymentType == LoanPaymentType::Late)
|
||||
{
|
||||
TenthBips32 const lateInterestRate{loan->at(sfLateInterestRate)};
|
||||
Number const latePaymentFee = loan->at(sfLatePaymentFee);
|
||||
|
||||
auto const latePaymentComponents = detail::computeLatePayment(
|
||||
asset,
|
||||
view,
|
||||
principalOutstandingProxy,
|
||||
nextDueDateProxy,
|
||||
periodic,
|
||||
lateInterestRate,
|
||||
loanScale,
|
||||
latePaymentFee,
|
||||
amount,
|
||||
managementFeeRate,
|
||||
j);
|
||||
|
||||
if (latePaymentComponents.has_value())
|
||||
{
|
||||
return doPayment(
|
||||
*latePaymentComponents,
|
||||
totalValueOutstandingProxy,
|
||||
principalOutstandingProxy,
|
||||
managementFeeOutstandingProxy,
|
||||
paymentRemainingProxy,
|
||||
prevPaymentDateProxy,
|
||||
nextDueDateProxy,
|
||||
paymentInterval);
|
||||
}
|
||||
|
||||
if (latePaymentComponents.error())
|
||||
{
|
||||
// error() will be the TER returned if a payment is not made. It
|
||||
// will only evaluate to true if it's unsuccessful.
|
||||
return std::unexpected(latePaymentComponents.error());
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::loanMakePayment : invalid late payment result");
|
||||
JLOG(j.error()) << "Late payment computation failed unexpectedly.";
|
||||
return std::unexpected(tecINTERNAL);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// regular periodic payment handling
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
paymentType == LoanPaymentType::Regular || paymentType == LoanPaymentType::Overpayment,
|
||||
"xrpl::loanMakePayment",
|
||||
"regular payment type");
|
||||
|
||||
// Keep a running total of the actual parts paid
|
||||
LoanPaymentParts totalParts;
|
||||
Number totalPaid;
|
||||
std::size_t numPayments = 0;
|
||||
|
||||
while ((amount >= (totalPaid + periodic.totalDue)) && paymentRemainingProxy > 0 &&
|
||||
numPayments < kLoanMaximumPaymentsPerTransaction)
|
||||
{
|
||||
// Try to make more payments
|
||||
XRPL_ASSERT_PARTS(
|
||||
periodic.trackedPrincipalDelta >= 0,
|
||||
"xrpl::loanMakePayment",
|
||||
"payment pays non-negative principal");
|
||||
|
||||
totalPaid += periodic.totalDue;
|
||||
totalParts += detail::doPayment(
|
||||
periodic,
|
||||
totalValueOutstandingProxy,
|
||||
principalOutstandingProxy,
|
||||
managementFeeOutstandingProxy,
|
||||
paymentRemainingProxy,
|
||||
prevPaymentDateProxy,
|
||||
nextDueDateProxy,
|
||||
paymentInterval);
|
||||
++numPayments;
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
(periodic.specialCase == detail::PaymentSpecialCase::Final) ==
|
||||
(paymentRemainingProxy == 0),
|
||||
"xrpl::loanMakePayment",
|
||||
"final payment is the final payment");
|
||||
|
||||
// Don't compute the next payment if this was the last payment
|
||||
if (periodic.specialCase == detail::PaymentSpecialCase::Final)
|
||||
break;
|
||||
|
||||
periodic = detail::ExtendedPaymentComponents{
|
||||
detail::computePaymentComponents(
|
||||
view.rules(),
|
||||
asset,
|
||||
loanScale,
|
||||
totalValueOutstandingProxy,
|
||||
principalOutstandingProxy,
|
||||
managementFeeOutstandingProxy,
|
||||
periodicPayment,
|
||||
periodicRate,
|
||||
paymentRemainingProxy,
|
||||
managementFeeRate),
|
||||
serviceFee};
|
||||
}
|
||||
|
||||
if (numPayments == 0)
|
||||
{
|
||||
JLOG(j.warn()) << "Regular loan payment amount is insufficient. Due: " << periodic.totalDue
|
||||
<< ", paid: " << amount;
|
||||
return std::unexpected(tecINSUFFICIENT_PAYMENT);
|
||||
}
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
totalParts.principalPaid + totalParts.interestPaid + totalParts.feePaid == totalPaid,
|
||||
"xrpl::loanMakePayment",
|
||||
"payment parts add up");
|
||||
XRPL_ASSERT_PARTS(totalParts.valueChange == 0, "xrpl::loanMakePayment", "no value change");
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// overpayment handling
|
||||
//
|
||||
// If the "fixCleanup3_1_3" amendment is enabled, truncate "amount",
|
||||
// at the loan scale. If the raw value is used, the overpayment
|
||||
// amount could be meaningless dust. Trying to process such a small
|
||||
// amount will, at best, waste time when all the result values round
|
||||
// to zero. At worst, it can cause logical errors with tiny amounts
|
||||
// of interest that don't add up correctly.
|
||||
auto const roundedAmount = view.rules().enabled(fixCleanup3_1_3)
|
||||
? roundToAsset(asset, amount, loanScale, Number::RoundingMode::TowardsZero)
|
||||
: amount;
|
||||
if (paymentType == LoanPaymentType::Overpayment && loan->isFlag(lsfLoanOverpayment) &&
|
||||
paymentRemainingProxy > 0 && totalPaid < roundedAmount &&
|
||||
numPayments < kLoanMaximumPaymentsPerTransaction)
|
||||
{
|
||||
TenthBips32 const overpaymentInterestRate{loan->at(sfOverpaymentInterestRate)};
|
||||
TenthBips32 const overpaymentFeeRate{loan->at(sfOverpaymentFee)};
|
||||
|
||||
// It shouldn't be possible for the overpayment to be greater than
|
||||
// totalValueOutstanding, because that would have been processed as
|
||||
// another normal payment. But cap it just in case.
|
||||
Number const overpaymentRaw =
|
||||
std::min(roundedAmount - totalPaid, *totalValueOutstandingProxy);
|
||||
|
||||
bool const fixEnabled = view.rules().enabled(fixCleanup3_2_0);
|
||||
Number const overpayment = fixEnabled
|
||||
? roundToAsset(asset, overpaymentRaw, loanScale, Number::RoundingMode::Downward)
|
||||
: overpaymentRaw;
|
||||
|
||||
// Post-amendment, the rounded overpayment can be zero; pre-amendment
|
||||
// it's always positive given the surrounding guards.
|
||||
if (!fixEnabled || overpayment > 0)
|
||||
{
|
||||
detail::ExtendedPaymentComponents const overpaymentComponents =
|
||||
detail::computeOverpaymentComponents(
|
||||
view.rules(),
|
||||
asset,
|
||||
loanScale,
|
||||
overpayment,
|
||||
overpaymentInterestRate,
|
||||
overpaymentFeeRate,
|
||||
managementFeeRate);
|
||||
|
||||
// Don't process an overpayment if the whole amount (or more!)
|
||||
// gets eaten by fees and interest.
|
||||
if (overpaymentComponents.trackedPrincipalDelta > 0)
|
||||
{
|
||||
XRPL_ASSERT_PARTS(
|
||||
overpaymentComponents.untrackedInterest >= beast::kZero,
|
||||
"xrpl::loanMakePayment",
|
||||
"overpayment penalty did not reduce value of loan");
|
||||
// Can't just use `periodicPayment` here, because it might
|
||||
// change
|
||||
auto periodicPaymentProxy = loan->at(sfPeriodicPayment);
|
||||
if (auto const overResult = detail::doOverpayment(
|
||||
view.rules(),
|
||||
asset,
|
||||
loanScale,
|
||||
overpaymentComponents,
|
||||
totalValueOutstandingProxy,
|
||||
principalOutstandingProxy,
|
||||
managementFeeOutstandingProxy,
|
||||
periodicPaymentProxy,
|
||||
periodicRate,
|
||||
paymentRemainingProxy,
|
||||
managementFeeRate,
|
||||
j))
|
||||
{
|
||||
totalParts += *overResult;
|
||||
}
|
||||
else if (overResult.error())
|
||||
{
|
||||
// error() will be the TER returned if a payment is not
|
||||
// made. It will only evaluate to true if it's unsuccessful.
|
||||
// Otherwise, tesSUCCESS means nothing was done, so
|
||||
// continue.
|
||||
return std::unexpected(overResult.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the final results are rounded, to double-check that the
|
||||
// intermediate steps were rounded.
|
||||
XRPL_ASSERT(
|
||||
isRounded(asset, totalParts.principalPaid, loanScale) &&
|
||||
totalParts.principalPaid >= beast::kZero,
|
||||
"xrpl::loanMakePayment : total principal paid is valid");
|
||||
XRPL_ASSERT(
|
||||
isRounded(asset, totalParts.interestPaid, loanScale) &&
|
||||
totalParts.interestPaid >= beast::kZero,
|
||||
"xrpl::loanMakePayment : total interest paid is valid");
|
||||
XRPL_ASSERT(
|
||||
isRounded(asset, totalParts.valueChange, loanScale),
|
||||
"xrpl::loanMakePayment : loan value change is valid");
|
||||
XRPL_ASSERT(
|
||||
isRounded(asset, totalParts.feePaid, loanScale) && totalParts.feePaid >= beast::kZero,
|
||||
"xrpl::loanMakePayment : fee paid is valid");
|
||||
return totalParts;
|
||||
}
|
||||
} // namespace xrpl
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
@@ -7,11 +7,11 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/NFTokenHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <xrpl/beast/utility/instrumentation.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/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -1,10 +1,10 @@
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/ledger_entries/OfferHelpers.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.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_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
|
||||
#include <xrpl/ledger_entries/PaymentChannelHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
|
||||
#include <xrpl/ledger_entries/PermissionedDEXHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <xrpl/beast/utility/instrumentation.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/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -1,10 +1,10 @@
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OracleHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/OracleHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/ledger_entries/VaultHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
@@ -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.3.0-b1"
|
||||
// clang-format on
|
||||
;
|
||||
|
||||
|
||||
@@ -633,6 +633,20 @@ STObject::getAccountID(SField const& field) const
|
||||
return getFieldByValue<STAccount>(field);
|
||||
}
|
||||
|
||||
AccountID
|
||||
STObject::getInitiator() const
|
||||
{
|
||||
// If sfDelegate is present, the delegate account is the initiator
|
||||
// note: if a delegate is specified, its authorization to act on behalf of the account is
|
||||
// enforced in `Transactor::invokeCheckPermission`
|
||||
// cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`)
|
||||
if (isFieldPresent(sfDelegate))
|
||||
return getAccountID(sfDelegate);
|
||||
|
||||
// Default initiator
|
||||
return getAccountID(sfAccount);
|
||||
}
|
||||
|
||||
Blob
|
||||
STObject::getFieldVL(SField const& field) const
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ STTx::STTx(STObject&& object)
|
||||
{
|
||||
applyTemplate(getTxFormat(txType_)->getSOTemplate()); // may throw
|
||||
tid_ = getHash(HashPrefix::TransactionId);
|
||||
buildBatchTxns();
|
||||
buildBatchTxnIds();
|
||||
}
|
||||
|
||||
STTx::STTx(SerialIter& sit) : STObject(sfTransaction)
|
||||
@@ -89,7 +89,7 @@ STTx::STTx(SerialIter& sit) : STObject(sfTransaction)
|
||||
|
||||
applyTemplate(getTxFormat(txType_)->getSOTemplate()); // May throw
|
||||
tid_ = getHash(HashPrefix::TransactionId);
|
||||
buildBatchTxns();
|
||||
buildBatchTxnIds();
|
||||
}
|
||||
|
||||
STTx::STTx(TxType type, std::function<void(STObject&)> assembler) : STObject(sfTransaction)
|
||||
@@ -110,7 +110,7 @@ STTx::STTx(TxType type, std::function<void(STObject&)> assembler) : STObject(sfT
|
||||
logicError("Transaction type was mutated during assembly");
|
||||
|
||||
tid_ = getHash(HashPrefix::TransactionId);
|
||||
buildBatchTxns();
|
||||
buildBatchTxnIds();
|
||||
}
|
||||
|
||||
STBase*
|
||||
@@ -279,9 +279,12 @@ STTx::checkSign(Rules const& rules) const
|
||||
return std::unexpected("Sponsor: " + ret.error());
|
||||
}
|
||||
|
||||
// Verify batch signer signatures here so they are cached with the rest
|
||||
// of signature checking.
|
||||
if (isFieldPresent(sfBatchSigners))
|
||||
// Verify the batch signer signatures here too, so they are cached with the
|
||||
// rest of signature checking (checkValidity / SF_SIGGOOD) and stay out of
|
||||
// the transaction engine. Gated on a batch (batchTxnIds_ seated) that
|
||||
// actually carries signers; a batch whose inners are all from the outer
|
||||
// account has no sfBatchSigners and needs no signer crypto.
|
||||
if (batchTxnIds_ && isFieldPresent(sfBatchSigners))
|
||||
{
|
||||
if (auto const ret = checkBatchSign(rules); !ret)
|
||||
return ret;
|
||||
@@ -304,28 +307,11 @@ STTx::checkBatchSign(Rules const& rules) const
|
||||
if (!isFieldPresent(sfBatchSigners))
|
||||
return std::unexpected("Missing BatchSigners field."); // LCOV_EXCL_LINE
|
||||
STArray const& signers{getFieldArray(sfBatchSigners)};
|
||||
// Bound signature verification to the protocol cap. This runs in
|
||||
// checkValidity (via checkSign) at relay / submit time, BEFORE preflight
|
||||
// and passesLocalChecks enforce the cap. Without this guard a malicious
|
||||
// peer could put an oversized sfBatchSigners array in a 1 MB blob and
|
||||
// force one signature verification per entry before any of those checks
|
||||
// (or the fee charge) runs.
|
||||
if (signers.size() > kMaxBatchSigners)
|
||||
return std::unexpected("BatchSigners array exceeds max entries.");
|
||||
// Defensive.
|
||||
if (!batchTxns_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("STTx::checkBatchSign : batch transactions not built");
|
||||
return std::unexpected("Missing inner transactions.");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto const txIds = getBatchTransactionIDs();
|
||||
for (auto const& signer : signers)
|
||||
{
|
||||
Blob const& signingPubKey = signer.getFieldVL(sfSigningPubKey);
|
||||
auto const result = signingPubKey.empty() ? checkBatchMultiSign(signer, rules, txIds)
|
||||
: checkBatchSingleSign(signer, txIds);
|
||||
auto const result = signingPubKey.empty() ? checkBatchMultiSign(signer, rules)
|
||||
: checkBatchSingleSign(signer);
|
||||
|
||||
if (!result)
|
||||
return result;
|
||||
@@ -455,11 +441,12 @@ STTx::checkSingleSign(STObject const& sigObject) const
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
STTx::checkBatchSingleSign(STObject const& batchSigner, std::vector<uint256> const& txIds) const
|
||||
STTx::checkBatchSingleSign(STObject const& batchSigner) const
|
||||
{
|
||||
XRPL_ASSERT(getTxnType() == ttBATCH, "STTx::checkBatchSingleSign : batch transaction");
|
||||
Serializer msg;
|
||||
serializeBatch(msg, getAccountID(sfAccount), getSeqValue(), getFlags(), txIds);
|
||||
serializeBatch(
|
||||
msg, getAccountID(sfAccount), getSeqValue(), getFlags(), getBatchTransactionIDs());
|
||||
finishMultiSigningData(batchSigner.getAccountID(sfAccount), msg);
|
||||
return singleSignHelper(batchSigner, msg.slice());
|
||||
}
|
||||
@@ -542,10 +529,7 @@ multiSignHelper(
|
||||
}
|
||||
|
||||
std::expected<void, std::string>
|
||||
STTx::checkBatchMultiSign(
|
||||
STObject const& batchSigner,
|
||||
Rules const& rules,
|
||||
std::vector<uint256> const& txIds) const
|
||||
STTx::checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const
|
||||
{
|
||||
XRPL_ASSERT(getTxnType() == ttBATCH, "STTx::checkBatchMultiSign : batch transaction");
|
||||
// We can ease the computational load inside the loop a bit by
|
||||
@@ -553,7 +537,8 @@ 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), getSeqValue(), getFlags(), getBatchTransactionIDs());
|
||||
dataStart.addBitString(batchSignerAccount);
|
||||
return multiSignHelper(
|
||||
batchSigner,
|
||||
@@ -592,79 +577,38 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
|
||||
}
|
||||
|
||||
void
|
||||
STTx::buildBatchTxns()
|
||||
STTx::buildBatchTxnIds()
|
||||
{
|
||||
// Precondition: the template must have been applied first, so the fields
|
||||
// (including sfRawTransactions) are canonical before the inner txns are
|
||||
// hashed. The constructors call this immediately after applying the
|
||||
// template; isFree() being false confirms a template is set.
|
||||
XRPL_ASSERT(!isFree(), "STTx::buildBatchTxns : template applied");
|
||||
if (getTxnType() != ttBATCH)
|
||||
XRPL_ASSERT(!isFree(), "STTx::buildBatchTxnIds : template applied");
|
||||
if (getTxnType() != ttBATCH || !isFieldPresent(sfRawTransactions))
|
||||
return;
|
||||
// A Batch always seats its inner transactions here, so every downstream
|
||||
// consumer can rely on them. sfRawTransactions is required by the format
|
||||
// (applyTemplate rejects a Batch without it); this guards a future change
|
||||
// that made it optional.
|
||||
if (!isFieldPresent(sfRawTransactions))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("STTx::buildBatchTxns : missing RawTransactions");
|
||||
Throw<std::runtime_error>("Batch has no RawTransactions.");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const& raw = getFieldArray(sfRawTransactions);
|
||||
if (raw.size() > kMaxBatchTxCount)
|
||||
Throw<std::runtime_error>("Batch has too many inner transactions.");
|
||||
|
||||
// Build and validate each inner as an STTx once. A malformed inner throws;
|
||||
// a nested batch is rejected before building it (a batch cannot contain a
|
||||
// batch, and building one would recurse).
|
||||
auto& txns = batchTxns_.emplace();
|
||||
txns.reserve(raw.size());
|
||||
// Seated for any batch with raw transactions. The count is validated in
|
||||
// preflight and at the relay boundary, so build every id here; this keeps
|
||||
// the invariant batchTxnIds_->size() == rawTransactions.size().
|
||||
auto& ids = batchTxnIds_.emplace();
|
||||
ids.reserve(raw.size());
|
||||
for (STObject const& rb : raw)
|
||||
{
|
||||
if (rb.getFieldU16(sfTransactionType) == ttBATCH)
|
||||
Throw<std::runtime_error>("Batch inner transaction cannot be a Batch.");
|
||||
|
||||
txns.push_back(std::make_shared<STTx const>(STObject{rb}));
|
||||
}
|
||||
ids.push_back(rb.getHash(HashPrefix::TransactionId));
|
||||
}
|
||||
|
||||
std::vector<uint256>
|
||||
std::vector<uint256> const&
|
||||
STTx::getBatchTransactionIDs() const
|
||||
{
|
||||
auto const& txns = getBatchTransactions();
|
||||
std::vector<uint256> ids;
|
||||
ids.reserve(txns.size());
|
||||
for (auto const& stx : txns)
|
||||
ids.push_back(stx->getTransactionID());
|
||||
return ids;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<STTx const>> const&
|
||||
STTx::getBatchTransactions() const
|
||||
{
|
||||
XRPL_ASSERT(getTxnType() == ttBATCH, "STTx::getBatchTransactions : batch transaction");
|
||||
XRPL_ASSERT(batchTxns_.has_value(), "STTx::getBatchTransactions : batch transactions built");
|
||||
XRPL_ASSERT(getTxnType() == ttBATCH, "STTx::getBatchTransactionIDs : batch transaction");
|
||||
XRPL_ASSERT(
|
||||
batchTxns_->size() == getFieldArray(sfRawTransactions).size(),
|
||||
"STTx::getBatchTransactions : batch transactions size mismatch");
|
||||
return *batchTxns_;
|
||||
}
|
||||
|
||||
AccountID
|
||||
STTx::getInitiator() const
|
||||
{
|
||||
// If sfDelegate is present, the delegate account is the initiator
|
||||
// note: if a delegate is specified, its authorization to act on behalf of the account is
|
||||
// enforced in `Transactor::invokeCheckPermission`
|
||||
// cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`)
|
||||
if (isFieldPresent(sfDelegate))
|
||||
return getAccountID(sfDelegate);
|
||||
|
||||
// Default initiator
|
||||
return getAccountID(sfAccount);
|
||||
batchTxnIds_.has_value(), "STTx::getBatchTransactionIDs : batch transaction IDs built");
|
||||
XRPL_ASSERT(
|
||||
batchTxnIds_->size() == getFieldArray(sfRawTransactions).size(),
|
||||
"STTx::getBatchTransactionIDs : batch transaction IDs size mismatch");
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): guarded by assert above
|
||||
return *batchTxnIds_;
|
||||
}
|
||||
|
||||
AccountID
|
||||
@@ -810,62 +754,86 @@ invalidMPTAmountInTx(STObject const& tx)
|
||||
}
|
||||
|
||||
static bool
|
||||
isBatchRawTransactionOkay(STTx const& tx, std::string& reason)
|
||||
isBatchRawTransactionOkay(STObject const& st, std::string& reason)
|
||||
{
|
||||
if (!tx.isFieldPresent(sfRawTransactions))
|
||||
if (!st.isFieldPresent(sfRawTransactions))
|
||||
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 (st.getFieldU16(sfTransactionType) != ttBATCH)
|
||||
{
|
||||
reason = "Only Batch transactions may contain raw transactions.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tx.isFieldPresent(sfBatchSigners) &&
|
||||
tx.getFieldArray(sfBatchSigners).size() > kMaxBatchSigners)
|
||||
if (st.isFieldPresent(sfBatchSigners) &&
|
||||
st.getFieldArray(sfBatchSigners).size() > kMaxBatchSigners)
|
||||
{
|
||||
reason = "BatchSigners array exceeds max entries.";
|
||||
reason = "Batch Signers array exceeds max entries.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inner structure (type, template, no nesting, count) is validated when the
|
||||
// batch STTx is constructed; here we only run each inner's local checks.
|
||||
for (auto const& inner : tx.getBatchTransactions())
|
||||
auto const& rawTxns = st.getFieldArray(sfRawTransactions);
|
||||
if (rawTxns.size() > kMaxBatchTxCount)
|
||||
{
|
||||
if (!passesLocalChecks(*inner, reason))
|
||||
reason = "Raw Transactions array exceeds max entries.";
|
||||
return false;
|
||||
}
|
||||
for (STObject raw : rawTxns)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto const tt = safeCast<TxType>(raw.getFieldU16(sfTransactionType));
|
||||
if (tt == ttBATCH)
|
||||
{
|
||||
reason = "Raw Transactions may not contain batch transactions.";
|
||||
return false;
|
||||
}
|
||||
|
||||
raw.applyTemplate(getTxFormat(tt)->getSOTemplate());
|
||||
|
||||
// passesLocalChecks recurses back into isBatchRawTransactionOkay,
|
||||
// but an inner can never be a batch (rejected above), so the
|
||||
// recursion terminates at depth 1.
|
||||
if (!passesLocalChecks(raw, reason))
|
||||
return false;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
reason = e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
passesLocalChecks(STTx const& tx, std::string& reason)
|
||||
passesLocalChecks(STObject const& st, std::string& reason)
|
||||
{
|
||||
if (!isMemoOkay(tx, reason))
|
||||
if (!isMemoOkay(st, reason))
|
||||
return false;
|
||||
|
||||
if (!isAccountFieldOkay(tx))
|
||||
if (!isAccountFieldOkay(st))
|
||||
{
|
||||
reason = "An account field is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPseudoTx(tx))
|
||||
if (isPseudoTx(st))
|
||||
{
|
||||
reason = "Cannot submit pseudo transactions.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (invalidMPTAmountInTx(tx))
|
||||
if (invalidMPTAmountInTx(st))
|
||||
{
|
||||
reason = "Amount can not be MPT.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isBatchRawTransactionOkay(tx, reason))
|
||||
if (!isBatchRawTransactionOkay(st, reason))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
#include <xrpl/json/to_string.h> // IWYU pragma: keep
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h>
|
||||
#include <xrpl/ledger_entries/DelegateHelpers.h>
|
||||
#include <xrpl/ledger_entries/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/OfferHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -177,9 +177,9 @@ applyBatchTransactions(
|
||||
|
||||
int applied = 0;
|
||||
|
||||
for (auto const& stx : batchTxn.getBatchTransactions())
|
||||
for (STObject rb : batchTxn.getFieldArray(sfRawTransactions))
|
||||
{
|
||||
auto const result = applyOneTransaction(*stx);
|
||||
auto const result = applyOneTransaction(STTx{std::move(rb)});
|
||||
XRPL_ASSERT(
|
||||
result.applied == (isTesSuccess(result.ter) || isTecClaim(result.ter)),
|
||||
"Outer Batch failure, inner transaction should not be applied");
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/NFTokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/OfferHelpers.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/PermissionedDEXHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/OfferHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#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/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/RawView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#include <xrpl/tx/transactors/check/CheckCancel.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -21,9 +19,6 @@ namespace xrpl {
|
||||
NotTEC
|
||||
CheckCancel::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.rules.enabled(fixCleanup3_3_0) && ctx.tx[sfCheckID] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -52,9 +51,6 @@ CheckCash::checkExtraFeatures(xrpl::PreflightContext const& ctx)
|
||||
NotTEC
|
||||
CheckCash::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.rules.enabled(fixCleanup3_3_0) && ctx.tx[sfCheckID] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
// Exactly one of Amount or DeliverMin must be present.
|
||||
auto const optAmount = ctx.tx[~sfAmount];
|
||||
auto const optDeliverMin = ctx.tx[~sfDeliverMin];
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h> // IWYU pragma: keep
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h> // IWYU pragma: keep
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger_entries/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger_entries/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/ledger_entries/DelegateHelpers.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger_entries/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger_entries/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger_entries/AMMHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user