diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000000..5f4187b008 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,200 @@ +--- +Checks: "-*, + bugprone-argument-comment, + bugprone-assert-side-effect, + bugprone-bad-signal-to-kill-thread, + bugprone-bool-pointer-implicit-conversion, + bugprone-casting-through-void, + bugprone-chained-comparison, + bugprone-compare-pointer-to-member-virtual-function, + bugprone-copy-constructor-init, + bugprone-dangling-handle, + bugprone-dynamic-static-initializers, + bugprone-fold-init-type, + bugprone-forward-declaration-namespace, + bugprone-inaccurate-erase, + bugprone-incorrect-enable-if, + bugprone-incorrect-roundings, + bugprone-infinite-loop, + bugprone-integer-division, + bugprone-lambda-function-name, + bugprone-macro-parentheses, + bugprone-macro-repeated-side-effects, + bugprone-misplaced-operator-in-strlen-in-alloc, + bugprone-misplaced-pointer-arithmetic-in-alloc, + bugprone-misplaced-widening-cast, + bugprone-multi-level-implicit-pointer-conversion, + bugprone-multiple-new-in-one-expression, + bugprone-multiple-statement-macro, + bugprone-no-escape, + bugprone-non-zero-enum-to-bool-conversion, + bugprone-parent-virtual-call, + bugprone-posix-return, + bugprone-redundant-branch-condition, + bugprone-shared-ptr-array-mismatch, + bugprone-signal-handler, + bugprone-signed-char-misuse, + bugprone-sizeof-container, + bugprone-spuriously-wake-up-functions, + bugprone-standalone-empty, + bugprone-string-constructor, + bugprone-string-integer-assignment, + bugprone-string-literal-with-embedded-nul, + bugprone-stringview-nullptr, + bugprone-suspicious-enum-usage, + bugprone-suspicious-include, + bugprone-suspicious-memory-comparison, + bugprone-suspicious-memset-usage, + bugprone-suspicious-realloc-usage, + bugprone-suspicious-semicolon, + bugprone-suspicious-string-compare, + bugprone-swapped-arguments, + bugprone-terminating-continue, + bugprone-throw-keyword-missing, + bugprone-undefined-memory-manipulation, + bugprone-undelegated-constructor, + bugprone-unhandled-exception-at-new, + bugprone-unique-ptr-array-mismatch, + bugprone-unsafe-functions, + bugprone-virtual-near-miss, + cppcoreguidelines-no-suspend-with-lock, + cppcoreguidelines-virtual-class-destructor, + hicpp-ignored-remove-result, + misc-definitions-in-headers, + misc-header-include-cycle, + misc-misplaced-const, + misc-static-assert, + misc-throw-by-value-catch-by-reference, + misc-unused-alias-decls, + misc-unused-using-decls, + readability-duplicate-include, + readability-enum-initial-value, + readability-misleading-indentation, + readability-non-const-parameter, + readability-redundant-declaration, + readability-reference-to-constructed-temporary, + modernize-deprecated-headers, + modernize-make-shared, + modernize-make-unique, + performance-implicit-conversion-in-loop, + performance-move-constructor-init, + performance-trivially-destructible + " +# --- +# checks that have some issues that need to be resolved: +# +# bugprone-empty-catch, +# bugprone-crtp-constructor-accessibility, +# bugprone-inc-dec-in-conditions, +# bugprone-reserved-identifier, +# bugprone-move-forwarding-reference, +# bugprone-unused-local-non-trivial-variable, +# bugprone-return-const-ref-from-parameter, +# bugprone-switch-missing-default-case, +# bugprone-sizeof-expression, +# bugprone-suspicious-stringview-data-usage, +# bugprone-suspicious-missing-comma, +# bugprone-pointer-arithmetic-on-polymorphic-object, +# bugprone-optional-value-conversion, +# bugprone-too-small-loop-variable, +# bugprone-unused-return-value, +# bugprone-use-after-move, +# bugprone-unhandled-self-assignment, +# bugprone-unused-raii, +# +# cppcoreguidelines-misleading-capture-default-by-value, +# cppcoreguidelines-init-variables, +# cppcoreguidelines-pro-type-member-init, +# cppcoreguidelines-pro-type-static-cast-downcast, +# cppcoreguidelines-use-default-member-init, +# cppcoreguidelines-rvalue-reference-param-not-moved, +# +# llvm-namespace-comment, +# misc-const-correctness, +# misc-include-cleaner, +# misc-redundant-expression, +# +# readability-avoid-nested-conditional-operator, +# readability-avoid-return-with-void-value, +# readability-braces-around-statements, +# readability-container-contains, +# readability-container-size-empty, +# readability-convert-member-functions-to-static, +# readability-const-return-type, +# readability-else-after-return, +# readability-implicit-bool-conversion, +# readability-inconsistent-declaration-parameter-name, +# readability-identifier-naming, +# readability-make-member-function-const, +# readability-math-missing-parentheses, +# readability-redundant-inline-specifier, +# readability-redundant-member-init, +# readability-redundant-casting, +# readability-redundant-string-init, +# readability-simplify-boolean-expr, +# readability-static-definition-in-anonymous-namespace, +# readability-suspicious-call-argument, +# readability-use-std-min-max, +# readability-static-accessed-through-instance, +# +# modernize-concat-nested-namespaces, +# modernize-pass-by-value, +# modernize-type-traits, +# modernize-use-designated-initializers, +# modernize-use-emplace, +# modernize-use-equals-default, +# modernize-use-equals-delete, +# modernize-use-override, +# modernize-use-ranges, +# modernize-use-starts-ends-with, +# modernize-use-std-numbers, +# modernize-use-using, +# +# performance-faster-string-find, +# performance-for-range-copy, +# performance-inefficient-vector-operation, +# performance-move-const-arg, +# performance-no-automatic-move, +# --- +# +CheckOptions: + # readability-braces-around-statements.ShortStatementLines: 2 + # readability-identifier-naming.MacroDefinitionCase: UPPER_CASE + # readability-identifier-naming.ClassCase: CamelCase + # readability-identifier-naming.StructCase: CamelCase + # readability-identifier-naming.UnionCase: CamelCase + # readability-identifier-naming.EnumCase: CamelCase + # readability-identifier-naming.EnumConstantCase: CamelCase + # readability-identifier-naming.ScopedEnumConstantCase: CamelCase + # readability-identifier-naming.GlobalConstantCase: UPPER_CASE + # readability-identifier-naming.GlobalConstantPrefix: "k" + # readability-identifier-naming.GlobalVariableCase: CamelCase + # readability-identifier-naming.GlobalVariablePrefix: "g" + # readability-identifier-naming.ConstexprFunctionCase: camelBack + # readability-identifier-naming.ConstexprMethodCase: camelBack + # readability-identifier-naming.ClassMethodCase: camelBack + # readability-identifier-naming.ClassMemberCase: camelBack + # readability-identifier-naming.ClassConstantCase: UPPER_CASE + # readability-identifier-naming.ClassConstantPrefix: "k" + # readability-identifier-naming.StaticConstantCase: UPPER_CASE + # readability-identifier-naming.StaticConstantPrefix: "k" + # readability-identifier-naming.StaticVariableCase: UPPER_CASE + # readability-identifier-naming.StaticVariablePrefix: "k" + # readability-identifier-naming.ConstexprVariableCase: UPPER_CASE + # readability-identifier-naming.ConstexprVariablePrefix: "k" + # readability-identifier-naming.LocalConstantCase: camelBack + # readability-identifier-naming.LocalVariableCase: camelBack + # readability-identifier-naming.TemplateParameterCase: CamelCase + # readability-identifier-naming.ParameterCase: camelBack + # readability-identifier-naming.FunctionCase: camelBack + # readability-identifier-naming.MemberCase: camelBack + # readability-identifier-naming.PrivateMemberSuffix: _ + # readability-identifier-naming.ProtectedMemberSuffix: _ + # readability-identifier-naming.PublicMemberSuffix: "" + # readability-identifier-naming.FunctionIgnoredRegexp: ".*tag_invoke.*" + bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true +# bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc +# misc-include-cleaner.IgnoreHeaders: '.*/(detail|impl)/.*;.*(expected|unexpected).*;.*ranges_lower_bound\.h;time.h;stdlib.h;__chrono/.*;fmt/chrono.h;boost/uuid/uuid_hash.hpp' +# +# HeaderFilterRegex: '^.*/(src|tests)/.*\.(h|hpp)$' +WarningsAsErrors: "*" diff --git a/.cmake-format.yaml b/.cmake-format.yaml index 1a3337fe8c..40c1101208 100644 --- a/.cmake-format.yaml +++ b/.cmake-format.yaml @@ -29,7 +29,7 @@ format: disable: false _help_line_width: - How wide to allow formatted cmake files - line_width: 120 + line_width: 100 _help_tab_size: - How many spaces to tab for indent tab_size: 4 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..66e319e0e7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,56 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop + + - package-ecosystem: github-actions + directory: .github/actions/build-deps/ + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop + + - package-ecosystem: github-actions + directory: .github/actions/generate-version/ + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop + + - package-ecosystem: github-actions + directory: .github/actions/print-env/ + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop + + - package-ecosystem: github-actions + directory: .github/actions/setup-conan/ + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 46f6b7500a..be6a92eea2 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Determine changed files # This step checks whether any files have changed that should # cause the next jobs to run. We do it this way rather than @@ -46,7 +46,7 @@ jobs: # that Github considers any skipped jobs to have passed, and in # turn the required checks as well. id: changes - uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5 + uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 with: files: | # These paths are unique to `on-pr.yml`. @@ -65,9 +65,12 @@ jobs: .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 + .clang-tidy .codecov.yml cmake/** conan/** @@ -107,6 +110,17 @@ jobs: if: ${{ needs.should-run.outputs.go == 'true' }} uses: ./.github/workflows/reusable-check-rename.yml + clang-tidy: + needs: should-run + if: ${{ needs.should-run.outputs.go == 'true' }} + uses: ./.github/workflows/reusable-clang-tidy.yml + permissions: + issues: write + contents: read + with: + check_only_changed: true + create_issue_on_failure: false + build-test: needs: should-run if: ${{ needs.should-run.outputs.go == 'true' }} @@ -156,6 +170,7 @@ jobs: needs: - check-levelization - check-rename + - clang-tidy - build-test - upload-recipe - notify-clio diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 210670f5a1..c7e0e8c3aa 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -22,9 +22,12 @@ on: - ".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" + - ".clang-tidy" - ".codecov.yml" - "cmake/**" - "conan/**" @@ -60,6 +63,15 @@ defaults: shell: bash jobs: + clang-tidy: + uses: ./.github/workflows/reusable-clang-tidy.yml + permissions: + issues: write + contents: read + with: + check_only_changed: false + create_issue_on_failure: ${{ github.event_name == 'schedule' }} + build-test: uses: ./.github/workflows/reusable-build-test.yml strategy: diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index f43275201c..7793d1e3ab 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,4 +14,4 @@ jobs: uses: XRPLF/actions/.github/workflows/pre-commit.yml@320be44621ca2a080f05aeb15817c44b84518108 with: runs_on: ubuntu-latest - container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-ab4d1f0" }' + container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }' diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index f61559d6d3..acde57fd91 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -4,6 +4,18 @@ name: Build and publish documentation on: push: + branches: + - "develop" + - "release*" + paths: + - ".github/workflows/publish-docs.yml" + - "*.md" + - "**/*.md" + - "docs/**" + - "include/**" + - "src/libxrpl/**" + - "src/xrpld/**" + pull_request: paths: - ".github/workflows/publish-docs.yml" - "*.md" @@ -23,7 +35,9 @@ defaults: env: BUILD_DIR: build - NPROC_SUBTRACT: 2 + # ubuntu-latest has only 2 CPUs for private repositories + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for--private-repositories + NPROC_SUBTRACT: ${{ github.event.repository.private && '1' || '2' }} jobs: publish: @@ -33,7 +47,7 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Get number of processors uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf @@ -65,7 +79,7 @@ jobs: cmake --build . --target docs --parallel ${BUILD_NPROC} - name: Publish documentation - if: ${{ github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch }} + if: ${{ github.event_name == 'push' }} uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 85b973ea0c..6060a208fe 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -101,10 +101,10 @@ jobs: steps: - name: Cleanup workspace (macOS and Windows) if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }} - uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf + uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare runner uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d @@ -177,7 +177,7 @@ jobs: - name: Upload the binary (Linux) if: ${{ github.repository_owner == 'XRPLF' && runner.os == 'Linux' }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: xrpld-${{ inputs.config_name }} path: ${{ env.BUILD_DIR }}/xrpld @@ -254,7 +254,7 @@ jobs: - name: Upload coverage report if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: disable_search: true disable_telem: true diff --git a/.github/workflows/reusable-check-levelization.yml b/.github/workflows/reusable-check-levelization.yml index 29a1dc1480..ae3eed0f16 100644 --- a/.github/workflows/reusable-check-levelization.yml +++ b/.github/workflows/reusable-check-levelization.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check levelization run: .github/scripts/levelization/generate.sh - name: Check for differences diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index a73ac49b7d..0e335ab9ca 100644 --- a/.github/workflows/reusable-check-rename.yml +++ b/.github/workflows/reusable-check-rename.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check definitions run: .github/scripts/rename/definitions.sh . - name: Check copyright notices diff --git a/.github/workflows/reusable-clang-tidy-files.yml b/.github/workflows/reusable-clang-tidy-files.yml new file mode 100644 index 0000000000..d36dea747c --- /dev/null +++ b/.github/workflows/reusable-clang-tidy-files.yml @@ -0,0 +1,162 @@ +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: Release + +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@2cbf481018d930656e9276fcc20dc0e3a0be5b6d + with: + enable_ccache: false + + - name: Print build environment + uses: ./.github/actions/print-env + + - 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}" ${TARGETS} 2>&1 | tee clang-tidy-output.txt + + - name: Upload clang-tidy output + if: steps.run_clang_tidy.outcome != 'success' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: clang-tidy-results + path: clang-tidy-output.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 new file mode 100644 index 0000000000..7050d3509f --- /dev/null +++ b/.github/workflows/reusable-clang-tidy.yml @@ -0,0 +1,55 @@ +name: Clang-tidy check + +on: + workflow_call: + inputs: + check_only_changed: + description: "Check only changed files in PR. If false, checks all files in the repository." + type: boolean + default: false + create_issue_on_failure: + description: "Whether to create an issue if the check failed" + type: boolean + default: false + +defaults: + run: + shell: bash + +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 }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Get changed C++ files + id: changed_files + uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + with: + files: | + **/*.cpp + **/*.h + **/*.ipp + separator: " " + + - name: Get changed clang-tidy configuration + id: changed_clang_tidy + uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + with: + files: | + .clang-tidy + + 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 }} diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index c975347307..b1232a138f 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -29,10 +29,10 @@ jobs: matrix: ${{ steps.generate.outputs.matrix }} steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: 3.13 diff --git a/.github/workflows/reusable-upload-recipe.yml b/.github/workflows/reusable-upload-recipe.yml index 79af516fb3..6245fd06e1 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -43,7 +43,7 @@ jobs: container: ghcr.io/xrplf/ci/ubuntu-noble:gcc-13-sha-5dd7158 steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Generate build version number id: version diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 60696a9769..df8aa43a18 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -64,10 +64,10 @@ jobs: steps: - name: Cleanup workspace (macOS and Windows) if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }} - uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf + uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare runner uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d diff --git a/.gitignore b/.gitignore index a1c2f034d1..60e8fef56c 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,9 @@ gmon.out # Locally patched Conan recipes external/conan-center-index/ +# Local conan directory +.conan + # XCode IDE. *.pbxuser !default.pbxuser @@ -72,5 +75,8 @@ DerivedData /.claude /CLAUDE.md +# Direnv's directory +/.direnv + # clangd cache /.cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9117fe0d3e..6e04c752e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,6 +57,16 @@ repos: - .git/COMMIT_EDITMSG stages: [commit-msg] + - repo: local + hooks: + - id: nix-fmt + name: Format Nix files + entry: nix --extra-experimental-features 'nix-command flakes' fmt + language: system + types: + - nix + pass_filenames: true + exclude: | (?x)^( external/.*| diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c434506f7..764e917498 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ project(xrpl) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) include(CompilationEnv) @@ -38,16 +39,16 @@ include(Ccache) # make GIT_COMMIT_HASH define available to all sources find_package(Git) if (Git_FOUND) - execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse HEAD - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gch) + execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse + HEAD OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gch) if (gch) set(GIT_COMMIT_HASH "${gch}") message(STATUS gch: ${GIT_COMMIT_HASH}) add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}") endif () - execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse --abbrev-ref HEAD - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gb) + execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse + --abbrev-ref HEAD OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gb) if (gb) set(GIT_BRANCH "${gb}") message(STATUS gb: ${GIT_BRANCH}) @@ -67,7 +68,8 @@ include(FetchContent) include(ExternalProject) include(CMakeFuncs) # must come *after* ExternalProject b/c it overrides one function in EP if (target) - message(FATAL_ERROR "The target option has been removed - use native cmake options to control build") + message(FATAL_ERROR "The target option has been removed - use native cmake options to control build" + ) endif () include(XrplSanity) @@ -76,7 +78,8 @@ include(XrplSettings) # this check has to remain in the top-level cmake because of the early return statement if (packages_only) if (NOT TARGET rpm) - message(FATAL_ERROR "packages_only requested, but targets were not created - is docker installed?") + message(FATAL_ERROR "packages_only requested, but targets were not created - is docker installed?" + ) endif () return() endif () @@ -118,7 +121,8 @@ target_link_libraries( option(rocksdb "Enable RocksDB" ON) if (rocksdb) find_package(RocksDB REQUIRED) - set_target_properties(RocksDB::rocksdb PROPERTIES INTERFACE_COMPILE_DEFINITIONS XRPL_ROCKSDB_AVAILABLE=1) + set_target_properties(RocksDB::rocksdb PROPERTIES INTERFACE_COMPILE_DEFINITIONS + XRPL_ROCKSDB_AVAILABLE=1) target_link_libraries(xrpl_libs INTERFACE RocksDB::rocksdb) endif () diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a928065ef2..4bb1db8689 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -251,6 +251,29 @@ pip3 install pre-commit pre-commit install ``` +## Clang-tidy + +All code must pass `clang-tidy` checks according to the settings in [`.clang-tidy`](./.clang-tidy). + +There is a Continuous Integration job that runs clang-tidy on pull requests. The CI will check: + +- All changed C++ files (`.cpp`, `.h`, `.ipp`) when only code files are modified +- **All files in the repository** when the `.clang-tidy` configuration file is changed + +This ensures that configuration changes don't introduce new warnings across the codebase. + +### Running clang-tidy locally + +Before running clang-tidy, you must build the project to generate required files (particularly protobuf headers). Refer to [`BUILD.md`](./BUILD.md) for build instructions. + +Then run clang-tidy on your local changes: + +``` +run-clang-tidy -p build src tests +``` + +This will check all source files in the `src` and `tests` directories using the compile commands from your `build` directory. + ## Contracts and instrumentation We are using [Antithesis](https://antithesis.com/) for continuous fuzzing, diff --git a/cmake/Ccache.cmake b/cmake/Ccache.cmake index 000883cf6a..e34627ad8a 100644 --- a/cmake/Ccache.cmake +++ b/cmake/Ccache.cmake @@ -43,7 +43,8 @@ set(CMAKE_VS_GLOBALS "CLToolExe=cl.exe" "CLToolPath=${CMAKE_BINARY_DIR}" "TrackF # By default Visual Studio generators will use /Zi to capture debug information, which is not compatible with ccache, so # tell it to use /Z7 instead. if (MSVC) - foreach (var_ CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE) + foreach (var_ CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE) string(REPLACE "/Zi" "/Z7" ${var_} "${${var_}}") endforeach () endif () diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake index fcc4d44133..74796718c1 100644 --- a/cmake/CodeCoverage.cmake +++ b/cmake/CodeCoverage.cmake @@ -180,7 +180,8 @@ elseif (DEFINED ENV{CODE_COVERAGE_GCOV_TOOL}) set(GCOV_TOOL "$ENV{CODE_COVERAGE_GCOV_TOOL}") elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") if (APPLE) - execute_process(COMMAND xcrun -f llvm-cov OUTPUT_VARIABLE LLVMCOV_PATH OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND xcrun -f llvm-cov OUTPUT_VARIABLE LLVMCOV_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE) else () find_program(LLVMCOV_PATH llvm-cov) endif () @@ -199,8 +200,8 @@ foreach (LANG ${LANGUAGES}) if ("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") endif () - elseif (NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES - "(LLVM)?[Ff]lang") + elseif (NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" AND NOT "${CMAKE_${LANG}_COMPILER_ID}" + MATCHES "(LLVM)?[Ff]lang") message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") endif () endforeach () @@ -321,14 +322,16 @@ function (setup_target_for_coverage_gcovr) endif () if ("--output" IN_LIST GCOVR_ADDITIONAL_ARGS) - message(FATAL_ERROR "Unsupported --output option detected in GCOVR_ADDITIONAL_ARGS! Aborting...") + message(FATAL_ERROR "Unsupported --output option detected in GCOVR_ADDITIONAL_ARGS! Aborting..." + ) else () if ((Coverage_FORMAT STREQUAL "html-details") OR (Coverage_FORMAT STREQUAL "html-nested")) set(GCOVR_OUTPUT_FILE ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html) set(GCOVR_CREATE_FOLDER ${PROJECT_BINARY_DIR}/${Coverage_NAME}) elseif (Coverage_FORMAT STREQUAL "html-single") set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.html) - elseif ((Coverage_FORMAT STREQUAL "json-summary") OR (Coverage_FORMAT STREQUAL "json-details") + elseif ((Coverage_FORMAT STREQUAL "json-summary") OR (Coverage_FORMAT STREQUAL + "json-details") OR (Coverage_FORMAT STREQUAL "coveralls")) set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.json) elseif (Coverage_FORMAT STREQUAL "txt") @@ -452,8 +455,10 @@ function (setup_target_for_coverage_gcovr) COMMENT "Running gcovr to produce code coverage report.") # Show info where to find the report - add_custom_command(TARGET ${Coverage_NAME} POST_BUILD COMMAND echo - COMMENT "Code coverage report saved in ${GCOVR_OUTPUT_FILE} formatted as ${Coverage_FORMAT}") + add_custom_command( + TARGET ${Coverage_NAME} POST_BUILD COMMAND echo + COMMENT "Code coverage report saved in ${GCOVR_OUTPUT_FILE} formatted as ${Coverage_FORMAT}" + ) endfunction () # setup_target_for_coverage_gcovr function (add_code_coverage_to_target name scope) @@ -463,8 +468,9 @@ function (add_code_coverage_to_target name scope) separate_arguments(COVERAGE_C_LINKER_FLAGS NATIVE_COMMAND "${COVERAGE_C_LINKER_FLAGS}") # Add compiler options to the target - target_compile_options(${name} ${scope} $<$:${COVERAGE_CXX_COMPILER_FLAGS}> - $<$:${COVERAGE_C_COMPILER_FLAGS}>) + target_compile_options( + ${name} ${scope} $<$:${COVERAGE_CXX_COMPILER_FLAGS}> + $<$:${COVERAGE_C_COMPILER_FLAGS}>) target_link_libraries(${name} ${scope} $<$:${COVERAGE_CXX_LINKER_FLAGS}> $<$:${COVERAGE_C_LINKER_FLAGS}>) diff --git a/cmake/XrplCompiler.cmake b/cmake/XrplCompiler.cmake index f7ac8c2b71..8093004b2e 100644 --- a/cmake/XrplCompiler.cmake +++ b/cmake/XrplCompiler.cmake @@ -17,7 +17,8 @@ link_libraries(Xrpl::common) if (NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif () -set_target_properties(common PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE ${CMAKE_POSITION_INDEPENDENT_CODE}) +set_target_properties(common PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE + ${CMAKE_POSITION_INDEPENDENT_CODE}) set(CMAKE_CXX_EXTENSIONS OFF) target_compile_definitions( common @@ -37,7 +38,8 @@ if (MSVC) # remove existing exception flag since we set it to -EHa string(REGEX REPLACE "[-/]EH[a-z]+" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - foreach (var_ CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE) + foreach (var_ CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE) # also remove dynamic runtime string(REGEX REPLACE "[-/]MD[d]*" " " ${var_} "${${var_}}") @@ -143,20 +145,23 @@ if (voidstar) elseif (NOT is_linux) message(FATAL_ERROR "Antithesis instrumentation requires Linux, aborting...") elseif (NOT (is_clang AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16.0)) - message(FATAL_ERROR "Antithesis instrumentation requires Clang version 16 or later, aborting...") + message(FATAL_ERROR "Antithesis instrumentation requires Clang version 16 or later, aborting..." + ) endif () endif () if (use_mold) # use mold linker if available - execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=mold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=mold -Wl,--version ERROR_QUIET + OUTPUT_VARIABLE LD_VERSION) if ("${LD_VERSION}" MATCHES "mold") target_link_libraries(common INTERFACE -fuse-ld=mold) endif () unset(LD_VERSION) elseif (use_gold AND is_gcc) # use gold linker if available - execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET + OUTPUT_VARIABLE LD_VERSION) #[=========================================================[ NOTE: THE gold linker inserts -rpath as DT_RUNPATH by default instead of DT_RPATH, so you might have slightly @@ -186,7 +191,8 @@ elseif (use_gold AND is_gcc) unset(LD_VERSION) elseif (use_lld) # use lld linker if available - execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=lld -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=lld -Wl,--version ERROR_QUIET + OUTPUT_VARIABLE LD_VERSION) if ("${LD_VERSION}" MATCHES "LLD") target_link_libraries(common INTERFACE -fuse-ld=lld) endif () diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index 9a847bfcb9..e54dd09953 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -14,7 +14,8 @@ target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/x PROTOS include/xrpl/proto/xrpl.proto) file(GLOB_RECURSE protos "include/xrpl/proto/org/*.proto") -target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto PROTOS "${protos}") +target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto + PROTOS "${protos}") target_protobuf_sources( xrpl.libpb xrpl/proto LANGUAGE grpc @@ -24,8 +25,9 @@ target_protobuf_sources( GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc) target_compile_options( - xrpl.libpb PUBLIC $<$:-wd4996> $<$: --system-header-prefix="google/protobuf" - -Wno-deprecated-dynamic-exception-spec > + xrpl.libpb + PUBLIC $<$:-wd4996> $<$: + --system-header-prefix="google/protobuf" -Wno-deprecated-dynamic-exception-spec > PRIVATE $<$:-wd4065> $<$>:-Wno-deprecated-declarations>) target_link_libraries(xrpl.libpb PUBLIC protobuf::libprotobuf gRPC::grpc++) @@ -73,7 +75,8 @@ target_link_libraries(xrpl.libxrpl.protocol PUBLIC xrpl.libxrpl.crypto xrpl.libx # Level 05 add_module(xrpl core) -target_link_libraries(xrpl.libxrpl.core PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json xrpl.libxrpl.protocol) +target_link_libraries(xrpl.libxrpl.core PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json + xrpl.libxrpl.protocol) # Level 06 add_module(xrpl resource) @@ -81,22 +84,23 @@ target_link_libraries(xrpl.libxrpl.resource PUBLIC xrpl.libxrpl.protocol) # Level 07 add_module(xrpl net) -target_link_libraries(xrpl.libxrpl.net PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json xrpl.libxrpl.protocol - xrpl.libxrpl.resource) +target_link_libraries(xrpl.libxrpl.net PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json + xrpl.libxrpl.protocol xrpl.libxrpl.resource) add_module(xrpl nodestore) -target_link_libraries(xrpl.libxrpl.nodestore PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json xrpl.libxrpl.protocol) +target_link_libraries(xrpl.libxrpl.nodestore PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json + xrpl.libxrpl.protocol) add_module(xrpl shamap) -target_link_libraries(xrpl.libxrpl.shamap PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.crypto xrpl.libxrpl.protocol - xrpl.libxrpl.nodestore) +target_link_libraries(xrpl.libxrpl.shamap PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.crypto + xrpl.libxrpl.protocol xrpl.libxrpl.nodestore) add_module(xrpl rdb) target_link_libraries(xrpl.libxrpl.rdb PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.core) add_module(xrpl server) -target_link_libraries(xrpl.libxrpl.server PUBLIC xrpl.libxrpl.protocol xrpl.libxrpl.core xrpl.libxrpl.rdb - xrpl.libxrpl.resource) +target_link_libraries(xrpl.libxrpl.server PUBLIC xrpl.libxrpl.protocol xrpl.libxrpl.core + xrpl.libxrpl.rdb xrpl.libxrpl.resource) add_module(xrpl conditions) target_link_libraries(xrpl.libxrpl.conditions PUBLIC xrpl.libxrpl.server) diff --git a/cmake/XrplDocs.cmake b/cmake/XrplDocs.cmake index 69581a99c7..4892e390ce 100644 --- a/cmake/XrplDocs.cmake +++ b/cmake/XrplDocs.cmake @@ -65,8 +65,8 @@ add_custom_command( OUTPUT "${doxygen_index_file}" COMMAND "${CMAKE_COMMAND}" -E env "DOXYGEN_OUTPUT_DIRECTORY=${doxygen_output_directory}" "DOXYGEN_INCLUDE_PATH=${doxygen_include_path}" "DOXYGEN_TAGFILES=${doxygen_tagfiles}" - "DOXYGEN_PLANTUML_JAR_PATH=${doxygen_plantuml_jar_path}" "DOXYGEN_DOT_PATH=${doxygen_dot_path}" - "${DOXYGEN_EXECUTABLE}" "${doxyfile}" + "DOXYGEN_PLANTUML_JAR_PATH=${doxygen_plantuml_jar_path}" + "DOXYGEN_DOT_PATH=${doxygen_dot_path}" "${DOXYGEN_EXECUTABLE}" "${doxyfile}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DEPENDS "${dependencies}" "${tagfile}") add_custom_target(docs DEPENDS "${doxygen_index_file}" SOURCES "${dependencies}") diff --git a/cmake/XrplInstall.cmake b/cmake/XrplInstall.cmake index b8cf4fd32f..4cbf381f87 100644 --- a/cmake/XrplInstall.cmake +++ b/cmake/XrplInstall.cmake @@ -41,11 +41,13 @@ install(TARGETS common INCLUDES DESTINATION include) -install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") install(EXPORT XrplExports FILE XrplTargets.cmake NAMESPACE Xrpl:: DESTINATION lib/cmake/xrpl) include(CMakePackageConfigHelpers) -write_basic_package_version_file(XrplConfigVersion.cmake VERSION ${xrpld_version} COMPATIBILITY SameMajorVersion) +write_basic_package_version_file(XrplConfigVersion.cmake VERSION ${xrpld_version} + COMPATIBILITY SameMajorVersion) if (is_root_project AND TARGET xrpld) install(TARGETS xrpld RUNTIME DESTINATION bin) @@ -72,5 +74,5 @@ if (is_root_project AND TARGET xrpld) ") endif () -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/XrplConfigVersion.cmake - DESTINATION lib/cmake/xrpl) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/XrplConfigVersion.cmake DESTINATION lib/cmake/xrpl) diff --git a/cmake/XrplInterface.cmake b/cmake/XrplInterface.cmake index f471b37dd7..dbf907c7b4 100644 --- a/cmake/XrplInterface.cmake +++ b/cmake/XrplInterface.cmake @@ -33,10 +33,13 @@ target_compile_definitions( target_compile_options( opts INTERFACE $<$,$>:-Wsuggest-override> - $<$:-Wno-maybe-uninitialized> $<$:-fno-omit-frame-pointer> - $<$:-pg> $<$,$>:-p>) + $<$:-Wno-maybe-uninitialized> + $<$:-fno-omit-frame-pointer> + $<$:-pg> + $<$,$>:-p>) -target_link_libraries(opts INTERFACE $<$:-pg> $<$,$>:-p>) +target_link_libraries(opts INTERFACE $<$:-pg> + $<$,$>:-p>) if (jemalloc) find_package(jemalloc REQUIRED) diff --git a/cmake/XrplSanity.cmake b/cmake/XrplSanity.cmake index 5055414abd..489ad026e1 100644 --- a/cmake/XrplSanity.cmake +++ b/cmake/XrplSanity.cmake @@ -19,7 +19,8 @@ if (NOT is_multiconfig) endif () if (is_clang) # both Clang and AppleClang - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16.0) + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS + 16.0) message(FATAL_ERROR "This project requires clang 16 or later") endif () elseif (is_gcc) @@ -32,7 +33,8 @@ endif () if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") message(FATAL_ERROR "Builds (in-source) are not allowed in " "${CMAKE_CURRENT_SOURCE_DIR}. Please remove CMakeCache.txt and the CMakeFiles " - "directory from ${CMAKE_CURRENT_SOURCE_DIR} and try building in a separate directory.") + "directory from ${CMAKE_CURRENT_SOURCE_DIR} and try building in a separate directory." + ) endif () if (MSVC AND CMAKE_GENERATOR_PLATFORM STREQUAL "Win32") diff --git a/cmake/XrplSettings.cmake b/cmake/XrplSettings.cmake index 6d332be19d..5c753fdcda 100644 --- a/cmake/XrplSettings.cmake +++ b/cmake/XrplSettings.cmake @@ -70,7 +70,8 @@ if (is_linux AND NOT SANITIZER) else () set(TRUNCATED_LOGS_DEFAULT OFF) endif () - option(TRUNCATED_THREAD_NAME_LOGS "Show warnings about truncated thread names on Linux." ${TRUNCATED_LOGS_DEFAULT}) + option(TRUNCATED_THREAD_NAME_LOGS "Show warnings about truncated thread names on Linux." + ${TRUNCATED_LOGS_DEFAULT}) if (TRUNCATED_THREAD_NAME_LOGS) add_compile_definitions(TRUNCATED_THREAD_NAME_LOGS) endif () @@ -92,11 +93,13 @@ endif () option(jemalloc "Enables jemalloc for heap profiling" OFF) option(werr "treat warnings as errors" OFF) -option(local_protobuf "Force a local build of protobuf instead of looking for an installed version." OFF) +option(local_protobuf + "Force a local build of protobuf instead of looking for an installed version." OFF) option(local_grpc "Force a local build of gRPC instead of looking for an installed version." OFF) # the remaining options are obscure and rarely used -option(beast_no_unit_test_inline "Prevents unit test definitions from being inserted into global table" OFF) +option(beast_no_unit_test_inline + "Prevents unit test definitions from being inserted into global table" OFF) option(single_io_service_thread "Restricts the number of threads calling io_context::run to one. \ This can be useful when debugging." OFF) option(boost_show_deprecated "Allow boost to fail on deprecated usage. Only useful if you're trying\ diff --git a/cmake/XrplValidatorKeys.cmake b/cmake/XrplValidatorKeys.cmake index 5fa7e14886..58672aada5 100644 --- a/cmake/XrplValidatorKeys.cmake +++ b/cmake/XrplValidatorKeys.cmake @@ -1,4 +1,6 @@ -option(validator_keys "Enables building of validator-keys tool as a separate target (imported via FetchContent)" OFF) +option(validator_keys + "Enables building of validator-keys tool as a separate target (imported via FetchContent)" + OFF) if (validator_keys) git_branch(current_branch) @@ -8,8 +10,9 @@ if (validator_keys) endif () message(STATUS "Tracking ValidatorKeys branch: ${current_branch}") - FetchContent_Declare(validator_keys GIT_REPOSITORY https://github.com/ripple/validator-keys-tool.git - GIT_TAG "${current_branch}") + FetchContent_Declare( + validator_keys GIT_REPOSITORY https://github.com/ripple/validator-keys-tool.git + GIT_TAG "${current_branch}") FetchContent_MakeAvailable(validator_keys) set_target_properties(validator-keys PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") install(TARGETS validator-keys RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/cmake/add_module.cmake b/cmake/add_module.cmake index a3cb247fd2..73618e8be3 100644 --- a/cmake/add_module.cmake +++ b/cmake/add_module.cmake @@ -15,11 +15,12 @@ include(isolate_headers) function (add_module parent name) set(target ${PROJECT_NAME}.lib${parent}.${name}) add_library(${target} OBJECT) - file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}/*.cpp") + file(GLOB_RECURSE sources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}/*.cpp") target_sources(${target} PRIVATE ${sources}) target_include_directories(${target} PUBLIC "$") isolate_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/include/${parent}/${name}" PUBLIC) - isolate_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}" - PRIVATE) + isolate_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}" PRIVATE) endfunction () diff --git a/cmake/deps/Boost.cmake b/cmake/deps/Boost.cmake index fe62fcb79c..a7b145364d 100644 --- a/cmake/deps/Boost.cmake +++ b/cmake/deps/Boost.cmake @@ -39,6 +39,7 @@ if (SANITIZERS_ENABLED AND is_clang) endif () message(STATUS "Adding [${Boost_INCLUDE_DIRS}] to sanitizer blacklist") file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt "src:${Boost_INCLUDE_DIRS}/*") - target_compile_options(opts INTERFACE # ignore boost headers for sanitizing - -fsanitize-blacklist=${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt) + target_compile_options( + opts INTERFACE # ignore boost headers for sanitizing + -fsanitize-blacklist=${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt) endif () diff --git a/cspell.config.yaml b/cspell.config.yaml index b2f4a33769..e2b20ac098 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -6,6 +6,7 @@ ignorePaths: - docs/**/*.puml - cmake/** - LICENSE.md + - .clang-tidy language: en allowCompoundWords: true # TODO (#6334) ignoreRandomStrings: true @@ -172,6 +173,9 @@ words: - nftokens - nftpage - nikb + - nixfmt + - nixos + - nixpkgs - nonxrp - noripple - nudb diff --git a/docs/build/environment.md b/docs/build/environment.md index c6b735ba48..c67877a082 100644 --- a/docs/build/environment.md +++ b/docs/build/environment.md @@ -3,6 +3,8 @@ environment complete with Git, Python, Conan, CMake, and a C++ compiler. This document exists to help readers set one up on any of the Big Three platforms: Linux, macOS, or Windows. +As an alternative to system packages, the Nix development shell can be used to provide a development environment. See [using nix development shell](./nix.md) for more details. + [BUILD.md]: ../../BUILD.md ## Linux diff --git a/docs/build/nix.md b/docs/build/nix.md new file mode 100644 index 0000000000..33bb3711d0 --- /dev/null +++ b/docs/build/nix.md @@ -0,0 +1,95 @@ +# Using Nix Development Shell for xrpld Development + +This guide explains how to use Nix to set up a reproducible development environment for xrpld. Using Nix eliminates the need to manually install utilities and ensures consistent tooling across different machines. + +## Benefits of Using Nix + +- **Reproducible environment**: Everyone gets the same versions of tools and compilers +- **No system pollution**: Dependencies are isolated and don't affect your system packages +- **Multiple compiler versions**: Easily switch between different GCC and Clang versions +- **Quick setup**: Get started with a single command +- **Works on Linux and macOS**: Consistent experience across platforms + +## Install Nix + +Please follow [the official installation instructions of nix package manager](https://nixos.org/download/) for your system. + +## Entering the Development Shell + +### Basic Usage + +From the root of the xrpld repository, enter the default development shell: + +```bash +nix --experimental-features 'nix-command flakes' develop +``` + +This will: + +- Download and set up all required development tools (CMake, Ninja, Conan, etc.) +- Configure the appropriate compiler for your platform: + - **macOS**: Apple Clang (default system compiler) + - **Linux**: GCC 15 + +The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster. + +> [!TIP] +> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`: +> +> ```bash +> mkdir -p ~/.config/nix +> echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf +> ``` +> +> After this, you can simply use `nix develop` instead. + +> [!NOTE] +> The examples below assume you've enabled flakes in your config. If you haven't, add `--experimental-features 'nix-command flakes'` after each `nix` command. + +### Choosing a different compiler + +A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`. +Use `nix flake show` to see all the available development shells. + +Use `nix develop .#no_compiler` to use the compiler from your system. + +### Example Usage + +```bash +# Use GCC 14 +nix develop .#gcc14 + +# Use Clang 19 +nix develop .#clang19 + +# Use default for your platform +nix develop +``` + +### Using a different shell + +`nix develop` opens bash by default. If you want to use another shell this could be done by adding `-c` flag. For example: + +```bash +nix develop -c zsh +``` + +## Building xrpld with Nix + +Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.). + +## Automatic Activation with direnv + +[direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory. + +## Conan and Prebuilt Packages + +Please note that there is no guarantee that binaries from conan cache will work when using nix. If you encounter any errors, please use `--build '*'` to force conan to compile everything from source: + +```bash +conan install .. --output-folder . --build '*' --settings build_type=Release +``` + +## Updating `flake.lock` file + +To update `flake.lock` to the latest revision use `nix flake update` command. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..fd43f5b683 --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1769461804, + "narHash": "sha256-6h5sROT/3CTHvzPy9koKBmoCa2eJKh4fzQK8eYFEgl8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b579d443b37c9c5373044201ea77604e37e748c8", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..4c500f1933 --- /dev/null +++ b/flake.nix @@ -0,0 +1,16 @@ +{ + description = "Nix related things for xrpld"; + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + }; + + outputs = + { nixpkgs, ... }: + let + forEachSystem = (import ./nix/utils.nix { inherit nixpkgs; }).forEachSystem; + in + { + devShells = forEachSystem (import ./nix/devshell.nix); + formatter = forEachSystem ({ pkgs, ... }: pkgs.nixfmt); + }; +} diff --git a/include/xrpl/basics/MallocTrim.h b/include/xrpl/basics/MallocTrim.h new file mode 100644 index 0000000000..2d0cf989ba --- /dev/null +++ b/include/xrpl/basics/MallocTrim.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include +#include +#include + +namespace xrpl { + +// cSpell:ignore ptmalloc + +// ----------------------------------------------------------------------------- +// Allocator interaction note: +// - This facility invokes glibc's malloc_trim(0) on Linux/glibc to request that +// ptmalloc return free heap pages to the OS. +// - If an alternative allocator (e.g. jemalloc or tcmalloc) is linked or +// preloaded (LD_PRELOAD), calling glibc's malloc_trim typically has no effect +// on the *active* heap. The call is harmless but may not reclaim memory +// because those allocators manage their own arenas. +// - Only glibc sbrk/arena space is eligible for trimming; large mmap-backed +// allocations are usually returned to the OS on free regardless of trimming. +// - Call at known reclamation points (e.g., after cache sweeps / online delete) +// and consider rate limiting to avoid churn. +// ----------------------------------------------------------------------------- + +struct MallocTrimReport +{ + bool supported{false}; + int trimResult{-1}; + std::int64_t rssBeforeKB{-1}; + std::int64_t rssAfterKB{-1}; + std::chrono::microseconds durationUs{-1}; + std::int64_t minfltDelta{-1}; + std::int64_t majfltDelta{-1}; + + [[nodiscard]] std::int64_t + deltaKB() const noexcept + { + if (rssBeforeKB < 0 || rssAfterKB < 0) + return 0; + return rssAfterKB - rssBeforeKB; + } +}; + +/** + * @brief Attempt to return freed memory to the operating system. + * + * On Linux with glibc malloc, this issues ::malloc_trim(0), which may release + * free space from ptmalloc arenas back to the kernel. On other platforms, or if + * a different allocator is in use, this function is a no-op and the report will + * indicate that trimming is unsupported or had no effect. + * + * @param tag Identifier for logging/debugging purposes. + * @param journal Journal for diagnostic logging. + * @return Report containing before/after metrics and the trim result. + * + * @note If an alternative allocator (jemalloc/tcmalloc) is linked or preloaded, + * calling glibc's malloc_trim may have no effect on the active heap. The + * call is harmless but typically does not reclaim memory under those + * allocators. + * + * @note Only memory served from glibc's sbrk/arena heaps is eligible for trim. + * Large allocations satisfied via mmap are usually returned on free + * independently of trimming. + * + * @note Intended for use after operations that free significant memory (e.g., + * cache sweeps, ledger cleanup, online delete). Consider rate limiting. + */ +MallocTrimReport +mallocTrim(std::string_view tag, beast::Journal journal); + +} // namespace xrpl diff --git a/include/xrpl/beast/container/aged_multiset.h b/include/xrpl/beast/container/aged_multiset.h index f55e7b34c1..8d906c694f 100644 --- a/include/xrpl/beast/container/aged_multiset.h +++ b/include/xrpl/beast/container/aged_multiset.h @@ -15,5 +15,4 @@ template < class Allocator = std::allocator> using aged_multiset = detail::aged_ordered_container; - } diff --git a/include/xrpl/beast/container/aged_unordered_map.h b/include/xrpl/beast/container/aged_unordered_map.h index 585fc6e5a4..520ffe5848 100644 --- a/include/xrpl/beast/container/aged_unordered_map.h +++ b/include/xrpl/beast/container/aged_unordered_map.h @@ -17,5 +17,4 @@ template < class Allocator = std::allocator>> using aged_unordered_map = detail::aged_unordered_container; - } diff --git a/include/xrpl/beast/container/aged_unordered_multimap.h b/include/xrpl/beast/container/aged_unordered_multimap.h index f2c31b4370..dc6338949b 100644 --- a/include/xrpl/beast/container/aged_unordered_multimap.h +++ b/include/xrpl/beast/container/aged_unordered_multimap.h @@ -17,5 +17,4 @@ template < class Allocator = std::allocator>> using aged_unordered_multimap = detail::aged_unordered_container; - } diff --git a/include/xrpl/beast/container/aged_unordered_set.h b/include/xrpl/beast/container/aged_unordered_set.h index e5879e5919..a1c032e159 100644 --- a/include/xrpl/beast/container/aged_unordered_set.h +++ b/include/xrpl/beast/container/aged_unordered_set.h @@ -16,5 +16,4 @@ template < class Allocator = std::allocator> using aged_unordered_set = detail::aged_unordered_container; - } diff --git a/include/xrpl/nodestore/Backend.h b/include/xrpl/nodestore/Backend.h index 7c3ea57bb8..36fd36ec00 100644 --- a/include/xrpl/nodestore/Backend.h +++ b/include/xrpl/nodestore/Backend.h @@ -77,16 +77,16 @@ public: If the object is not found or an error is encountered, the result will indicate the condition. @note This will be called concurrently. - @param key A pointer to the key data. + @param hash The hash of the object. @param pObject [out] The created object if successful. @return The result of the operation. */ virtual Status - fetch(void const* key, std::shared_ptr* pObject) = 0; + fetch(uint256 const& hash, std::shared_ptr* pObject) = 0; /** Fetch a batch synchronously. */ virtual std::pair>, Status> - fetchBatch(std::vector const& hashes) = 0; + fetchBatch(std::vector const& hashes) = 0; /** Store a single object. Depending on the implementation this may happen immediately diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index f83a4b442d..c0c1c387db 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -15,10 +15,10 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. -XRPL_FIX (LedgerNodeID, Supported::yes, VoteBehavior::DefaultYes) +XRPL_FIX (LedgerNodeID, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo) -XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo) @@ -32,7 +32,7 @@ XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo) -XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(Batch, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo) // Check flags in Credential transactions diff --git a/include/xrpl/shamap/SHAMap.h b/include/xrpl/shamap/SHAMap.h index db5ae81b2a..8ab52df4da 100644 --- a/include/xrpl/shamap/SHAMap.h +++ b/include/xrpl/shamap/SHAMap.h @@ -342,7 +342,7 @@ private: cacheLookup(SHAMapHash const& hash) const; void - canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr& node) const; + canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr&) const; // database operations intr_ptr::SharedPtr diff --git a/nix/devshell.nix b/nix/devshell.nix new file mode 100644 index 0000000000..1d907f4d87 --- /dev/null +++ b/nix/devshell.nix @@ -0,0 +1,140 @@ +{ pkgs, ... }: +let + commonPackages = with pkgs; [ + ccache + cmake + conan + gcovr + git + gnumake + llvmPackages_21.clang-tools + ninja + perl # needed for openssl + pkg-config + pre-commit + python314 + ]; + + # Supported compiler versions + gccVersion = pkgs.lib.range 13 15; + clangVersions = pkgs.lib.range 18 21; + + defaultCompiler = if pkgs.stdenv.isDarwin then "apple-clang" else "gcc"; + defaultGccVersion = pkgs.lib.last gccVersion; + defaultClangVersion = pkgs.lib.last clangVersions; + + strToCompilerEnv = + compiler: version: + ( + if compiler == "gcc" then + let + gccPkg = pkgs."gcc${toString version}Stdenv" or null; + in + if gccPkg != null && builtins.elem version gccVersion then + gccPkg + else + throw "Invalid GCC version: ${toString version}. Must be one of: ${toString gccVersion}" + else if compiler == "clang" then + let + clangPkg = pkgs."llvmPackages_${toString version}".stdenv or null; + in + if clangPkg != null && builtins.elem version clangVersions then + clangPkg + else + throw "Invalid Clang version: ${toString version}. Must be one of: ${toString clangVersions}" + else if compiler == "apple-clang" || compiler == "none" then + pkgs.stdenvNoCC + else + throw "Invalid compiler: ${compiler}. Must be one of: gcc, clang, apple-clang, none" + ); + + # Helper function to create a shell with a specific compiler + makeShell = + { + compiler ? defaultCompiler, + version ? ( + if compiler == "gcc" then + defaultGccVersion + else if compiler == "clang" then + defaultClangVersion + else + null + ), + }: + let + compilerStdEnv = strToCompilerEnv compiler version; + + compilerName = + if compiler == "apple-clang" then + "clang" + else if compiler == "none" then + null + else + compiler; + + gccOnMacWarning = + if pkgs.stdenv.isDarwin && compiler == "gcc" then + '' + echo "WARNING: Using GCC on macOS with Conan may not work." + echo " Consider using 'nix develop .#clang' or the default shell instead." + echo "" + '' + else + ""; + + compilerVersion = + if compilerName != null then + '' + echo "Compiler: " + ${compilerName} --version + '' + else + '' + echo "No compiler specified - using system compiler" + ''; + + shellAttrs = { + packages = commonPackages; + + shellHook = '' + echo "Welcome to xrpld development shell"; + ${gccOnMacWarning}${compilerVersion} + ''; + }; + in + pkgs.mkShell.override { stdenv = compilerStdEnv; } shellAttrs; + + # Generate shells for each compiler version + gccShells = builtins.listToAttrs ( + map (version: { + name = "gcc${toString version}"; + value = makeShell { + compiler = "gcc"; + version = version; + }; + }) gccVersion + ); + + clangShells = builtins.listToAttrs ( + map (version: { + name = "clang${toString version}"; + value = makeShell { + compiler = "clang"; + version = version; + }; + }) clangVersions + ); + +in +gccShells +// clangShells +// { + # Default shells + default = makeShell { }; + gcc = makeShell { compiler = "gcc"; }; + clang = makeShell { compiler = "clang"; }; + + # No compiler + no-compiler = makeShell { compiler = "none"; }; + apple-clang = makeShell { compiler = "apple-clang"; }; +} diff --git a/nix/utils.nix b/nix/utils.nix new file mode 100644 index 0000000000..821d60a6f6 --- /dev/null +++ b/nix/utils.nix @@ -0,0 +1,19 @@ +{ nixpkgs }: +{ + forEachSystem = + function: + nixpkgs.lib.genAttrs + [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ] + ( + system: + function { + inherit system; + pkgs = import nixpkgs { inherit system; }; + } + ); +} diff --git a/src/libxrpl/basics/MallocTrim.cpp b/src/libxrpl/basics/MallocTrim.cpp new file mode 100644 index 0000000000..1b0932b39d --- /dev/null +++ b/src/libxrpl/basics/MallocTrim.cpp @@ -0,0 +1,157 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include + +#if defined(__GLIBC__) && BOOST_OS_LINUX +#include + +#include +#include + +// Require RUSAGE_THREAD for thread-scoped page fault tracking +#ifndef RUSAGE_THREAD +#error "MallocTrim rusage instrumentation requires RUSAGE_THREAD on Linux/glibc" +#endif + +namespace { + +bool +getRusageThread(struct rusage& ru) +{ + return ::getrusage(RUSAGE_THREAD, &ru) == 0; // LCOV_EXCL_LINE +} + +} // namespace +#endif + +namespace xrpl { + +namespace detail { + +// cSpell:ignore statm + +#if defined(__GLIBC__) && BOOST_OS_LINUX + +inline int +mallocTrimWithPad(std::size_t padBytes) +{ + return ::malloc_trim(padBytes); +} + +long +parseStatmRSSkB(std::string const& statm) +{ + // /proc/self/statm format: size resident shared text lib data dt + // We want the second field (resident) which is in pages + std::istringstream iss(statm); + long size, resident; + if (!(iss >> size >> resident)) + return -1; + + // Convert pages to KB + long const pageSize = ::sysconf(_SC_PAGESIZE); + if (pageSize <= 0) + return -1; + + return (resident * pageSize) / 1024; +} + +#endif // __GLIBC__ && BOOST_OS_LINUX + +} // namespace detail + +MallocTrimReport +mallocTrim(std::string_view tag, beast::Journal journal) +{ + // LCOV_EXCL_START + + MallocTrimReport report; + +#if !(defined(__GLIBC__) && BOOST_OS_LINUX) + JLOG(journal.debug()) << "malloc_trim not supported on this platform (tag=" << tag << ")"; +#else + // Keep glibc malloc_trim padding at 0 (default): 12h Mainnet tests across 0/256KB/1MB/16MB + // showed no clear, consistent benefit from custom padding—0 provided the best overall balance + // of RSS reduction and trim-latency stability without adding a tuning surface. + constexpr std::size_t TRIM_PAD = 0; + + report.supported = true; + + if (journal.debug()) + { + auto readFile = [](std::string const& path) -> std::string { + std::ifstream ifs(path, std::ios::in | std::ios::binary); + if (!ifs.is_open()) + return {}; + + // /proc files are often not seekable; read as a stream. + std::ostringstream oss; + oss << ifs.rdbuf(); + return oss.str(); + }; + + std::string const tagStr{tag}; + std::string const statmPath = "/proc/self/statm"; + + auto const statmBefore = readFile(statmPath); + long const rssBeforeKB = detail::parseStatmRSSkB(statmBefore); + + struct rusage ru0{}; + bool const have_ru0 = getRusageThread(ru0); + + auto const t0 = std::chrono::steady_clock::now(); + + report.trimResult = detail::mallocTrimWithPad(TRIM_PAD); + + auto const t1 = std::chrono::steady_clock::now(); + + struct rusage ru1{}; + bool const have_ru1 = getRusageThread(ru1); + + auto const statmAfter = readFile(statmPath); + long const rssAfterKB = detail::parseStatmRSSkB(statmAfter); + + // Populate report fields + report.rssBeforeKB = rssBeforeKB; + report.rssAfterKB = rssAfterKB; + report.durationUs = std::chrono::duration_cast(t1 - t0); + + if (have_ru0 && have_ru1) + { + report.minfltDelta = ru1.ru_minflt - ru0.ru_minflt; + report.majfltDelta = ru1.ru_majflt - ru0.ru_majflt; + } + + std::int64_t const deltaKB = (rssBeforeKB < 0 || rssAfterKB < 0) + ? 0 + : (static_cast(rssAfterKB) - static_cast(rssBeforeKB)); + + JLOG(journal.debug()) << "malloc_trim tag=" << tagStr << " result=" << report.trimResult + << " pad=" << TRIM_PAD << " bytes" + << " rss_before=" << rssBeforeKB << "kB" + << " rss_after=" << rssAfterKB << "kB" + << " delta=" << deltaKB << "kB" + << " duration_us=" << report.durationUs.count() + << " minflt_delta=" << report.minfltDelta + << " majflt_delta=" << report.majfltDelta; + } + else + { + report.trimResult = detail::mallocTrimWithPad(TRIM_PAD); + } + +#endif + + return report; + + // LCOV_EXCL_STOP +} + +} // namespace xrpl diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 7a00170fb7..837d5dfee6 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1390,14 +1390,14 @@ addEmptyHolding( dstId, index.key, sleDst, - /*auth=*/false, - /*noRipple=*/true, - /*freeze=*/false, + /*bAuth=*/false, + /*bNoRipple=*/true, + /*bFreeze=*/false, /*deepFreeze*/ false, - /*balance=*/STAmount{Issue{currency, noAccount()}}, - /*limit=*/STAmount{Issue{currency, dstId}}, - /*qualityIn=*/0, - /*qualityOut=*/0, + /*saBalance=*/STAmount{Issue{currency, noAccount()}}, + /*saLimit=*/STAmount{Issue{currency, dstId}}, + /*uSrcQualityIn=*/0, + /*uSrcQualityOut=*/0, journal); } diff --git a/src/libxrpl/nodestore/DatabaseNodeImp.cpp b/src/libxrpl/nodestore/DatabaseNodeImp.cpp index 5596cb4853..d1452dba86 100644 --- a/src/libxrpl/nodestore/DatabaseNodeImp.cpp +++ b/src/libxrpl/nodestore/DatabaseNodeImp.cpp @@ -33,7 +33,7 @@ DatabaseNodeImp::fetchNodeObject( try { - status = backend_->fetch(hash.data(), &nodeObject); + status = backend_->fetch(hash, &nodeObject); } catch (std::exception const& e) { @@ -68,18 +68,10 @@ DatabaseNodeImp::fetchBatch(std::vector const& hashes) using namespace std::chrono; auto const before = steady_clock::now(); - std::vector batch{}; - batch.reserve(hashes.size()); - for (size_t i = 0; i < hashes.size(); ++i) - { - auto const& hash = hashes[i]; - batch.push_back(&hash); - } - // Get the node objects that match the hashes from the backend. To protect // against the backends returning fewer or more results than expected, the // container is resized to the number of hashes. - auto results = backend_->fetchBatch(batch).first; + auto results = backend_->fetchBatch(hashes).first; XRPL_ASSERT( results.size() == hashes.size() || results.empty(), "number of output objects either matches number of input hashes or is empty"); diff --git a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp index 26d8c30931..e058fa76ac 100644 --- a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp +++ b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp @@ -105,7 +105,7 @@ DatabaseRotatingImp::fetchNodeObject( std::shared_ptr nodeObject; try { - status = backend->fetch(hash.data(), &nodeObject); + status = backend->fetch(hash, &nodeObject); } catch (std::exception const& e) { diff --git a/src/libxrpl/nodestore/backend/MemoryFactory.cpp b/src/libxrpl/nodestore/backend/MemoryFactory.cpp index 8ac23a0bb6..b11d90610a 100644 --- a/src/libxrpl/nodestore/backend/MemoryFactory.cpp +++ b/src/libxrpl/nodestore/backend/MemoryFactory.cpp @@ -116,10 +116,9 @@ public: //-------------------------------------------------------------------------- Status - fetch(void const* key, std::shared_ptr* pObject) override + fetch(uint256 const& hash, std::shared_ptr* pObject) override { XRPL_ASSERT(db_, "xrpl::NodeStore::MemoryBackend::fetch : non-null database"); - uint256 const hash(uint256::fromVoid(key)); std::lock_guard _(db_->mutex); @@ -134,14 +133,14 @@ public: } std::pair>, Status> - fetchBatch(std::vector const& hashes) override + fetchBatch(std::vector const& hashes) override { std::vector> results; results.reserve(hashes.size()); for (auto const& h : hashes) { std::shared_ptr nObj; - Status status = fetch(h->begin(), &nObj); + Status status = fetch(h, &nObj); if (status != ok) results.push_back({}); else diff --git a/src/libxrpl/nodestore/backend/NuDBFactory.cpp b/src/libxrpl/nodestore/backend/NuDBFactory.cpp index e8efa464af..4d7e7be668 100644 --- a/src/libxrpl/nodestore/backend/NuDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/NuDBFactory.cpp @@ -179,17 +179,17 @@ public: } Status - fetch(void const* key, std::shared_ptr* pno) override + fetch(uint256 const& hash, std::shared_ptr* pno) override { Status status; pno->reset(); nudb::error_code ec; db_.fetch( - key, - [key, pno, &status](void const* data, std::size_t size) { + hash.data(), + [&hash, pno, &status](void const* data, std::size_t size) { nudb::detail::buffer bf; auto const result = nodeobject_decompress(data, size, bf); - DecodedBlob decoded(key, result.first, result.second); + DecodedBlob decoded(hash.data(), result.first, result.second); if (!decoded.wasOk()) { status = dataCorrupt; @@ -207,14 +207,14 @@ public: } std::pair>, Status> - fetchBatch(std::vector const& hashes) override + fetchBatch(std::vector const& hashes) override { std::vector> results; results.reserve(hashes.size()); for (auto const& h : hashes) { std::shared_ptr nObj; - Status status = fetch(h->begin(), &nObj); + Status status = fetch(h, &nObj); if (status != ok) results.push_back({}); else diff --git a/src/libxrpl/nodestore/backend/NullFactory.cpp b/src/libxrpl/nodestore/backend/NullFactory.cpp index 4ecca46a9a..ab5b7d0117 100644 --- a/src/libxrpl/nodestore/backend/NullFactory.cpp +++ b/src/libxrpl/nodestore/backend/NullFactory.cpp @@ -36,13 +36,13 @@ public: } Status - fetch(void const*, std::shared_ptr*) override + fetch(uint256 const&, std::shared_ptr*) override { return notFound; } std::pair>, Status> - fetchBatch(std::vector const& hashes) override + fetchBatch(std::vector const& hashes) override { return {}; } diff --git a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp index c84c5f6982..01bc74f5ed 100644 --- a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp @@ -244,7 +244,7 @@ public: //-------------------------------------------------------------------------- Status - fetch(void const* key, std::shared_ptr* pObject) override + fetch(uint256 const& hash, std::shared_ptr* pObject) override { XRPL_ASSERT(m_db, "xrpl::NodeStore::RocksDBBackend::fetch : non-null database"); pObject->reset(); @@ -252,7 +252,7 @@ public: Status status(ok); rocksdb::ReadOptions const options; - rocksdb::Slice const slice(static_cast(key), m_keyBytes); + rocksdb::Slice const slice(std::bit_cast(hash.data()), m_keyBytes); std::string string; @@ -260,7 +260,7 @@ public: if (getStatus.ok()) { - DecodedBlob decoded(key, string.data(), string.size()); + DecodedBlob decoded(hash.data(), string.data(), string.size()); if (decoded.wasOk()) { @@ -295,14 +295,14 @@ public: } std::pair>, Status> - fetchBatch(std::vector const& hashes) override + fetchBatch(std::vector const& hashes) override { std::vector> results; results.reserve(hashes.size()); for (auto const& h : hashes) { std::shared_ptr nObj; - Status status = fetch(h->begin(), &nObj); + Status status = fetch(h, &nObj); if (status != ok) results.push_back({}); else @@ -332,9 +332,8 @@ public: EncodedBlob encoded(e); wb.Put( - rocksdb::Slice(reinterpret_cast(encoded.getKey()), m_keyBytes), - rocksdb::Slice( - reinterpret_cast(encoded.getData()), encoded.getSize())); + rocksdb::Slice(std::bit_cast(encoded.getKey()), m_keyBytes), + rocksdb::Slice(std::bit_cast(encoded.getData()), encoded.getSize())); } rocksdb::WriteOptions const options; diff --git a/src/libxrpl/protocol/Keylet.cpp b/src/libxrpl/protocol/Keylet.cpp index 2c65bb9ed4..6f9656c4ea 100644 --- a/src/libxrpl/protocol/Keylet.cpp +++ b/src/libxrpl/protocol/Keylet.cpp @@ -9,7 +9,7 @@ bool Keylet::check(STLedgerEntry const& sle) const { XRPL_ASSERT( - sle.getType() != ltANY || sle.getType() != ltCHILD, + sle.getType() != ltANY && sle.getType() != ltCHILD, "xrpl::Keylet::check : valid input type"); if (type == ltANY) diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 26cca980ed..94db50842a 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -1024,7 +1024,7 @@ public: // Charlie - queue a transaction, with a higher fee // than default env(noop(charlie), fee(15), queued); - checkMetrics(*this, env, 6, initQueueMax, 4, 3, 256); + checkMetrics(*this, env, 6, initQueueMax, 4, 3, 257); BEAST_EXPECT(env.seq(alice) == aliceSeq); BEAST_EXPECT(env.seq(bob) == bobSeq); diff --git a/src/test/nodestore/TestBase.h b/src/test/nodestore/TestBase.h index 4a4d21002e..cb2a8e3bd5 100644 --- a/src/test/nodestore/TestBase.h +++ b/src/test/nodestore/TestBase.h @@ -138,7 +138,7 @@ public: { std::shared_ptr object; - Status const status = backend.fetch(batch[i]->getHash().cbegin(), &object); + Status const status = backend.fetch(batch[i]->getHash(), &object); BEAST_EXPECT(status == ok); @@ -158,7 +158,7 @@ public: { std::shared_ptr object; - Status const status = backend.fetch(batch[i]->getHash().cbegin(), &object); + Status const status = backend.fetch(batch[i]->getHash(), &object); BEAST_EXPECT(status == notFound); } diff --git a/src/test/nodestore/Timing_test.cpp b/src/test/nodestore/Timing_test.cpp index dae131e5e7..b537e3abb7 100644 --- a/src/test/nodestore/Timing_test.cpp +++ b/src/test/nodestore/Timing_test.cpp @@ -314,7 +314,7 @@ public: std::shared_ptr obj; std::shared_ptr result; obj = seq1_.obj(dist_(gen_)); - backend_.fetch(obj->getHash().data(), &result); + backend_.fetch(obj->getHash(), &result); suite_.expect(result && isSame(result, obj)); } catch (std::exception const& e) @@ -377,9 +377,9 @@ public: { try { - auto const key = seq2_.key(i); + auto const hash = seq2_.key(i); std::shared_ptr result; - backend_.fetch(key.data(), &result); + backend_.fetch(hash, &result); suite_.expect(!result); } catch (std::exception const& e) @@ -449,9 +449,9 @@ public: { if (rand_(gen_) < missingNodePercent) { - auto const key = seq2_.key(dist_(gen_)); + auto const hash = seq2_.key(dist_(gen_)); std::shared_ptr result; - backend_.fetch(key.data(), &result); + backend_.fetch(hash, &result); suite_.expect(!result); } else @@ -459,7 +459,7 @@ public: std::shared_ptr obj; std::shared_ptr result; obj = seq1_.obj(dist_(gen_)); - backend_.fetch(obj->getHash().data(), &result); + backend_.fetch(obj->getHash(), &result); suite_.expect(result && isSame(result, obj)); } } @@ -540,8 +540,7 @@ public: std::shared_ptr result; auto const j = older_(gen_); obj = seq1_.obj(j); - std::shared_ptr result1; - backend_.fetch(obj->getHash().data(), &result); + backend_.fetch(obj->getHash(), &result); suite_.expect(result != nullptr); suite_.expect(isSame(result, obj)); } @@ -559,7 +558,7 @@ public: std::shared_ptr result; auto const j = recent_(gen_); obj = seq1_.obj(j); - backend_.fetch(obj->getHash().data(), &result); + backend_.fetch(obj->getHash(), &result); suite_.expect(!result || isSame(result, obj)); break; } diff --git a/src/tests/libxrpl/basics/MallocTrim.cpp b/src/tests/libxrpl/basics/MallocTrim.cpp new file mode 100644 index 0000000000..f01bd91bbf --- /dev/null +++ b/src/tests/libxrpl/basics/MallocTrim.cpp @@ -0,0 +1,209 @@ +#include + +#include + +#include + +using namespace xrpl; + +// cSpell:ignore statm + +#if defined(__GLIBC__) && BOOST_OS_LINUX +namespace xrpl::detail { +long +parseStatmRSSkB(std::string const& statm); +} // namespace xrpl::detail +#endif + +TEST(MallocTrimReport, structure) +{ + // Test default construction + MallocTrimReport report; + EXPECT_EQ(report.supported, false); + EXPECT_EQ(report.trimResult, -1); + EXPECT_EQ(report.rssBeforeKB, -1); + EXPECT_EQ(report.rssAfterKB, -1); + EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1}); + EXPECT_EQ(report.minfltDelta, -1); + EXPECT_EQ(report.majfltDelta, -1); + EXPECT_EQ(report.deltaKB(), 0); + + // Test deltaKB calculation - memory freed + report.rssBeforeKB = 1000; + report.rssAfterKB = 800; + EXPECT_EQ(report.deltaKB(), -200); + + // Test deltaKB calculation - memory increased + report.rssBeforeKB = 500; + report.rssAfterKB = 600; + EXPECT_EQ(report.deltaKB(), 100); + + // Test deltaKB calculation - no change + report.rssBeforeKB = 1234; + report.rssAfterKB = 1234; + EXPECT_EQ(report.deltaKB(), 0); +} + +#if defined(__GLIBC__) && BOOST_OS_LINUX +TEST(parseStatmRSSkB, standard_format) +{ + using xrpl::detail::parseStatmRSSkB; + + // Test standard format: size resident shared text lib data dt + // Assuming 4KB page size: resident=1000 pages = 4000 KB + { + std::string statm = "25365 1000 2377 0 0 5623 0"; + long result = parseStatmRSSkB(statm); + // Note: actual result depends on system page size + // On most systems it's 4KB, so 1000 pages = 4000 KB + EXPECT_GT(result, 0); + } + + // Test with newline + { + std::string statm = "12345 2000 1234 0 0 3456 0\n"; + long result = parseStatmRSSkB(statm); + EXPECT_GT(result, 0); + } + + // Test with tabs + { + std::string statm = "12345\t2000\t1234\t0\t0\t3456\t0"; + long result = parseStatmRSSkB(statm); + EXPECT_GT(result, 0); + } + + // Test zero resident pages + { + std::string statm = "25365 0 2377 0 0 5623 0"; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, 0); + } + + // Test with extra whitespace + { + std::string statm = " 25365 1000 2377 "; + long result = parseStatmRSSkB(statm); + EXPECT_GT(result, 0); + } + + // Test empty string + { + std::string statm = ""; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, -1); + } + + // Test malformed data (only one field) + { + std::string statm = "25365"; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, -1); + } + + // Test malformed data (non-numeric) + { + std::string statm = "abc def ghi"; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, -1); + } + + // Test malformed data (second field non-numeric) + { + std::string statm = "25365 abc 2377"; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, -1); + } +} +#endif + +TEST(mallocTrim, without_debug_logging) +{ + beast::Journal journal{beast::Journal::getNullSink()}; + + MallocTrimReport report = mallocTrim("without_debug", journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + EXPECT_EQ(report.supported, true); + EXPECT_GE(report.trimResult, 0); + EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1}); + EXPECT_EQ(report.minfltDelta, -1); + EXPECT_EQ(report.majfltDelta, -1); +#else + EXPECT_EQ(report.supported, false); + EXPECT_EQ(report.trimResult, -1); + EXPECT_EQ(report.rssBeforeKB, -1); + EXPECT_EQ(report.rssAfterKB, -1); + EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1}); + EXPECT_EQ(report.minfltDelta, -1); + EXPECT_EQ(report.majfltDelta, -1); +#endif +} + +TEST(mallocTrim, empty_tag) +{ + beast::Journal journal{beast::Journal::getNullSink()}; + MallocTrimReport report = mallocTrim("", journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + EXPECT_EQ(report.supported, true); + EXPECT_GE(report.trimResult, 0); +#else + EXPECT_EQ(report.supported, false); +#endif +} + +TEST(mallocTrim, with_debug_logging) +{ + struct DebugSink : public beast::Journal::Sink + { + DebugSink() : Sink(beast::severities::kDebug, false) + { + } + void + write(beast::severities::Severity, std::string const&) override + { + } + void + writeAlways(beast::severities::Severity, std::string const&) override + { + } + }; + + DebugSink sink; + beast::Journal journal{sink}; + + MallocTrimReport report = mallocTrim("debug_test", journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + EXPECT_EQ(report.supported, true); + EXPECT_GE(report.trimResult, 0); + EXPECT_GE(report.durationUs.count(), 0); + EXPECT_GE(report.minfltDelta, 0); + EXPECT_GE(report.majfltDelta, 0); +#else + EXPECT_EQ(report.supported, false); + EXPECT_EQ(report.trimResult, -1); + EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1}); + EXPECT_EQ(report.minfltDelta, -1); + EXPECT_EQ(report.majfltDelta, -1); +#endif +} + +TEST(mallocTrim, repeated_calls) +{ + beast::Journal journal{beast::Journal::getNullSink()}; + + // Call malloc_trim multiple times to ensure it's safe + for (int i = 0; i < 5; ++i) + { + MallocTrimReport report = mallocTrim("iteration_" + std::to_string(i), journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + EXPECT_EQ(report.supported, true); + EXPECT_GE(report.trimResult, 0); +#else + EXPECT_EQ(report.supported, false); +#endif + } +} diff --git a/src/xrpld/app/ledger/InboundLedger.h b/src/xrpld/app/ledger/InboundLedger.h index d9f263493c..628c9c539b 100644 --- a/src/xrpld/app/ledger/InboundLedger.h +++ b/src/xrpld/app/ledger/InboundLedger.h @@ -134,13 +134,13 @@ private: takeHeader(std::string const& data); void - receiveNode(protocol::TMLedgerData& packet, SHAMapAddNode&); + receiveNode(protocol::TMLedgerData& packet, SHAMapAddNode& san); bool - takeTxRootNode(std::string const& data, SHAMapAddNode&); + takeTxRootNode(std::string const& data, SHAMapAddNode& san); bool - takeAsRootNode(std::string const& data, SHAMapAddNode&); + takeAsRootNode(std::string const& data, SHAMapAddNode& san); std::vector neededTxHashes(int max, SHAMapSyncFilter* filter) const; diff --git a/src/xrpld/app/ledger/detail/TransactionAcquire.cpp b/src/xrpld/app/ledger/detail/TransactionAcquire.cpp index a430996bb8..44dc0e7df4 100644 --- a/src/xrpld/app/ledger/detail/TransactionAcquire.cpp +++ b/src/xrpld/app/ledger/detail/TransactionAcquire.cpp @@ -180,15 +180,12 @@ TransactionAcquire::takeNodes( for (auto& d : data) { - if (d.first.isRoot() && mHaveRoot) - { - JLOG(journal_.debug()) << "Got root TXS node, already have it"; - continue; - } - if (d.first.isRoot()) { - if (!mMap->addRootNode(SHAMapHash{hash_}, std::move(d.second), nullptr).isGood()) + if (mHaveRoot) + JLOG(journal_.debug()) << "Got root TXS node, already have it"; + else if (!mMap->addRootNode(SHAMapHash{hash_}, std::move(d.second), nullptr) + .isGood()) JLOG(journal_.warn()) << "TX acquire got bad root node"; else mHaveRoot = true; diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 91cc387d54..1162bc497a 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -1053,6 +1054,8 @@ public: << "; size after: " << cachedSLEs_.size(); } + mallocTrim("doSweep", m_journal); + // Set timer to do another sweep later. setSweepTimer(); }