From 8490206228a6413d41449a01d092ef3ab3b5e1d5 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Sun, 3 May 2026 22:42:44 +0100 Subject: [PATCH 01/18] chore: Ignore identifier-naming update in git blame (#7066) Co-authored-by: Bart Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 0cf704b051..d61cab7e03 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -5,6 +5,8 @@ # This file is sorted in reverse chronological order, with the most recent commits at the top. # The commits listed here are ignored by git blame, which is useful for formatting-only commits that would otherwise obscure the history of changes to a file. +# refactor: Enable clang-tidy `readability-identifier-naming` check (#6571) +8995564ed6b9e453e144bb663303072a3c1ba305 # refactor: Enable remaining clang-tidy `cppcoreguidelines` checks (#6538) 72f4cb097f626b08b02fc3efcb4aa11cb2e7adb8 # refactor: Rename system name from 'ripple' to 'xrpld' (#6347) From d0500738426d924ffe51267ed589767ab9c5d4bc Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 4 May 2026 13:51:07 +0100 Subject: [PATCH 02/18] ci: Rewrite clang-tidy workflow(s) in a reusable manner (#7062) --- .clang-tidy | 12 +- .github/workflows/on-pr.yml | 5 +- .github/workflows/on-trigger.yml | 3 - .../workflows/reusable-clang-tidy-files.yml | 175 --------------- .github/workflows/reusable-clang-tidy.yml | 212 +++++++++++++++--- cspell.config.yaml | 1 + 6 files changed, 193 insertions(+), 215 deletions(-) delete mode 100644 .github/workflows/reusable-clang-tidy-files.yml diff --git a/.clang-tidy b/.clang-tidy index daacf4f00e..33569be50a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -4,8 +4,8 @@ Checks: "-*, bugprone-assert-side-effect, bugprone-bad-signal-to-kill-thread, bugprone-bool-pointer-implicit-conversion, - bugprone-casting-through-void, bugprone-capturing-this-in-member-variable, + bugprone-casting-through-void, bugprone-chained-comparison, bugprone-compare-pointer-to-member-virtual-function, bugprone-copy-constructor-init, @@ -24,10 +24,10 @@ Checks: "-*, bugprone-lambda-function-name, bugprone-macro-parentheses, bugprone-macro-repeated-side-effects, + bugprone-misleading-setter-of-reference, bugprone-misplaced-operator-in-strlen-in-alloc, bugprone-misplaced-pointer-arithmetic-in-alloc, bugprone-misplaced-widening-cast, - bugprone-misleading-setter-of-reference, bugprone-move-forwarding-reference, bugprone-multi-level-implicit-pointer-conversion, bugprone-multiple-new-in-one-expression, @@ -73,10 +73,10 @@ Checks: "-*, bugprone-unhandled-self-assignment, bugprone-unique-ptr-array-mismatch, bugprone-unsafe-functions, - bugprone-use-after-move, + bugprone-unused-local-non-trivial-variable, bugprone-unused-raii, bugprone-unused-return-value, - bugprone-unused-local-non-trivial-variable, + bugprone-use-after-move, bugprone-virtual-near-miss, cppcoreguidelines-init-variables, cppcoreguidelines-misleading-capture-default-by-value, @@ -88,6 +88,7 @@ Checks: "-*, cppcoreguidelines-use-enum-class, cppcoreguidelines-virtual-class-destructor, hicpp-ignored-remove-result, + llvm-namespace-comment, misc-const-correctness, misc-definitions-in-headers, misc-header-include-cycle, @@ -99,6 +100,7 @@ Checks: "-*, misc-unused-alias-decls, misc-unused-using-decls, modernize-concat-nested-namespaces, + modernize-deprecated-headers, modernize-make-shared, modernize-make-unique, modernize-pass-by-value, @@ -114,8 +116,6 @@ Checks: "-*, modernize-use-starts-ends-with, modernize-use-std-numbers, modernize-use-using, - modernize-deprecated-headers, - llvm-namespace-comment, performance-faster-string-find, performance-for-range-copy, performance-implicit-conversion-in-loop, diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 28299a1264..d95f3a6c00 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -58,15 +58,12 @@ jobs: # Keep the paths below in sync with those in `on-trigger.yml`. .github/actions/build-deps/** - .github/actions/build-test/** .github/actions/generate-version/** .github/actions/setup-conan/** .github/scripts/strategy-matrix/** - .github/workflows/reusable-build.yml .github/workflows/reusable-build-test-config.yml .github/workflows/reusable-build-test.yml .github/workflows/reusable-clang-tidy.yml - .github/workflows/reusable-clang-tidy-files.yml .github/workflows/reusable-strategy-matrix.yml .github/workflows/reusable-test.yml .github/workflows/reusable-upload-recipe.yml @@ -176,4 +173,4 @@ jobs: runs-on: ubuntu-latest steps: - name: Fail - run: false + run: exit 1 diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 5856c67bd3..11d98bffb7 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -15,15 +15,12 @@ on: # Keep the paths below in sync with those in `on-pr.yml`. - ".github/actions/build-deps/**" - - ".github/actions/build-test/**" - ".github/actions/generate-version/**" - ".github/actions/setup-conan/**" - ".github/scripts/strategy-matrix/**" - - ".github/workflows/reusable-build.yml" - ".github/workflows/reusable-build-test-config.yml" - ".github/workflows/reusable-build-test.yml" - ".github/workflows/reusable-clang-tidy.yml" - - ".github/workflows/reusable-clang-tidy-files.yml" - ".github/workflows/reusable-strategy-matrix.yml" - ".github/workflows/reusable-test.yml" - ".github/workflows/reusable-upload-recipe.yml" diff --git a/.github/workflows/reusable-clang-tidy-files.yml b/.github/workflows/reusable-clang-tidy-files.yml deleted file mode 100644 index 7b35b2d968..0000000000 --- a/.github/workflows/reusable-clang-tidy-files.yml +++ /dev/null @@ -1,175 +0,0 @@ -name: Run clang-tidy on files - -on: - workflow_call: - inputs: - files: - description: "List of files to check (empty means check all files)" - type: string - default: "" - create_issue_on_failure: - description: "Whether to create an issue if the check failed" - type: boolean - default: false - -defaults: - run: - shell: bash - -env: - # Conan installs the generators in the build/generators directory, see the - # layout() method in conanfile.py. We then run CMake from the build directory. - BUILD_DIR: build - BUILD_TYPE: Debug # Debug so that ASSERTS and such participate in clang-tidy check - -jobs: - run-clang-tidy: - name: Run clang tidy - runs-on: ["self-hosted", "Linux", "X64", "heavy"] - container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2" - permissions: - issues: write - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Prepare runner - uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab - with: - enable_ccache: false - - - name: Print build environment - uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 - - - name: Get number of processors - uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf - id: nproc - - - name: Setup Conan - uses: ./.github/actions/setup-conan - - - name: Build dependencies - uses: ./.github/actions/build-deps - with: - build_nproc: ${{ steps.nproc.outputs.nproc }} - build_type: ${{ env.BUILD_TYPE }} - log_verbosity: verbose - - - name: Configure CMake - working-directory: ${{ env.BUILD_DIR }} - run: | - cmake \ - -G 'Ninja' \ - -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ - -Dtests=ON \ - -Dwerr=ON \ - -Dxrpld=ON \ - .. - - # clang-tidy needs headers generated from proto files - - name: Build libxrpl.libpb - working-directory: ${{ env.BUILD_DIR }} - run: | - ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb - - - name: Run clang tidy - id: run_clang_tidy - continue-on-error: true - env: - TARGETS: ${{ inputs.files != '' && inputs.files || 'src tests' }} - run: | - run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee clang-tidy-output.txt - - - name: Upload clang-tidy output - if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: clang-tidy-results - path: clang-tidy-output.txt - retention-days: 30 - - - name: Generate git diff - if: ${{ steps.run_clang_tidy.outcome != 'success' }} - run: | - git diff | tee clang-tidy-git-diff.txt - - - name: Upload clang-tidy diff output - if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: clang-tidy-git-diff - path: clang-tidy-git-diff.txt - retention-days: 30 - - - name: Create an issue - if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }} - id: create_issue - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - # Prepare issue body with clang-tidy output - cat > issue.md < filtered-output.txt || true - - # If filtered output is empty, use original (might be a different error format) - if [ ! -s filtered-output.txt ]; then - cp clang-tidy-output.txt filtered-output.txt - fi - - # Truncate if too large - head -c 60000 filtered-output.txt >> issue.md - if [ "$(wc -c < filtered-output.txt)" -gt 60000 ]; then - echo "" >> issue.md - echo "... (output truncated, see artifacts for full output)" >> issue.md - fi - - rm filtered-output.txt - else - echo "No output file found" >> issue.md - fi - - cat >> issue.md < create_issue.log - - created_issue="$(sed 's|.*/||' create_issue.log)" - echo "created_issue=$created_issue" >> $GITHUB_OUTPUT - echo "Created issue #$created_issue" - - rm -f create_issue.log issue.md clang-tidy-output.txt - - - name: Fail the workflow if clang-tidy failed - if: ${{ steps.run_clang_tidy.outcome != 'success' }} - run: | - echo "Clang-tidy check failed!" - exit 1 diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 7a8bf6de57..a678fe4c8e 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -1,4 +1,4 @@ -name: Clang-tidy check +name: Run clang-tidy on files on: workflow_call: @@ -16,40 +16,198 @@ defaults: run: shell: bash +env: + BUILD_DIR: build + BUILD_TYPE: Debug # Debug so that ASSERTS and such participate in clang-tidy check + + OUTPUT_FILE: clang-tidy-output.txt + DIFF_FILE: clang-tidy-git-diff.txt + ISSUE_FILE: clang-tidy-issue.md + jobs: determine-files: - name: Determine files to check if: ${{ inputs.check_only_changed }} - runs-on: ubuntu-latest - outputs: - clang_tidy_config_changed: ${{ steps.changed_clang_tidy.outputs.any_changed }} - any_cpp_changed: ${{ steps.changed_files.outputs.any_changed }} - all_changed_files: ${{ steps.changed_files.outputs.all_changed_files }} + permissions: + contents: read + uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@12f5dbc98a2260259a66970e57fa4d26fb7f285c + + run-clang-tidy: + name: Run clang tidy + needs: [determine-files] + if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }} + runs-on: ["self-hosted", "Linux", "X64", "heavy"] + container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2" + permissions: + contents: read steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Get changed C++ files - id: changed_files - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 + - name: Prepare runner + uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab with: - files: | - **/*.cpp - **/*.h - **/*.ipp - separator: " " + enable_ccache: false - - name: Get changed clang-tidy configuration - id: changed_clang_tidy - uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 + - name: Print build environment + uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 + + - name: Get number of processors + uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf + id: nproc + + - name: Setup Conan + uses: ./.github/actions/setup-conan + + - name: Build dependencies + uses: ./.github/actions/build-deps with: - files: | - .clang-tidy + build_nproc: ${{ steps.nproc.outputs.nproc }} + build_type: ${{ env.BUILD_TYPE }} + log_verbosity: verbose - run-clang-tidy: - needs: [determine-files] - if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_cpp_changed == 'true' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }} - uses: ./.github/workflows/reusable-clang-tidy-files.yml - with: - files: ${{ (needs.determine-files.outputs.clang_tidy_config_changed != 'true' && inputs.check_only_changed) && needs.determine-files.outputs.all_changed_files || '' }} - create_issue_on_failure: ${{ inputs.create_issue_on_failure }} + - name: Configure CMake + working-directory: ${{ env.BUILD_DIR }} + run: | + cmake \ + -G 'Ninja' \ + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -Dtests=ON \ + -Dwerr=ON \ + -Dxrpld=ON \ + .. + + # clang-tidy needs headers generated from proto files + - name: Build libxrpl.libpb + working-directory: ${{ env.BUILD_DIR }} + run: | + ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb + + - name: Run clang tidy + id: run_clang_tidy + continue-on-error: true + env: + TARGETS: ${{ (needs.determine-files.outputs.clang_tidy_config_changed != 'true' && inputs.check_only_changed) && needs.determine-files.outputs.cpp_changed_files || 'src tests' }} + run: | + set -o pipefail + run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}" + + - name: Upload clang-tidy output + if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + path: ${{ env.OUTPUT_FILE }} + archive: false + retention-days: 30 + + - name: Generate git diff + if: ${{ steps.run_clang_tidy.outcome != 'success' }} + run: | + git diff | tee "${DIFF_FILE}" + + - name: Upload clang-tidy diff output + if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + path: ${{ env.DIFF_FILE }} + archive: false + retention-days: 30 + + - name: Write issue header + if: ${{ steps.run_clang_tidy.outcome != 'success' }} + run: | + # Prepare issue body with clang-tidy output + cat > "${ISSUE_FILE}" < filtered-output.txt || true + + # If filtered output is empty, use original (might be a different error format) + if [ ! -s filtered-output.txt ]; then + cp "${OUTPUT_FILE}" filtered-output.txt + fi + + # Truncate if too large + head -c 60000 filtered-output.txt >> "${ISSUE_FILE}" + if [ "$(wc -c < filtered-output.txt)" -gt 60000 ]; then + echo "" >> "${ISSUE_FILE}" + echo "... (output truncated, see artifacts for full output)" >> "${ISSUE_FILE}" + fi + + rm filtered-output.txt + else + echo "No output file found" >> "${ISSUE_FILE}" + fi + + - name: Append issue footer + if: ${{ steps.run_clang_tidy.outcome != 'success' }} + run: | + cat >> "${ISSUE_FILE}" < create_issue.log + + - name: Output created issue number + run: | + created_issue="$(sed 's|.*/||' create_issue.log)" + echo "created_issue=$created_issue" >> $GITHUB_OUTPUT + echo "Created issue #$created_issue" diff --git a/cspell.config.yaml b/cspell.config.yaml index 028f02191e..5aaeeae5e3 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -151,6 +151,7 @@ words: - lseq - lsmf - ltype + - mathbunnyru - mcmodel - MEMORYSTATUSEX - MPTAMM From e092c524098c8b745c9acdc4fd83f0e171879f41 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 5 May 2026 14:49:13 +0100 Subject: [PATCH 03/18] ci: Use XRPLF/create-issue (#7076) --- .github/workflows/reusable-clang-tidy.yml | 57 ++++------------------- cspell.config.yaml | 1 + 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index a678fe4c8e..0df235b80f 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -39,6 +39,7 @@ jobs: container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2" permissions: contents: read + issues: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -116,24 +117,16 @@ jobs: - name: Write issue header if: ${{ steps.run_clang_tidy.outcome != 'success' }} run: | - # Prepare issue body with clang-tidy output cat > "${ISSUE_FILE}" < filtered-output.txt || true @@ -161,53 +154,21 @@ jobs: cat >> "${ISSUE_FILE}" < create_issue.log - - - name: Output created issue number - run: | - created_issue="$(sed 's|.*/||' create_issue.log)" - echo "created_issue=$created_issue" >> $GITHUB_OUTPUT - echo "Created issue #$created_issue" diff --git a/cspell.config.yaml b/cspell.config.yaml index 5aaeeae5e3..e2c1bad46c 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -114,6 +114,7 @@ words: - gcovr - ghead - Gnutella + - godexsoft - gpgcheck - gpgkey - hotwallet From 6e6fb9cdf3e850d4637655709d35e7d56970133e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 5 May 2026 17:31:46 +0100 Subject: [PATCH 04/18] ci: Run pre-commit on diff in clang-tidy workflow (#7078) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/reusable-clang-tidy.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 0df235b80f..10e36f957f 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -93,6 +93,11 @@ jobs: set -o pipefail run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}" + - name: Print errors + if: ${{ steps.run_clang_tidy.outcome != 'success' }} + run: | + sed '/error\||/!d' "${OUTPUT_FILE}" + - name: Upload clang-tidy output if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -101,13 +106,24 @@ jobs: archive: false retention-days: 30 + - name: Check for changes + id: files_changed + continue-on-error: true + run: | + git diff --exit-code + + - name: Fix style + if: ${{ steps.files_changed.outcome != 'success' }} + run: | + pre-commit run --all-files || true + - name: Generate git diff - if: ${{ steps.run_clang_tidy.outcome != 'success' }} + if: ${{ steps.files_changed.outcome != 'success' }} run: | git diff | tee "${DIFF_FILE}" - name: Upload clang-tidy diff output - if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} + if: ${{ github.event.repository.visibility == 'public' && steps.files_changed.outcome != 'success' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: path: ${{ env.DIFF_FILE }} From 27f7fdb3a610eb0e2b1db17d9cbffcbd7ef90c9c Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 5 May 2026 17:32:43 +0100 Subject: [PATCH 05/18] chore: Do not duplicate sanitizer flags (#7058) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../workflows/reusable-build-test-config.yml | 1 - BUILD.md | 6 +- cmake/XrplSanitizers.cmake | 241 +++++------------- conan/profiles/ci | 2 +- conan/profiles/sanitizers | 182 +++++++------ cspell.config.yaml | 1 + docs/build/sanitizers.md | 45 +--- 7 files changed, 179 insertions(+), 299 deletions(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index ae998faebd..528934f6de 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -143,7 +143,6 @@ jobs: working-directory: ${{ env.BUILD_DIR }} env: BUILD_TYPE: ${{ inputs.build_type }} - SANITIZERS: ${{ inputs.sanitizers }} CMAKE_ARGS: ${{ inputs.cmake_args }} run: | cmake \ diff --git a/BUILD.md b/BUILD.md index cf0a685abd..5479242f91 100644 --- a/BUILD.md +++ b/BUILD.md @@ -530,16 +530,16 @@ stored inside the build directory, as either of: ## Sanitizers To build dependencies and xrpld with sanitizer instrumentation, set the -`SANITIZERS` environment variable (only once before running conan and cmake) and use the `sanitizers` profile in conan: +`SANITIZERS` environment variable when running `conan install` and use the `sanitizers` profile: ```bash export SANITIZERS=address,undefinedbehavior conan install .. --output-folder . --profile:all sanitizers --build missing --settings build_type=Debug - -cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -Dxrpld=ON -Dtests=ON .. ``` +You can then build and test as usual, with the generated `xrpld` binary containing the sanitizer instrumentation. When you run it, it will report any sanitizer errors it detects in the console output. + See [Sanitizers docs](./docs/build/sanitizers.md) for more details. ## Options diff --git a/cmake/XrplSanitizers.cmake b/cmake/XrplSanitizers.cmake index 2228381286..64f1841bfb 100644 --- a/cmake/XrplSanitizers.cmake +++ b/cmake/XrplSanitizers.cmake @@ -1,140 +1,33 @@ #[===================================================================[ - Configure sanitizers based on environment variables. + Apply sanitizer flags built by the Conan profile. - This module reads the following environment variables: - - SANITIZERS: The sanitizers to enable. Possible values: - - "address" - - "address,undefinedbehavior" - - "thread" - - "thread,undefinedbehavior" - - "undefinedbehavior" + Parsing, validation, and flag construction are performed in conan/profiles/sanitizers. + This module reads the following CMake variables injected by the Conan toolchain via extra_variables: - The compiler type and platform are detected in CompilationEnv.cmake. - The sanitizer compile options are applied to the 'common' interface library - which is linked to all targets in the project. + - SANITIZERS: The active sanitizers (e.g. "address,undefinedbehavior"). + - SANITIZERS_COMPILER_FLAGS: Space-separated compiler flags. + - SANITIZERS_LINKER_FLAGS: Space-separated linker flags. - Internal flag variables set by this module: - - - SANITIZER_TYPES: List of sanitizer types to enable (e.g., "address", - "thread", "undefined"). And two more flags for undefined behavior sanitizer (e.g., "float-divide-by-zero", "unsigned-integer-overflow"). - This list is joined with commas and passed to -fsanitize=. - - - SANITIZERS_COMPILE_FLAGS: Compiler flags for sanitizer instrumentation. - Includes: - * -fno-omit-frame-pointer: Preserves frame pointers for stack traces - * -O1: Minimum optimization for reasonable performance - * -fsanitize=: Enables sanitizer instrumentation - * -fsanitize-ignorelist=: (Clang only) Compile-time ignorelist - * -mcmodel=large/medium: (GCC only) Code model for large binaries - * -Wno-stringop-overflow: (GCC only) Suppresses false positive warnings - * -Wno-tsan: (For GCC TSAN combination only) Suppresses atomic_thread_fence warnings - - - SANITIZERS_LINK_FLAGS: Linker flags for sanitizer runtime libraries. - Includes: - * -fsanitize=: Links sanitizer runtime libraries - * -mcmodel=large/medium: (GCC only) Matches compile-time code model - - - SANITIZERS_RELOCATION_FLAGS: (GCC only, x86_64 only) Code model flags for linking. - Used to handle large instrumented binaries on x86_64: - * -mcmodel=large: For AddressSanitizer (prevents relocation errors) - * -mcmodel=medium: For ThreadSanitizer (large model is incompatible) - On ARM64, these flags are omitted since GCC does not support - -mcmodel=large with -fPIC, and -mcmodel=medium does not exist. + The flags are applied to the 'common' interface library which is linked to all targets in the project. #]===================================================================] +include_guard(GLOBAL) include(CompilationEnv) -# Read environment variable -set(SANITIZERS "") -if(DEFINED ENV{SANITIZERS}) - set(SANITIZERS "$ENV{SANITIZERS}") -endif() - -# Set SANITIZERS_ENABLED flag for use in other modules -if(SANITIZERS MATCHES "address|thread|undefinedbehavior") - set(SANITIZERS_ENABLED TRUE) -else() +if(NOT DEFINED SANITIZERS) set(SANITIZERS_ENABLED FALSE) return() endif() +set(SANITIZERS_ENABLED TRUE) -# Sanitizers are not supported on Windows/MSVC -if(is_msvc) - message( - FATAL_ERROR - "Sanitizers are not supported on Windows/MSVC. " - "Please unset the SANITIZERS environment variable." - ) -endif() +message(STATUS "=== Configuring Sanitizers ===") +message(STATUS " SANITIZERS: ${SANITIZERS}") +message(STATUS " Compile flags: ${SANITIZERS_COMPILER_FLAGS}") +message(STATUS " Link flags: ${SANITIZERS_LINKER_FLAGS}") -message(STATUS "Configuring sanitizers: ${SANITIZERS}") - -# Parse SANITIZERS value to determine which sanitizers to enable -set(enable_asan FALSE) -set(enable_tsan FALSE) -set(enable_ubsan FALSE) - -# Normalize SANITIZERS into a list -set(san_list "${SANITIZERS}") -string(REPLACE "," ";" san_list "${san_list}") -separate_arguments(san_list) - -foreach(san IN LISTS san_list) - if(san STREQUAL "address") - set(enable_asan TRUE) - elseif(san STREQUAL "thread") - set(enable_tsan TRUE) - elseif(san STREQUAL "undefinedbehavior") - set(enable_ubsan TRUE) - else() - message( - FATAL_ERROR - "Unsupported sanitizer type: ${san}" - "Supported: address, thread, undefinedbehavior and their combinations." - ) - endif() -endforeach() - -# Validate sanitizer compatibility -if(enable_asan AND enable_tsan) - message( - FATAL_ERROR - "AddressSanitizer and ThreadSanitizer are incompatible and cannot be enabled simultaneously. " - "Use 'address' or 'thread', optionally with 'undefinedbehavior'." - ) -endif() - -# Frame pointer is required for meaningful stack traces. Sanitizers recommend minimum of -O1 for reasonable performance -set(SANITIZERS_COMPILE_FLAGS "-fno-omit-frame-pointer" "-O1") - -# Build the sanitizer flags list -set(SANITIZER_TYPES) - -if(enable_asan) - list(APPEND SANITIZER_TYPES "address") -elseif(enable_tsan) - list(APPEND SANITIZER_TYPES "thread") -endif() - -if(enable_ubsan) - # UB sanitizer flags - list(APPEND SANITIZER_TYPES "undefined" "float-divide-by-zero") - if(is_clang) - # Clang supports additional UB checks. More info here - # https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html - list(APPEND SANITIZER_TYPES "unsigned-integer-overflow") - endif() -endif() - -# Configure code model for GCC on amd64 Use large code model for ASAN to avoid relocation errors Use medium code model -# for TSAN (large is not compatible with TSAN) -set(SANITIZERS_RELOCATION_FLAGS) - -# Compiler-specific configuration +# GCC with sanitizers is incompatible with mold, gold, and lld linkers. +# Namely, the instrumented binary exceeds size limits imposed by these linkers. if(is_gcc) - # Disable mold, gold and lld linkers for GCC with sanitizers Use default linker (bfd/ld) which is more lenient with - # mixed code models This is needed since the size of instrumented binary exceeds the limits set by mold, lld and - # gold linkers set(use_mold OFF CACHE BOOL "Use mold linker" FORCE) set(use_gold OFF CACHE BOOL "Use gold linker" FORCE) set(use_lld OFF CACHE BOOL "Use lld linker" FORCE) @@ -142,82 +35,62 @@ if(is_gcc) STATUS " Disabled mold, gold, and lld linkers for GCC with sanitizers" ) - - # Suppress false positive warnings in GCC with stringop-overflow - list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-stringop-overflow") - - if(is_amd64 AND enable_asan) - message(STATUS " Using large code model (-mcmodel=large)") - list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=large") - list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=large") - elseif(enable_tsan) - # GCC doesn't support atomic_thread_fence with tsan. Suppress warnings. - list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-tsan") - if(is_amd64) - message(STATUS " Using medium code model (-mcmodel=medium)") - list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=medium") - list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=medium") - endif() - endif() - - # Join sanitizer flags with commas for -fsanitize option - list(JOIN SANITIZER_TYPES "," SANITIZER_TYPES_STR) - - # Add sanitizer to compile and link flags - list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") - set(SANITIZERS_LINK_FLAGS - "${SANITIZERS_RELOCATION_FLAGS}" - "-fsanitize=${SANITIZER_TYPES_STR}" - ) -elseif(is_clang) - # Add ignorelist for Clang (GCC doesn't support this) Use CMAKE_SOURCE_DIR to get the path to the ignorelist - set(IGNORELIST_PATH - "${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt" - ) - if(NOT EXISTS "${IGNORELIST_PATH}") - message( - FATAL_ERROR - "Sanitizer ignorelist not found: ${IGNORELIST_PATH}" - ) - endif() - - list( - APPEND SANITIZERS_COMPILE_FLAGS - "-fsanitize-ignorelist=${IGNORELIST_PATH}" - ) - message(STATUS " Using sanitizer ignorelist: ${IGNORELIST_PATH}") - - # Join sanitizer flags with commas for -fsanitize option - list(JOIN SANITIZER_TYPES "," SANITIZER_TYPES_STR) - - # Add sanitizer to compile and link flags - list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") - set(SANITIZERS_LINK_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") endif() -message(STATUS " Compile flags: ${SANITIZERS_COMPILE_FLAGS}") -message(STATUS " Link flags: ${SANITIZERS_LINK_FLAGS}") +# Flags arrive as space-separated strings; split into CMake lists before use +separate_arguments( + sanitizers_compiler_flags + UNIX_COMMAND + "${SANITIZERS_COMPILER_FLAGS}" +) +separate_arguments( + sanitizers_linker_flags + UNIX_COMMAND + "${SANITIZERS_LINKER_FLAGS}" +) -# Apply the sanitizer flags to the 'common' interface library This is the same library used by XrplCompiler.cmake target_compile_options( common INTERFACE - $<$:${SANITIZERS_COMPILE_FLAGS}> - $<$:${SANITIZERS_COMPILE_FLAGS}> + $<$:${sanitizers_compiler_flags}> + $<$:${sanitizers_compiler_flags}> ) +target_link_options(common INTERFACE ${sanitizers_linker_flags}) -# Apply linker flags -target_link_options(common INTERFACE ${SANITIZERS_LINK_FLAGS}) +# This module appends -fsanitize-ignorelist= for Clang builds. +# The ignorelist path contains CMAKE_SOURCE_DIR, so it must be set here, rather than in the Conan profile. +# GCC does not support -fsanitize-ignorelist. +if(is_clang) + set(ignorelist_path + "${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt" + ) + if(NOT EXISTS "${ignorelist_path}") + message( + FATAL_ERROR + "Sanitizer ignorelist not found: ${ignorelist_path}" + ) + endif() + target_compile_options( + common + INTERFACE + $<$:-fsanitize-ignorelist=${ignorelist_path}> + $<$:-fsanitize-ignorelist=${ignorelist_path}> + ) + message(STATUS " Ignorelist: ${ignorelist_path}") +endif() # Define SANITIZERS macro for BuildInfo.cpp set(sanitizers_list) -if(enable_asan) +if(SANITIZERS MATCHES "address") + set(enable_asan ON) list(APPEND sanitizers_list "ASAN") endif() -if(enable_tsan) +if(SANITIZERS MATCHES "thread") + set(enable_tsan ON) list(APPEND sanitizers_list "TSAN") endif() -if(enable_ubsan) +if(SANITIZERS MATCHES "undefinedbehavior") + set(enable_ubsan ON) list(APPEND sanitizers_list "UBSAN") endif() diff --git a/conan/profiles/ci b/conan/profiles/ci index c4c0898ad5..ae93187026 100644 --- a/conan/profiles/ci +++ b/conan/profiles/ci @@ -1 +1 @@ - include(sanitizers) +include(sanitizers) diff --git a/conan/profiles/sanitizers b/conan/profiles/sanitizers index 800e6eb48c..4a05fda734 100644 --- a/conan/profiles/sanitizers +++ b/conan/profiles/sanitizers @@ -3,96 +3,120 @@ include(default) {% set arch = detect_api.detect_arch() %} {% set sanitizers = os.getenv("SANITIZERS") %} -[conf] -{% if sanitizers %} - {% if compiler == "gcc" %} - {% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %} - {% set sanitizer_list = [] %} - {% set defines = [] %} - {% set model_code = "" %} - {% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1", "-Wno-stringop-overflow"] %} +{% if not sanitizers %} +{# Sanitizers not configured; no additional settings needed #} +{% else %} - {% if "address" in sanitizers %} - {% set _ = sanitizer_list.append("address") %} - {% if arch == "x86_64" %} - {% set model_code = "-mcmodel=large" %} - {% endif %} - {% set _ = defines.append("BOOST_USE_ASAN")%} - {% set _ = defines.append("BOOST_USE_UCONTEXT")%} - {% elif "thread" in sanitizers %} - {% set _ = sanitizer_list.append("thread") %} - {% if arch == "x86_64" %} - {% set model_code = "-mcmodel=medium" %} - {% endif %} - {% set _ = extra_cxxflags.append("-Wno-tsan") %} - {% set _ = defines.append("BOOST_USE_TSAN")%} - {% set _ = defines.append("BOOST_USE_UCONTEXT")%} - {% endif %} +{% if compiler == "msvc" %} + {{ "Sanitizers are not supported on Windows/MSVC. Please unset the SANITIZERS environment variable." }} +{% endif %} - {% if "undefinedbehavior" in sanitizers %} - {% set _ = sanitizer_list.append("undefined") %} - {% set _ = sanitizer_list.append("float-divide-by-zero") %} - {% endif %} +{% set known_sanitizers = ["address", "thread", "undefinedbehavior"] %} +{% set provided_sanitizers = [] %} +{% for san in sanitizers.split(",") %} + {% set san = san.strip() %} + {% if san not in known_sanitizers %} + {{ "Unknown sanitizer in SANITIZERS: " ~ san }} + {% endif %} + {% set _ = provided_sanitizers.append(san) %} +{% endfor %} - {% set sanitizer_flags = "-fsanitize=" ~ ",".join(sanitizer_list) ~ " " ~ model_code %} +{% set enable_asan = "address" in provided_sanitizers %} +{% set enable_tsan = "thread" in provided_sanitizers %} +{% set enable_ubsan = "undefinedbehavior" in provided_sanitizers %} - tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}'] - tools.build:sharedlinkflags+=['{{sanitizer_flags}}'] - tools.build:exelinkflags+=['{{sanitizer_flags}}'] - tools.build:defines+={{defines}} - {% endif %} - {% elif compiler == "apple-clang" or compiler == "clang" %} - {% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %} - {% set sanitizer_list = [] %} - {% set defines = [] %} - {% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1"] %} +{% if enable_asan and enable_tsan %} + {{ "AddressSanitizer and ThreadSanitizer are incompatible and cannot be enabled simultaneously." }} +{% endif %} - {% if "address" in sanitizers %} - {% set _ = sanitizer_list.append("address") %} - {% set _ = defines.append("BOOST_USE_ASAN")%} - {% set _ = defines.append("BOOST_USE_UCONTEXT")%} - {% elif "thread" in sanitizers %} - {% set _ = sanitizer_list.append("thread") %} - {% set _ = defines.append("BOOST_USE_TSAN")%} - {% set _ = defines.append("BOOST_USE_UCONTEXT")%} - {% endif %} +{% set sanitizer_types = [] %} +{% set defines = [] %} - {% if "undefinedbehavior" in sanitizers %} - {% set _ = sanitizer_list.append("undefined") %} - {% set _ = sanitizer_list.append("float-divide-by-zero") %} - {% set _ = sanitizer_list.append("unsigned-integer-overflow") %} - {% endif %} +{% if enable_asan %} + {% set _ = sanitizer_types.append("address") %} + {% set _ = defines.append("BOOST_USE_ASAN") %} + {% set _ = defines.append("BOOST_USE_UCONTEXT") %} +{% elif enable_tsan %} + {% set _ = sanitizer_types.append("thread") %} + {% set _ = defines.append("BOOST_USE_TSAN") %} + {% set _ = defines.append("BOOST_USE_UCONTEXT") %} +{% endif %} - {% set sanitizer_flags = "-fsanitize=" ~ ",".join(sanitizer_list) %} - - tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}'] - tools.build:sharedlinkflags+=['{{sanitizer_flags}}'] - tools.build:exelinkflags+=['{{sanitizer_flags}}'] - tools.build:defines+={{defines}} - {% endif %} +{% if enable_ubsan %} + {% set _ = sanitizer_types.append("undefined") %} + {% set _ = sanitizer_types.append("float-divide-by-zero") %} + {# Clang supports additional UB checks beyond the GCC baseline #} + {% if compiler == "clang" or compiler == "apple-clang" %} + {% set _ = sanitizer_types.append("unsigned-integer-overflow") %} {% endif %} {% endif %} +{# Frame pointer required for meaningful stack traces; -O1 for reasonable performance #} +{% set compile_flags = ["-fno-omit-frame-pointer", "-O1"] %} + +{% if compiler == "gcc" %} + {# Suppress false positive warnings with GCC #} + {% set _ = compile_flags.append("-Wno-stringop-overflow") %} + + {% set relocation_flags = [] %} + + {% if arch == "x86_64" and enable_asan %} + {# Large code model prevents relocation errors in instrumented ASAN binaries #} + {% set _ = compile_flags.append("-mcmodel=large") %} + {% set _ = relocation_flags.append("-mcmodel=large") %} + {% elif enable_tsan %} + {# GCC doesn't support atomic_thread_fence with TSAN; suppress warnings #} + {% set _ = compile_flags.append("-Wno-tsan") %} + {% if arch == "x86_64" %} + {# Medium code model for TSAN; large is incompatible #} + {% set _ = compile_flags.append("-mcmodel=medium") %} + {% set _ = relocation_flags.append("-mcmodel=medium") %} + {% endif %} + {% endif %} + + {% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %} + {% set _ = compile_flags.append(fsanitize) %} + {% set _ = relocation_flags.append(fsanitize) %} + + {% set sanitizer_compiler_flags = " ".join(compile_flags) %} + {% set sanitizer_linker_flags = " ".join(relocation_flags) %} +{% elif compiler == "clang" or compiler == "apple-clang" %} + {% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %} + {% set _ = compile_flags.append(fsanitize) %} + + {% set sanitizer_compiler_flags = " ".join(compile_flags) %} + {% set sanitizer_linker_flags = fsanitize %} +{% endif %} + +[conf] +tools.build:defines+={{defines}} +tools.build:cxxflags+=['{{sanitizer_compiler_flags}}'] +tools.build:sharedlinkflags+=['{{sanitizer_linker_flags}}'] +tools.build:exelinkflags+=['{{sanitizer_linker_flags}}'] + tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"] +# &: means "apply only to the consumer/root package" +&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags}}"} + [options] -{% if sanitizers %} - {% if "address" in sanitizers %} - # Build Boost.Context with ucontext backend (not fcontext) so that - # ASAN fiber-switching annotations (__sanitizer_start/finish_switch_fiber) - # are compiled into the library. fcontext (assembly) has no ASAN support. - # define=BOOST_USE_ASAN=1 is critical: it must be defined when building - # Boost.Context itself so the ucontext backend compiles in the ASAN annotations. - boost/*:extra_b2_flags=context-impl=ucontext address-sanitizer=on define=BOOST_USE_ASAN=1 - boost/*:without_context=False - # Boost stacktrace fails to build with some sanitizers - boost/*:without_stacktrace=True - {% elif "thread" in sanitizers %} - # Build Boost.Context with ucontext backend for TSAN. fcontext (assembly) - # has no TSAN annotations, so without this the BOOST_USE_TSAN/BOOST_USE_UCONTEXT - # defines in [conf] would be ineffective. - boost/*:extra_b2_flags=context-impl=ucontext thread-sanitizer=on define=BOOST_USE_TSAN=1 - boost/*:without_context=False - boost/*:without_stacktrace=True - {% endif %} +{% if enable_asan %} + # Build Boost.Context with ucontext backend (not fcontext) so that + # ASAN fiber-switching annotations (__sanitizer_start/finish_switch_fiber) + # are compiled into the library. fcontext (assembly) has no ASAN support. + # define=BOOST_USE_ASAN=1 is critical: it must be defined when building + # Boost.Context itself so the ucontext backend compiles in the ASAN annotations. + boost/*:extra_b2_flags=context-impl=ucontext address-sanitizer=on define=BOOST_USE_ASAN=1 + boost/*:without_context=False + # Boost stacktrace fails to build with some sanitizers + boost/*:without_stacktrace=True +{% elif enable_tsan %} + # Build Boost.Context with ucontext backend for TSAN. fcontext (assembly) + # has no TSAN annotations, so without this the BOOST_USE_TSAN/BOOST_USE_UCONTEXT + # defines in [conf] would be ineffective. + boost/*:extra_b2_flags=context-impl=ucontext thread-sanitizer=on define=BOOST_USE_TSAN=1 + boost/*:without_context=False + boost/*:without_stacktrace=True +{% endif %} + {% endif %} diff --git a/cspell.config.yaml b/cspell.config.yaml index e2c1bad46c..8b8589a8c9 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -71,6 +71,7 @@ words: - citardauq - clawback - clawbacks + - cmaketoolchain - coeffs - coldwallet - compr diff --git a/docs/build/sanitizers.md b/docs/build/sanitizers.md index e4949bcf86..6e7284fd06 100644 --- a/docs/build/sanitizers.md +++ b/docs/build/sanitizers.md @@ -1,15 +1,17 @@ # Sanitizer Configuration for Xrpld -This document explains how to properly configure and run sanitizers (AddressSanitizer, undefinedbehaviorSanitizer, ThreadSanitizer) with the xrpld project. +This document explains how to properly configure and run sanitizers (`AddressSanitizer`, `UndefinedBehaviorSanitizer`, `ThreadSanitizer`) with the xrpld project. Corresponding suppression files are located in the `sanitizers/suppressions` directory. +> [!CAUTION] +> Do not mix Address and Thread sanitizers - they are incompatible. +> Also, we don't yet support MSVC sanitizers, so this is only for Clang/GCC builds. + - [Sanitizer Configuration for Xrpld](#sanitizer-configuration-for-xrpld) - [Building with Sanitizers](#building-with-sanitizers) - [Summary](#summary) - [Build steps:](#build-steps) - [Install dependencies](#install-dependencies) - - [Call CMake](#call-cmake) - - [Build](#build) - [Running Tests with Sanitizers](#running-tests-with-sanitizers) - [AddressSanitizer (ASAN)](#addresssanitizer-asan) - [ThreadSanitizer (TSan)](#threadsanitizer-tsan) @@ -33,9 +35,13 @@ Corresponding suppression files are located in the `sanitizers/suppressions` dir Follow the same instructions as mentioned in [BUILD.md](../../BUILD.md) but with the following changes: 1. Make sure you have a clean build directory. -2. Set the `SANITIZERS` environment variable before calling conan install and cmake. Only set it once. Make sure both conan and cmake read the same values. +2. Set the `SANITIZERS` environment variable before calling `conan install`. Only set it once. Example: `export SANITIZERS=address,undefinedbehavior` -3. Optionally use `--profile:all sanitizers` with Conan to build dependencies with sanitizer instrumentation. [!NOTE]Building with sanitizer-instrumented dependencies is slower but produces fewer false positives. +3. Use `--profile:all sanitizers` with Conan to build dependencies with sanitizer instrumentation. + + > [!NOTE] + > Building with sanitizer-instrumented dependencies is slower but produces fewer false positives. + 4. Set `ASAN_OPTIONS`, `LSAN_OPTIONS`, `UBSAN_OPTIONS` and `TSAN_OPTIONS` environment variables to configure sanitizer behavior when running executables. [More details below](#running-tests-with-sanitizers). --- @@ -51,36 +57,13 @@ cd .build #### Install dependencies -The `SANITIZERS` environment variable is used by both Conan and CMake. +The `SANITIZERS` environment variable is used during `conan install` command. ```bash -export SANITIZERS=address,undefinedbehavior -# Standard build (without instrumenting dependencies) -conan install .. --output-folder . --build missing --settings build_type=Debug - -# Or with sanitizer-instrumented dependencies (takes longer but fewer false positives) -conan install .. --output-folder . --profile:all sanitizers --build missing --settings build_type=Debug +SANITIZERS=address,undefinedbehavior conan install .. --output-folder . --build missing --settings build_type=Debug --profile:all sanitizers ``` -[!CAUTION] -Do not mix Address and Thread sanitizers - they are incompatible. - -Since you already set the `SANITIZERS` environment variable when running Conan, same values will be read for the next part. - -#### Call CMake - -```bash -cmake .. -G Ninja \ - -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ - -DCMAKE_BUILD_TYPE=Debug \ - -Dtests=ON -Dxrpld=ON -``` - -#### Build - -```bash -cmake --build . --parallel 4 -``` +Proceed with the rest of the build instructions as mentioned in [BUILD.md](../../BUILD.md). ## Running Tests with Sanitizers From 5e1c35f7f7de77f967c5f6ea4b5636a22bedd145 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Tue, 5 May 2026 13:18:26 -0400 Subject: [PATCH 06/18] fix: Fix regressions in `server_definitions` (#7008) --- src/test/rpc/ServerDefinitions_test.cpp | 51 ++++++++++++++----- .../server_info/ServerDefinitions.cpp | 25 ++++----- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/test/rpc/ServerDefinitions_test.cpp b/src/test/rpc/ServerDefinitions_test.cpp index d53c845726..bac1b4f7a5 100644 --- a/src/test/rpc/ServerDefinitions_test.cpp +++ b/src/test/rpc/ServerDefinitions_test.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include + namespace xrpl::test { class ServerDefinitions_test : public beast::unit_test::Suite @@ -37,20 +40,23 @@ public: { auto const firstField = result[jss::result][jss::FIELDS][0u]; - BEAST_EXPECT(firstField[0u].asString() == "Generic"); + BEAST_EXPECT(firstField[0u].asString() == "Invalid"); BEAST_EXPECT(firstField[1][jss::isSerialized].asBool() == false); BEAST_EXPECT(firstField[1][jss::isSigningField].asBool() == false); BEAST_EXPECT(firstField[1][jss::isVLEncoded].asBool() == false); - BEAST_EXPECT(firstField[1][jss::nth].asUInt() == 0); + BEAST_EXPECT(firstField[1][jss::nth].asInt() == -1); BEAST_EXPECT(firstField[1][jss::type].asString() == "Unknown"); } - BEAST_EXPECT( - result[jss::result][jss::LEDGER_ENTRY_TYPES]["AccountRoot"].asUInt() == 97); - BEAST_EXPECT( - result[jss::result][jss::TRANSACTION_RESULTS]["tecDIR_FULL"].asUInt() == 121); - BEAST_EXPECT(result[jss::result][jss::TRANSACTION_TYPES]["Payment"].asUInt() == 0); - BEAST_EXPECT(result[jss::result][jss::TYPES]["AccountID"].asUInt() == 8); + { + auto const field = result[jss::result][jss::FIELDS][6u]; + BEAST_EXPECT(field[0u].asString() == "LedgerEntryType"); + BEAST_EXPECT(field[1][jss::isSerialized].asBool() == true); + BEAST_EXPECT(field[1][jss::isSigningField].asBool() == true); + BEAST_EXPECT(field[1][jss::isVLEncoded].asBool() == false); + BEAST_EXPECT(field[1][jss::nth].asUInt() == 1); + BEAST_EXPECT(field[1][jss::type].asString() == "UInt16"); + } // check exception SFields { @@ -74,17 +80,34 @@ public: BEAST_EXPECT(fieldExists("index")); } + // verify no duplicate field names in FIELDS array + { + std::set fieldNames; + for (auto const& field : result[jss::result][jss::FIELDS]) + { + auto const name = field[0u].asString(); + BEAST_EXPECT(fieldNames.insert(name).second); + } + } + // test that base_uint types are replaced with "Hash" prefix { auto const types = result[jss::result][jss::TYPES]; - BEAST_EXPECT(types["Hash128"].asUInt() == 4); - BEAST_EXPECT(types["Hash160"].asUInt() == 17); - BEAST_EXPECT(types["Hash192"].asUInt() == 21); - BEAST_EXPECT(types["Hash256"].asUInt() == 5); - BEAST_EXPECT(types["Hash384"].asUInt() == 22); - BEAST_EXPECT(types["Hash512"].asUInt() == 23); + BEAST_EXPECT(types.isMember("Hash128") && types["Hash128"].asUInt() == 4); + BEAST_EXPECT(types.isMember("Hash160") && types["Hash160"].asUInt() == 17); + BEAST_EXPECT(types.isMember("Hash192") && types["Hash192"].asUInt() == 21); + BEAST_EXPECT(types.isMember("Hash256") && types["Hash256"].asUInt() == 5); + BEAST_EXPECT(types.isMember("Hash384") && types["Hash384"].asUInt() == 22); + BEAST_EXPECT(types.isMember("Hash512") && types["Hash512"].asUInt() == 23); } + BEAST_EXPECT( + result[jss::result][jss::LEDGER_ENTRY_TYPES]["AccountRoot"].asUInt() == 97); + BEAST_EXPECT( + result[jss::result][jss::TRANSACTION_RESULTS]["tecDIR_FULL"].asUInt() == 121); + BEAST_EXPECT(result[jss::result][jss::TRANSACTION_TYPES]["Payment"].asUInt() == 0); + BEAST_EXPECT(result[jss::result][jss::TYPES]["AccountID"].asUInt() == 8); + // test the properties of the LEDGER_ENTRY_FLAGS section { BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS)); diff --git a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp index ca887bb0c8..6f75dec61b 100644 --- a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp +++ b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp @@ -149,18 +149,6 @@ ServerDefinitions::ServerDefinitions() : defs_{json::ObjectValue} defs_[jss::FIELDS] = json::ArrayValue; uint32_t i = 0; - { - json::Value a = json::ArrayValue; - a[0U] = "Generic"; - json::Value v = json::ObjectValue; - v[jss::nth] = 0; - v[jss::isVLEncoded] = false; - v[jss::isSerialized] = false; - v[jss::isSigningField] = false; - v[jss::type] = "Unknown"; - a[1U] = v; - defs_[jss::FIELDS][i++] = a; - } { json::Value a = json::ArrayValue; @@ -227,21 +215,28 @@ ServerDefinitions::ServerDefinitions() : defs_{json::ObjectValue} defs_[jss::FIELDS][i++] = a; } - for (auto const& [code, field] : xrpl::SField::getKnownCodeToField()) + // copy into a sorted map to ensure deterministic output order (sorted by fieldCode) + static std::map const kSORTED_FIELDS( + xrpl::SField::getKnownCodeToField().begin(), xrpl::SField::getKnownCodeToField().end()); + + for (auto const& [code, field] : kSORTED_FIELDS) { if (field->fieldName.empty()) continue; json::Value innerObj = json::ObjectValue; - uint32_t type = field->fieldType; + int32_t const type = field->fieldType; innerObj[jss::nth] = field->fieldValue; // whether the field is variable-length encoded this means that the length is included // before the content innerObj[jss::isVLEncoded] = - (type == 7U /* Blob */ || type == 8U /* AccountID */ || type == 19U /* Vector256 */); + (type == STI_VL || type == STI_ACCOUNT || type == STI_VECTOR256); + static_assert( + STI_VL == 7U && STI_ACCOUNT == 8U && STI_VECTOR256 == 19U, + "STI_VL, STI_ACCOUNT, STI_VECTOR256 must be 7, 8, 19 respectively"); // whether the field is included in serialization innerObj[jss::isSerialized] = From 50244a86373a4be5ca96eb97ed4bbe4843e6bd42 Mon Sep 17 00:00:00 2001 From: Vet <109047316+xVet@users.noreply.github.com> Date: Wed, 6 May 2026 13:19:57 +0200 Subject: [PATCH 07/18] chore: Update default values of base and owner reserve to 1/0.2 (#6382) Co-authored-by: Bart --- cfg/xrpld-example.cfg | 4 ++-- src/test/rpc/AccountTx_test.cpp | 2 +- src/xrpld/core/Config.h | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cfg/xrpld-example.cfg b/cfg/xrpld-example.cfg index 4b17bf0500..45269903b2 100644 --- a/cfg/xrpld-example.cfg +++ b/cfg/xrpld-example.cfg @@ -1258,7 +1258,7 @@ # default. Don't change this without understanding the consequences. # # Example: -# account_reserve = 10000000 # 10 XRP +# account_reserve = 1000000 # 1 XRP # # owner_reserve = # @@ -1270,7 +1270,7 @@ # default. Don't change this without understanding the consequences. # # Example: -# owner_reserve = 2000000 # 2 XRP +# owner_reserve = 200000 # 0.2 XRP # #------------------------------------------------------------------------------- # diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 4de0928555..f1b7533eaa 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -774,7 +774,7 @@ class AccountTx_test : public beast::unit_test::Suite // All it takes is a large enough XRP payment to resurrect // becky's account. Try too small a payment. - env(pay(alice, becky, drops(env.current()->fees().accountReserve(0)) - XRP(1)), + env(pay(alice, becky, drops(env.current()->fees().accountReserve(0)) - drops(1)), Ter(tecNO_DST_INSUF_XRP)); env.close(); diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index 10fbadaae7..4936906a9a 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -51,10 +51,10 @@ struct FeeSetup XRPAmount reference_fee{10}; /** The account reserve requirement in drops. */ - XRPAmount account_reserve{10 * kDROPS_PER_XRP}; + XRPAmount account_reserve{1'000'000}; // 1 XRP /** The per-owned item reserve requirement in drops. */ - XRPAmount owner_reserve{2 * kDROPS_PER_XRP}; + XRPAmount owner_reserve{200'000}; // 0.2 XRP /* (Remember to update the example cfg files when changing any of these * values.) */ From a4720d044902d26c5f8a7a021b947e471d961f73 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Wed, 6 May 2026 14:55:24 +0200 Subject: [PATCH 08/18] chore: Mark empty transactor invariants as future work (#7080) --- .../tx/transactors/account/AccountDelete.cpp | 2 ++ .../tx/transactors/account/AccountSet.cpp | 2 ++ .../tx/transactors/account/SetRegularKey.cpp | 2 ++ .../tx/transactors/account/SignerListSet.cpp | 2 ++ .../tx/transactors/bridge/XChainBridge.cpp | 16 ++++++++++++++++ src/libxrpl/tx/transactors/check/CheckCancel.cpp | 2 ++ src/libxrpl/tx/transactors/check/CheckCash.cpp | 2 ++ src/libxrpl/tx/transactors/check/CheckCreate.cpp | 2 ++ .../transactors/credentials/CredentialAccept.cpp | 2 ++ .../transactors/credentials/CredentialCreate.cpp | 2 ++ .../transactors/credentials/CredentialDelete.cpp | 2 ++ .../tx/transactors/delegate/DelegateSet.cpp | 2 ++ src/libxrpl/tx/transactors/dex/AMMBid.cpp | 2 ++ src/libxrpl/tx/transactors/dex/AMMClawback.cpp | 2 ++ src/libxrpl/tx/transactors/dex/AMMCreate.cpp | 2 ++ src/libxrpl/tx/transactors/dex/AMMDelete.cpp | 2 ++ src/libxrpl/tx/transactors/dex/AMMDeposit.cpp | 2 ++ src/libxrpl/tx/transactors/dex/AMMVote.cpp | 2 ++ src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp | 2 ++ src/libxrpl/tx/transactors/dex/OfferCancel.cpp | 2 ++ src/libxrpl/tx/transactors/dex/OfferCreate.cpp | 2 ++ src/libxrpl/tx/transactors/did/DIDDelete.cpp | 2 ++ src/libxrpl/tx/transactors/did/DIDSet.cpp | 2 ++ .../tx/transactors/escrow/EscrowCancel.cpp | 2 ++ .../tx/transactors/escrow/EscrowCreate.cpp | 2 ++ .../tx/transactors/escrow/EscrowFinish.cpp | 2 ++ .../lending/LoanBrokerCoverClawback.cpp | 2 ++ .../lending/LoanBrokerCoverDeposit.cpp | 2 ++ .../lending/LoanBrokerCoverWithdraw.cpp | 2 ++ .../tx/transactors/lending/LoanBrokerDelete.cpp | 2 ++ .../tx/transactors/lending/LoanBrokerSet.cpp | 2 ++ .../tx/transactors/lending/LoanDelete.cpp | 2 ++ .../tx/transactors/lending/LoanManage.cpp | 2 ++ src/libxrpl/tx/transactors/lending/LoanPay.cpp | 2 ++ src/libxrpl/tx/transactors/lending/LoanSet.cpp | 2 ++ .../tx/transactors/nft/NFTokenAcceptOffer.cpp | 2 ++ src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp | 2 ++ .../tx/transactors/nft/NFTokenCancelOffer.cpp | 2 ++ .../tx/transactors/nft/NFTokenCreateOffer.cpp | 2 ++ src/libxrpl/tx/transactors/nft/NFTokenMint.cpp | 2 ++ src/libxrpl/tx/transactors/nft/NFTokenModify.cpp | 2 ++ .../tx/transactors/oracle/OracleDelete.cpp | 2 ++ src/libxrpl/tx/transactors/oracle/OracleSet.cpp | 2 ++ .../tx/transactors/payment/DepositPreauth.cpp | 2 ++ src/libxrpl/tx/transactors/payment/Payment.cpp | 2 ++ .../payment_channel/PaymentChannelClaim.cpp | 2 ++ .../payment_channel/PaymentChannelCreate.cpp | 2 ++ .../payment_channel/PaymentChannelFund.cpp | 2 ++ .../PermissionedDomainDelete.cpp | 2 ++ .../PermissionedDomainSet.cpp | 2 ++ src/libxrpl/tx/transactors/system/Batch.cpp | 2 ++ src/libxrpl/tx/transactors/system/Change.cpp | 2 ++ .../tx/transactors/system/LedgerStateFix.cpp | 2 ++ .../tx/transactors/system/TicketCreate.cpp | 2 ++ src/libxrpl/tx/transactors/token/Clawback.cpp | 2 ++ .../tx/transactors/token/MPTokenAuthorize.cpp | 2 ++ .../transactors/token/MPTokenIssuanceCreate.cpp | 2 ++ .../transactors/token/MPTokenIssuanceDestroy.cpp | 2 ++ .../tx/transactors/token/MPTokenIssuanceSet.cpp | 2 ++ src/libxrpl/tx/transactors/token/TrustSet.cpp | 2 ++ .../tx/transactors/vault/VaultClawback.cpp | 2 ++ src/libxrpl/tx/transactors/vault/VaultCreate.cpp | 2 ++ src/libxrpl/tx/transactors/vault/VaultDelete.cpp | 2 ++ .../tx/transactors/vault/VaultDeposit.cpp | 2 ++ src/libxrpl/tx/transactors/vault/VaultSet.cpp | 2 ++ .../tx/transactors/vault/VaultWithdraw.cpp | 2 ++ 66 files changed, 146 insertions(+) diff --git a/src/libxrpl/tx/transactors/account/AccountDelete.cpp b/src/libxrpl/tx/transactors/account/AccountDelete.cpp index f9d1913dff..b6fb3be683 100644 --- a/src/libxrpl/tx/transactors/account/AccountDelete.cpp +++ b/src/libxrpl/tx/transactors/account/AccountDelete.cpp @@ -422,6 +422,7 @@ AccountDelete::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -432,6 +433,7 @@ AccountDelete::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/account/AccountSet.cpp b/src/libxrpl/tx/transactors/account/AccountSet.cpp index da24640bfa..2ab87c78c4 100644 --- a/src/libxrpl/tx/transactors/account/AccountSet.cpp +++ b/src/libxrpl/tx/transactors/account/AccountSet.cpp @@ -663,11 +663,13 @@ AccountSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool AccountSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/account/SetRegularKey.cpp b/src/libxrpl/tx/transactors/account/SetRegularKey.cpp index bc59ed4fc1..65e6840aa0 100644 --- a/src/libxrpl/tx/transactors/account/SetRegularKey.cpp +++ b/src/libxrpl/tx/transactors/account/SetRegularKey.cpp @@ -86,6 +86,7 @@ SetRegularKey::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -96,6 +97,7 @@ SetRegularKey::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/account/SignerListSet.cpp b/src/libxrpl/tx/transactors/account/SignerListSet.cpp index 3060abe6df..ccc04c8155 100644 --- a/src/libxrpl/tx/transactors/account/SignerListSet.cpp +++ b/src/libxrpl/tx/transactors/account/SignerListSet.cpp @@ -411,6 +411,7 @@ SignerListSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -421,6 +422,7 @@ SignerListSet::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp index 09e5a7284a..33da5c417a 100644 --- a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp @@ -2228,6 +2228,7 @@ XChainCreateBridge::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -2238,6 +2239,7 @@ XChainCreateBridge::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } @@ -2247,6 +2249,7 @@ BridgeModify::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -2257,6 +2260,7 @@ BridgeModify::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } @@ -2266,11 +2270,13 @@ XChainClaim::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool XChainClaim::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } @@ -2280,6 +2286,7 @@ XChainCommit::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -2290,6 +2297,7 @@ XChainCommit::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } @@ -2299,6 +2307,7 @@ XChainCreateClaimID::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -2309,6 +2318,7 @@ XChainCreateClaimID::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } @@ -2318,6 +2328,7 @@ XChainAddClaimAttestation::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -2328,6 +2339,7 @@ XChainAddClaimAttestation::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } @@ -2337,6 +2349,7 @@ XChainAddAccountCreateAttestation::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -2347,6 +2360,7 @@ XChainAddAccountCreateAttestation::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } @@ -2356,6 +2370,7 @@ XChainCreateAccountCommit::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -2366,6 +2381,7 @@ XChainCreateAccountCommit::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/check/CheckCancel.cpp b/src/libxrpl/tx/transactors/check/CheckCancel.cpp index c30c116e58..7f1708fd81 100644 --- a/src/libxrpl/tx/transactors/check/CheckCancel.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCancel.cpp @@ -107,11 +107,13 @@ CheckCancel::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool CheckCancel::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index 34288425a5..bef04510f8 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -594,11 +594,13 @@ CheckCash::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool CheckCash::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/check/CheckCreate.cpp b/src/libxrpl/tx/transactors/check/CheckCreate.cpp index 6a13819615..4c0f1858b6 100644 --- a/src/libxrpl/tx/transactors/check/CheckCreate.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCreate.cpp @@ -253,11 +253,13 @@ CheckCreate::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool CheckCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp index 0dd21d3bd5..2d27d1bf23 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp @@ -129,6 +129,7 @@ CredentialAccept::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -139,6 +140,7 @@ CredentialAccept::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp index e54c48287f..9d6231afe1 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp @@ -184,6 +184,7 @@ CredentialCreate::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -194,6 +195,7 @@ CredentialCreate::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp b/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp index 42880bfdf8..595064e824 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp @@ -102,6 +102,7 @@ CredentialDelete::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -112,6 +113,7 @@ CredentialDelete::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index baf208a305..feae97720f 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -179,11 +179,13 @@ DelegateSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool DelegateSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/dex/AMMBid.cpp b/src/libxrpl/tx/transactors/dex/AMMBid.cpp index d4783c783b..894e6a9451 100644 --- a/src/libxrpl/tx/transactors/dex/AMMBid.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMBid.cpp @@ -384,11 +384,13 @@ AMMBid::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool AMMBid::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp index 112d290e72..438b0c5c59 100644 --- a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp @@ -393,11 +393,13 @@ AMMClawback::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool AMMClawback::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp index c90ea0a6ab..a2557b9abb 100644 --- a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp @@ -398,11 +398,13 @@ AMMCreate::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool AMMCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/dex/AMMDelete.cpp b/src/libxrpl/tx/transactors/dex/AMMDelete.cpp index 0ca7fd7c34..d9056fcbc0 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDelete.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDelete.cpp @@ -72,11 +72,13 @@ AMMDelete::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool AMMDelete::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index d2995c309f..087065d51a 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -1013,11 +1013,13 @@ AMMDeposit::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool AMMDeposit::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/dex/AMMVote.cpp b/src/libxrpl/tx/transactors/dex/AMMVote.cpp index 4ab3653792..eb4a5e87be 100644 --- a/src/libxrpl/tx/transactors/dex/AMMVote.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMVote.cpp @@ -255,11 +255,13 @@ AMMVote::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool AMMVote::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index bae9d098a3..0ecd6a4fa2 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -1151,11 +1151,13 @@ AMMWithdraw::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool AMMWithdraw::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/dex/OfferCancel.cpp b/src/libxrpl/tx/transactors/dex/OfferCancel.cpp index 72682149a3..547db00dca 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCancel.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCancel.cpp @@ -75,11 +75,13 @@ OfferCancel::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool OfferCancel::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index b69ed766c5..f5bcdcf2ce 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -978,11 +978,13 @@ OfferCreate::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool OfferCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/did/DIDDelete.cpp b/src/libxrpl/tx/transactors/did/DIDDelete.cpp index a323822b9c..9f9f54e6f6 100644 --- a/src/libxrpl/tx/transactors/did/DIDDelete.cpp +++ b/src/libxrpl/tx/transactors/did/DIDDelete.cpp @@ -76,11 +76,13 @@ DIDDelete::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool DIDDelete::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/did/DIDSet.cpp b/src/libxrpl/tx/transactors/did/DIDSet.cpp index 69bb9dd839..fbed821280 100644 --- a/src/libxrpl/tx/transactors/did/DIDSet.cpp +++ b/src/libxrpl/tx/transactors/did/DIDSet.cpp @@ -156,11 +156,13 @@ DIDSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool DIDSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index 9a48123201..05d6a29454 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -225,6 +225,7 @@ EscrowCancel::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -235,6 +236,7 @@ EscrowCancel::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index d5bbe17325..dd9c1b84b4 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -536,6 +536,7 @@ EscrowCreate::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -546,6 +547,7 @@ EscrowCreate::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 116466a1a3..fb6c77c61a 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -405,6 +405,7 @@ EscrowFinish::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -415,6 +416,7 @@ EscrowFinish::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index 14bc5f7a51..c59a02d4c2 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -365,6 +365,7 @@ LoanBrokerCoverClawback::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -375,6 +376,7 @@ LoanBrokerCoverClawback::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp index a8fa91b648..a35878f5c6 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp @@ -139,6 +139,7 @@ LoanBrokerCoverDeposit::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -149,6 +150,7 @@ LoanBrokerCoverDeposit::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index ca4136be0a..127d23a270 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -192,6 +192,7 @@ LoanBrokerCoverWithdraw::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -202,6 +203,7 @@ LoanBrokerCoverWithdraw::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp index 0d6d6e6cd6..2c5767f9cc 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp @@ -197,6 +197,7 @@ LoanBrokerDelete::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -207,6 +208,7 @@ LoanBrokerDelete::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp index bf00a344a7..97ef73515a 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp @@ -284,6 +284,7 @@ LoanBrokerSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -294,6 +295,7 @@ LoanBrokerSet::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp index ff33bfb9fb..928ba67db4 100644 --- a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp @@ -147,11 +147,13 @@ LoanDelete::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool LoanDelete::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/lending/LoanManage.cpp b/src/libxrpl/tx/transactors/lending/LoanManage.cpp index d1cd5de505..648f33b980 100644 --- a/src/libxrpl/tx/transactors/lending/LoanManage.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanManage.cpp @@ -440,11 +440,13 @@ LoanManage::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool LoanManage::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index b8d73bfd65..bfd78ea82b 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -651,11 +651,13 @@ LoanPay::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool LoanPay::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index 9503eeef03..c921017d15 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -652,11 +652,13 @@ LoanSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool LoanSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp index 06bd1301cb..2aa2720e46 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp @@ -574,6 +574,7 @@ NFTokenAcceptOffer::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -584,6 +585,7 @@ NFTokenAcceptOffer::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp b/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp index fb57b14c15..95791d220e 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp @@ -100,11 +100,13 @@ NFTokenBurn::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool NFTokenBurn::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp index e76d7677aa..9186d32424 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp @@ -103,6 +103,7 @@ NFTokenCancelOffer::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -113,6 +114,7 @@ NFTokenCancelOffer::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp index 9ca5220a8c..727290a80c 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp @@ -97,6 +97,7 @@ NFTokenCreateOffer::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -107,6 +108,7 @@ NFTokenCreateOffer::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp index c7cb37ad94..358e9b6bd6 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp @@ -349,11 +349,13 @@ NFTokenMint::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool NFTokenMint::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp b/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp index b56ee9b061..a21196ff57 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp @@ -74,6 +74,7 @@ NFTokenModify::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -84,6 +85,7 @@ NFTokenModify::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp index c6896b78d4..99f2a14900 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp @@ -94,6 +94,7 @@ OracleDelete::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -104,6 +105,7 @@ OracleDelete::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp index 7c2a817ed1..ea828256b8 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp @@ -334,11 +334,13 @@ OracleSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool OracleSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp index e4a060ea31..2e298a70d5 100644 --- a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp +++ b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp @@ -303,6 +303,7 @@ DepositPreauth::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -313,6 +314,7 @@ DepositPreauth::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/payment/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp index d283874296..d7a1a63ef1 100644 --- a/src/libxrpl/tx/transactors/payment/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -684,11 +684,13 @@ Payment::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool Payment::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp index ec2d4dd70d..13c6a9b235 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp @@ -207,6 +207,7 @@ PaymentChannelClaim::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -217,6 +218,7 @@ PaymentChannelClaim::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp index c86590aa98..bbc8d9f13a 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp @@ -192,6 +192,7 @@ PaymentChannelCreate::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -202,6 +203,7 @@ PaymentChannelCreate::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index 4d2590c382..e392df213a 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -112,6 +112,7 @@ PaymentChannelFund::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -122,6 +123,7 @@ PaymentChannelFund::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp index befae454b7..0f62c5d28f 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp @@ -79,6 +79,7 @@ PermissionedDomainDelete::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -89,6 +90,7 @@ PermissionedDomainDelete::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp index 92f260db4e..f794844a57 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp @@ -137,6 +137,7 @@ PermissionedDomainSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -147,6 +148,7 @@ PermissionedDomainSet::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/system/Batch.cpp b/src/libxrpl/tx/transactors/system/Batch.cpp index f9ae9dc912..a7c9ef52a0 100644 --- a/src/libxrpl/tx/transactors/system/Batch.cpp +++ b/src/libxrpl/tx/transactors/system/Batch.cpp @@ -523,11 +523,13 @@ Batch::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool Batch::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/system/Change.cpp b/src/libxrpl/tx/transactors/system/Change.cpp index 5f51f935f3..13c09e8187 100644 --- a/src/libxrpl/tx/transactors/system/Change.cpp +++ b/src/libxrpl/tx/transactors/system/Change.cpp @@ -416,11 +416,13 @@ Change::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool Change::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp index 2bc71f8c86..2f30311e6f 100644 --- a/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp +++ b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp @@ -78,6 +78,7 @@ LedgerStateFix::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -88,6 +89,7 @@ LedgerStateFix::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/system/TicketCreate.cpp b/src/libxrpl/tx/transactors/system/TicketCreate.cpp index 75095772dd..f690243d86 100644 --- a/src/libxrpl/tx/transactors/system/TicketCreate.cpp +++ b/src/libxrpl/tx/transactors/system/TicketCreate.cpp @@ -140,6 +140,7 @@ TicketCreate::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -150,6 +151,7 @@ TicketCreate::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/token/Clawback.cpp b/src/libxrpl/tx/transactors/token/Clawback.cpp index 8c99ce9b08..0a524ac6d0 100644 --- a/src/libxrpl/tx/transactors/token/Clawback.cpp +++ b/src/libxrpl/tx/transactors/token/Clawback.cpp @@ -284,11 +284,13 @@ Clawback::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool Clawback::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index c0fbb7f10b..dea65bd5a0 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -162,6 +162,7 @@ MPTokenAuthorize::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -172,6 +173,7 @@ MPTokenAuthorize::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index 64652a80b0..567f5c3480 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -178,6 +178,7 @@ MPTokenIssuanceCreate::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -188,6 +189,7 @@ MPTokenIssuanceCreate::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp index 80b185641a..be03d78c64 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp @@ -64,6 +64,7 @@ MPTokenIssuanceDestroy::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -74,6 +75,7 @@ MPTokenIssuanceDestroy::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index fb0ff06ac2..05089621c6 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -384,6 +384,7 @@ MPTokenIssuanceSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -394,6 +395,7 @@ MPTokenIssuanceSet::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index 27252aef46..3c0d9ed7ae 100644 --- a/src/libxrpl/tx/transactors/token/TrustSet.cpp +++ b/src/libxrpl/tx/transactors/token/TrustSet.cpp @@ -682,11 +682,13 @@ TrustSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool TrustSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp index 8743227e5c..b6d92a6083 100644 --- a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp @@ -461,6 +461,7 @@ VaultClawback::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -471,6 +472,7 @@ VaultClawback::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp index 0346503ae7..f5831e46aa 100644 --- a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp @@ -255,11 +255,13 @@ VaultCreate::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool VaultCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp index 58b0fdee82..20415b7816 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp @@ -218,11 +218,13 @@ VaultDelete::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool VaultDelete::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp index 501ba58ca5..4b8d8b5c18 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp @@ -284,6 +284,7 @@ VaultDeposit::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -294,6 +295,7 @@ VaultDeposit::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/vault/VaultSet.cpp b/src/libxrpl/tx/transactors/vault/VaultSet.cpp index 92dbb198f3..78627f22b7 100644 --- a/src/libxrpl/tx/transactors/vault/VaultSet.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultSet.cpp @@ -185,11 +185,13 @@ VaultSet::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool VaultSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index 6932dc21f2..94c6f0f6d2 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -297,6 +297,7 @@ VaultWithdraw::visitInvariantEntry( std::shared_ptr const&, std::shared_ptr const&) { + // No transaction-specific invariants yet (future work). } bool @@ -307,6 +308,7 @@ VaultWithdraw::finalizeInvariants( ReadView const&, beast::Journal const&) { + // No transaction-specific invariants yet (future work). return true; } From fcae50a487f8164e2c19665dcd02c0155a07a649 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 6 May 2026 15:36:42 +0100 Subject: [PATCH 09/18] chore: Update conan.lock (#7081) Co-authored-by: Bart --- BUILD.md | 2 +- conan.lock | 8 ++++---- conanfile.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/BUILD.md b/BUILD.md index 5479242f91..632832c002 100644 --- a/BUILD.md +++ b/BUILD.md @@ -141,7 +141,7 @@ Alternatively, you can pull our recipes from the repository and export them loca ```bash # Define which recipes to export. -recipes=('abseil' 'ed25519' 'grpc' 'm4' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi') +recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi') # Selectively check out the recipes from our CCI fork. cd external diff --git a/conan.lock b/conan.lock index f1d6ed3fa5..3daed35014 100644 --- a/conan.lock +++ b/conan.lock @@ -10,13 +10,13 @@ "rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86", "re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1774398111.888", "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12", - "openssl/3.6.1#e6399de266349245a4542fc5f6c71552%1774458290.139", - "nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1774883011.384", + "openssl/3.6.2#4789bbf131b77d0515d15e094c8f697f%1778071755.506", + "nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1775040983.408", "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914", "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492", "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03", "libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736", - "jemalloc/5.3.0#e951da9cf599e956cebc117880d2d9f8%1729241615.244", + "jemalloc/5.3.0#c671e612af76700db5957c9857978a1c%1776700030.961", "gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152", "grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1774467387.342", "ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772", @@ -32,7 +32,7 @@ "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12", "nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707", "msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649", - "m4/1.4.19#5d7a4994e5875d76faf7acf3ed056036%1774365463.87", + "m4/1.4.19#4523e4347b55cd26ae918bd5770cab9a%1778062762.471", "cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1774439249.183", "b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1774439233.447", "automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56", diff --git a/conanfile.py b/conanfile.py index 66de2e2d57..4f70ad5b33 100644 --- a/conanfile.py +++ b/conanfile.py @@ -32,7 +32,7 @@ class Xrpl(ConanFile): "grpc/1.78.1", "libarchive/3.8.1", "nudb/2.0.9", - "openssl/3.6.1", + "openssl/3.6.2", "secp256k1/0.7.1", "soci/4.0.3", "zlib/1.3.1", From 13b72a4120bc1facd4f444419d241b8ed7af67c3 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 6 May 2026 18:05:11 +0100 Subject: [PATCH 10/18] chore: Update zlib to 1.3.2, sqlite to 3.53.0, libarchive to 3.8.7, jemalloc to 5.3.1, boost to 1.91.0 (#7084) --- conan.lock | 18 +++++++++--------- conanfile.py | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/conan.lock b/conan.lock index 3daed35014..e2eb8d871a 100644 --- a/conan.lock +++ b/conan.lock @@ -1,9 +1,9 @@ { "version": "0.5", "requires": [ - "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809", + "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987", - "sqlite3/3.51.0#66aa11eabd0e34954c5c1c061ad44abe%1774467355.988", + "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1776096494.149", "soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231", "snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878", "secp256k1/0.7.1#481881709eb0bdd0185a12b912bbe8ad%1770910500.329", @@ -15,19 +15,19 @@ "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914", "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492", "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03", - "libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736", - "jemalloc/5.3.0#c671e612af76700db5957c9857978a1c%1776700030.961", + "libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1776147552.838", + "jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228", "gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152", "grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1774467387.342", "ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772", "date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772", "c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1774439234.681", "bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837", - "boost/1.90.0#d5e8defe7355494953be18524a7f135b%1769454080.269", + "boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1778050991.9", "abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196" ], "build_requires": [ - "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809", + "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503", "strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1774447376.964", "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12", "nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707", @@ -48,13 +48,13 @@ "lz4/1.10.0" ], "boost/[>=1.83.0 <1.91.0]": [ - "boost/1.90.0" + "boost/1.91.0" ], "sqlite3/[>=3.44 <4]": [ - "sqlite3/3.51.0" + "sqlite3/3.53.0" ], "boost/1.83.0": [ - "boost/1.90.0" + "boost/1.91.0" ], "lz4/[>=1.9.4 <2]": [ "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504" diff --git a/conanfile.py b/conanfile.py index 4f70ad5b33..2cf5aefbc2 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,4 +1,3 @@ -import os import re from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout @@ -30,12 +29,12 @@ class Xrpl(ConanFile): requires = [ "ed25519/2015.03", "grpc/1.78.1", - "libarchive/3.8.1", + "libarchive/3.8.7", "nudb/2.0.9", "openssl/3.6.2", "secp256k1/0.7.1", "soci/4.0.3", - "zlib/1.3.1", + "zlib/1.3.2", ] test_requires = [ @@ -57,6 +56,7 @@ class Xrpl(ConanFile): "tests": False, "unity": False, "xrpld": False, + "boost/*:without_cobalt": True, "boost/*:without_context": False, "boost/*:without_coroutine": True, "boost/*:without_coroutine2": False, @@ -130,13 +130,13 @@ class Xrpl(ConanFile): self.options["boost"].without_cobalt = True def requirements(self): - self.requires("boost/1.90.0", force=True, transitive_headers=True) + self.requires("boost/1.91.0", force=True, transitive_headers=True) self.requires("date/3.0.4", transitive_headers=True) self.requires("lz4/1.10.0", force=True) self.requires("protobuf/6.33.5", force=True) - self.requires("sqlite3/3.51.0", force=True) + self.requires("sqlite3/3.53.0", force=True) if self.options.jemalloc: - self.requires("jemalloc/5.3.0") + self.requires("jemalloc/5.3.1") if self.options.rocksdb: self.requires("rocksdb/10.5.1") self.requires("xxhash/0.8.3", transitive_headers=True) From 8e2aa33f6488de34d06ed2edd90d12154639ece6 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 7 May 2026 00:31:10 +0100 Subject: [PATCH 11/18] chore: Add IWYU pragma for `boost::optional` to fix clang-tidy (#7088) --- .github/workflows/reusable-clang-tidy.yml | 2 +- src/libxrpl/server/State.cpp | 2 +- src/libxrpl/server/Wallet.cpp | 2 +- src/test/core/SociDB_test.cpp | 2 +- src/test/overlay/short_read_test.cpp | 2 +- src/xrpld/app/misc/detail/AmendmentTable.cpp | 2 +- src/xrpld/app/misc/detail/Transaction.cpp | 2 +- src/xrpld/app/rdb/backend/detail/Node.cpp | 2 +- src/xrpld/app/rdb/detail/PeerFinder.cpp | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 10e36f957f..e979e7179a 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -29,7 +29,7 @@ jobs: if: ${{ inputs.check_only_changed }} permissions: contents: read - uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@12f5dbc98a2260259a66970e57fa4d26fb7f285c + uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@224f3c48d3014d082a1129237b8291ff0b0a331f run-clang-tidy: name: Run clang tidy diff --git a/src/libxrpl/server/State.cpp b/src/libxrpl/server/State.cpp index 3d2ed37e98..b9cb7c6ff2 100644 --- a/src/libxrpl/server/State.cpp +++ b/src/libxrpl/server/State.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include // IWYU pragma: keep #include #include diff --git a/src/libxrpl/server/Wallet.cpp b/src/libxrpl/server/Wallet.cpp index ee579c7b9f..3f2be6267a 100644 --- a/src/libxrpl/server/Wallet.cpp +++ b/src/libxrpl/server/Wallet.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include // IWYU pragma: keep #include #include diff --git a/src/test/core/SociDB_test.cpp b/src/test/core/SociDB_test.cpp index 29aeca7ce5..f4c6fb04f1 100644 --- a/src/test/core/SociDB_test.cpp +++ b/src/test/core/SociDB_test.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include // IWYU pragma: keep #include #include diff --git a/src/test/overlay/short_read_test.cpp b/src/test/overlay/short_read_test.cpp index 1b608edee7..95ab5e8c20 100644 --- a/src/test/overlay/short_read_test.cpp +++ b/src/test/overlay/short_read_test.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include // IWYU pragma: keep #include #include diff --git a/src/xrpld/app/misc/detail/AmendmentTable.cpp b/src/xrpld/app/misc/detail/AmendmentTable.cpp index 56b97d3f7e..8289b58f99 100644 --- a/src/xrpld/app/misc/detail/AmendmentTable.cpp +++ b/src/xrpld/app/misc/detail/AmendmentTable.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include // IWYU pragma: keep #include #include #include diff --git a/src/xrpld/app/misc/detail/Transaction.cpp b/src/xrpld/app/misc/detail/Transaction.cpp index 2975a5f622..21953c68d7 100644 --- a/src/xrpld/app/misc/detail/Transaction.cpp +++ b/src/xrpld/app/misc/detail/Transaction.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include // IWYU pragma: keep #include #include diff --git a/src/xrpld/app/rdb/backend/detail/Node.cpp b/src/xrpld/app/rdb/backend/detail/Node.cpp index 8530fb2e65..4b8c318be8 100644 --- a/src/xrpld/app/rdb/backend/detail/Node.cpp +++ b/src/xrpld/app/rdb/backend/detail/Node.cpp @@ -39,7 +39,7 @@ #include #include -#include +#include // IWYU pragma: keep #include #include diff --git a/src/xrpld/app/rdb/detail/PeerFinder.cpp b/src/xrpld/app/rdb/detail/PeerFinder.cpp index 7227c65663..2481b63d2b 100644 --- a/src/xrpld/app/rdb/detail/PeerFinder.cpp +++ b/src/xrpld/app/rdb/detail/PeerFinder.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include // IWYU pragma: keep #include #include From 8c71ec803ddc98506eb341f342f45e3221f94052 Mon Sep 17 00:00:00 2001 From: Bart Date: Thu, 7 May 2026 06:34:47 -0400 Subject: [PATCH 12/18] fix: Restore clang-tidy change to section name in config (#7091) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- src/libxrpl/server/Port.cpp | 2 +- src/test/jtx/impl/envconfig.cpp | 8 ++++---- src/xrpld/app/main/GRPCServer.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libxrpl/server/Port.cpp b/src/libxrpl/server/Port.cpp index 228b65df43..ee8492c51e 100644 --- a/src/libxrpl/server/Port.cpp +++ b/src/libxrpl/server/Port.cpp @@ -287,7 +287,7 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log) populate(section, "admin", log, port.admin_nets_v4, port.admin_nets_v6); populate( - section, "secureGateway", log, port.secure_gateway_nets_v4, port.secure_gateway_nets_v6); + section, "secure_gateway", log, port.secure_gateway_nets_v4, port.secure_gateway_nets_v6); set(port.user, "user", section); set(port.password, "password", section); diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index 93d976a7c9..474a16165a 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -72,7 +72,7 @@ secureGateway(std::unique_ptr cfg) { (*cfg)[PORT_RPC].set("admin", ""); (*cfg)[PORT_WS].set("admin", ""); - (*cfg)[PORT_RPC].set("secureGateway", getEnvLocalhostAddr()); + (*cfg)[PORT_RPC].set("secure_gateway", getEnvLocalhostAddr()); return cfg; } @@ -89,8 +89,8 @@ secureGatewayLocalnet(std::unique_ptr cfg) { (*cfg)[PORT_RPC].set("admin", ""); (*cfg)[PORT_WS].set("admin", ""); - (*cfg)[PORT_RPC].set("secureGateway", "127.0.0.0/8"); - (*cfg)[PORT_WS].set("secureGateway", "127.0.0.0/8"); + (*cfg)[PORT_RPC].set("secure_gateway", "127.0.0.0/8"); + (*cfg)[PORT_WS].set("secure_gateway", "127.0.0.0/8"); return cfg; } std::unique_ptr @@ -127,7 +127,7 @@ addGrpcConfigWithSecureGateway(std::unique_ptr cfg, std::string const& s // Check https://man7.org/linux/man-pages/man7/ip.7.html // "ip_local_port_range" section for using 0 ports (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("secureGateway", secureGateway); + (*cfg)[SECTION_PORT_GRPC].set("secure_gateway", secureGateway); return cfg; } diff --git a/src/xrpld/app/main/GRPCServer.cpp b/src/xrpld/app/main/GRPCServer.cpp index e0f0798dbc..8427d41397 100644 --- a/src/xrpld/app/main/GRPCServer.cpp +++ b/src/xrpld/app/main/GRPCServer.cpp @@ -361,7 +361,7 @@ GRPCServerImpl::GRPCServerImpl(Application& app) Throw("Error setting grpc server address"); } - auto const optSecureGateway = section.get("secureGateway"); + auto const optSecureGateway = section.get("secure_gateway"); if (optSecureGateway) { try From d67e06102a1aa4432e37bdc976b7c8c62f24acb2 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 7 May 2026 11:36:36 +0100 Subject: [PATCH 13/18] chore: Upgrade Clang sanitizer to `clang-22` and switch `gcc-15` sanitizer to Release (#7079) --- .github/scripts/strategy-matrix/generate.py | 26 +++++---- .github/scripts/strategy-matrix/linux.json | 63 ++++++++++++--------- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index dec41a2610..6f00c69416 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -72,7 +72,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: skip = False if ( f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15" - and build_type == "Debug" + and build_type == "Release" and architecture["platform"] == "linux/amd64" ): skip = False @@ -90,8 +90,9 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: ): cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=1000 {cmake_args}" skip = False + elif os["distro_version"] == "trixie": if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20" + f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22" and build_type == "Debug" and architecture["platform"] == "linux/amd64" ): @@ -188,8 +189,9 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: # We skip all clang 20+ on arm64 due to Boost build error. if ( - f"{os['compiler_name']}-{os['compiler_version']}" - in ["clang-20", "clang-21"] + os["compiler_name"] == "clang" + and os["compiler_version"].isdigit() + and int(os["compiler_version"]) >= 20 and architecture["platform"] == "linux/arm64" ): continue @@ -238,13 +240,14 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: # Add Address and UB sanitizers as separate configurations for specific # bookworm distros. Thread sanitizer is currently disabled (see below). # GCC-Asan xrpld-embedded tests are failing because of https://github.com/google/sanitizers/issues/856 - if os[ - "distro_version" - ] == "bookworm" and f"{os['compiler_name']}-{os['compiler_version']}" in [ - "gcc-15", - "clang-20", - ]: - # Add ASAN configuration. + if ( + os["distro_version"] == "bookworm" + and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15" + ) or ( + os["distro_version"] == "trixie" + and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22" + ): + # Add ASAN and UBSAN configurations for both gcc-15 and clang-22 configurations.append( { "config_name": config_name + "-asan", @@ -257,7 +260,6 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: "sanitizers": "address", } ) - # Add UBSAN configuration. configurations.append( { "config_name": config_name + "-ubsan", diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 4943579be8..1b9af523cb 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -15,196 +15,203 @@ "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "21", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" + }, + { + "distro_name": "debian", + "distro_version": "trixie", + "compiler_name": "clang", + "compiler_version": "22", + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "jammy", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "ab4d1f0" + "image_sha": "4c086b9" } ], "build_type": ["Debug", "Release"], From d6c4e6cb936bb01c923cf25f5396a536ce24f26d Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 7 May 2026 08:48:55 -0400 Subject: [PATCH 14/18] fix: Cap the base fee for LoanPay (#6969) (#6970) Co-authored-by: Bart --- .../tx/transactors/lending/LoanPay.cpp | 21 +++ src/test/app/Loan_test.cpp | 151 +++++++++++++----- 2 files changed, 132 insertions(+), 40 deletions(-) diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index bfd78ea82b..ef93f0dc1b 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -141,6 +141,23 @@ LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx) NumberRoundModeGuard const mg( tx.isFlag(tfLoanOverpayment) ? Number::RoundingMode::Upward : Number::RoundingMode::Downward); + + static_assert(kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION % kLOAN_PAYMENTS_PER_FEE_INCREMENT == 0); + std::int64_t constexpr kMAX_FEE_INCREMENTS = + kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION / kLOAN_PAYMENTS_PER_FEE_INCREMENT; + + if (view.rules().enabled(fixSecurity3_1_3) && + amount >= regularPayment * kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION) + { + // The payment handler will never process more than + // loanMaximumPaymentsPerTransaction payments (including overpayments), + // and one fee increment is charged for every + // loanPaymentsPerFeeIncrement, so don't charge more than + // loanMaximumPaymentsPerTransaction / loanPaymentsPerFeeIncrement fee + // increments. + return kMAX_FEE_INCREMENTS * normalCost; + } + // Estimate how many payments will be made Number const numPaymentEstimate = static_cast(amount / regularPayment); @@ -149,6 +166,10 @@ LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx) auto const feeIncrements = std::max( std::int64_t(1), static_cast(numPaymentEstimate / kLOAN_PAYMENTS_PER_FEE_INCREMENT)); + XRPL_ASSERT( + !view.rules().enabled(fixSecurity3_1_3) || feeIncrements <= kMAX_FEE_INCREMENTS, + "xrpl::LoanPay::calculateBaseFee : number of fee increments is in " + "range"); return feeIncrements * normalCost; } diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index f189196a82..713168a4ee 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -2797,8 +2797,8 @@ protected: pseudoAcct, tfLoanOverpayment, [&](Keylet const& loanKeylet, VerifyLoanStatus const& verifyLoanStatus) { - // Estimate optimal values for loanPaymentsPerFeeIncrement and - // loanMaximumPaymentsPerTransaction. + // Estimate optimal values for kLOAN_PAYMENTS_PER_FEE_INCREMENT and + // kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION. using namespace loan; auto const state = getCurrentState(env, broker, verifyLoanStatus.keylet); @@ -2816,7 +2816,8 @@ protected: // Make all but the final payment auto const numPayments = (state.paymentRemaining - 2); STAmount const bigPayment{broker.asset, totalDue * numPayments}; - XRPAmount const bigFee{baseFee * (numPayments / loanPaymentsPerFeeIncrement + 1)}; + XRPAmount const bigFee{ + baseFee * (numPayments / kLOAN_PAYMENTS_PER_FEE_INCREMENT + 1)}; time("ten payments", [&]() { env(pay(borrower, loanKeylet.key, bigPayment), Fee(bigFee)); }); @@ -4753,15 +4754,17 @@ protected: } void - testDosLoanPay() + testDosLoanPay(FeatureBitset features) { + bool const feeCapped = features[fixSecurity3_1_3]; + // From FIND-005 - testcase << "DoS LoanPay"; + testcase << "DoS LoanPay: fee calculation " << (feeCapped ? "capped" : "uncapped"); using namespace jtx; using namespace std::chrono_literals; using namespace Lending; - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -4770,6 +4773,8 @@ protected: env.fund(XRP(1'000'000), issuer, lender, borrower); env.close(); + BEAST_EXPECT(feeCapped == env.current()->rules().enabled(fixSecurity3_1_3)); + PrettyAsset const iouAsset = issuer[iouCurrency_]; env(trust(lender, iouAsset(100'000'000))); env(trust(borrower, iouAsset(100'000'000))); @@ -4782,52 +4787,117 @@ protected: using namespace loan; auto const loanSetFee = Fee(env.current()->fees().base * 2); - Number const principalRequest{1, 3}; + Number const principalRequest{3959'37, -2}; auto const baseFee = env.current()->fees().base; - auto createJson = env.json( + auto const createJson = env.json( set(borrower, broker.brokerID, principalRequest), Fee(loanSetFee), - Json(sfCounterpartySignature, json::ObjectValue)); - - createJson["ClosePaymentFee"] = "0"; - createJson["GracePeriod"] = 60; - createJson["InterestRate"] = 20930; - createJson["LateInterestRate"] = 77049; - createJson["LatePaymentFee"] = "0"; - createJson["LoanServiceFee"] = "0"; - createJson["OverpaymentFee"] = 7; - createJson["OverpaymentInterestRate"] = 66653; - createJson["PaymentInterval"] = 60; - createJson["PaymentTotal"] = 3239184; - createJson["PrincipalRequested"] = "3959.37"; + Json(sfCounterpartySignature, json::ObjectValue), + kCLOSE_PAYMENT_FEE(0), + kGRACE_PERIOD(60), + kINTEREST_RATE(TenthBips32(20930)), + kLATE_INTEREST_RATE(TenthBips32(77049)), + kLATE_PAYMENT_FEE(0), + kLOAN_SERVICE_FEE(0), + kOVERPAYMENT_FEE(TenthBips32(7)), + kOVERPAYMENT_INTEREST_RATE(TenthBips32(66653)), + kPAYMENT_INTERVAL(60), + kPAYMENT_TOTAL(3239184)); + // There are enough payments due on this loan that it only needs to be + // created once, and can be paid on multiple times. Just don't create a + // gazillion test cases. auto const brokerStateBefore = env.le(keylet::loanbroker(broker.brokerID)); auto const loanSequence = brokerStateBefore->at(sfLoanSequence); auto const keylet = keylet::loan(broker.brokerID, loanSequence); - createJson = env.json(createJson, Sig(sfCounterpartySignature, lender)); - env(createJson, Ter(tesSUCCESS)); + env(createJson, Sig(sfCounterpartySignature, lender)); env.close(); - auto const stateBefore = getCurrentState(env, broker, keylet); - BEAST_EXPECT(stateBefore.paymentRemaining == 3239184); - BEAST_EXPECT(stateBefore.paymentRemaining > kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION); + auto const roundedPayment = [&]() { + auto const stateBefore = getCurrentState(env, broker, keylet); + BEAST_EXPECT(stateBefore.paymentRemaining == 3239184); + BEAST_EXPECT(stateBefore.paymentRemaining > kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION); - auto loanPayTx = env.json(pay(borrower, keylet.key, STAmount{broker.asset, Number{}})); - Number const amount{395937, -2}; - loanPayTx["Amount"]["value"] = to_string(amount); - XRPAmount const payFee{ - baseFee * - std::int64_t( - amount / stateBefore.periodicPayment / kLOAN_PAYMENTS_PER_FEE_INCREMENT + 1)}; - env(loanPayTx, Ter(tesSUCCESS), Fee(payFee)); - env.close(); + return roundToAsset( + iouAsset, + stateBefore.periodicPayment, + stateBefore.loanScale, + Number::RoundingMode::Upward); + }(); - auto const stateAfter = getCurrentState(env, broker, keylet); - BEAST_EXPECT( - stateAfter.paymentRemaining == - stateBefore.paymentRemaining - kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION); + auto test = [&](int const payFactor, + int const feeFactor, + TER const expectedTer = tesSUCCESS) { + auto const stateBefore = getCurrentState(env, broker, keylet); + BEAST_EXPECT(stateBefore.paymentRemaining <= 3239184); + BEAST_EXPECT(stateBefore.paymentRemaining > kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION); + + Number const amount = roundedPayment * payFactor; + auto loanPayTx = env.json(pay(borrower, keylet.key, STAmount{broker.asset, amount})); + XRPAmount const payFee{baseFee * feeFactor}; + env(loanPayTx, Ter(expectedTer), Fee(payFee)); + env.close(); + auto const expectedChange = isTesSuccess(expectedTer) + ? std::min(kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION, payFactor) + : 0; + + auto const stateAfter = getCurrentState(env, broker, keylet); + BEAST_EXPECT( + stateAfter.paymentRemaining == stateBefore.paymentRemaining - expectedChange); + }; + + std::int64_t constexpr kMAX_FEE_INCREMENTS = + kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION / kLOAN_PAYMENTS_PER_FEE_INCREMENT; + + TER const failWithoutFix = feeCapped ? (TER)tesSUCCESS : (TER)telINSUF_FEE_P; + + // * Amount well above threshold -> capped fee + // The original test case - way over the limit - more fee is always ok + test(1819878, 363976); + // The capped fee is only sufficient if the amendment is enabled. + test(1819878, kMAX_FEE_INCREMENTS, failWithoutFix); + + // * Amount exactly at threshold -> capped fee + test(kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION, kMAX_FEE_INCREMENTS); + // More fee is always ok + test(kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION, kMAX_FEE_INCREMENTS + 10); + + // * Amount below threshold -> normal calculation + test(1, 1); + test(kLOAN_PAYMENTS_PER_FEE_INCREMENT * 2, 2); + test(0, 0, temBAD_AMOUNT); + test(0, 1, temBAD_AMOUNT); + // Fee difference rounds evenly + test( + kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - 10, + ((kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - 10) / kLOAN_PAYMENTS_PER_FEE_INCREMENT) - 1, + telINSUF_FEE_P); + test( + kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - 10, + ((kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - 10) / kLOAN_PAYMENTS_PER_FEE_INCREMENT)); + // More fee is always ok + test( + kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - 10, + ((kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - 10) / kLOAN_PAYMENTS_PER_FEE_INCREMENT) + 3); + // Fee rounds up + for (int under = 1; under < kLOAN_PAYMENTS_PER_FEE_INCREMENT; ++under) + { + test( + kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - under, + kMAX_FEE_INCREMENTS - 1, + telINSUF_FEE_P); + test(kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - under, kMAX_FEE_INCREMENTS); + } + // Only when you get one less fee increment can you pay less + test( + kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - kLOAN_PAYMENTS_PER_FEE_INCREMENT, + kMAX_FEE_INCREMENTS - 1); + // And again, more fee is always ok. + test( + kLOAN_MAXIMUM_PAYMENTS_PER_TRANSACTION - kLOAN_PAYMENTS_PER_FEE_INCREMENT, + kMAX_FEE_INCREMENTS); } void @@ -7248,7 +7318,8 @@ public: testLoanPayDebtDecreaseInvariant(); testWrongMaxDebtBehavior(); testLoanPayComputePeriodicPaymentValidTotalInterestInvariant(); - testDosLoanPay(); + testDosLoanPay(all | fixSecurity3_1_3); + testDosLoanPay(all - fixSecurity3_1_3); testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant(); testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant(); testLoanNextPaymentDueDateOverflow(); From af89854a4367942d258006d739a3cb7be749cd63 Mon Sep 17 00:00:00 2001 From: Olek <115580134+oleks-rip@users.noreply.github.com> Date: Thu, 7 May 2026 08:57:50 -0400 Subject: [PATCH 15/18] fix: Stop tx processing if failed to delete expired credentials (#6715) (#6962) Co-authored-by: Ed Hennis Co-authored-by: Ayaz Salikhov Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../xrpl/ledger/helpers/CredentialHelpers.h | 8 +- .../ledger/helpers/CredentialHelpers.cpp | 35 +++-- .../ledger/helpers/PermissionedDEXHelpers.cpp | 2 +- src/libxrpl/tx/Transactor.cpp | 9 +- .../credentials/CredentialAccept.cpp | 4 +- .../credentials/CredentialDelete.cpp | 2 +- src/test/app/Credentials_test.cpp | 123 ++++++++++++++++++ .../handlers/orderbook/DepositAuthorized.cpp | 2 +- 8 files changed, 164 insertions(+), 21 deletions(-) diff --git a/include/xrpl/ledger/helpers/CredentialHelpers.h b/include/xrpl/ledger/helpers/CredentialHelpers.h index c60bd6c7fa..e06d225934 100644 --- a/include/xrpl/ledger/helpers/CredentialHelpers.h +++ b/include/xrpl/ledger/helpers/CredentialHelpers.h @@ -19,14 +19,10 @@ namespace credentials { // Check if credential sfExpiration field has passed ledger's parentCloseTime bool -checkExpired(std::shared_ptr const& sleCredential, NetClock::time_point const& closed); - -// Return true if any expired credential was found in arr (and deleted) -bool -removeExpired(ApplyView& view, STVector256 const& arr, beast::Journal const j); +checkExpired(SLE const& sleCredential, NetClock::time_point const& closed); // Actually remove a credentials object from the ledger -TER +[[nodiscard]] TER deleteSLE(ApplyView& view, std::shared_ptr const& sleCredential, beast::Journal j); // Amendment and parameters checks for sfCredentialIDs field diff --git a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp index 22122216bc..05e45a404c 100644 --- a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -33,15 +35,16 @@ namespace xrpl { namespace credentials { bool -checkExpired(std::shared_ptr const& sleCredential, NetClock::time_point const& closed) +checkExpired(SLE const& sleCredential, NetClock::time_point const& closed) { std::uint32_t const exp = - (*sleCredential)[~sfExpiration].value_or(std::numeric_limits::max()); + sleCredential[~sfExpiration].value_or(std::numeric_limits::max()); std::uint32_t const now = closed.time_since_epoch().count(); return now > exp; } -bool +[[nodiscard]] +static Expected removeExpired(ApplyView& view, STVector256 const& arr, beast::Journal const j) { auto const closeTime = view.header().parentCloseTime; @@ -53,11 +56,13 @@ removeExpired(ApplyView& view, STVector256 const& arr, beast::Journal const j) auto const k = keylet::credential(h); auto const sleCred = view.peek(k); - if (sleCred && checkExpired(sleCred, closeTime)) + if (sleCred && checkExpired(*sleCred, closeTime)) { JLOG(j.trace()) << "Credentials are expired. Cred: " << sleCred->getText(); // delete expired credentials even if the transaction failed - deleteSLE(view, sleCred, j); + auto const err = deleteSLE(view, sleCred, j); + if (view.rules().enabled(fixSecurity3_1_3) && !isTesSuccess(err)) + return Unexpected(err); foundExpired = true; } } @@ -205,7 +210,7 @@ validDomain(ReadView const& view, uint256 domainID, AccountID const& subject) // allows expired credentials to be deleted by any transaction. if (sleCredential) { - if (checkExpired(sleCredential, closeTime)) + if (checkExpired(*sleCredential, closeTime)) { foundExpired = true; continue; @@ -324,7 +329,10 @@ verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, b credentials.pushBack(keyletCredential.key); } - bool const foundExpired = credentials::removeExpired(view, credentials, j); + auto const foundExpired = credentials::removeExpired(view, credentials, j); + if (!foundExpired.has_value()) + return foundExpired.error(); + for (auto const& h : credentials) { auto sleCredential = view.read(keylet::credential(h)); @@ -335,7 +343,7 @@ verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, b return tesSUCCESS; } - return foundExpired ? tecEXPIRED : tecNO_PERMISSION; + return *foundExpired ? tecEXPIRED : tecNO_PERMISSION; } TER @@ -355,8 +363,15 @@ verifyDepositPreauth( bool const credentialsPresent = tx.isFieldPresent(sfCredentialIDs); - if (credentialsPresent && credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j)) - return tecEXPIRED; + if (credentialsPresent) + { + auto const foundExpired = + credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j); + if (!foundExpired.has_value()) + return foundExpired.error(); + if (*foundExpired) + return tecEXPIRED; + } if (sleDst && ((sleDst->getFlags() & lsfDepositAuth) != 0u)) { diff --git a/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp b/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp index 456c4e95d6..7b4194cccf 100644 --- a/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp +++ b/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp @@ -35,7 +35,7 @@ accountInDomain(ReadView const& view, AccountID const& account, Domain const& do if (!sleCred || !sleCred->isFlag(lsfAccepted)) return false; - return !credentials::checkExpired(sleCred, view.header().parentCloseTime); + return !credentials::checkExpired(*sleCred, view.header().parentCloseTime); }); return inDomain; diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 79dec33c66..aff46cc4bc 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -1010,7 +1010,14 @@ removeExpiredCredentials(ApplyView& view, std::vector const& creds, bea for (auto const& index : creds) { if (auto const sle = view.peek(keylet::credential(index))) - credentials::deleteSLE(view, sle, viewJ); + { + if (auto const ter = credentials::deleteSLE(view, sle, viewJ); !isTesSuccess(ter)) + { + JLOG(viewJ.error()) + << "removeExpiredCredentials: failed to delete expired credential. Err: " + << transToken(ter); + } + } } } diff --git a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp index 2d27d1bf23..22e0df5df5 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp @@ -105,8 +105,10 @@ CredentialAccept::doApply() auto const credType(ctx_.tx[sfCredentialType]); Keylet const credentialKey = keylet::credential(account_, issuer, credType); auto const sleCred = view().peek(credentialKey); // Checked in preclaim() + if (!sleCred) + return tefINTERNAL; // LCOV_EXCL_LINE - if (checkExpired(sleCred, view().header().parentCloseTime)) + if (checkExpired(*sleCred, view().header().parentCloseTime)) { JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText(); // delete expired credentials even if the transaction failed diff --git a/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp b/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp index 595064e824..e83a8b09dc 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp @@ -87,7 +87,7 @@ CredentialDelete::doApply() return tefINTERNAL; // LCOV_EXCL_LINE if ((subject != account_) && (issuer != account_) && - !checkExpired(sleCred, ctx_.view().header().parentCloseTime)) + !checkExpired(*sleCred, ctx_.view().header().parentCloseTime)) { JLOG(j_.trace()) << "Can't delete non-expired credential."; return tecNO_PERMISSION; diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index fa180d0603..fced18c387 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -5,9 +5,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include #include @@ -24,11 +27,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -1028,6 +1033,121 @@ struct Credentials_test : public beast::unit_test::Suite } } + void + testRemoveExpiredCorruption(FeatureBitset features) + { + bool const fixEnabled = features[fixSecurity3_1_3]; + testcase( + "removeExpired ignores deleteSLE failure " + + (fixEnabled ? std::string(" after fix") : std::string(" before fix"))); + + using namespace test::jtx; + + char const credType[] = "abcde"; + Account const issuer{"issuer"}; + Account const subject{"subject"}; + Account const becky{"becky"}; + + Env env{*this, features}; + env.fund(XRP(10000), issuer, subject, becky); + env.close(); + + // Create credential with short expiration + auto jv = credentials::create(subject, issuer, credType); + uint32_t const expiration = + env.current()->header().parentCloseTime.time_since_epoch().count() + 40; + jv[sfExpiration.jsonName] = expiration; + env(jv); + env.close(); + + auto const credLE = credentials::ledgerEntry(env, subject, issuer, credType); + std::string const credIdx = credLE[jss::result][jss::index].asString(); + + // Subject accepts the credential + env(credentials::accept(subject, issuer, credType)); + env.close(); + + // Build the credential keylet + auto const credKeylet = + keylet::credential(subject.id(), issuer.id(), Slice(credType, std::strlen(credType))); + + // Verify credential exists and is accepted + { + auto const sleCred = env.current()->read(credKeylet); + BEAST_EXPECT(sleCred && sleCred->getFlags() & lsfAccepted); + } + + // Create DepositPreauth + env(deposit::authCredentials(becky, {{subject, credType}})); + env.close(); + // env(); + auto jtx = env.jt(pay(subject, becky, XRP(100)), credentials::Ids({credIdx})); + if (!BEAST_EXPECT(jtx.stx)) + return; + auto const stx = std::make_shared(*jtx.stx); + + // Create PermissionedDomain + env(pdomain::setTx(becky, {{issuer, credType}})); + env.close(); + auto const objects = pdomain::getObjects(becky, env); + if (!BEAST_EXPECT(!objects.empty())) + return; + auto const domain = objects.begin()->first; + + using namespace std::chrono_literals; + env.close(50s); + + // Verify time has advanced past expiration + { + auto const sleCred = env.current()->read(credKeylet); + BEAST_EXPECT( + sleCred && + xrpl::credentials::checkExpired(*sleCred, env.current()->header().parentCloseTime)); + } + + // Create an ApplyViewImpl on top of the current closed ledger + // and corrupt it by erasing the issuer's account SLE + auto const open = env.current(); + ApplyViewImpl av(&*open, TapNone); + + // Erase the issuer's account to simulate ledger corruption + auto sleIssuer = av.peek(keylet::account(issuer.id())); + if (!BEAST_EXPECT(sleIssuer)) + return; + av.erase(sleIssuer); + BEAST_EXPECT(!av.exists(keylet::account(issuer.id()))); + + // Credential still exists before removeExpired + BEAST_EXPECT(av.exists(credKeylet)); + + // Call removeExpired on the corrupted view + STVector256 credHashes; + credHashes.pushBack(credKeylet.key); + beast::Journal const j{beast::Journal::getNullSink()}; + + auto const dpTer = xrpl::verifyDepositPreauth(*stx, av, subject, becky, {}, j); + auto sleCredAfter = av.read(credKeylet); + BEAST_EXPECT(sleCredAfter && (sleCredAfter->getFlags() & lsfAccepted)); + + auto const domTer = xrpl::verifyValidDomain(av, subject.id(), domain, j); + sleCredAfter = av.read(credKeylet); + BEAST_EXPECT(sleCredAfter && (sleCredAfter->getFlags() & lsfAccepted)); + + if (fixEnabled) + { + // removeExpired returns error, cred wasn't deleted + BEAST_EXPECT(dpTer == tecINTERNAL); + BEAST_EXPECT(domTer == tecINTERNAL); + } + else + { + // removeExpired returns true (claims it found & deleted expired + // creds) + BEAST_EXPECT(dpTer == tecEXPIRED); + BEAST_EXPECT(isTesSuccess(domTer)); + } + } + void run() override { @@ -1043,6 +1163,9 @@ struct Credentials_test : public beast::unit_test::Suite testFlags(all - fixInvalidTxFlags); testFlags(all); testRPC(); + + testRemoveExpiredCorruption(all - fixSecurity3_1_3); + testRemoveExpiredCorruption(all | fixSecurity3_1_3); } }; diff --git a/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp b/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp index 7e9f891e7f..399df7c6ca 100644 --- a/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp +++ b/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp @@ -141,7 +141,7 @@ doDepositAuthorized(RPC::JsonContext& context) return result; } - if (credentials::checkExpired(sleCred, ledger->header().parentCloseTime)) + if (credentials::checkExpired(*sleCred, ledger->header().parentCloseTime)) { RPC::injectError(RpcBadCredentials, "credentials are expired", result); return result; From 7afdd71a54d562b32a50b29a5aa00bb997dc9053 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Thu, 7 May 2026 18:04:30 +0100 Subject: [PATCH 16/18] chore: More fixes for bad renames (#7092) --- include/xrpl/basics/base_uint.h | 118 +++++++++--------- .../xrpl/basics/partitioned_unordered_map.h | 2 +- include/xrpl/basics/random.h | 2 +- include/xrpl/basics/safe_cast.h | 4 +- include/xrpl/basics/scope.h | 2 +- include/xrpl/basics/spinlock.h | 2 +- include/xrpl/beast/asio/io_latency_probe.h | 18 +-- .../detail/aged_unordered_container.h | 2 +- include/xrpl/protocol/AccountID.h | 2 +- include/xrpl/protocol/STBitString.h | 6 +- include/xrpl/protocol/STObject.h | 4 +- include/xrpl/protocol/Serializer.h | 10 +- include/xrpl/protocol/UintTypes.h | 10 +- include/xrpl/server/detail/BaseHTTPPeer.h | 2 +- include/xrpl/server/detail/BasePeer.h | 2 +- include/xrpl/server/detail/Door.h | 4 +- include/xrpl/server/detail/ServerImpl.h | 4 +- include/xrpl/server/detail/io_list.h | 30 ++--- include/xrpl/shamap/SHAMap.h | 2 +- include/xrpl/shamap/SHAMapItem.h | 2 +- src/libxrpl/basics/Log.cpp | 2 +- src/libxrpl/basics/ResolverAsio.cpp | 4 +- .../beast/clock/basic_seconds_clock.cpp | 2 +- src/libxrpl/ledger/Dir.cpp | 11 +- src/libxrpl/protocol/Indexes.cpp | 2 +- src/libxrpl/protocol/Quality.cpp | 14 +-- src/libxrpl/server/Port.cpp | 2 +- src/libxrpl/tx/applySteps.cpp | 6 +- src/test/app/MultiSign_test.cpp | 4 +- src/test/app/Vault_test.cpp | 16 +-- src/test/basics/base_uint_test.cpp | 14 +-- .../beast/beast_io_latency_probe_test.cpp | 2 +- src/test/protocol/STIssue_test.cpp | 6 +- src/xrpld/app/main/Application.cpp | 8 +- src/xrpld/app/main/GRPCServer.cpp | 6 +- src/xrpld/app/rdb/backend/detail/Node.cpp | 2 +- src/xrpld/overlay/detail/Handshake.cpp | 4 +- src/xrpld/overlay/detail/OverlayImpl.cpp | 4 +- src/xrpld/peerfinder/detail/Bootcache.cpp | 4 +- src/xrpld/peerfinder/detail/Handouts.h | 2 +- src/xrpld/peerfinder/detail/Livecache.h | 4 +- src/xrpld/peerfinder/detail/Logic.h | 6 +- 42 files changed, 177 insertions(+), 176 deletions(-) diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h index 15fc4f6966..3f38d4049b 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -62,7 +62,7 @@ struct IsContiguousContainer : std::true_type number of bits. */ template -class BaseUint +class BaseUInt { static_assert((Bits % 32) == 0, "The length of a base_uint in bits must be a multiple of 32."); @@ -160,7 +160,7 @@ private: explicit VoidHelper() = default; }; - explicit BaseUint(void const* data, VoidHelper) + explicit BaseUInt(void const* data, VoidHelper) { memcpy(data_.data(), data, kBYTES); } @@ -244,15 +244,15 @@ private: } public: - constexpr BaseUint() : data_{} + constexpr BaseUInt() : data_{} { } - constexpr BaseUint(beast::Zero) : data_{} + constexpr BaseUInt(beast::Zero) : data_{} { } - explicit BaseUint(std::uint64_t b) + explicit BaseUInt(std::uint64_t b) { *this = b; } @@ -260,7 +260,7 @@ public: // This constructor is intended to be used at compile time since it might // throw at runtime. Consider declaring this constructor consteval once // we get to C++23. - explicit constexpr BaseUint(std::string_view sv) noexcept(false) + explicit constexpr BaseUInt(std::string_view sv) noexcept(false) : data_(parseFromStringViewThrows(sv)) { } @@ -270,11 +270,11 @@ public: class = std::enable_if_t< detail::IsContiguousContainer::value && std::is_trivially_copyable_v>> - explicit BaseUint(Container const& c) + explicit BaseUInt(Container const& c) { XRPL_ASSERT( c.size() * sizeof(typename Container::value_type) == size(), - "xrpl::base_uint::base_uint(Container auto) : input size match"); + "xrpl::BaseUInt::BaseUInt(Container auto) : input size match"); std::memcpy(data_.data(), c.data(), size()); } @@ -282,12 +282,12 @@ public: std::enable_if_t< detail::IsContiguousContainer::value && std::is_trivially_copyable_v, - BaseUint&> + BaseUInt&> operator=(Container const& c) { XRPL_ASSERT( c.size() * sizeof(typename Container::value_type) == size(), - "xrpl::base_uint::operator=(Container auto) : input size match"); + "xrpl::BaseUInt::operator=(Container auto) : input size match"); std::memcpy(data_.data(), c.data(), size()); return *this; } @@ -295,14 +295,14 @@ public: /* Construct from a raw pointer. The buffer pointed to by `data` must be at least Bits/8 bytes. */ - static BaseUint + static BaseUInt fromVoid(void const* data) { - return BaseUint(data, VoidHelper()); + return BaseUInt(data, VoidHelper()); } template - static std::optional + static std::optional fromVoidChecked(T const& from) { if (from.size() != size()) @@ -328,10 +328,10 @@ public: return *this == beast::kZERO; } - constexpr BaseUint + constexpr BaseUInt operator~() const { - BaseUint ret; + BaseUInt ret; for (int i = 0; i < kWIDTH; i++) ret.data_[i] = ~data_[i]; @@ -339,7 +339,7 @@ public: return ret; } - BaseUint& + BaseUInt& operator=(std::uint64_t uHost) { *this = beast::kZERO; @@ -357,8 +357,8 @@ public: return *this; } - BaseUint& - operator^=(BaseUint const& b) + BaseUInt& + operator^=(BaseUInt const& b) { for (int i = 0; i < kWIDTH; i++) data_[i] ^= b.data_[i]; @@ -366,8 +366,8 @@ public: return *this; } - BaseUint& - operator&=(BaseUint const& b) + BaseUInt& + operator&=(BaseUInt const& b) { for (int i = 0; i < kWIDTH; i++) data_[i] &= b.data_[i]; @@ -375,8 +375,8 @@ public: return *this; } - BaseUint& - operator|=(BaseUint const& b) + BaseUInt& + operator|=(BaseUInt const& b) { for (int i = 0; i < kWIDTH; i++) data_[i] |= b.data_[i]; @@ -384,7 +384,7 @@ public: return *this; } - BaseUint& + BaseUInt& operator++() { // prefix operator @@ -398,17 +398,17 @@ public: return *this; } - BaseUint + BaseUInt operator++(int) { // postfix operator - BaseUint const ret = *this; + BaseUInt const ret = *this; ++(*this); return ret; } - BaseUint& + BaseUInt& operator--() { for (int i = kWIDTH - 1; i >= 0; --i) @@ -423,32 +423,32 @@ public: return *this; } - BaseUint + BaseUInt operator--(int) { // postfix operator - BaseUint const ret = *this; + BaseUInt const ret = *this; --(*this); return ret; } - [[nodiscard]] BaseUint + [[nodiscard]] BaseUInt next() const { auto ret = *this; return ++ret; } - [[nodiscard]] BaseUint + [[nodiscard]] BaseUInt prev() const { auto ret = *this; return --ret; } - BaseUint& - operator+=(BaseUint const& b) + BaseUInt& + operator+=(BaseUInt const& b) { std::uint64_t carry = 0; @@ -466,7 +466,7 @@ public: template friend void - hash_append(Hasher& h, BaseUint const& a) noexcept + hash_append(Hasher& h, BaseUInt const& a) noexcept { // Do not allow any endian transformations on this memory h(a.data_.data(), sizeof(a.data_)); @@ -509,7 +509,7 @@ public: return kBYTES; } - BaseUint& + BaseUInt& operator=(beast::Zero) { data_.fill(0); @@ -534,14 +534,14 @@ public: } }; -using uint128 = BaseUint<128>; -using uint160 = BaseUint<160>; -using uint256 = BaseUint<256>; -using uint192 = BaseUint<192>; +using uint128 = BaseUInt<128>; +using uint160 = BaseUInt<160>; +using uint256 = BaseUInt<256>; +using uint192 = BaseUInt<192>; template [[nodiscard]] constexpr std::strong_ordering -operator<=>(BaseUint const& lhs, BaseUint const& rhs) +operator<=>(BaseUInt const& lhs, BaseUInt const& rhs) { // This comparison might seem wrong on a casual inspection because it // compares data internally stored as std::uint32_t byte-by-byte. But @@ -562,7 +562,7 @@ operator<=>(BaseUint const& lhs, BaseUint const& rhs) template [[nodiscard]] constexpr bool -operator==(BaseUint const& lhs, BaseUint const& rhs) +operator==(BaseUInt const& lhs, BaseUInt const& rhs) { return (lhs <=> rhs) == 0; } @@ -570,59 +570,59 @@ operator==(BaseUint const& lhs, BaseUint const& rhs) //------------------------------------------------------------------------------ template constexpr bool -operator==(BaseUint const& a, std::uint64_t b) +operator==(BaseUInt const& a, std::uint64_t b) { - return a == BaseUint(b); + return a == BaseUInt(b); } //------------------------------------------------------------------------------ template -constexpr BaseUint -operator^(BaseUint const& a, BaseUint const& b) +constexpr BaseUInt +operator^(BaseUInt const& a, BaseUInt const& b) { - return BaseUint(a) ^= b; + return BaseUInt(a) ^= b; } template -constexpr BaseUint -operator&(BaseUint const& a, BaseUint const& b) +constexpr BaseUInt +operator&(BaseUInt const& a, BaseUInt const& b) { - return BaseUint(a) &= b; + return BaseUInt(a) &= b; } template -constexpr BaseUint -operator|(BaseUint const& a, BaseUint const& b) +constexpr BaseUInt +operator|(BaseUInt const& a, BaseUInt const& b) { - return BaseUint(a) |= b; + return BaseUInt(a) |= b; } template -constexpr BaseUint -operator+(BaseUint const& a, BaseUint const& b) +constexpr BaseUInt +operator+(BaseUInt const& a, BaseUInt const& b) { - return BaseUint(a) += b; + return BaseUInt(a) += b; } //------------------------------------------------------------------------------ template inline std::string -to_string(BaseUint const& a) +to_string(BaseUInt const& a) { return strHex(a.cbegin(), a.cend()); } template inline std::string -toShortString(BaseUint const& a) +toShortString(BaseUInt const& a) { - static_assert(BaseUint::kBYTES > 4, "For 4 bytes or less, use a native type"); + static_assert(BaseUInt::kBYTES > 4, "For 4 bytes or less, use a native type"); return strHex(a.cbegin(), a.cbegin() + 4) + "..."; } template inline std::ostream& -operator<<(std::ostream& out, BaseUint const& u) +operator<<(std::ostream& out, BaseUInt const& u) { return out << to_string(u); } @@ -650,7 +650,7 @@ static_assert(sizeof(uint256) == 256 / 8, "There should be no padding bytes"); namespace beast { template -struct IsUniquelyRepresented> : public std::true_type +struct IsUniquelyRepresented> : public std::true_type { explicit IsUniquelyRepresented() = default; }; diff --git a/include/xrpl/basics/partitioned_unordered_map.h b/include/xrpl/basics/partitioned_unordered_map.h index 5f32cca8af..3bf64985e5 100644 --- a/include/xrpl/basics/partitioned_unordered_map.h +++ b/include/xrpl/basics/partitioned_unordered_map.h @@ -236,7 +236,7 @@ public: map_.resize(partitions_); XRPL_ASSERT( partitions_, - "xrpl::partitioned_unordered_map::partitioned_unordered_map : " + "xrpl::PartitionedUnorderedMap::PartitionedUnorderedMap : " "nonzero partitions"); } diff --git a/include/xrpl/basics/random.h b/include/xrpl/basics/random.h index ca51ef9364..7dfade5bda 100644 --- a/include/xrpl/basics/random.h +++ b/include/xrpl/basics/random.h @@ -94,7 +94,7 @@ template std::enable_if_t && detail::is_engine::value, Integral> randInt(Engine& engine, Integral min, Integral max) { - XRPL_ASSERT(max > min, "xrpl::rand_int : max over min inputs"); + XRPL_ASSERT(max > min, "xrpl::randInt : max over min inputs"); // This should have no state and constructing it should // be very cheap. If that turns out not to be the case diff --git a/include/xrpl/basics/safe_cast.h b/include/xrpl/basics/safe_cast.h index b9a7aa1fa0..e35495bcf7 100644 --- a/include/xrpl/basics/safe_cast.h +++ b/include/xrpl/basics/safe_cast.h @@ -81,7 +81,7 @@ safeDowncast(Src* s) noexcept return static_cast(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) #else auto* result = dynamic_cast(s); - XRPL_ASSERT(result != nullptr, "xrpl::safe_downcast : pointer downcast is valid"); + XRPL_ASSERT(result != nullptr, "xrpl::safeDowncast : pointer downcast is valid"); return result; #endif } @@ -94,7 +94,7 @@ safeDowncast(Src& s) noexcept #ifndef NDEBUG XRPL_ASSERT( dynamic_cast>>(&s) != nullptr, - "xrpl::safe_downcast : reference downcast is valid"); + "xrpl::safeDowncast : reference downcast is valid"); #endif return static_cast(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) } diff --git a/include/xrpl/basics/scope.h b/include/xrpl/basics/scope.h index 64f91727fa..cd2a5299b2 100644 --- a/include/xrpl/basics/scope.h +++ b/include/xrpl/basics/scope.h @@ -205,7 +205,7 @@ class ScopeUnlock public: explicit ScopeUnlock(std::unique_lock& lock) noexcept(true) : plock_(&lock) { - XRPL_ASSERT(plock_->owns_lock(), "xrpl::scope_unlock::scope_unlock : mutex must be locked"); + XRPL_ASSERT(plock_->owns_lock(), "xrpl::ScopeUnlock::ScopeUnlock : mutex must be locked"); plock_->unlock(); } diff --git a/include/xrpl/basics/spinlock.h b/include/xrpl/basics/spinlock.h index 3518b94680..2cc00efdef 100644 --- a/include/xrpl/basics/spinlock.h +++ b/include/xrpl/basics/spinlock.h @@ -103,7 +103,7 @@ public: { XRPL_ASSERT( index >= 0 && (mask_ != 0), - "xrpl::packed_spinlock::packed_spinlock : valid index and mask"); + "xrpl::PackedSpinlock::PackedSpinlock : valid index and mask"); } [[nodiscard]] bool diff --git a/include/xrpl/beast/asio/io_latency_probe.h b/include/xrpl/beast/asio/io_latency_probe.h index 4ba985e579..ce3929a394 100644 --- a/include/xrpl/beast/asio/io_latency_probe.h +++ b/include/xrpl/beast/asio/io_latency_probe.h @@ -15,7 +15,7 @@ namespace beast { /** Measures handler latency on an io_context queue. */ template -class IoLatencyProbe +class IOLatencyProbe { private: using duration = typename Clock::duration; @@ -30,12 +30,12 @@ private: bool cancel_{false}; public: - IoLatencyProbe(duration const& period, boost::asio::io_context& ios) + IOLatencyProbe(duration const& period, boost::asio::io_context& ios) : period_(period), ios_(ios), timer_(ios_) { } - ~IoLatencyProbe() + ~IOLatencyProbe() { std::unique_lock lock(mutex_); cancel(lock, true); @@ -85,7 +85,7 @@ public: { std::scoped_lock const lock(mutex_); if (cancel_) - throw std::logic_error("io_latency_probe is canceled"); + throw std::logic_error("IOLatencyProbe is canceled"); boost::asio::post( ios_, SampleOp(std::forward(handler), Clock::now(), false, this)); } @@ -100,7 +100,7 @@ public: { std::scoped_lock const lock(mutex_); if (cancel_) - throw std::logic_error("io_latency_probe is canceled"); + throw std::logic_error("IOLatencyProbe is canceled"); boost::asio::post( ios_, SampleOp(std::forward(handler), Clock::now(), true, this)); } @@ -140,18 +140,18 @@ private: Handler handler; time_point start; bool repeat; - IoLatencyProbe* probe; + IOLatencyProbe* probe; SampleOp( Handler const& handler, time_point const& start, bool repeat, - IoLatencyProbe* probe) + IOLatencyProbe* probe) : handler(handler), start(start), repeat(repeat), probe(probe) { XRPL_ASSERT( probe, - "beast::io_latency_probe::sample_op::sample_op : non-null " + "beast::IOLatencyProbe::SampleOp::SampleOp : non-null " "probe input"); probe->addref(); } @@ -164,7 +164,7 @@ private: { XRPL_ASSERT( probe, - "beast::io_latency_probe::sample_op::sample_op(sample_op&&) : " + "beast::IOLatencyProbe::SampleOp::SampleOp(SampleOp&&) : " "non-null probe input"); from.probe = nullptr; } diff --git a/include/xrpl/beast/container/detail/aged_unordered_container.h b/include/xrpl/beast/container/detail/aged_unordered_container.h index 78a2a5a32d..7162c237d6 100644 --- a/include/xrpl/beast/container/detail/aged_unordered_container.h +++ b/include/xrpl/beast/container/detail/aged_unordered_container.h @@ -1370,7 +1370,7 @@ private: buck_.resize(size() + additional, cont_); XRPL_ASSERT( loadFactor() <= maxLoadFactor(), - "beast::detail::AgedUnorderedContainer::maybe_rehash : maximum " + "beast::detail::AgedUnorderedContainer::maybeRehash : maximum " "load factor"); } diff --git a/include/xrpl/protocol/AccountID.h b/include/xrpl/protocol/AccountID.h index e22c3b8edd..0b15f651bc 100644 --- a/include/xrpl/protocol/AccountID.h +++ b/include/xrpl/protocol/AccountID.h @@ -25,7 +25,7 @@ public: } // namespace detail /** A 160-bit unsigned that uniquely identifies an account. */ -using AccountID = BaseUint<160, detail::AccountIDTag>; +using AccountID = BaseUInt<160, detail::AccountIDTag>; /** Convert AccountID to base58 checked string */ std::string diff --git a/include/xrpl/protocol/STBitString.h b/include/xrpl/protocol/STBitString.h index efb98cfe27..8a7e5a6030 100644 --- a/include/xrpl/protocol/STBitString.h +++ b/include/xrpl/protocol/STBitString.h @@ -16,7 +16,7 @@ class STBitString final : public STBase, public CountedObject> static_assert(Bits > 0, "Number of bits must be positive"); public: - using value_type = BaseUint; + using value_type = BaseUInt; private: value_type value_{}; @@ -46,7 +46,7 @@ public: template void - setValue(BaseUint const& v); + setValue(BaseUInt const& v); [[nodiscard]] value_type const& value() const; @@ -157,7 +157,7 @@ STBitString::add(Serializer& s) const template template void -STBitString::setValue(BaseUint const& v) +STBitString::setValue(BaseUInt const& v) { value_ = v; } diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index a9e46e8717..12a5a5dca4 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -381,7 +381,7 @@ public: template void - setFieldH160(SField const& field, BaseUint<160, Tag> const& v); + setFieldH160(SField const& field, BaseUInt<160, Tag> const& v); STObject& peekFieldObject(SField const& field); @@ -1143,7 +1143,7 @@ STObject::at(OptionaledField const& of) -> OptionalProxy template void -STObject::setFieldH160(SField const& field, BaseUint<160, Tag> const& v) +STObject::setFieldH160(SField const& field, BaseUInt<160, Tag> const& v) { STBase* rf = getPField(field, true); diff --git a/include/xrpl/protocol/Serializer.h b/include/xrpl/protocol/Serializer.h index 385c09009a..81706e152a 100644 --- a/include/xrpl/protocol/Serializer.h +++ b/include/xrpl/protocol/Serializer.h @@ -102,7 +102,7 @@ public: template int - addBitString(BaseUint const& v) + addBitString(BaseUInt const& v) { return addRaw(v.data(), v.size()); } @@ -151,7 +151,7 @@ public: template bool - getBitString(BaseUint& data, int offset) const + getBitString(BaseUInt& data, int offset) const { auto success = (offset + (Bits / 8)) <= data_.size(); if (success) @@ -369,7 +369,7 @@ public: geti64(); template - BaseUint + BaseUInt getBitString(); uint128 @@ -428,7 +428,7 @@ public: }; template -BaseUint +BaseUInt SerialIter::getBitString() { auto const n = Bits / 8; @@ -442,7 +442,7 @@ SerialIter::getBitString() used_ += n; remain_ -= n; - return BaseUint::fromVoid(x); + return BaseUInt::fromVoid(x); } } // namespace xrpl diff --git a/include/xrpl/protocol/UintTypes.h b/include/xrpl/protocol/UintTypes.h index 6fb0648b5f..322c58ea1f 100644 --- a/include/xrpl/protocol/UintTypes.h +++ b/include/xrpl/protocol/UintTypes.h @@ -30,21 +30,21 @@ public: /** Directory is an index into the directory of offer books. The last 64 bits of this are the quality. */ -using Directory = BaseUint<256, detail::DirectoryTag>; +using Directory = BaseUInt<256, detail::DirectoryTag>; /** Currency is a hash representing a specific currency. */ -using Currency = BaseUint<160, detail::CurrencyTag>; +using Currency = BaseUInt<160, detail::CurrencyTag>; /** NodeID is a 160-bit hash representing one node. */ -using NodeID = BaseUint<160, detail::NodeIDTag>; +using NodeID = BaseUInt<160, detail::NodeIDTag>; /** MPTID is a 192-bit value representing MPT Issuance ID, * which is a concatenation of a 32-bit sequence (big endian) * and a 160-bit account */ -using MPTID = BaseUint<192>; +using MPTID = BaseUInt<192>; /** Domain is a 256-bit hash representing a specific domain. */ -using Domain = BaseUint<256>; +using Domain = BaseUInt<256>; /** XRP currency. */ Currency const& diff --git a/include/xrpl/server/detail/BaseHTTPPeer.h b/include/xrpl/server/detail/BaseHTTPPeer.h index 0260cd6b84..0706482992 100644 --- a/include/xrpl/server/detail/BaseHTTPPeer.h +++ b/include/xrpl/server/detail/BaseHTTPPeer.h @@ -30,7 +30,7 @@ namespace xrpl { /** Represents an active connection. */ template -class BaseHTTPPeer : public IoList::Work, public Session +class BaseHTTPPeer : public IOList::Work, public Session { protected: using clock_type = std::chrono::system_clock; diff --git a/include/xrpl/server/detail/BasePeer.h b/include/xrpl/server/detail/BasePeer.h index 4aec164fe2..3705ef448e 100644 --- a/include/xrpl/server/detail/BasePeer.h +++ b/include/xrpl/server/detail/BasePeer.h @@ -17,7 +17,7 @@ namespace xrpl { // Common part of all peers template -class BasePeer : public IoList::Work +class BasePeer : public IOList::Work { protected: using clock_type = std::chrono::system_clock; diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index 811bf68a74..f1a622b173 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -39,7 +39,7 @@ namespace xrpl { /** A listening socket. */ template -class Door : public IoList::Work, public std::enable_shared_from_this> +class Door : public IOList::Work, public std::enable_shared_from_this> { private: using clock_type = std::chrono::steady_clock; @@ -53,7 +53,7 @@ private: using stream_type = boost::beast::tcp_stream; // Detects SSL on a socket - class Detector : public IoList::Work, public std::enable_shared_from_this + class Detector : public IOList::Work, public std::enable_shared_from_this { private: Port const& port_; diff --git a/include/xrpl/server/detail/ServerImpl.h b/include/xrpl/server/detail/ServerImpl.h index ce3a1c19c7..a6d53fa4d8 100644 --- a/include/xrpl/server/detail/ServerImpl.h +++ b/include/xrpl/server/detail/ServerImpl.h @@ -78,7 +78,7 @@ private: int high_ = 0; std::array hist_{}; - IoList ios_; + IOList ios_; public: ServerImpl(Handler& handler, boost::asio::io_context& ioContext, beast::Journal journal); @@ -97,7 +97,7 @@ public: void close() override; - IoList& + IOList& ios() { return ios_; diff --git a/include/xrpl/server/detail/io_list.h b/include/xrpl/server/detail/io_list.h index a7037b683c..4daa23fb7e 100644 --- a/include/xrpl/server/detail/io_list.h +++ b/include/xrpl/server/detail/io_list.h @@ -12,7 +12,7 @@ namespace xrpl { /** Manages a set of objects performing asynchronous I/O. */ -class IoList final +class IOList final { public: class Work @@ -21,8 +21,8 @@ public: void destroy(); - friend class IoList; - IoList* ios_ = nullptr; + friend class IOList; + IOList* ios_ = nullptr; public: virtual ~Work() @@ -30,13 +30,13 @@ public: destroy(); } - /** Return the IoList associated with the work. + /** Return the IOList associated with the work. Requirements: - The call to IoList::emplace to + The call to IOList::emplace to create the work has already returned. */ - IoList& + IOList& ios() { return *ios_; @@ -59,17 +59,17 @@ private: std::function f_; public: - IoList() = default; + IOList() = default; /** Destroy the list. Effects: - Closes the IoList if it was not previously + Closes the IOList if it was not previously closed. No finisher is invoked in this case. Blocks until all work is destroyed. */ - ~IoList() + ~IOList() { destroy(); } @@ -159,7 +159,7 @@ public: template void -IoList::Work::destroy() +IOList::Work::destroy() { if (!ios_) return; @@ -179,7 +179,7 @@ IoList::Work::destroy() template void -IoList::destroy() +IOList::destroy() { close(); join(); @@ -187,9 +187,9 @@ IoList::destroy() template std::shared_ptr -IoList::emplace(Args&&... args) +IOList::emplace(Args&&... args) { - static_assert(std::is_base_of_v, "T must derive from IoList::Work"); + static_assert(std::is_base_of_v, "T must derive from IOList::Work"); if (closed_) return nullptr; auto sp = std::make_shared(std::forward(args)...); @@ -211,7 +211,7 @@ IoList::emplace(Args&&... args) template void -IoList::close(Finisher&& f) +IOList::close(Finisher&& f) { std::unique_lock lock(m_); if (closed_) @@ -237,7 +237,7 @@ IoList::close(Finisher&& f) template void -IoList::join() +IOList::join() { std::unique_lock lock(m_); cv_.wait(lock, [&] { return closed_ && n_ == 0; }); diff --git a/include/xrpl/shamap/SHAMap.h b/include/xrpl/shamap/SHAMap.h index ba48e1927b..cca800fa40 100644 --- a/include/xrpl/shamap/SHAMap.h +++ b/include/xrpl/shamap/SHAMap.h @@ -624,7 +624,7 @@ private: inline SHAMap::ConstIterator::ConstIterator(SHAMap const* map) : map_(map) { - XRPL_ASSERT(map_, "xrpl::SHAMap::const_iterator::const_iterator : non-null input"); + XRPL_ASSERT(map_, "xrpl::SHAMap::ConstIterator::ConstIterator : non-null input"); if (auto temp = map_->peekFirstItem(stack_)) item_ = temp->peekItem().get(); diff --git a/include/xrpl/shamap/SHAMapItem.h b/include/xrpl/shamap/SHAMapItem.h index d781dbf894..41558197bf 100644 --- a/include/xrpl/shamap/SHAMapItem.h +++ b/include/xrpl/shamap/SHAMapItem.h @@ -139,7 +139,7 @@ inline boost::intrusive_ptr makeShamapitem(uint256 const& tag, Slice data) { XRPL_ASSERT( - data.size() <= megabytes(16), "xrpl::make_shamapitem : maximum input size"); + data.size() <= megabytes(16), "xrpl::makeShamapitem : maximum input size"); // NOLINTNEXTLINE(misc-const-correctness) std::uint8_t* raw = detail::gSlabber.allocate(data.size()); diff --git a/src/libxrpl/basics/Log.cpp b/src/libxrpl/basics/Log.cpp index a629d865b8..ac9eef582a 100644 --- a/src/libxrpl/basics/Log.cpp +++ b/src/libxrpl/basics/Log.cpp @@ -273,7 +273,7 @@ Logs::toString(LogSeverity s) return "Fatal"; // LCOV_EXCL_START default: - UNREACHABLE("xrpl::Logs::to_string : invalid severity"); + UNREACHABLE("xrpl::Logs::toString : invalid severity"); return "Unknown"; // LCOV_EXCL_STOP } diff --git a/src/libxrpl/basics/ResolverAsio.cpp b/src/libxrpl/basics/ResolverAsio.cpp index 4e64c280c7..4a5ceb3d8d 100644 --- a/src/libxrpl/basics/ResolverAsio.cpp +++ b/src/libxrpl/basics/ResolverAsio.cpp @@ -231,7 +231,7 @@ public: void doStop(CompletionCounter) { - XRPL_ASSERT(stop_called == true, "xrpl::ResolverAsioImpl::do_stop : stopping"); + XRPL_ASSERT(stop_called == true, "xrpl::ResolverAsioImpl::doStop : stopping"); if (!stopped.exchange(true)) { @@ -369,7 +369,7 @@ public: void doResolve(std::vector const& names, HandlerType const& handler, CompletionCounter) { - XRPL_ASSERT(!names.empty(), "xrpl::ResolverAsioImpl::do_resolve : names non-empty"); + XRPL_ASSERT(!names.empty(), "xrpl::ResolverAsioImpl::doResolve : names non-empty"); if (!stop_called) { diff --git a/src/libxrpl/beast/clock/basic_seconds_clock.cpp b/src/libxrpl/beast/clock/basic_seconds_clock.cpp index 2c8ac34b6a..886887dd97 100644 --- a/src/libxrpl/beast/clock/basic_seconds_clock.cpp +++ b/src/libxrpl/beast/clock/basic_seconds_clock.cpp @@ -40,7 +40,7 @@ static_assert(std::atomic::is_always_lock_free); SecondsClockThread::~SecondsClockThread() { XRPL_ASSERT( - thread_.joinable(), "beast::seconds_clock_thread::~seconds_clock_thread : thread joinable"); + thread_.joinable(), "beast::SecondsClockThread::~SecondsClockThread : thread joinable"); { std::scoped_lock const lock(mut_); stop_ = true; diff --git a/src/libxrpl/ledger/Dir.cpp b/src/libxrpl/ledger/Dir.cpp index a633257b27..8744d0bc67 100644 --- a/src/libxrpl/ledger/Dir.cpp +++ b/src/libxrpl/ledger/Dir.cpp @@ -54,14 +54,14 @@ const_iterator::operator==(ConstIterator const& other) const XRPL_ASSERT( view_ == other.view_ && root_.key == other.root_.key, - "xrpl::const_iterator::operator== : views and roots are matching"); + "xrpl::Dir::ConstIterator::operator== : views and roots are matching"); return page_.key == other.page_.key && index_ == other.index_; } const_iterator::reference const_iterator::operator*() const { - XRPL_ASSERT(index_ != beast::kZERO, "xrpl::const_iterator::operator* : nonzero index"); + XRPL_ASSERT(index_ != beast::kZERO, "xrpl::Dir::ConstIterator::operator* : nonzero index"); if (!cache_) cache_ = view_->read(keylet::child(index_)); return *cache_; @@ -70,7 +70,7 @@ const_iterator::operator*() const const_iterator& const_iterator::operator++() { - XRPL_ASSERT(index_ != beast::kZERO, "xrpl::const_iterator::operator++ : nonzero index"); + XRPL_ASSERT(index_ != beast::kZERO, "xrpl::Dir::ConstIterator::operator++ : nonzero index"); if (++it_ != std::end(*indexes_)) { index_ = *it_; @@ -84,7 +84,8 @@ const_iterator::operator++() const_iterator const_iterator::operator++(int) { - XRPL_ASSERT(index_ != beast::kZERO, "xrpl::const_iterator::operator++(int) : nonzero index"); + XRPL_ASSERT( + index_ != beast::kZERO, "xrpl::Dir::ConstIterator::operator++(int) : nonzero index"); ConstIterator tmp(*this); ++(*this); return tmp; @@ -103,7 +104,7 @@ const_iterator::nextPage() { page_ = keylet::page(root_, next); sle_ = view_->read(page_); - XRPL_ASSERT(sle_, "xrpl::const_iterator::next_page : non-null SLE"); + XRPL_ASSERT(sle_, "xrpl::Dir::ConstIterator::nextPage : non-null SLE"); indexes_ = &sle_->getFieldV256(sfIndexes); if (indexes_->empty()) { diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index b37428a6bf..a8ade0de0f 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -287,7 +287,7 @@ quality(Keylet const& k, std::uint64_t q) noexcept Keylet NextT::operator()(Keylet const& k) const { - XRPL_ASSERT(k.type == ltDIR_NODE, "xrpl::keylet::next_t::operator() : valid input type"); + XRPL_ASSERT(k.type == ltDIR_NODE, "xrpl::keylet::NextT::operator() : valid input type"); return {ltDIR_NODE, getQualityNext(k.key)}; } diff --git a/src/libxrpl/protocol/Quality.cpp b/src/libxrpl/protocol/Quality.cpp index d3997cf2db..35a3a3b3a5 100644 --- a/src/libxrpl/protocol/Quality.cpp +++ b/src/libxrpl/protocol/Quality.cpp @@ -62,10 +62,10 @@ ceilInImpl(Amounts const& amount, STAmount const& limit, bool roundUp, Quality c // Clamp out if (result.out > amount.out) result.out = amount.out; - XRPL_ASSERT(result.in == limit, "xrpl::ceil_in_impl : result matches limit"); + XRPL_ASSERT(result.in == limit, "xrpl::ceilInImpl : result matches limit"); return result; } - XRPL_ASSERT(amount.in <= limit, "xrpl::ceil_in_impl : result inside limit"); + XRPL_ASSERT(amount.in <= limit, "xrpl::ceilInImpl : result inside limit"); return amount; } @@ -91,10 +91,10 @@ ceilOutImpl(Amounts const& amount, STAmount const& limit, bool roundUp, Quality // Clamp in if (result.in > amount.in) result.in = amount.in; - XRPL_ASSERT(result.out == limit, "xrpl::ceil_out_impl : result matches limit"); + XRPL_ASSERT(result.out == limit, "xrpl::ceilOutImpl : result matches limit"); return result; } - XRPL_ASSERT(amount.out <= limit, "xrpl::ceil_out_impl : result inside limit"); + XRPL_ASSERT(amount.out <= limit, "xrpl::ceilOutImpl : result inside limit"); return amount; } @@ -114,10 +114,10 @@ Quality composedQuality(Quality const& lhs, Quality const& rhs) { STAmount const lhsRate(lhs.rate()); - XRPL_ASSERT(lhsRate != beast::kZERO, "xrpl::composed_quality : nonzero left input"); + XRPL_ASSERT(lhsRate != beast::kZERO, "xrpl::composedQuality : nonzero left input"); STAmount const rhsRate(rhs.rate()); - XRPL_ASSERT(rhsRate != beast::kZERO, "xrpl::composed_quality : nonzero right input"); + XRPL_ASSERT(rhsRate != beast::kZERO, "xrpl::composedQuality : nonzero right input"); STAmount const rate(mulRound(lhsRate, rhsRate, lhsRate.asset(), true)); @@ -125,7 +125,7 @@ composedQuality(Quality const& lhs, Quality const& rhs) std::uint64_t const storedMantissa(rate.mantissa()); XRPL_ASSERT( - (storedExponent > 0) && (storedExponent <= 255), "xrpl::composed_quality : valid exponent"); + (storedExponent > 0) && (storedExponent <= 255), "xrpl::composedQuality : valid exponent"); return Quality((storedExponent << (64 - 8)) | storedMantissa); } diff --git a/src/libxrpl/server/Port.cpp b/src/libxrpl/server/Port.cpp index ee8492c51e..9a6b6dce35 100644 --- a/src/libxrpl/server/Port.cpp +++ b/src/libxrpl/server/Port.cpp @@ -61,7 +61,7 @@ operator<<(std::ostream& os, Port const& p) if (!p.secure_gateway_nets_v4.empty() || !p.secure_gateway_nets_v6.empty()) { - os << "secureGateway nets:"; + os << "secure_gateway nets:"; for (auto const& net : p.secure_gateway_nets_v4) { os << net.to_string(); diff --git a/src/libxrpl/tx/applySteps.cpp b/src/libxrpl/tx/applySteps.cpp index 7833341808..5f73cbba87 100644 --- a/src/libxrpl/tx/applySteps.cpp +++ b/src/libxrpl/tx/applySteps.cpp @@ -158,7 +158,7 @@ invokePreflight(PreflightContext const& ctx) // Should never happen // LCOV_EXCL_START JLOG(ctx.j.fatal()) << "Unknown transaction type in preflight: " << e.txnType; - UNREACHABLE("xrpl::invoke_preflight : unknown transaction type"); + UNREACHABLE("xrpl::invokePreflight : unknown transaction type"); return {temUNKNOWN, TxConsequences{temUNKNOWN}}; // LCOV_EXCL_STOP } @@ -217,7 +217,7 @@ invokePreclaim(PreclaimContext const& ctx) // Should never happen // LCOV_EXCL_START JLOG(ctx.j.fatal()) << "Unknown transaction type in preclaim: " << e.txnType; - UNREACHABLE("xrpl::invoke_preclaim : unknown transaction type"); + UNREACHABLE("xrpl::invokePreclaim : unknown transaction type"); return temUNKNOWN; // LCOV_EXCL_STOP } @@ -307,7 +307,7 @@ invokeApply(ApplyContext& ctx) // Should never happen // LCOV_EXCL_START JLOG(ctx.journal.fatal()) << "Unknown transaction type in apply: " << e.txnType; - UNREACHABLE("xrpl::invoke_apply : unknown transaction type"); + UNREACHABLE("xrpl::invokeApply : unknown transaction type"); return {temUNKNOWN, false}; // LCOV_EXCL_STOP } diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 55e1b73158..a4e972f504 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -1420,8 +1420,8 @@ public: uint8_t tag2[] = "hello world some ascii 32b long"; // including 1 byte for NUL - uint256 bogieTag = xrpl::BaseUint<256>::fromVoid(tag1); - uint256 demonTag = xrpl::BaseUint<256>::fromVoid(tag2); + uint256 bogieTag = xrpl::BaseUInt<256>::fromVoid(tag1); + uint256 demonTag = xrpl::BaseUInt<256>::fromVoid(tag2); // Attach phantom signers to alice and use them for a transaction. env(signers(alice, 1, {{bogie_, 1, bogieTag}, {demon_, 1, demonTag}})); diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index b0b93e0b3f..e8c1a16a70 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -206,7 +206,7 @@ class Vault_test : public beast::unit_test::Suite { testcase(prefix + " fail to set domain on public vault"); auto tx = vault.set({.owner = owner, .id = keylet.key}); - tx[sfDomainID] = to_string(BaseUint<256>(42ul)); + tx[sfDomainID] = to_string(BaseUInt<256>(42ul)); env(tx, Ter{tecNO_PERMISSION}); env.close(); } @@ -678,14 +678,14 @@ class Vault_test : public beast::unit_test::Suite env(tx); tx[sfFlags] = tx[sfFlags].asUInt() | tfVaultPrivate; - tx[sfDomainID] = to_string(BaseUint<256>(42ul)); + tx[sfDomainID] = to_string(BaseUInt<256>(42ul)); env(tx, Ter{temDISABLED}); { auto tx = vault.set({.owner = owner, .id = keylet.key}); env(tx, kDATA("Test")); - tx[sfDomainID] = to_string(BaseUint<256>(13ul)); + tx[sfDomainID] = to_string(BaseUInt<256>(13ul)); env(tx, Ter{temDISABLED}); } }, @@ -786,12 +786,12 @@ class Vault_test : public beast::unit_test::Suite testcase("disabled permissioned domain"); auto [tx, keylet] = vault.create({.owner = owner, .asset = xrpIssue()}); - tx[sfDomainID] = to_string(BaseUint<256>(42ul)); + tx[sfDomainID] = to_string(BaseUInt<256>(42ul)); env(tx, Ter{temDISABLED}); { auto tx = vault.set({.owner = owner, .id = keylet.key}); - tx[sfDomainID] = to_string(BaseUint<256>(42ul)); + tx[sfDomainID] = to_string(BaseUInt<256>(42ul)); env(tx, Ter{temDISABLED}); } @@ -1079,7 +1079,7 @@ class Vault_test : public beast::unit_test::Suite { auto tx = tx1; - tx[sfDomainID] = to_string(BaseUint<256>(42ul)); + tx[sfDomainID] = to_string(BaseUInt<256>(42ul)); env(tx, Ter{temMALFORMED}); } @@ -1238,7 +1238,7 @@ class Vault_test : public beast::unit_test::Suite Vault& vault) { auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); tx[sfFlags] = tfVaultPrivate; - tx[sfDomainID] = to_string(BaseUint<256>(42ul)); + tx[sfDomainID] = to_string(BaseUInt<256>(42ul)); testcase("non-existing domain"); env(tx, Ter{tecOBJECT_NOT_FOUND}); }); @@ -3065,7 +3065,7 @@ class Vault_test : public beast::unit_test::Suite { testcase("private vault cannot set non-existing domain"); auto tx = vault.set({.owner = owner, .id = keylet.key}); - tx[sfDomainID] = to_string(BaseUint<256>(42ul)); + tx[sfDomainID] = to_string(BaseUInt<256>(42ul)); env(tx, Ter{tecOBJECT_NOT_FOUND}); } diff --git a/src/test/basics/base_uint_test.cpp b/src/test/basics/base_uint_test.cpp index cf5ece6240..8d5b56d480 100644 --- a/src/test/basics/base_uint_test.cpp +++ b/src/test/basics/base_uint_test.cpp @@ -49,7 +49,7 @@ struct Nonhash struct base_uint_test : beast::unit_test::Suite { - using test96 = BaseUint<96>; + using test96 = BaseUInt<96>; static_assert(std::is_copy_constructible_v); static_assert(std::is_copy_assignable_v); @@ -68,7 +68,7 @@ struct base_uint_test : beast::unit_test::Suite for (auto const& arg : kTEST_ARGS) { - xrpl::BaseUint<64> const u{arg.first}, v{arg.second}; + xrpl::BaseUInt<64> const u{arg.first}, v{arg.second}; BEAST_EXPECT(u < v); BEAST_EXPECT(u <= v); BEAST_EXPECT(u != v); @@ -99,7 +99,7 @@ struct base_uint_test : beast::unit_test::Suite for (auto const& arg : kTEST_ARGS) { - xrpl::BaseUint<96> const u{arg.first}, v{arg.second}; + xrpl::BaseUInt<96> const u{arg.first}, v{arg.second}; BEAST_EXPECT(u < v); BEAST_EXPECT(u <= v); BEAST_EXPECT(u != v); @@ -327,16 +327,16 @@ struct base_uint_test : beast::unit_test::Suite // Verify that constexpr base_uints interpret a string the same // way parseHex() does. - struct StrBaseUint + struct StrBaseUInt { char const* const str; test96 tst; - constexpr StrBaseUint(char const* s) : str(s), tst(s) + constexpr StrBaseUInt(char const* s) : str(s), tst(s) { } }; - constexpr StrBaseUint kTEST_CASES[] = { + constexpr StrBaseUInt kTEST_CASES[] = { "000000000000000000000000", "000000000000000000000001", "fedcba9876543210ABCDEF91", @@ -344,7 +344,7 @@ struct base_uint_test : beast::unit_test::Suite "800000000000000000000000", "fFfFfFfFfFfFfFfFfFfFfFfF"}; - for (StrBaseUint const& t : kTEST_CASES) + for (StrBaseUInt const& t : kTEST_CASES) { test96 t96; BEAST_EXPECT(t96.parseHex(t.str)); diff --git a/src/test/beast/beast_io_latency_probe_test.cpp b/src/test/beast/beast_io_latency_probe_test.cpp index a884256986..5f183ef091 100644 --- a/src/test/beast/beast_io_latency_probe_test.cpp +++ b/src/test/beast/beast_io_latency_probe_test.cpp @@ -112,7 +112,7 @@ class io_latency_probe_test : public beast::unit_test::Suite, public beast::test struct TestSampler { - beast::IoLatencyProbe probe; + beast::IOLatencyProbe probe; std::vector durations; TestSampler(std::chrono::milliseconds interval, boost::asio::io_context& ios) diff --git a/src/test/protocol/STIssue_test.cpp b/src/test/protocol/STIssue_test.cpp index 04364510f0..1d6d750355 100644 --- a/src/test/protocol/STIssue_test.cpp +++ b/src/test/protocol/STIssue_test.cpp @@ -55,7 +55,7 @@ public: auto const data = "00000000000000000000000055534400000000000000000000000000000000" "000000000000000000"; - BaseUint<320> uint; + BaseUInt<320> uint; (void)uint.parseHex(data); SerialIter iter(Slice(uint.data(), uint.size())); STIssue const stissue(iter, sfAsset); @@ -89,7 +89,7 @@ public: auto const data = "0000000000000000000000005553440000000000ae123a8556f3cf91154711" "376afb0f894f832b3d"; - BaseUint<320> uint; + BaseUInt<320> uint; (void)uint.parseHex(data); SerialIter iter(Slice(uint.data(), uint.size())); STIssue const stissue(iter, sfAsset); @@ -103,7 +103,7 @@ public: try { auto const data = "0000000000000000000000000000000000000000"; - BaseUint<160> uint; + BaseUInt<160> uint; (void)uint.parseHex(data); SerialIter iter(Slice(uint.data(), uint.size())); STIssue const stissue(iter, sfAsset); diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 800d398eca..b8975f748b 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -141,16 +141,16 @@ fixConfigPorts(Config& config, Endpoints const& endpoints); class ApplicationImp : public Application, public BasicApp { private: - class IoLatencySampler + class IOLatencySampler { private: beast::insight::Event event_; beast::Journal journal_; - beast::IoLatencyProbe probe_; + beast::IOLatencyProbe probe_; std::atomic lastSample_; public: - IoLatencySampler( + IOLatencySampler( beast::insight::Event ev, beast::Journal journal, std::chrono::milliseconds interval, @@ -272,7 +272,7 @@ public: std::unique_ptr resolver_; - IoLatencySampler io_latency_sampler_; + IOLatencySampler io_latency_sampler_; std::unique_ptr grpcServer_; // NOLINTEND(readability-identifier-naming) diff --git a/src/xrpld/app/main/GRPCServer.cpp b/src/xrpld/app/main/GRPCServer.cpp index 8427d41397..d89735f521 100644 --- a/src/xrpld/app/main/GRPCServer.cpp +++ b/src/xrpld/app/main/GRPCServer.cpp @@ -376,8 +376,8 @@ GRPCServerImpl::GRPCServerImpl(Application& app) if (addr.is_unspecified()) { JLOG(journal_.error()) << "Can't pass unspecified IP in " - << "secureGateway section of port_grpc"; - Throw("Unspecified IP in secureGateway section"); + << "secure_gateway section of port_grpc"; + Throw("Unspecified IP in secure_gateway section"); } secureGatewayIPs_.emplace_back(addr); @@ -386,7 +386,7 @@ GRPCServerImpl::GRPCServerImpl(Application& app) catch (std::exception const&) { JLOG(journal_.error()) << "Error parsing secure gateway IPs for grpc server"; - Throw("Error parsing secureGateway section"); + Throw("Error parsing secure_gateway section"); } } diff --git a/src/xrpld/app/rdb/backend/detail/Node.cpp b/src/xrpld/app/rdb/backend/detail/Node.cpp index 4b8c318be8..0bf6086ead 100644 --- a/src/xrpld/app/rdb/backend/detail/Node.cpp +++ b/src/xrpld/app/rdb/backend/detail/Node.cpp @@ -87,7 +87,7 @@ toString(TableType type) return "AccountTransactions"; // LCOV_EXCL_START default: - UNREACHABLE("xrpl::detail::to_string : invalid TableType"); + UNREACHABLE("xrpl::detail::toString : invalid TableType"); return "Unknown"; // LCOV_EXCL_STOP } diff --git a/src/xrpld/overlay/detail/Handshake.cpp b/src/xrpld/overlay/detail/Handshake.cpp index af0585f8ad..b32d5280e2 100644 --- a/src/xrpld/overlay/detail/Handshake.cpp +++ b/src/xrpld/overlay/detail/Handshake.cpp @@ -132,7 +132,7 @@ makeFeaturesResponseHeader( this topic, see https://github.com/openssl/openssl/issues/5509 and https://github.com/XRPLF/rippled/issues/2413. */ -static std::optional> +static std::optional> hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t)) { constexpr std::size_t kSSL_MINIMUM_FINISHED_LENGTH = 12; @@ -145,7 +145,7 @@ hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t)) sha512_hasher const h; - BaseUint<512> cookie; + BaseUInt<512> cookie; SHA512(buf, len, cookie.data()); return cookie; } diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index e21d00a1e7..4db379cee5 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -475,14 +475,14 @@ OverlayImpl::addActive(std::shared_ptr const& peer) { auto const result = peers_.emplace(peer->slot(), peer); - XRPL_ASSERT(result.second, "xrpl::OverlayImpl::add_active : peer is inserted"); + XRPL_ASSERT(result.second, "xrpl::OverlayImpl::addActive : peer is inserted"); (void)result.second; } { auto const result = ids_.emplace( std::piecewise_construct, std::make_tuple(peer->id()), std::make_tuple(peer)); - XRPL_ASSERT(result.second, "xrpl::OverlayImpl::add_active : peer ID is inserted"); + XRPL_ASSERT(result.second, "xrpl::OverlayImpl::addActive : peer ID is inserted"); (void)result.second; } diff --git a/src/xrpld/peerfinder/detail/Bootcache.cpp b/src/xrpld/peerfinder/detail/Bootcache.cpp index 140d0f01c6..b2e5795661 100644 --- a/src/xrpld/peerfinder/detail/Bootcache.cpp +++ b/src/xrpld/peerfinder/detail/Bootcache.cpp @@ -143,7 +143,7 @@ Bootcache::onSuccess(beast::IP::Endpoint const& endpoint) ++entry.valence(); map_.erase(result.first); result = map_.insert(value_type(endpoint, entry)); - XRPL_ASSERT(result.second, "xrpl::PeerFinder::Bootcache::on_success : endpoint inserted"); + XRPL_ASSERT(result.second, "xrpl::PeerFinder::Bootcache::onSuccess : endpoint inserted"); } Entry const& entry(result.first->right); JLOG(journal_.info()) << beast::Leftw(18) << "Bootcache connect " << endpoint << " with " @@ -166,7 +166,7 @@ Bootcache::onFailure(beast::IP::Endpoint const& endpoint) --entry.valence(); map_.erase(result.first); result = map_.insert(value_type(endpoint, entry)); - XRPL_ASSERT(result.second, "xrpl::PeerFinder::Bootcache::on_failure : endpoint inserted"); + XRPL_ASSERT(result.second, "xrpl::PeerFinder::Bootcache::onFailure : endpoint inserted"); } Entry const& entry(result.first->right); auto const n(std::abs(entry.valence())); diff --git a/src/xrpld/peerfinder/detail/Handouts.h b/src/xrpld/peerfinder/detail/Handouts.h index f4c7483e3f..b26a44015d 100644 --- a/src/xrpld/peerfinder/detail/Handouts.h +++ b/src/xrpld/peerfinder/detail/Handouts.h @@ -23,7 +23,7 @@ template std::size_t handoutOne(Target& t, HopContainer& h) { - XRPL_ASSERT(!t.full(), "xrpl::PeerFinder::detail::handout_one : target is not full"); + XRPL_ASSERT(!t.full(), "xrpl::PeerFinder::detail::handoutOne : target is not full"); for (auto it = h.begin(); it != h.end(); ++it) { auto const& e = *it; diff --git a/src/xrpld/peerfinder/detail/Livecache.h b/src/xrpld/peerfinder/detail/Livecache.h index 063fb622cf..3222a13d60 100644 --- a/src/xrpld/peerfinder/detail/Livecache.h +++ b/src/xrpld/peerfinder/detail/Livecache.h @@ -499,7 +499,7 @@ Livecache::HopsT::insert(Element& e) { XRPL_ASSERT( e.endpoint.hops <= Tuning::kMAX_HOPS + 1, - "xrpl::PeerFinder::Livecache::hops_t::insert : maximum input hops"); + "xrpl::PeerFinder::Livecache::HopsT::insert : maximum input hops"); // This has security implications without a shuffle lists_[e.endpoint.hops].push_front(e); ++hist_[e.endpoint.hops]; @@ -511,7 +511,7 @@ Livecache::HopsT::reinsert(Element& e, std::uint32_t numHops) { XRPL_ASSERT( numHops <= Tuning::kMAX_HOPS + 1, - "xrpl::PeerFinder::Livecache::hops_t::reinsert : maximum hops input"); + "xrpl::PeerFinder::Livecache::HopsT::reinsert : maximum hops input"); auto& list = lists_[e.endpoint.hops]; list.erase(list.iterator_to(e)); diff --git a/src/xrpld/peerfinder/detail/Logic.h b/src/xrpld/peerfinder/detail/Logic.h index 89e7336a33..831942d45c 100644 --- a/src/xrpld/peerfinder/detail/Logic.h +++ b/src/xrpld/peerfinder/detail/Logic.h @@ -745,12 +745,12 @@ public: // The object must exist in our table XRPL_ASSERT( slots.contains(slot->remoteEndpoint()), - "xrpl::PeerFinder::Logic::on_endpoints : valid slot input"); + "xrpl::PeerFinder::Logic::onEndpoints : valid slot input"); // Must be handshaked! XRPL_ASSERT( slot->state() == Slot::State::Active, - "xrpl::PeerFinder::Logic::on_endpoints : valid slot state"); + "xrpl::PeerFinder::Logic::onEndpoints : valid slot state"); clock_type::time_point const now(clock.now()); @@ -762,7 +762,7 @@ public: for (auto const& ep : list) { - XRPL_ASSERT(ep.hops, "xrpl::PeerFinder::Logic::on_endpoints : nonzero hops"); + XRPL_ASSERT(ep.hops, "xrpl::PeerFinder::Logic::onEndpoints : nonzero hops"); slot->recent.insert(ep.address, ep.hops); From 4a9f72c73e93a75db95445689ff7c45990d5ff04 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 7 May 2026 18:14:01 +0100 Subject: [PATCH 17/18] style: Make .clang-tidy style a bit more consistent with Clio (#7096) --- .clang-tidy | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 33569be50a..15d354a8c3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -156,7 +156,13 @@ Checks: "-*, # readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names # readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable # --- + CheckOptions: + bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true + bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc + + misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*;.*fwd\\.h(pp)?;time.h;stdlib.h;sqlite3.h;netinet/in\\.h;sys/resource\\.h;sys/sysinfo\\.h;linux/sysinfo\\.h;__chrono/.*;bits/.*;_abort\\.h;boost/uuid/uuid_hash.hpp;boost/beast/core/flat_buffer\\.hpp;boost/beast/http/field\\.hpp;boost/beast/http/dynamic_body\\.hpp;boost/beast/http/message\\.hpp;boost/beast/http/read\\.hpp;boost/beast/http/write\\.hpp;openssl/obj_mac\\.h" + readability-braces-around-statements.ShortStatementLines: 2 readability-identifier-naming.MacroDefinitionCase: UPPER_CASE readability-identifier-naming.ClassCase: CamelCase @@ -191,9 +197,7 @@ CheckOptions: readability-identifier-naming.ProtectedMemberSuffix: _ readability-identifier-naming.PublicMemberSuffix: "" readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$" - bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true - bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc - misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*;.*fwd\\.h(pp)?;time.h;stdlib.h;sqlite3.h;netinet/in\\.h;sys/resource\\.h;sys/sysinfo\\.h;linux/sysinfo\\.h;__chrono/.*;bits/.*;_abort\\.h;boost/uuid/uuid_hash.hpp;boost/beast/core/flat_buffer\\.hpp;boost/beast/http/field\\.hpp;boost/beast/http/dynamic_body\\.hpp;boost/beast/http/message\\.hpp;boost/beast/http/read\\.hpp;boost/beast/http/write\\.hpp;openssl/obj_mac\\.h" + HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp|ipp)$' ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$' WarningsAsErrors: "*" From 4f8142fd10aebf5c3151195c3992a63c2b763c99 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Thu, 7 May 2026 21:02:09 +0200 Subject: [PATCH 18/18] fix: Numerically-stable (1+r)^n-1 in computePaymentFactor (#7033) --- include/xrpl/ledger/helpers/LendingHelpers.h | 21 +- src/libxrpl/ledger/helpers/LendingHelpers.cpp | 136 ++++++- .../tx/transactors/lending/LoanSet.cpp | 1 + src/test/app/LendingHelpers_test.cpp | 373 +++++++++++++++--- src/test/app/Loan_test.cpp | 213 +++++++++- 5 files changed, 660 insertions(+), 84 deletions(-) diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index 83ce8e5efa..b9711c4053 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -184,6 +184,7 @@ checkLoanGuards( LoanState computeTheoreticalLoanState( + Rules const& rules, Number const& periodicPayment, Number const& periodicRate, std::uint32_t const paymentRemaining, @@ -353,6 +354,7 @@ struct LoanStateDeltas Expected, TER> tryOverpayment( + Rules const& rules, Asset const& asset, std::int32_t loanScale, ExtendedPaymentComponents const& overpaymentComponents, @@ -363,11 +365,17 @@ tryOverpayment( TenthBips16 const managementFeeRate, beast::Journal j); -Number -computeRaisedRate(Number const& periodicRate, std::uint32_t paymentsRemaining); +[[nodiscard]] Number +computePowerMinusOne(Number const& periodicRate, std::uint32_t paymentsRemaining); -Number -computePaymentFactor(Number const& periodicRate, std::uint32_t paymentsRemaining); +[[nodiscard]] Number +computePowerMinusOneHybrid(Number const& periodicRate, std::uint32_t paymentsRemaining); + +[[nodiscard]] Number +computePaymentFactor( + Rules const& rules, + Number const& periodicRate, + std::uint32_t paymentsRemaining); std::pair computeInterestAndFeeParts( @@ -378,12 +386,14 @@ computeInterestAndFeeParts( Number loanPeriodicPayment( + Rules const& rules, Number const& principalOutstanding, Number const& periodicRate, std::uint32_t paymentsRemaining); Number loanPrincipalFromPeriodicPayment( + Rules const& rules, Number const& periodicPayment, Number const& periodicRate, std::uint32_t paymentsRemaining); @@ -415,6 +425,7 @@ computeOverpaymentComponents( PaymentComponents computePaymentComponents( + Rules const& rules, Asset const& asset, std::int32_t scale, Number const& totalValueOutstanding, @@ -438,6 +449,7 @@ operator+(LoanState const& lhs, detail::LoanStateDeltas const& rhs); LoanProperties computeLoanProperties( + Rules const& rules, Asset const& asset, Number const& principalOutstanding, TenthBips32 interestRate, @@ -448,6 +460,7 @@ computeLoanProperties( LoanProperties computeLoanProperties( + Rules const& rules, Asset const& asset, Number const& principalOutstanding, Number const& periodicRate, diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 28b0c8976d..89d5ed8e35 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -110,14 +110,78 @@ LoanStateDeltas::nonNegative() managementFee = kNUM_ZERO; } -/* Computes (1 + periodicRate)^paymentsRemaining for amortization calculations. +/* Computes (1 + r)^n - 1 accurately even for near-zero r, where direct + * subtraction of `power(1 + r, n) - 1` suffers catastrophic cancellation. * - * Equation (5) from XLS-66 spec, Section A-2 Equation Glossary + * The binomial expansion gives + * (1 + r)^n - 1 = sum_{k=1}^{n} C(n,k) r^k + * = nr + C(n,2) r^2 + ... + r^n + * which is a sum of positive terms when r >= 0, avoiding cancellation. + * Each term is computed from the previous via + * term_{k+1} = term_k * r * (n - k) / (k + 1) + * + * The loop terminates early once the next term is below Number precision. */ Number -computeRaisedRate(Number const& periodicRate, std::uint32_t paymentsRemaining) +computePowerMinusOne(Number const& periodicRate, std::uint32_t paymentsRemaining) { - return power(1 + periodicRate, paymentsRemaining); + XRPL_ASSERT_PARTS( + periodicRate >= beast::kZERO, + "xrpl::detail::computePowerMinusOne", + "periodicRate is non-negative"); + + if (paymentsRemaining == 0 || periodicRate == beast::kZERO) + return kNUM_ZERO; + + // k = 1 term: C(n, 1) * r = n * r + Number term = paymentsRemaining * periodicRate; + Number sum = term; + for (std::uint32_t k = 1; k < paymentsRemaining; ++k) + { + // term_{k+1} from term_k: multiply by r * (n - k) / (k + 1) + term = term * periodicRate * (paymentsRemaining - k) / (k + 1); + Number const next = sum + term; + // adding this term fell below Number's precision + if (next == sum) + break; + sum = next; + } + return sum; +} + +/* Hybrid evaluator of (1 + r)^n - 1. + * + * The closed-form `power(1 + r, n) - 1` loses sig digits to cancellation + * when `r * n` is small: the result `~r*n` sits well below the `1` that + * dominates `(1+r)^n`, so most of Number's stored precision is consumed + * by the leading `1`. + * + * A threshold of `1e-9` preserves the closed-form path for any rate the + * lending code actually sees in practice (fixtures at moderate rates are bit-exact), + * while routing the pathological near-zero regime through the binomial + * expansion where cancellation is severe. + */ +Number +computePowerMinusOneHybrid(Number const& periodicRate, std::uint32_t paymentsRemaining) +{ + XRPL_ASSERT_PARTS( + periodicRate >= beast::kZERO, + "xrpl::detail::computePowerMinusOneHybrid", + "periodicRate is non-negative"); + + if (paymentsRemaining == 0 || periodicRate == beast::kZERO) + return kNUM_ZERO; + + // Threshold 1e-9 retains ~10 sig digits of (1+r)^n - 1 against + // Number's 19-digit mantissa: the leading "1" of (1+r)^n consumes + // ~log10(1/(r*n)) digits before the subtraction. Above this point + // closed form is accurate and ~30-500x faster than the binomial + // expansion. + Number const cancellationThreshold{1, -9}; + if (paymentsRemaining * periodicRate >= cancellationThreshold) + return power(1 + periodicRate, paymentsRemaining) - 1; + + return computePowerMinusOne(periodicRate, paymentsRemaining); } /* Computes the payment factor used in standard amortization formulas. @@ -126,7 +190,10 @@ computeRaisedRate(Number const& periodicRate, std::uint32_t paymentsRemaining) * Equation (6) from XLS-66 spec, Section A-2 Equation Glossary */ Number -computePaymentFactor(Number const& periodicRate, std::uint32_t paymentsRemaining) +computePaymentFactor( + Rules const& rules, + Number const& periodicRate, + std::uint32_t paymentsRemaining) { if (paymentsRemaining == 0) return kNUM_ZERO; @@ -135,7 +202,19 @@ computePaymentFactor(Number const& periodicRate, std::uint32_t paymentsRemaining if (periodicRate == beast::kZERO) return Number{1} / paymentsRemaining; - Number const raisedRate = computeRaisedRate(periodicRate, paymentsRemaining); + if (rules.enabled(fixCleanup3_2_0)) + { + Number const raisedRateMinusOne = + computePowerMinusOneHybrid(periodicRate, paymentsRemaining); + Number const raisedRate = 1 + raisedRateMinusOne; + + return (periodicRate * raisedRate) / raisedRateMinusOne; + } + + // Pre-fixCleanup3_2_0: direct subtraction `(1+r)^n - 1` suffers + // catastrophic cancellation at near-zero rates. Retained for + // amendment-gated bit-exact pre-fix behavior. + Number const raisedRate = power(1 + periodicRate, paymentsRemaining); return (periodicRate * raisedRate) / (raisedRate - 1); } @@ -147,6 +226,7 @@ computePaymentFactor(Number const& periodicRate, std::uint32_t paymentsRemaining */ Number loanPeriodicPayment( + Rules const& rules, Number const& principalOutstanding, Number const& periodicRate, std::uint32_t paymentsRemaining) @@ -158,7 +238,7 @@ loanPeriodicPayment( if (periodicRate == beast::kZERO) return principalOutstanding / paymentsRemaining; - return principalOutstanding * computePaymentFactor(periodicRate, paymentsRemaining); + return principalOutstanding * computePaymentFactor(rules, periodicRate, paymentsRemaining); } /* Reverse-calculates principal from periodic payment amount. @@ -168,6 +248,7 @@ loanPeriodicPayment( */ Number loanPrincipalFromPeriodicPayment( + Rules const& rules, Number const& periodicPayment, Number const& periodicRate, std::uint32_t paymentsRemaining) @@ -178,7 +259,7 @@ loanPrincipalFromPeriodicPayment( if (periodicRate == 0) return periodicPayment * paymentsRemaining; - return periodicPayment / computePaymentFactor(periodicRate, paymentsRemaining); + return periodicPayment / computePaymentFactor(rules, periodicRate, paymentsRemaining); } /* @@ -402,6 +483,7 @@ doPayment( */ Expected, TER> tryOverpayment( + Rules const& rules, Asset const& asset, std::int32_t loanScale, ExtendedPaymentComponents const& overpaymentComponents, @@ -414,7 +496,7 @@ tryOverpayment( { // Calculate what the loan state SHOULD be theoretically (at full precision) auto const theoreticalState = computeTheoreticalLoanState( - periodicPayment, periodicRate, paymentRemaining, managementFeeRate); + rules, periodicPayment, periodicRate, paymentRemaining, managementFeeRate); // Calculate the accumulated rounding errors. These need to be preserved // across the re-amortization to maintain consistency with the loan's @@ -432,6 +514,7 @@ tryOverpayment( // recalculates the periodic payment, total value, and management fees // for the remaining payment schedule. auto newLoanProperties = computeLoanProperties( + rules, asset, newTheoreticalPrincipal, periodicRate, @@ -445,9 +528,12 @@ tryOverpayment( // Calculate what the new loan state should be with the new periodic payment // including rounding errors - auto const newTheoreticalState = - computeTheoreticalLoanState( - newLoanProperties.periodicPayment, periodicRate, paymentRemaining, managementFeeRate) + + auto const newTheoreticalState = computeTheoreticalLoanState( + rules, + newLoanProperties.periodicPayment, + periodicRate, + paymentRemaining, + managementFeeRate) + errors; JLOG(j.debug()) << "new theoretical value: " << newTheoreticalState.valueOutstanding @@ -582,6 +668,7 @@ tryOverpayment( template Expected doOverpayment( + Rules const& rules, Asset const& asset, std::int32_t loanScale, ExtendedPaymentComponents const& overpaymentComponents, @@ -610,6 +697,7 @@ doOverpayment( // Attempt to re-amortize the loan with the overpayment applied. // This modifies the temporary copies, leaving the proxies unchanged. auto const ret = tryOverpayment( + rules, asset, loanScale, overpaymentComponents, @@ -823,8 +911,8 @@ computeFullPayment( // Calculate the theoretical principal based on the payment schedule. // This theoretical (unrounded) value is used to compute interest and // penalties accurately. - Number const theoreticalPrincipalOutstanding = - loanPrincipalFromPeriodicPayment(periodicPayment, periodicRate, paymentRemaining); + Number const theoreticalPrincipalOutstanding = loanPrincipalFromPeriodicPayment( + view.rules(), periodicPayment, periodicRate, paymentRemaining); // Full payment interest includes both accrued interest (time since last // payment) and prepayment penalty (for closing early). @@ -929,6 +1017,7 @@ PaymentComponents::trackedInterestPart() const */ PaymentComponents computePaymentComponents( + Rules const& rules, Asset const& asset, std::int32_t scale, Number const& totalValueOutstanding, @@ -966,7 +1055,7 @@ computePaymentComponents( // Calculate what the loan state SHOULD be after this payment (the target). // This is computed at full precision using the theoretical amortization. LoanState const trueTarget = computeTheoreticalLoanState( - periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate); + rules, periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate); // Round the target to the loan's scale to match how actual loan values // are stored. @@ -1379,6 +1468,7 @@ computeFullPaymentInterest( */ LoanState computeTheoreticalLoanState( + Rules const& rules, Number const& periodicPayment, Number const& periodicRate, std::uint32_t const paymentRemaining, @@ -1396,8 +1486,8 @@ computeTheoreticalLoanState( // Equation (30) from XLS-66 spec, Section A-2 Equation Glossary Number const totalValueOutstanding = periodicPayment * paymentRemaining; - Number const principalOutstanding = - detail::loanPrincipalFromPeriodicPayment(periodicPayment, periodicRate, paymentRemaining); + Number const principalOutstanding = detail::loanPrincipalFromPeriodicPayment( + rules, periodicPayment, periodicRate, paymentRemaining); // Equation (31) from XLS-66 spec, Section A-2 Equation Glossary Number const interestOutstandingGross = totalValueOutstanding - principalOutstanding; @@ -1488,6 +1578,7 @@ computeManagementFee( */ LoanProperties computeLoanProperties( + Rules const& rules, Asset const& asset, Number const& principalOutstanding, TenthBips32 interestRate, @@ -1499,6 +1590,7 @@ computeLoanProperties( auto const periodicRate = loanPeriodicRate(interestRate, paymentInterval); XRPL_ASSERT(interestRate == 0 || periodicRate > 0, "xrpl::computeLoanProperties : valid rate"); return computeLoanProperties( + rules, asset, principalOutstanding, periodicRate, @@ -1517,6 +1609,7 @@ computeLoanProperties( */ LoanProperties computeLoanProperties( + Rules const& rules, Asset const& asset, Number const& principalOutstanding, Number const& periodicRate, @@ -1525,7 +1618,7 @@ computeLoanProperties( std::int32_t minimumScale) { auto const periodicPayment = - detail::loanPeriodicPayment(principalOutstanding, periodicRate, paymentsRemaining); + detail::loanPeriodicPayment(rules, principalOutstanding, periodicRate, paymentsRemaining); auto const [totalValueOutstanding, loanScale] = [&]() { // only round up if there should be interest @@ -1573,10 +1666,10 @@ computeLoanProperties( // Compute the parts for the first payment. Ensure that the // principal payment will actually change the principal. auto const startingState = computeTheoreticalLoanState( - periodicPayment, periodicRate, paymentsRemaining, managementFeeRate); + rules, periodicPayment, periodicRate, paymentsRemaining, managementFeeRate); auto const firstPaymentState = computeTheoreticalLoanState( - periodicPayment, periodicRate, paymentsRemaining - 1, managementFeeRate); + rules, periodicPayment, periodicRate, paymentsRemaining - 1, managementFeeRate); // The unrounded principal part needs to be large enough to affect // the principal. What to do if not is left to the caller @@ -1733,6 +1826,7 @@ loanMakePayment( // payment is late or regular detail::ExtendedPaymentComponents periodic{ detail::computePaymentComponents( + view.rules(), asset, loanScale, totalValueOutstandingProxy, @@ -1841,6 +1935,7 @@ loanMakePayment( periodic = detail::ExtendedPaymentComponents{ detail::computePaymentComponents( + view.rules(), asset, loanScale, totalValueOutstandingProxy, @@ -1901,6 +1996,7 @@ loanMakePayment( // change auto periodicPaymentProxy = loan->at(sfPeriodicPayment); if (auto const overResult = detail::doOverpayment( + view.rules(), asset, loanScale, overpaymentComponents, diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index c921017d15..3f5bbfb1a3 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -418,6 +418,7 @@ LoanSet::doApply() auto const paymentTotal = tx[~sfPaymentTotal].value_or(kDEFAULT_PAYMENT_TOTAL); auto const properties = computeLoanProperties( + view.rules(), vaultAsset, principalRequested, interestRate, diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index cbfd9da884..96d0722732 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -17,63 +18,13 @@ namespace xrpl::test { class LendingHelpers_test : public beast::unit_test::Suite { - void - testComputeRaisedRate() - { - using namespace jtx; - using namespace xrpl::detail; - struct TestCase - { - std::string name; - Number periodicRate; - std::uint32_t paymentsRemaining; - Number expectedRaisedRate; - }; - - auto const testCases = std::vector{ - { - .name = "Zero payments remaining", - .periodicRate = Number{5, -2}, - .paymentsRemaining = 0, - .expectedRaisedRate = Number{1}, // (1 + r)^0 = 1 - }, - { - .name = "One payment remaining", - .periodicRate = Number{5, -2}, - .paymentsRemaining = 1, - .expectedRaisedRate = Number{105, -2}, - }, // 1.05^1 - { - .name = "Multiple payments remaining", - .periodicRate = Number{5, -2}, - .paymentsRemaining = 3, - .expectedRaisedRate = Number{1157625, -6}, - }, // 1.05^3 - { - .name = "Zero periodic rate", - .periodicRate = Number{0}, - .paymentsRemaining = 5, - .expectedRaisedRate = Number{1}, // (1 + 0)^5 = 1 - }}; - - for (auto const& tc : testCases) - { - testcase("computeRaisedRate: " + tc.name); - - auto const computedRaisedRate = - computeRaisedRate(tc.periodicRate, tc.paymentsRemaining); - BEAST_EXPECTS( - computedRaisedRate == tc.expectedRaisedRate, - "Raised rate mismatch: expected " + to_string(tc.expectedRaisedRate) + ", got " + - to_string(computedRaisedRate)); - } - } - void testComputePaymentFactor() { using namespace jtx; using namespace xrpl::detail; + Env const env{*this}; + auto const& rules = env.current()->rules(); struct TestCase { std::string name; @@ -114,7 +65,7 @@ class LendingHelpers_test : public beast::unit_test::Suite testcase("computePaymentFactor: " + tc.name); auto const computedPaymentFactor = - computePaymentFactor(tc.periodicRate, tc.paymentsRemaining); + computePaymentFactor(rules, tc.periodicRate, tc.paymentsRemaining); BEAST_EXPECTS( computedPaymentFactor == tc.expectedPaymentFactor, "Payment factor mismatch: expected " + to_string(tc.expectedPaymentFactor) + @@ -127,6 +78,8 @@ class LendingHelpers_test : public beast::unit_test::Suite { using namespace jtx; using namespace xrpl::detail; + Env const env{*this}; + auto const& rules = env.current()->rules(); struct TestCase { @@ -172,8 +125,8 @@ class LendingHelpers_test : public beast::unit_test::Suite { testcase("loanPeriodicPayment: " + tc.name); - auto const computedPeriodicPayment = - loanPeriodicPayment(tc.principalOutstanding, tc.periodicRate, tc.paymentsRemaining); + auto const computedPeriodicPayment = loanPeriodicPayment( + rules, tc.principalOutstanding, tc.periodicRate, tc.paymentsRemaining); BEAST_EXPECTS( computedPeriodicPayment == tc.expectedPeriodicPayment, "Periodic payment mismatch: expected " + to_string(tc.expectedPeriodicPayment) + @@ -186,6 +139,8 @@ class LendingHelpers_test : public beast::unit_test::Suite { using namespace jtx; using namespace xrpl::detail; + Env const env{*this}; + auto const& rules = env.current()->rules(); struct TestCase { @@ -232,7 +187,7 @@ class LendingHelpers_test : public beast::unit_test::Suite testcase("loanPrincipalFromPeriodicPayment: " + tc.name); auto const computedPrincipalOutstanding = loanPrincipalFromPeriodicPayment( - tc.periodicPayment, tc.periodicRate, tc.paymentsRemaining); + rules, tc.periodicPayment, tc.periodicRate, tc.paymentsRemaining); BEAST_EXPECTS( computedPrincipalOutstanding == tc.expectedPrincipalOutstanding, "Principal outstanding mismatch: expected " + @@ -241,6 +196,294 @@ class LendingHelpers_test : public beast::unit_test::Suite } } + void + testComputePowerMinusOne() + { + using namespace jtx; + using namespace xrpl::detail; + + // Edge cases. + { + testcase("computePowerMinusOne: zero rate returns zero"); + BEAST_EXPECT(computePowerMinusOne(0, 5) == 0); + } + { + testcase("computePowerMinusOne: zero paymentsRemaining returns zero"); + Number const fivePercent{5, -2}; + BEAST_EXPECT(computePowerMinusOne(fivePercent, 0) == 0); + } + // (1.05)^3 - 1 = 0.157625, computed independently by hand. + { + testcase("computePowerMinusOne: standard case (1.05)^3 - 1 = 0.157625"); + Number const r{5, -2}; + Number const expected{157625, -6}; + BEAST_EXPECT(computePowerMinusOne(r, 3) == expected); + } + // (1+1)^1 - 1 = 1. + { + testcase("computePowerMinusOne: r=1, n=1"); + BEAST_EXPECT(computePowerMinusOne(1, 1) == 1); + } + + // Property check at near-zero rate (the bug regime): for n=2 the + // mathematical identity is `(1+r)^2 - 1 = 2r + r^2`. We compute + // `2r + r^2` by direct multiplication in Number arithmetic — a + // path that doesn't share any code with the binomial loop — and + // assert the two paths agree. + { + testcase("computePowerMinusOne: near-zero rate matches independent 2r + r^2"); + // r = 1 TenthBips32 over 600s payment interval, computed + // independently below using xrpl::detail::loanPeriodicRate. + Number const r = loanPeriodicRate(TenthBips32{1}, 600); + Number const independentExpected = 2 * r + r * r; // (1+r)^2 - 1 + BEAST_EXPECT(computePowerMinusOne(r, 2) == independentExpected); + } + // Same property at n=3: (1+r)^3 - 1 = 3r + 3r^2 + r^3. + { + testcase("computePowerMinusOne: near-zero rate matches independent 3r + 3r^2 + r^3"); + Number const r = loanPeriodicRate(TenthBips32{1}, 600); + Number const independentExpected = 3 * r + 3 * r * r + r * r * r; + BEAST_EXPECT(computePowerMinusOne(r, 3) == independentExpected); + } + + // Larger-n stress test for the loop's early-termination logic. + // At very small r the binomial terms decrease by a factor of + // ~r*(n-k)/(k+1) per step, so even at n=1000 the loop should + // terminate in a small handful of iterations. Cross-check the + // result against the hybrid (which dispatches to this same + // binomial path when r*n < 1e-9). + { + testcase("computePowerMinusOne: large n, early termination matches hybrid output"); + // r*n = 1e-10 and 1e-12 — both clearly below the 1e-9 threshold. + Number const r1{1, -13}; + std::uint32_t const n1 = 1'000; + Number const r2{1, -15}; + std::uint32_t const n2 = 1'000; + BEAST_EXPECT(computePowerMinusOne(r1, n1) == computePowerMinusOneHybrid(r1, n1)); + BEAST_EXPECT(computePowerMinusOne(r2, n2) == computePowerMinusOneHybrid(r2, n2)); + BEAST_EXPECT(computePowerMinusOne(r1, n1) > 0); + BEAST_EXPECT(computePowerMinusOne(r2, n2) > 0); + } + } + + // Direct tests of `computePowerMinusOneHybrid`. Verifies the dispatcher + // picks the right branch and produces the right result on each side + // of the threshold. + void + testComputePowerMinusOneHybrid() + { + using namespace jtx; + using namespace xrpl::detail; + + // Above threshold (r * n >= 1e-9): hybrid must agree with the closed + // form `power(1+r, n) - 1` exactly (it is the closed form). + { + testcase("computePowerMinusOneHybrid: r*n >= 1e-9 uses closed form (bit-exact match)"); + + struct AboveThreshold + { + std::string name; + Number r; + std::uint32_t n; + }; + auto const cases = std::vector{ + {"r=5%, n=3", Number{5, -2}, 3}, + {"r=0.1%, n=1000", Number{1, -3}, 1'000}, + {"r=1e-7, n=100 (above threshold by 10x)", Number{1, -7}, 100}, + }; + for (auto const& tc : cases) + { + Number const closed = power(1 + tc.r, tc.n) - 1; + Number const hybrid = computePowerMinusOneHybrid(tc.r, tc.n); + BEAST_EXPECTS( + hybrid == closed, + tc.name + ": closed=" + to_string(closed) + ", hybrid=" + to_string(hybrid)); + } + } + + // Below threshold (r * n < 1e-9): hybrid must agree with + // `computePowerMinusOne` (the binomial expansion). At this regime + // the closed form is provably wrong (cancellation); we verify the + // dispatcher routes to the binomial path. + { + testcase( + "computePowerMinusOneHybrid: r*n < 1e-9 uses binomial expansion (bit-exact match)"); + + struct BelowThreshold + { + std::string name; + Number r; + std::uint32_t n; + }; + auto const cases = std::vector{ + // bug regime: r = 1 TenthBips32 over 600s payment interval + // → r ≈ 1.9e-10, r*n ≈ 3.8e-10 < 1e-9. + {"bug regime: r~1.9e-10, n=2", loanPeriodicRate(TenthBips32{1}, 600), 2}, + {"r=1e-12, n=100", Number{1, -12}, 100}, + }; + for (auto const& tc : cases) + { + Number const binom = computePowerMinusOne(tc.r, tc.n); + Number const hybrid = computePowerMinusOneHybrid(tc.r, tc.n); + BEAST_EXPECTS( + hybrid == binom, + tc.name + ": binom=" + to_string(binom) + ", hybrid=" + to_string(hybrid)); + } + } + + // Edge cases. + { + testcase("computePowerMinusOneHybrid: edge cases"); + Number const fivePercent{5, -2}; + BEAST_EXPECT(computePowerMinusOneHybrid(0, 100) == 0); + BEAST_EXPECT(computePowerMinusOneHybrid(fivePercent, 0) == 0); + BEAST_EXPECT(computePowerMinusOneHybrid(0, 0) == 0); + } + + // Threshold boundary: r*n = 1e-9 exactly. Hybrid uses `>=` against + // the threshold, so this case must take the closed-form branch. + // We also verify that the binomial path agrees with the closed + // form to high precision at this crossover — confirming the + // threshold is placed where both paths give "adequate" answers. + { + testcase("computePowerMinusOneHybrid: threshold boundary r*n = 1e-9"); + + // Construct exactly r*n = 1e-9 with two distinct (r, n) pairs. + struct Boundary + { + std::string name; + Number r; + std::uint32_t n; + }; + auto const cases = std::vector{ + {"r=1e-9, n=1", Number{1, -9}, 1}, + {"r=1e-12, n=1000", Number{1, -12}, 1'000}, + }; + + for (auto const& tc : cases) + { + Number const closed = power(1 + tc.r, tc.n) - 1; + Number const hybrid = computePowerMinusOneHybrid(tc.r, tc.n); + Number const binom = computePowerMinusOne(tc.r, tc.n); + + // At exact threshold, hybrid must take closed-form path: + // bit-exact match with closed. + BEAST_EXPECTS( + hybrid == closed, + tc.name + ": hybrid should equal closed at threshold; got hybrid=" + + to_string(hybrid) + ", closed=" + to_string(closed)); + + // Closed-form and binomial must agree at the threshold to + // within Number's post-subtraction precision (~10 sig + // digits of `r*n = 1e-9`, i.e. ~1e-19 absolute error). + Number const tolerance{1, -18}; + Number const diff = abs(closed - binom); + BEAST_EXPECTS( + diff < tolerance, + tc.name + ": closed and binomial diverge at threshold by " + to_string(diff)); + } + } + } + + // Regression: at near-zero rate, `loanPrincipalFromPeriodicPayment` + // must satisfy `principal <= periodicPayment * paymentsRemaining` for + // any non-negative rate. The naive closed-form path violated this + // bound due to catastrophic cancellation in `(1+r)^n - 1`. + void + testLoanPrincipalFromPeriodicPaymentNearZeroRate() + { + testcase("loanPrincipalFromPeriodicPayment: principal <= payment*n at near-zero rate"); + using namespace jtx; + using namespace xrpl::detail; + Env const env{*this}; + auto const& rules = env.current()->rules(); + + // Inputs from the bug reproduction in Loan_test.cpp: + // InterestRate = 1 TenthBips32 (0.001 % per year), + // PaymentInterval = 600 s, principal = 100, 3 payments. + // periodicRate is ~1.9e-10. + auto const periodicRate = loanPeriodicRate(TenthBips32{1}, 600); + auto const periodicPayment = loanPeriodicPayment(rules, 100, periodicRate, 3); + + for (auto const n : {3u, 2u, 1u}) + { + auto const computed = + loanPrincipalFromPeriodicPayment(rules, periodicPayment, periodicRate, n); + auto const upperBound = periodicPayment * Number{n}; + BEAST_EXPECTS( + computed <= upperBound, + "n=" + std::to_string(n) + ": payment*n=" + to_string(upperBound) + + ", principal=" + to_string(computed)); + } + } + + // Regression: `computeTheoreticalLoanState` must produce a non-negative + // `interestDue` for any non-negative rate. Pre-fix, near-zero rates + // produced a negative `interestDue` because `(1+r)^n - 1` lost most of + // its precision to cancellation. + void + testComputeTheoreticalLoanStateNearZeroRate() + { + testcase("computeTheoreticalLoanState: non-negative interestDue at near-zero rate"); + using namespace jtx; + using namespace xrpl::detail; + Env const env{*this}; + auto const& rules = env.current()->rules(); + + auto const periodicRate = loanPeriodicRate(TenthBips32{1}, 600); + auto const periodicPayment = loanPeriodicPayment(rules, 100, periodicRate, 3); + + auto const state = + computeTheoreticalLoanState(rules, periodicPayment, periodicRate, 2, TenthBips32{0}); + + BEAST_EXPECT(state.principalOutstanding <= state.valueOutstanding); + BEAST_EXPECT(state.interestDue >= 0); + BEAST_EXPECT(state.managementFeeDue == 0); + } + + // Direct gating proof: at near-zero rate, `computePaymentFactor` must + // return different values with `fixCleanup3_2_0` disabled vs enabled. + // The enabled path agrees with an independent polynomial reference; + // the disabled path diverges by a measurable amount due to the + // catastrophic cancellation in `(1+r)^n - 1`. + void + testComputePaymentFactorNearZeroRate() + { + testcase("computePaymentFactor: near-zero rate, amendment disabled vs enabled"); + using namespace jtx; + using namespace xrpl::detail; + + Number const r = loanPeriodicRate(TenthBips32{1}, 600); + std::uint32_t const n = 3; + + // Independent reference: expand F(r,3) = r*(1+r)^3/((1+r)^3-1) + // algebraically for n=3, dividing numerator and denominator by r: + // F(r,3) = (1 + 3r + 3r^2 + r^3) / (3 + 3r + r^2) + // No power(), no binomial series — pure polynomial arithmetic in + // Number. + Number const reference = (1 + 3 * r + 3 * r * r + r * r * r) / (3 + 3 * r + r * r); + + // Pre-fix: closed form power(1+r, n) - 1 suffers catastrophic + // cancellation when r*n ~ 5.7e-10. + Env const envBug{*this, testableAmendments() - fixCleanup3_2_0}; + Number const buggyFactor = computePaymentFactor(envBug.current()->rules(), r, n); + + // Post-fix: hybrid binomial path avoids cancellation. + Env const envFix{*this}; + Number const correctFactor = computePaymentFactor(envFix.current()->rules(), r, n); + + // The amendment must change the computed factor in this regime. + BEAST_EXPECT(buggyFactor != correctFactor); + + // The fixed factor must agree with the polynomial reference to + // within a few ULPs of Number's 19-digit precision. + BEAST_EXPECT(abs(correctFactor - reference) < Number(1, -15)); + + // The buggy factor must diverge from the reference by a measurable + // amount — empirically ~1e-10 in this regime. + BEAST_EXPECT(abs(buggyFactor - reference) > Number(1, -12)); + } + void testComputeOverpaymentComponents() { @@ -606,6 +849,7 @@ class LendingHelpers_test : public beast::unit_test::Suite asset, loanScale, overpaymentAmount, TenthBips32(0), TenthBips32(0), managementFeeRate); auto const loanProperties = computeLoanProperties( + env.current()->rules(), asset, loanPrincipal, loanInterestRate, @@ -615,6 +859,7 @@ class LendingHelpers_test : public beast::unit_test::Suite loanScale); auto const ret = tryOverpayment( + env.current()->rules(), asset, loanScale, overpaymentComponents, @@ -697,6 +942,7 @@ class LendingHelpers_test : public beast::unit_test::Suite managementFeeRate); auto const loanProperties = computeLoanProperties( + env.current()->rules(), asset, loanPrincipal, loanInterestRate, @@ -706,6 +952,7 @@ class LendingHelpers_test : public beast::unit_test::Suite loanScale); auto const ret = tryOverpayment( + env.current()->rules(), asset, loanScale, overpaymentComponents, @@ -790,6 +1037,7 @@ class LendingHelpers_test : public beast::unit_test::Suite managementFeeRate); auto const loanProperties = computeLoanProperties( + env.current()->rules(), asset, loanPrincipal, loanInterestRate, @@ -799,6 +1047,7 @@ class LendingHelpers_test : public beast::unit_test::Suite loanScale); auto const ret = tryOverpayment( + env.current()->rules(), asset, loanScale, overpaymentComponents, @@ -889,6 +1138,7 @@ class LendingHelpers_test : public beast::unit_test::Suite managementFeeRate); auto const loanProperties = computeLoanProperties( + env.current()->rules(), asset, loanPrincipal, loanInterestRate, @@ -898,6 +1148,7 @@ class LendingHelpers_test : public beast::unit_test::Suite loanScale); auto const ret = tryOverpayment( + env.current()->rules(), asset, loanScale, overpaymentComponents, @@ -996,6 +1247,7 @@ class LendingHelpers_test : public beast::unit_test::Suite managementFeeRate); auto const loanProperties = computeLoanProperties( + env.current()->rules(), asset, loanPrincipal, loanInterestRate, @@ -1005,6 +1257,7 @@ class LendingHelpers_test : public beast::unit_test::Suite loanScale); auto const ret = tryOverpayment( + env.current()->rules(), asset, loanScale, overpaymentComponents, @@ -1103,6 +1356,7 @@ class LendingHelpers_test : public beast::unit_test::Suite managementFeeRate); auto const loanProperties = computeLoanProperties( + env.current()->rules(), asset, loanPrincipal, loanInterestRate, @@ -1112,6 +1366,7 @@ class LendingHelpers_test : public beast::unit_test::Suite loanScale); auto const ret = tryOverpayment( + env.current()->rules(), asset, loanScale, overpaymentComponents, @@ -1199,8 +1454,12 @@ public: testLoanLatePaymentInterest(); testLoanPeriodicPayment(); testLoanPrincipalFromPeriodicPayment(); - testComputeRaisedRate(); + testLoanPrincipalFromPeriodicPaymentNearZeroRate(); testComputePaymentFactor(); + testComputePowerMinusOne(); + testComputePowerMinusOneHybrid(); + testComputeTheoreticalLoanStateNearZeroRate(); + testComputePaymentFactorNearZeroRate(); testComputeOverpaymentComponents(); testComputeInterestAndFeeParts(); } diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 713168a4ee..47c07d240d 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -713,6 +713,7 @@ protected: auto const total = loanParams.payTotal.value_or(LoanSet::kDEFAULT_PAYMENT_TOTAL); auto const feeRate = brokerParams.managementFeeRate; auto const props = computeLoanProperties( + env.current()->rules(), asset, principal, interest, @@ -918,6 +919,7 @@ protected: state.totalValue, state.principalOutstanding, state.managementFeeOutstanding); { auto const raw = computeTheoreticalLoanState( + env.current()->rules(), state.periodicPayment, periodicRate, state.paymentRemaining, @@ -961,6 +963,7 @@ protected: std::size_t totalPaymentsMade = 0; xrpl::LoanState currentTrueState = computeTheoreticalLoanState( + env.current()->rules(), state.periodicPayment, periodicRate, state.paymentRemaining, @@ -990,6 +993,7 @@ protected: validateBorrowerBalance(); // Compute the expected principal amount auto const paymentComponents = xrpl::detail::computePaymentComponents( + env.current()->rules(), broker.asset.raw(), state.loanScale, state.totalValue, @@ -1010,6 +1014,7 @@ protected: paymentComponents.trackedManagementFeeDelta); xrpl::LoanState const nextTrueState = computeTheoreticalLoanState( + env.current()->rules(), state.periodicPayment, periodicRate, state.paymentRemaining - 1, @@ -1407,6 +1412,7 @@ protected: auto state = getCurrentState(env, broker, keylet, verifyLoanStatus); auto const loanProperties = computeLoanProperties( + env.current()->rules(), broker.asset.raw(), state.principalOutstanding, state.interestRate, @@ -2540,6 +2546,7 @@ protected: { auto const raw = computeTheoreticalLoanState( + env.current()->rules(), state.periodicPayment, periodicRate, state.paymentRemaining, @@ -2577,6 +2584,7 @@ protected: std::size_t totalPaymentsMade = 0; xrpl::LoanState currentTrueState = computeTheoreticalLoanState( + env.current()->rules(), state.periodicPayment, periodicRate, state.paymentRemaining, @@ -2586,6 +2594,7 @@ protected: { // Compute the expected principal amount auto const paymentComponents = xrpl::detail::computePaymentComponents( + env.current()->rules(), broker.asset.raw(), state.loanScale, state.totalValue, @@ -2603,6 +2612,7 @@ protected: ", periodic payment: " + to_string(roundedPeriodicPayment)); xrpl::LoanState const nextTrueState = computeTheoreticalLoanState( + env.current()->rules(), state.periodicPayment, periodicRate, state.paymentRemaining - 1, @@ -5586,7 +5596,11 @@ protected: auto const periodicRate = loanPeriodicRate(interestRateValue, state.paymentInterval); auto const rawLoanState = computeTheoreticalLoanState( - state.periodicPayment, periodicRate, state.paymentRemaining, managementFeeRate); + env.current()->rules(), + state.periodicPayment, + periodicRate, + state.paymentRemaining, + managementFeeRate); auto const parentCloseTime = env.current()->parentCloseTime(); auto const startDateSeconds = @@ -5815,6 +5829,7 @@ protected: auto state = getCurrentState(env, broker, loanKeylet); Number const periodicRate = loanPeriodicRate(state.interestRate, state.paymentInterval); auto const components = xrpl::detail::computePaymentComponents( + env.current()->rules(), asset.raw(), state.loanScale, state.totalValue, @@ -5848,7 +5863,10 @@ protected: // schedule auto const fullPaymentInterest = computeFullPaymentInterest( xrpl::detail::loanPrincipalFromPeriodicPayment( - after.periodicPayment, periodicRate2, after.paymentRemaining), + env.current()->rules(), + after.periodicPayment, + periodicRate2, + after.paymentRemaining), periodicRate2, env.current()->parentCloseTime(), after.paymentInterval, @@ -5881,7 +5899,10 @@ protected: auto const prevClamped = std::min(after.previousPaymentDate, nowSecs); auto const fullPaymentInterestClamped = computeFullPaymentInterest( xrpl::detail::loanPrincipalFromPeriodicPayment( - after.periodicPayment, periodicRate2, after.paymentRemaining), + env.current()->rules(), + after.periodicPayment, + periodicRate2, + after.paymentRemaining), periodicRate2, env.current()->parentCloseTime(), after.paymentInterval, @@ -7284,6 +7305,188 @@ protected: attemptWithdrawShares(depositorB, sharesLpB, tesSUCCESS); } + // A near-zero interest rate on a 100 USD loan + // produces total interest of ~6 units at loanScale -9. Numerical error + // in the amortization formula pushes the theoretical principal above + // the theoretical value, producing a negative theoretical interest. + // The payment delta then exceeds the actual outstanding interest, + // violating XRPL_ASSERT_PARTS in computePaymentComponents. + void + testBugInterestDueDeltaCrash() + { + testcase("bug: LoanPay asserts 'interest due delta' on near-zero rate"); + + using namespace jtx; + using namespace std::chrono_literals; + Env env(*this, all_); + + Account const issuer{"issuer"}; + Account const lender{"lender"}; + Account const borrower{"borrower"}; + + env.fund(XRP(1'000'000), issuer, lender, borrower); + env.close(); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const iouAsset = issuer["USD"]; + env(trust(lender, iouAsset(1'000'000'000))); + env(trust(borrower, iouAsset(1'000'000'000))); + env(pay(issuer, lender, iouAsset(5'000'000))); + env(pay(issuer, borrower, iouAsset(5'000'000))); + env.close(); + + BrokerParameters const brokerParams{ + .vaultDeposit = 1'000'000, + .debtMax = 1'000'000, + .coverRateMin = TenthBips32{0}, + .coverDeposit = 0, + .managementFeeRate = TenthBips16{0}, + .coverRateLiquidation = TenthBips32{0}}; + + BrokerInfo const broker{createVaultAndBroker(env, iouAsset, lender, brokerParams)}; + + using namespace loan; + + auto const loanSetFee = Fee(env.current()->fees().base * 2); + Number const principalRequest{100}; + + auto createJson = env.json( + set(borrower, broker.brokerID, principalRequest), + Fee(loanSetFee), + Json(sfCounterpartySignature, json::ObjectValue)); + + createJson["InterestRate"] = 1; // minimum non-zero rate + createJson["PaymentTotal"] = 3; + createJson["PaymentInterval"] = 600; + + auto const brokerStateBefore = env.le(keylet::loanbroker(broker.brokerID)); + auto const loanSequence = brokerStateBefore->at(sfLoanSequence); + auto const keylet = keylet::loan(broker.brokerID, loanSequence); + + createJson = env.json(createJson, Sig(sfCounterpartySignature, lender)); + env(createJson, Ter(tesSUCCESS)); + env.close(); + + // For principal=100, n=3 the amortization schedule produces a + // periodic payment ≈ 33.33 USD. We pay 35 USD, which is more than + // one period's worth — enough for the LoanPay path to enter + // computePaymentComponents and reach the assertion that fires + // when the bug is present. With the fix, the tx applies cleanly. + env(pay(borrower, keylet.key, iouAsset(35)), Ter(tesSUCCESS)); + env.close(); + } + + // Integration test: full lifecycle of a $1B loan in the bug regime. + // Verifies that the vault collects the economically-correct interest + // income and that conservation holds at the trust-line level. + // + // Pre-fix (closed-form `power(1+r, n) - 1`): vault collected only + // ~$0.058 per $1B due to cancellation of `(1+r)^n - 1` at r*n ~ 5.7e-10. + // Post-fix (hybrid binomial path): vault collects ~$0.38 per $1B, + // matching the value computed independently with arbitrary-precision + // Decimal arithmetic. + void + testFullLifecycleVaultPnLNearZeroRate() + { + testcase("integration: full loan lifecycle, vault interest at near-zero rate"); + + using namespace jtx; + using namespace jtx::loan; + using namespace std::chrono_literals; + Env env(*this, all_); + + Account const issuer{"issuer"}; + Account const lender{"lender"}; + Account const borrower{"borrower"}; + + env.fund(XRP(1'000'000), issuer, lender, borrower); + env.close(); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const iouAsset = issuer["USD"]; + STAmount const trustLimit{iouAsset.raw(), Number{1, 17}}; + env(trust(lender, trustLimit)); + env(trust(borrower, trustLimit)); + env.close(); + env(pay(issuer, lender, iouAsset(5'000'000'000LL))); + env(pay(issuer, borrower, iouAsset(5'000'000'000LL))); + env.close(); + + auto usdBalance = [&](Account const& a) { + return env.balance(a, iouAsset.raw().get()).value(); + }; + STAmount const borrowerStartBal = usdBalance(borrower); + + BrokerParameters const brokerParams{ + .vaultDeposit = Number{2, 9}, + .debtMax = Number{0}, + .coverRateMin = TenthBips32{0}, + .coverDeposit = 0, + .managementFeeRate = TenthBips16{0}, + .coverRateLiquidation = TenthBips32{0}}; + BrokerInfo const broker{createVaultAndBroker(env, iouAsset, lender, brokerParams)}; + + auto const vaultBefore = env.le(broker.vaultKeylet()); + BEAST_EXPECT(vaultBefore); + Number const vaultAvailableBefore = vaultBefore->at(sfAssetsAvailable); + + // Loan: $1B principal, 3 payments, 600s interval, rate=1 TenthBips32. + auto const loanSetFee = Fee(env.current()->fees().base * 2); + Number const principalRequest{1, 9}; + auto createJson = env.json( + set(borrower, broker.brokerID, principalRequest), + Fee(loanSetFee), + Json(sfCounterpartySignature, json::ObjectValue)); + createJson["InterestRate"] = 1; + createJson["PaymentTotal"] = 3; + createJson["PaymentInterval"] = 600; + + auto const brokerStateBefore = env.le(keylet::loanbroker(broker.brokerID)); + auto const loanSequence = brokerStateBefore->at(sfLoanSequence); + auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence); + createJson = env.json(createJson, Sig(sfCounterpartySignature, lender)); + env(createJson, Ter(tesSUCCESS)); + env.close(); + + auto const loanSle = env.le(loanKeylet); + BEAST_EXPECT(loanSle); + Number const expectedTotalInterest = + loanSle->at(sfTotalValueOutstanding) - loanSle->at(sfPrincipalOutstanding); + + env(pay(borrower, loanKeylet.key, iouAsset(1'500'000'000LL)), Ter(tesSUCCESS)); + env.close(); + + auto const vaultAfter = env.le(broker.vaultKeylet()); + Number const vaultAvailableAfter = vaultAfter->at(sfAssetsAvailable); + Number const vaultGain = vaultAvailableAfter - vaultAvailableBefore; + + STAmount const borrowerEndBal = usdBalance(borrower); + STAmount const borrowerNetOut = borrowerStartBal - borrowerEndBal; + + // Self-consistency: vault gained exactly the expected interest + // computed at LoanSet, and the borrower's outflow matches. + BEAST_EXPECT(vaultGain == expectedTotalInterest); + BEAST_EXPECT(Number(borrowerNetOut) == expectedTotalInterest); + + // Mathematical correctness: the total interest for this loan + // configuration is 0.38051750382930729983, calculated + // independently using 50-digit Decimal arithmetic (no + // cancellation possible at that precision). At Number's 19-digit + // mantissa this rounds to 0.38051750382930729 — the literal + // below. The vault's actual gain must agree to within + // sub-microcent precision. + Number const decimalReference{38051750382930729LL, -17}; + Number const tolerance{1, -6}; // 1e-6 USD = sub-microcent + Number const error = abs(vaultGain - decimalReference); + BEAST_EXPECTS( + error < tolerance, + "vault gain " + to_string(vaultGain) + " differs from Decimal reference " + + to_string(decimalReference) + " by " + to_string(error) + " — exceeds tolerance " + + to_string(tolerance)); + } + public: void run() override @@ -7292,6 +7495,10 @@ public: testLoanPayLateFullPaymentBypassesPenalties(); testLoanCoverMinimumRoundingExploit(); #endif + + testBugInterestDueDeltaCrash(); + testFullLifecycleVaultPnLNearZeroRate(); + testWithdrawReflectsUnrealizedLoss(); testInvalidLoanSet();