diff --git a/.github/actions/build-deps/action.yml b/.github/actions/build-deps/action.yml index 884601a851..9d52be1998 100644 --- a/.github/actions/build-deps/action.yml +++ b/.github/actions/build-deps/action.yml @@ -4,9 +4,6 @@ description: "Install Conan dependencies, optionally forcing a rebuild of all de # Note that actions do not support 'type' and all inputs are strings, see # https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#inputs. inputs: - build_dir: - description: "The directory where to build." - required: true build_type: description: 'The build type to use ("Debug", "Release").' required: true @@ -32,7 +29,6 @@ runs: - name: Install Conan dependencies shell: bash env: - BUILD_DIR: ${{ inputs.build_dir }} BUILD_NPROC: ${{ inputs.build_nproc }} BUILD_OPTION: ${{ inputs.force_build == 'true' && '*' || 'missing' }} BUILD_TYPE: ${{ inputs.build_type }} @@ -40,11 +36,8 @@ runs: SANITIZERS: ${{ inputs.sanitizers }} run: | echo 'Installing dependencies.' - mkdir -p "${BUILD_DIR}" - cd "${BUILD_DIR}" conan install \ --profile ci \ - --output-folder . \ --build="${BUILD_OPTION}" \ --options:host='&:tests=True' \ --options:host='&:xrpld=True' \ @@ -52,4 +45,4 @@ runs: --conf:all tools.build:jobs=${BUILD_NPROC} \ --conf:all tools.build:verbosity="${LOG_VERBOSITY}" \ --conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \ - .. + . diff --git a/.github/actions/print-env/action.yml b/.github/actions/print-env/action.yml index 6019a6de2f..3527ca6f02 100644 --- a/.github/actions/print-env/action.yml +++ b/.github/actions/print-env/action.yml @@ -11,12 +11,6 @@ runs: echo 'Checking environment variables.' set - echo 'Checking CMake version.' - cmake --version - - echo 'Checking Conan version.' - conan --version - - name: Check configuration (Linux and macOS) if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }} shell: bash @@ -27,17 +21,23 @@ runs: echo 'Checking environment variables.' env | sort - echo 'Checking CMake version.' - cmake --version - echo 'Checking compiler version.' ${{ runner.os == 'Linux' && '${CC}' || 'clang' }} --version - echo 'Checking Conan version.' - conan --version - echo 'Checking Ninja version.' ninja --version echo 'Checking nproc version.' nproc --version + + - name: Check configuration (all) + shell: bash + run: | + echo 'Checking Ccache version.' + ccache --version + + echo 'Checking CMake version.' + cmake --version + + echo 'Checking Conan version.' + conan --version diff --git a/.github/scripts/rename/README.md b/.github/scripts/rename/README.md index 392f0b1efc..8336f23bec 100644 --- a/.github/scripts/rename/README.md +++ b/.github/scripts/rename/README.md @@ -31,6 +31,9 @@ run from the repository root. the `xrpld` binary. 5. `.github/scripts/rename/namespace.sh`: This script will rename the C++ namespaces from `ripple` to `xrpl`. +6. `.github/scripts/rename/config.sh`: This script will rename the config from + `rippled.cfg` to `xrpld.cfg`, and updating the code accordingly. The old + filename will still be accepted. You can run all these scripts from the repository root as follows: @@ -40,4 +43,5 @@ You can run all these scripts from the repository root as follows: ./.github/scripts/rename/cmake.sh . ./.github/scripts/rename/binary.sh . ./.github/scripts/rename/namespace.sh . +./.github/scripts/rename/config.sh . ``` diff --git a/.github/scripts/rename/config.sh b/.github/scripts/rename/config.sh new file mode 100755 index 0000000000..7f36e8fd21 --- /dev/null +++ b/.github/scripts/rename/config.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Exit the script as soon as an error occurs. +set -e + +# On MacOS, ensure that GNU sed is installed and available as `gsed`. +SED_COMMAND=sed +if [[ "${OSTYPE}" == 'darwin'* ]]; then + if ! command -v gsed &> /dev/null; then + echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." + exit 1 + fi + SED_COMMAND=gsed +fi + +# This script renames the config from `rippled.cfg` to `xrpld.cfg`, and updates +# the code accordingly. The old filename will still be accepted. +# Usage: .github/scripts/rename/config.sh + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +DIRECTORY=$1 +echo "Processing directory: ${DIRECTORY}" +if [ ! -d "${DIRECTORY}" ]; then + echo "Error: Directory '${DIRECTORY}' does not exist." + exit 1 +fi +pushd ${DIRECTORY} + +# Add the xrpld.cfg to the .gitignore. +if ! grep -q 'xrpld.cfg' .gitignore; then + ${SED_COMMAND} -i '/rippled.cfg/a\ +/xrpld.cfg' .gitignore +fi + +# Rename the files. +if [ -e rippled.cfg ]; then + mv rippled.cfg xrpld.cfg +fi +if [ -e cfg/rippled-example.cfg ]; then + mv cfg/rippled-example.cfg cfg/xrpld-example.cfg +fi + +# Rename inside the files. +DIRECTORIES=("cfg" "cmake" "include" "src") +for DIRECTORY in "${DIRECTORIES[@]}"; do + echo "Processing directory: ${DIRECTORY}" + + find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.cmake" -o -name "*.txt" -o -name "*.cfg" -o -name "*.md" \) | while read -r FILE; do + echo "Processing file: ${FILE}" + ${SED_COMMAND} -i -E 's/rippled(-example)?[ .]cfg/xrpld\1.cfg/g' "${FILE}" + done +done +${SED_COMMAND} -i 's/rippled/xrpld/g' cfg/xrpld-example.cfg +${SED_COMMAND} -i 's/rippled/xrpld/g' src/test/core/Config_test.cpp +${SED_COMMAND} -i 's/ripplevalidators/xrplvalidators/g' src/test/core/Config_test.cpp +${SED_COMMAND} -i 's/rippleConfig/xrpldConfig/g' src/test/core/Config_test.cpp +${SED_COMMAND} -i 's@ripple/@xrpld/@g' src/test/core/Config_test.cpp +${SED_COMMAND} -i 's/Rippled/File/g' src/test/core/Config_test.cpp + + +# Restore the old config file name in the code that maintains support for now. +${SED_COMMAND} -i 's/configLegacyName = "xrpld.cfg"/configLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp + +# Restore an URL. +${SED_COMMAND} -i 's/connect-your-xrpld-to-the-xrp-test-net.html/connect-your-rippled-to-the-xrp-test-net.html/g' cfg/xrpld-example.cfg + +popd +echo "Renaming complete." diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index 144400fed4..374a92d7b6 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -232,6 +232,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: f"-{architecture['platform'][architecture['platform'].find('/')+1:]}" ) config_name += f"-{build_type.lower()}" + if "-Dcoverage=ON" in cmake_args: + config_name += "-coverage" if "-Dunity=ON" in cmake_args: config_name += "-unity" diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 748ee031c9..669754554c 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -15,196 +15,196 @@ "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "21", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "jammy", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" } ], "build_type": ["Debug", "Release"], diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index ff3d25812a..3aa48ac070 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -114,6 +114,9 @@ jobs: matrix: os: [linux, macos, windows] with: + # Enable ccache only for events targeting the XRPLF repository, since + # other accounts will not have access to our remote cache storage. + ccache_enabled: ${{ github.repository_owner == 'XRPLF' }} os: ${{ matrix.os }} secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index b5a56fb671..a95402ced7 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -66,6 +66,12 @@ jobs: strategy: fail-fast: ${{ github.event_name == 'merge_group' }} matrix: + # Enable ccache only for events targeting the XRPLF repository, since + # other accounts will not have access to our remote cache storage. + # However, we do not enable ccache for events targeting the master or a + # release branch, to protect against the rare case that the output + # produced by ccache is not identical to a regular compilation. + ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !(github.base_ref == 'master' || startsWith(github.base_ref, 'release')) }} os: [linux, macos, windows] with: os: ${{ matrix.os }} diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 6f0927a2b3..c37a82a2f3 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -22,7 +22,7 @@ defaults: shell: bash env: - BUILD_DIR: .build + BUILD_DIR: build NPROC_SUBTRACT: 2 jobs: diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 7d0ecdf309..a287f59adc 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -3,11 +3,6 @@ name: Build and test configuration on: workflow_call: inputs: - build_dir: - description: "The directory where to build." - required: true - type: string - build_only: description: 'Whether to only build or to build and test the code ("true", "false").' required: true @@ -15,8 +10,14 @@ on: build_type: description: 'The build type to use ("Debug", "Release").' - type: string required: true + type: string + + ccache_enabled: + description: "Whether to enable ccache." + required: false + type: boolean + default: false cmake_args: description: "Additional arguments to pass to CMake." @@ -26,8 +27,8 @@ on: cmake_target: description: "The CMake target to build." - type: string required: true + type: string runs_on: description: Runner to run the job on as a JSON string @@ -65,6 +66,11 @@ 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 + jobs: build-and-test: name: ${{ inputs.config_name }} @@ -72,8 +78,25 @@ jobs: container: ${{ inputs.image != '' && inputs.image || null }} timeout-minutes: 60 env: - ENABLED_VOIDSTAR: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }} - ENABLED_COVERAGE: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }} + # Use a namespace to keep the objects separate for each configuration. + CCACHE_NAMESPACE: ${{ inputs.config_name }} + # Ccache supports both Redis and HTTP endpoints. + # * For Redis, use the following format: redis://ip:port, see + # https://github.com/ccache/ccache/wiki/Redis-storage. Note that TLS is + # not directly supported by ccache, and requires use of a proxy. + # * For HTTP use the following format: http://ip:port/cache when using + # nginx as backend or http://ip:port|layout=bazel when using Bazel + # Remote Cache, see https://github.com/ccache/ccache/wiki/HTTP-storage. + # Note that HTTPS is not directly supported by ccache. + CCACHE_REMOTE_ONLY: true + CCACHE_REMOTE_STORAGE: http://cache.dev.ripplex.io:8080|layout=bazel + # Ignore the creation and modification timestamps on files, since the + # header files are copied into separate directories by CMake, which will + # otherwise result in cache misses. + CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime + # Determine if coverage and voidstar should be enabled. + COVERAGE_ENABLED: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }} + VOIDSTAR_ENABLED: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }} SANITIZERS_ENABLED: ${{ inputs.sanitizers != '' }} steps: - name: Cleanup workspace (macOS and Windows) @@ -86,7 +109,11 @@ jobs: - name: Prepare runner uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6 with: - disable_ccache: false + disable_ccache: ${{ !inputs.ccache_enabled }} + + - name: Set ccache log file + if: ${{ inputs.ccache_enabled && runner.debug == '1' }} + run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >> "${GITHUB_ENV}" - name: Print build environment uses: ./.github/actions/print-env @@ -103,7 +130,6 @@ jobs: - name: Build dependencies uses: ./.github/actions/build-deps with: - build_dir: ${{ inputs.build_dir }} build_nproc: ${{ steps.nproc.outputs.nproc }} build_type: ${{ inputs.build_type }} # Set the verbosity to "quiet" for Windows to avoid an excessive @@ -112,7 +138,7 @@ jobs: sanitizers: ${{ inputs.sanitizers }} - name: Configure CMake - working-directory: ${{ inputs.build_dir }} + working-directory: ${{ env.BUILD_DIR }} env: BUILD_TYPE: ${{ inputs.build_type }} SANITIZERS: ${{ inputs.sanitizers }} @@ -126,7 +152,7 @@ jobs: .. - name: Build the binary - working-directory: ${{ inputs.build_dir }} + working-directory: ${{ env.BUILD_DIR }} env: BUILD_NPROC: ${{ steps.nproc.outputs.nproc }} BUILD_TYPE: ${{ inputs.build_type }} @@ -138,11 +164,18 @@ jobs: --parallel "${BUILD_NPROC}" \ --target "${CMAKE_TARGET}" + - name: Show ccache statistics + if: ${{ inputs.ccache_enabled }} + run: | + ccache --show-stats -vv + if [ '${{ runner.debug }}' = '1' ]; then + cat "${CCACHE_LOGFILE}" + curl ${CCACHE_REMOTE_STORAGE%|*}/status || true + fi + - name: Upload the binary (Linux) if: ${{ github.repository_owner == 'XRPLF' && runner.os == 'Linux' }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - env: - BUILD_DIR: ${{ inputs.build_dir }} with: name: xrpld-${{ inputs.config_name }} path: ${{ env.BUILD_DIR }}/xrpld @@ -151,7 +184,7 @@ jobs: - name: Check linking (Linux) if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }} - working-directory: ${{ inputs.build_dir }} + working-directory: ${{ env.BUILD_DIR }} run: | ldd ./xrpld if [ "$(ldd ./xrpld | grep -E '(libstdc\+\+|libgcc)' | wc -l)" -eq 0 ]; then @@ -162,8 +195,8 @@ jobs: fi - name: Verify presence of instrumentation (Linux) - if: ${{ runner.os == 'Linux' && env.ENABLED_VOIDSTAR == 'true' }} - working-directory: ${{ inputs.build_dir }} + if: ${{ runner.os == 'Linux' && env.VOIDSTAR_ENABLED == 'true' }} + working-directory: ${{ env.BUILD_DIR }} run: | ./xrpld --version | grep libvoidstar @@ -177,7 +210,7 @@ jobs: - name: Run the separate tests if: ${{ !inputs.build_only }} - working-directory: ${{ inputs.build_dir }} + working-directory: ${{ env.BUILD_DIR }} # Windows locks some of the build files while running tests, and parallel jobs can collide env: BUILD_TYPE: ${{ inputs.build_type }} @@ -190,7 +223,7 @@ jobs: - name: Run the embedded tests if: ${{ !inputs.build_only }} - working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', inputs.build_dir, inputs.build_type) || inputs.build_dir }} + working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }} env: BUILD_NPROC: ${{ steps.nproc.outputs.nproc }} run: | @@ -205,8 +238,8 @@ jobs: netstat -an - name: Prepare coverage report - if: ${{ !inputs.build_only && env.ENABLED_COVERAGE == 'true' }} - working-directory: ${{ inputs.build_dir }} + if: ${{ !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} + working-directory: ${{ env.BUILD_DIR }} env: BUILD_NPROC: ${{ steps.nproc.outputs.nproc }} BUILD_TYPE: ${{ inputs.build_type }} @@ -218,13 +251,13 @@ jobs: --target coverage - name: Upload coverage report - if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.ENABLED_COVERAGE == 'true' }} + if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: disable_search: true disable_telem: true fail_ci_if_error: true - files: ${{ inputs.build_dir }}/coverage.xml + files: ${{ env.BUILD_DIR }}/coverage.xml plugins: noop token: ${{ secrets.CODECOV_TOKEN }} verbose: true diff --git a/.github/workflows/reusable-build-test.yml b/.github/workflows/reusable-build-test.yml index 075d66f11b..0086cbbfb5 100644 --- a/.github/workflows/reusable-build-test.yml +++ b/.github/workflows/reusable-build-test.yml @@ -8,21 +8,24 @@ name: Build and test on: workflow_call: inputs: - build_dir: - description: "The directory where to build." + ccache_enabled: + description: "Whether to enable ccache." required: false - type: string - default: ".build" + type: boolean + default: false + os: description: 'The operating system to use for the build ("linux", "macos", "windows").' required: true type: string + strategy_matrix: # TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations. description: 'The strategy matrix to use for generating the configurations ("minimal", "all").' required: false type: string default: "minimal" + secrets: CODECOV_TOKEN: description: "The Codecov token to use for uploading coverage reports." @@ -46,9 +49,9 @@ jobs: matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} max-parallel: 10 with: - build_dir: ${{ inputs.build_dir }} build_only: ${{ matrix.build_only }} build_type: ${{ matrix.build_type }} + ccache_enabled: ${{ inputs.ccache_enabled }} cmake_args: ${{ matrix.cmake_args }} cmake_target: ${{ matrix.cmake_target }} runs_on: ${{ toJSON(matrix.architecture.runner) }} diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index fb9ed2c6a8..af55084405 100644 --- a/.github/workflows/reusable-check-rename.yml +++ b/.github/workflows/reusable-check-rename.yml @@ -29,6 +29,8 @@ jobs: run: .github/scripts/rename/binary.sh . - name: Check namespaces run: .github/scripts/rename/namespace.sh . + - name: Check config name + run: .github/scripts/rename/config.sh . - name: Check for differences env: MESSAGE: | diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index ec283e564c..8a9993d37a 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -72,7 +72,7 @@ jobs: - name: Prepare runner uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6 with: - disable_ccache: false + disable_ccache: true - name: Print build environment uses: ./.github/actions/print-env @@ -92,7 +92,6 @@ jobs: - name: Build dependencies uses: ./.github/actions/build-deps with: - build_dir: .build build_nproc: ${{ steps.nproc.outputs.nproc }} build_type: ${{ matrix.build_type }} force_build: ${{ github.event_name == 'schedule' || github.event.inputs.force_source_build == 'true' }} diff --git a/.gitignore b/.gitignore index 55844462e5..c4e81408bb 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ gmon.out # Customized configs. /rippled.cfg +/xrpld.cfg /validators.txt # Locally patched Conan recipes diff --git a/CMakeLists.txt b/CMakeLists.txt index cb2fe05daf..adda3a2af9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,9 @@ elseif(is_msvc) add_compile_options(/wd4068) # Ignore unknown pragmas endif() +# Enable ccache to speed up builds. +include(Ccache) + # make GIT_COMMIT_HASH define available to all sources find_package(Git) if(Git_FOUND) diff --git a/cfg/validators-example.txt b/cfg/validators-example.txt index dbcff90f12..6eb49da697 100644 --- a/cfg/validators-example.txt +++ b/cfg/validators-example.txt @@ -1,7 +1,7 @@ # # Default validators.txt # -# This file is located in the same folder as your rippled.cfg file +# This file is located in the same folder as your xrpld.cfg file # and defines which validators your server trusts not to collude. # # This file is UTF-8 with DOS, UNIX, or Mac style line endings. diff --git a/cfg/rippled-example.cfg b/cfg/xrpld-example.cfg similarity index 94% rename from cfg/rippled-example.cfg rename to cfg/xrpld-example.cfg index 5db008431d..b5180dce52 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/xrpld-example.cfg @@ -29,18 +29,18 @@ # # Purpose # -# This file documents and provides examples of all rippled server process -# configuration options. When the rippled server instance is launched, it +# This file documents and provides examples of all xrpld server process +# configuration options. When the xrpld server instance is launched, it # looks for a file with the following name: # -# rippled.cfg +# xrpld.cfg # -# For more information on where the rippled server instance searches for the +# For more information on where the xrpld server instance searches for the # file, visit: # # https://xrpl.org/commandline-usage.html#generic-options # -# This file should be named rippled.cfg. This file is UTF-8 with DOS, UNIX, +# This file should be named xrpld.cfg. This file is UTF-8 with DOS, UNIX, # or Mac style end of lines. Blank lines and lines beginning with '#' are # ignored. Undefined sections are reserved. No escapes are currently defined. # @@ -89,8 +89,8 @@ # # # -# rippled offers various server protocols to clients making inbound -# connections. The listening ports rippled uses are "universal" ports +# xrpld offers various server protocols to clients making inbound +# connections. The listening ports xrpld uses are "universal" ports # which may be configured to handshake in one or more of the available # supported protocols. These universal ports simplify administration: # A single open port can be used for multiple protocols. @@ -103,7 +103,7 @@ # # A list of port names and key/value pairs. A port name must start with a # letter and contain only letters and numbers. The name is not case-sensitive. -# For each name in this list, rippled will look for a configuration file +# For each name in this list, xrpld will look for a configuration file # section with the same name and use it to create a listening port. The # name is informational only; the choice of name does not affect the function # of the listening port. @@ -134,7 +134,7 @@ # ip = 127.0.0.1 # protocol = http # -# When rippled is used as a command line client (for example, issuing a +# When xrpld is used as a command line client (for example, issuing a # server stop command), the first port advertising the http or https # protocol will be used to make the connection. # @@ -175,7 +175,7 @@ # same time. It is possible have both Websockets and Secure Websockets # together in one port. # -# NOTE If no ports support the peer protocol, rippled cannot +# NOTE If no ports support the peer protocol, xrpld cannot # receive incoming peer connections or become a superpeer. # # limit = @@ -194,7 +194,7 @@ # required. IP address restrictions, if any, will be checked in addition # to the credentials specified here. # -# When acting in the client role, rippled will supply these credentials +# When acting in the client role, xrpld will supply these credentials # using HTTP's Basic Authentication headers when making outbound HTTP/S # requests. # @@ -237,7 +237,7 @@ # WS, or WSS protocol interfaces. If administrative commands are # disabled for a port, these credentials have no effect. # -# When acting in the client role, rippled will supply these credentials +# When acting in the client role, xrpld will supply these credentials # in the submitted JSON for any administrative command requests when # invoking JSON-RPC commands on remote servers. # @@ -258,7 +258,7 @@ # resource controls will default to those for non-administrative users. # # The secure_gateway IP addresses are intended to represent -# proxies. Since rippled trusts these hosts, they must be +# proxies. Since xrpld trusts these hosts, they must be # responsible for properly authenticating the remote user. # # If some IP addresses are included for both "admin" and @@ -272,7 +272,7 @@ # Use the specified files when configuring SSL on the port. # # NOTE If no files are specified and secure protocols are selected, -# rippled will generate an internal self-signed certificate. +# xrpld will generate an internal self-signed certificate. # # The files have these meanings: # @@ -297,12 +297,12 @@ # Control the ciphers which the server will support over SSL on the port, # specified using the OpenSSL "cipher list format". # -# NOTE If unspecified, rippled will automatically configure a modern +# NOTE If unspecified, xrpld will automatically configure a modern # cipher suite. This default suite should be widely supported. # # You should not modify this string unless you have a specific # reason and cryptographic expertise. Incorrect modification may -# keep rippled from connecting to other instances of rippled or +# keep xrpld from connecting to other instances of xrpld or # prevent RPC and WebSocket clients from connecting. # # send_queue_limit = [1..65535] @@ -382,7 +382,7 @@ #----------------- # # These settings control security and access attributes of the Peer to Peer -# server section of the rippled process. Peer Protocol implements the +# server section of the xrpld process. Peer Protocol implements the # Ripple Payment protocol. It is over peer connections that transactions # and validations are passed from to machine to machine, to determine the # contents of validated ledgers. @@ -396,7 +396,7 @@ # true - enables compression # false - disables compression [default]. # -# The rippled server can save bandwidth by compressing its peer-to-peer communications, +# The xrpld server can save bandwidth by compressing its peer-to-peer communications, # at a cost of greater CPU usage. If you enable link compression, # the server automatically compresses communications with peer servers # that also have link compression enabled. @@ -432,7 +432,7 @@ # # [ips_fixed] # -# List of IP addresses or hostnames to which rippled should always attempt to +# List of IP addresses or hostnames to which xrpld should always attempt to # maintain peer connections with. This is useful for manually forming private # networks, for example to configure a validation server that connects to the # Ripple network through a public-facing server, or for building a set @@ -573,7 +573,7 @@ # # minimum_txn_in_ledger_standalone = # -# Like minimum_txn_in_ledger when rippled is running in standalone +# Like minimum_txn_in_ledger when xrpld is running in standalone # mode. Default: 1000. # # target_txn_in_ledger = @@ -710,7 +710,7 @@ # # [validator_token] # -# This is an alternative to [validation_seed] that allows rippled to perform +# This is an alternative to [validation_seed] that allows xrpld to perform # validation without having to store the validator keys on the network # connected server. The field should contain a single token in the form of a # base64-encoded blob. @@ -745,7 +745,7 @@ # # Specify the file by its name or path. # Unless an absolute path is specified, it will be considered relative to -# the folder in which the rippled.cfg file is located. +# the folder in which the xrpld.cfg file is located. # # Examples: # /home/ripple/validators.txt @@ -840,7 +840,7 @@ # # 0: Disable the ledger replay feature [default] # 1: Enable the ledger replay feature. With this feature enabled, when -# acquiring a ledger from the network, a rippled node only downloads +# acquiring a ledger from the network, a xrpld node only downloads # the ledger header and the transactions instead of the whole ledger. # And the ledger is built by applying the transactions to the parent # ledger. @@ -851,7 +851,7 @@ # #---------------- # -# The rippled server instance uses HTTPS GET requests in a variety of +# The xrpld server instance uses HTTPS GET requests in a variety of # circumstances, including but not limited to contacting trusted domains to # fetch information such as mapping an email address to a Ripple Payment # Network address. @@ -891,7 +891,7 @@ # #------------ # -# rippled creates 4 SQLite database to hold bookkeeping information +# xrpld creates 4 SQLite database to hold bookkeeping information # about transactions, local credentials, and various other things. # It also creates the NodeDB, which holds all the objects that # make up the current and historical ledgers. @@ -902,7 +902,7 @@ # the performance of the server. # # Partial pathnames will be considered relative to the location of -# the rippled.cfg file. +# the xrpld.cfg file. # # [node_db] Settings for the Node Database (required) # @@ -920,11 +920,11 @@ # type = NuDB # # NuDB is a high-performance database written by Ripple Labs and optimized -# for rippled and solid-state drives. +# for xrpld and solid-state drives. # # NuDB maintains its high speed regardless of the amount of history # stored. Online delete may be selected, but is not required. NuDB is -# available on all platforms that rippled runs on. +# available on all platforms that xrpld runs on. # # type = RocksDB # @@ -1049,7 +1049,7 @@ # # recovery_wait_seconds # The online delete process checks periodically -# that rippled is still in sync with the network, +# that xrpld is still in sync with the network, # and that the validated ledger is less than # 'age_threshold_seconds' old. If not, then continue # sleeping for this number of seconds and @@ -1069,8 +1069,8 @@ # The server creates and maintains 4 to 5 bookkeeping SQLite databases in # the 'database_path' location. If you omit this configuration setting, # the server creates a directory called "db" located in the same place as -# your rippled.cfg file. -# Partial pathnames are relative to the location of the rippled executable. +# your xrpld.cfg file. +# Partial pathnames are relative to the location of the xrpld executable. # # [sqlite] Tuning settings for the SQLite databases (optional) # @@ -1120,7 +1120,7 @@ # The default is "wal", which uses a write-ahead # log to implement database transactions. # Alternately, "memory" saves disk I/O, but if -# rippled crashes during a transaction, the +# xrpld crashes during a transaction, the # database is likely to be corrupted. # See https://www.sqlite.org/pragma.html#pragma_journal_mode # for more details about the available options. @@ -1130,7 +1130,7 @@ # synchronous Valid values: off, normal, full, extra # The default is "normal", which works well with # the "wal" journal mode. Alternatively, "off" -# allows rippled to continue as soon as data is +# allows xrpld to continue as soon as data is # passed to the OS, which can significantly # increase speed, but risks data corruption if # the host computer crashes before writing that @@ -1144,7 +1144,7 @@ # The default is "file", which will use files # for temporary database tables and indices. # Alternatively, "memory" may save I/O, but -# rippled does not currently use many, if any, +# xrpld does not currently use many, if any, # of these temporary objects. # See https://www.sqlite.org/pragma.html#pragma_temp_store # for more details about the available options. @@ -1173,7 +1173,7 @@ # # These settings are designed to help server administrators diagnose # problems, and obtain detailed information about the activities being -# performed by the rippled process. +# performed by the xrpld process. # # # @@ -1190,7 +1190,7 @@ # # Configuration parameters for the Beast. Insight stats collection module. # -# Insight is a module that collects information from the areas of rippled +# Insight is a module that collects information from the areas of xrpld # that have instrumentation. The configuration parameters control where the # collection metrics are sent. The parameters are expressed as key = value # pairs with no white space. The main parameter is the choice of server: @@ -1199,7 +1199,7 @@ # # Choice of server to send metrics to. Currently the only choice is # "statsd" which sends UDP packets to a StatsD daemon, which must be -# running while rippled is running. More information on StatsD is +# running while xrpld is running. More information on StatsD is # available here: # https://github.com/b/statsd_spec # @@ -1209,7 +1209,7 @@ # in the format, n.n.n.n:port. # # "prefix" A string prepended to each collected metric. This is used -# to distinguish between different running instances of rippled. +# to distinguish between different running instances of xrpld. # # If this section is missing, or the server type is unspecified or unknown, # statistics are not collected or reported. @@ -1236,7 +1236,7 @@ # # Example: # [perf] -# perf_log=/var/log/rippled/perf.log +# perf_log=/var/log/xrpld/perf.log # log_interval=2 # #------------------------------------------------------------------------------- @@ -1246,7 +1246,7 @@ #---------- # # The vote settings configure settings for the entire Ripple network. -# While a single instance of rippled cannot unilaterally enforce network-wide +# While a single instance of xrpld cannot unilaterally enforce network-wide # settings, these choices become part of the instance's vote during the # consensus process for each voting ledger. # @@ -1260,7 +1260,7 @@ # The reference transaction is the simplest form of transaction. # It represents an XRP payment between two parties. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xrpld will use an internal # default. Don't change this without understanding the consequences. # # Example: @@ -1272,7 +1272,7 @@ # account's XRP balance that is at or below the reserve may only be # spent on transaction fees, and not transferred out of the account. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xrpld will use an internal # default. Don't change this without understanding the consequences. # # Example: @@ -1284,7 +1284,7 @@ # each ledger item owned by the account. Ledger items an account may # own include trust lines, open orders, and tickets. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xrpld will use an internal # default. Don't change this without understanding the consequences. # # Example: @@ -1326,7 +1326,7 @@ # tool instead. # # This flag has no effect on the "sign" and "sign_for" command line options -# that rippled makes available. +# that xrpld makes available. # # The default value of this field is "false" # @@ -1405,7 +1405,7 @@ #-------------------- # # Administrators can use these values as a starting point for configuring -# their instance of rippled, but each value should be checked to make sure +# their instance of xrpld, but each value should be checked to make sure # it meets the business requirements for the organization. # # Server @@ -1415,7 +1415,7 @@ # "peer" # # Peer protocol open to everyone. This is required to accept -# incoming rippled connections. This does not affect automatic +# incoming xrpld connections. This does not affect automatic # or manual outgoing Peer protocol connections. # # "rpc" @@ -1432,7 +1432,7 @@ # # ETL commands for Clio. We recommend setting secure_gateway # in this section to a comma-separated list of the addresses -# of your Clio servers, in order to bypass rippled's rate limiting. +# of your Clio servers, in order to bypass xrpld's rate limiting. # # This port is commented out but can be enabled by removing # the '#' from each corresponding line including the entry under [server] @@ -1449,8 +1449,8 @@ # NOTE # # To accept connections on well known ports such as 80 (HTTP) or -# 443 (HTTPS), most operating systems will require rippled to -# run with administrator privileges, or else rippled will not start. +# 443 (HTTPS), most operating systems will require xrpld to +# run with administrator privileges, or else xrpld will not start. [server] port_rpc_admin_local @@ -1496,7 +1496,7 @@ secure_gateway = 127.0.0.1 #------------------------------------------------------------------------------- -# This is primary persistent datastore for rippled. This includes transaction +# This is primary persistent datastore for xrpld. This includes transaction # metadata, account states, and ledger headers. Helpful information can be # found at https://xrpl.org/capacity-planning.html#node-db-type # type=NuDB is recommended for non-validators with fast SSDs. Validators or @@ -1511,19 +1511,19 @@ secure_gateway = 127.0.0.1 # deletion. [node_db] type=NuDB -path=/var/lib/rippled/db/nudb +path=/var/lib/xrpld/db/nudb nudb_block_size=4096 online_delete=512 advisory_delete=0 [database_path] -/var/lib/rippled/db +/var/lib/xrpld/db # This needs to be an absolute directory reference, not a relative one. # Modify this value as required. [debug_logfile] -/var/log/rippled/debug.log +/var/log/xrpld/debug.log # To use the XRP test network # (see https://xrpl.org/connect-your-rippled-to-the-xrp-test-net.html), @@ -1533,7 +1533,7 @@ advisory_delete=0 # File containing trusted validator keys or validator list publishers. # Unless an absolute path is specified, it will be considered relative to the -# folder in which the rippled.cfg file is located. +# folder in which the xrpld.cfg file is located. [validators_file] validators.txt diff --git a/cmake/Ccache.cmake b/cmake/Ccache.cmake new file mode 100644 index 0000000000..092212075c --- /dev/null +++ b/cmake/Ccache.cmake @@ -0,0 +1,51 @@ +find_program(CCACHE_PATH "ccache") +if (NOT CCACHE_PATH) + return() +endif () + +# For Linux and macOS we can use the ccache binary directly. +if (NOT MSVC) + set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PATH}") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}") + message(STATUS "Found ccache: ${CCACHE_PATH}") + return() +endif () + +# For Windows more effort is required. The code below is a modified version of +# https://github.com/ccache/ccache/wiki/MS-Visual-Studio#usage-with-cmake. +if ("${CCACHE_PATH}" MATCHES "chocolatey") + message(DEBUG "Ccache path: ${CCACHE_PATH}") + # Chocolatey uses a shim executable that we cannot use directly, in which + # case we have to find the executable it points to. If we cannot find the + # target executable then we cannot use ccache. + find_program(BASH_PATH "bash") + if (NOT BASH_PATH) + message(WARNING "Could not find bash.") + return() + endif () + + execute_process( + COMMAND bash -c "export LC_ALL='en_US.UTF-8'; ${CCACHE_PATH} --shimgen-noop | grep -oP 'path to executable: \\K.+' | head -c -1" + OUTPUT_VARIABLE CCACHE_PATH) + + if (NOT CCACHE_PATH) + message(WARNING "Could not find ccache target.") + return() + endif () + file(TO_CMAKE_PATH "${CCACHE_PATH}" CCACHE_PATH) +endif () +message(STATUS "Found ccache: ${CCACHE_PATH}") + +# Tell cmake to use ccache for compiling with Visual Studio. +file(COPY_FILE + ${CCACHE_PATH} ${CMAKE_BINARY_DIR}/cl.exe + ONLY_IF_DIFFERENT) +set(CMAKE_VS_GLOBALS + "CLToolExe=cl.exe" + "CLToolPath=${CMAKE_BINARY_DIR}" + "TrackFileAccess=false" + "UseMultiToolTask=true") + +# By default Visual Studio generators will use /Zi, which is not compatible with +# ccache, so tell it to use /Z7 instead. +set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>") diff --git a/cmake/XrplInstall.cmake b/cmake/XrplInstall.cmake index 67aca8f048..310436998d 100644 --- a/cmake/XrplInstall.cmake +++ b/cmake/XrplInstall.cmake @@ -62,7 +62,7 @@ if (is_root_project AND TARGET xrpld) message (\"-- Skipping : \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/\${DEST}/\${NEWNAME}\") endif () endmacro() - copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/rippled-example.cfg\" etc rippled.cfg) + copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg\" etc xrpld.cfg) copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt\" etc validators.txt) ") install(CODE " diff --git a/conanfile.py b/conanfile.py index 8dc09da0af..48e28cb275 100644 --- a/conanfile.py +++ b/conanfile.py @@ -182,12 +182,10 @@ class Xrpl(ConanFile): libxrpl.libs = [ "xrpl", "xrpl.libpb", - "ed25519", - "secp256k1", ] # TODO: Fix the protobufs to include each other relative to - # `include/`, not `include/ripple/proto/`. - libxrpl.includedirs = ["include", "include/ripple/proto"] + # `include/`, not `include/xrpl/proto/`. + libxrpl.includedirs = ["include", "include/xrpl/proto"] libxrpl.requires = [ "boost::headers", "boost::chrono", diff --git a/include/xrpl/core/PerfLog.h b/include/xrpl/core/PerfLog.h index eb009de433..f8ca779963 100644 --- a/include/xrpl/core/PerfLog.h +++ b/include/xrpl/core/PerfLog.h @@ -40,7 +40,7 @@ public: using microseconds = std::chrono::microseconds; /** - * Configuration from [perf] section of rippled.cfg. + * Configuration from [perf] section of xrpld.cfg. */ struct Setup { diff --git a/include/xrpl/nodestore/README.md b/include/xrpl/nodestore/README.md index a5d1128d17..83da29d9ce 100644 --- a/include/xrpl/nodestore/README.md +++ b/include/xrpl/nodestore/README.md @@ -130,7 +130,7 @@ newer versions of RocksDB (TBD). ## Discussion RocksDBQuickFactory is intended to provide a testbed for comparing potential -rocksdb performance with the existing recommended configuration in rippled.cfg. +rocksdb performance with the existing recommended configuration in xrpld.cfg. Through various executions and profiling some conclusions are presented below. - If the write ahead log is enabled, insert speed soon clogs up under load. The diff --git a/src/libxrpl/nodestore/ManagerImp.cpp b/src/libxrpl/nodestore/ManagerImp.cpp index b53d03e668..58accd4108 100644 --- a/src/libxrpl/nodestore/ManagerImp.cpp +++ b/src/libxrpl/nodestore/ManagerImp.cpp @@ -18,8 +18,8 @@ void ManagerImp::missing_backend() { Throw( - "Your rippled.cfg is missing a [node_db] entry, " - "please see the rippled-example.cfg file!"); + "Your xrpld.cfg is missing a [node_db] entry, " + "please see the xrpld-example.cfg file!"); } // We shouldn't rely on global variables for lifetime management because their diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 86f09e6c02..139e8702eb 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -18,7 +19,7 @@ namespace detail { std::string configContents(std::string const& dbPath, std::string const& validatorsFile) { - static boost::format configContentsTemplate(R"rippleConfig( + static boost::format configContentsTemplate(R"xrpldConfig( [server] port_rpc port_peer @@ -51,14 +52,14 @@ protocol = wss [node_size] medium -# This is primary persistent datastore for rippled. This includes transaction +# This is primary persistent datastore for xrpld. This includes transaction # metadata, account states, and ledger headers. Helpful information can be # found on https://xrpl.org/capacity-planning.html#node-db-type # delete old ledgers while maintaining at least 2000. Do not require an # external administrative command to initiate deletion. [node_db] type=memory -path=/Users/dummy/ripple/config/db/rocksdb +path=/Users/dummy/xrpld/config/db/rocksdb open_files=2000 filter_bits=12 cache_mb=256 @@ -72,7 +73,7 @@ file_size_mult=2 # This needs to be an absolute directory reference, not a relative one. # Modify this value as required. [debug_logfile] -/Users/dummy/ripple/config/log/debug.log +/Users/dummy/xrpld/config/log/debug.log [sntp_servers] time.windows.com @@ -97,7 +98,7 @@ r.ripple.com 51235 [sqdb] backend=sqlite -)rippleConfig"); +)xrpldConfig"); std::string dbPathSection = dbPath.empty() ? "" : "[database_path]\n" + dbPath; @@ -107,9 +108,9 @@ backend=sqlite } /** - Write a rippled config file and remove when done. + Write a xrpld config file and remove when done. */ -class RippledCfgGuard : public xrpl::detail::FileDirGuard +class FileCfgGuard : public xrpl::detail::FileDirGuard { private: path dataDir_; @@ -119,17 +120,18 @@ private: Config config_; public: - RippledCfgGuard( + FileCfgGuard( beast::unit_test::suite& test, path subDir, path const& dbPath, + path const& configFile, path const& validatorsFile, bool useCounter = true, std::string confContents = "") : FileDirGuard( test, std::move(subDir), - path(Config::configFileName), + configFile, confContents.empty() ? configContents(dbPath.string(), validatorsFile.string()) : confContents, @@ -171,7 +173,7 @@ public: return fileExists(); } - ~RippledCfgGuard() + ~FileCfgGuard() { try { @@ -182,7 +184,7 @@ public: catch (std::exception& e) { // if we throw here, just let it die. - test_.log << "Error in ~RippledCfgGuard: " << e.what() << std::endl; + test_.log << "Error in ~FileCfgGuard: " << e.what() << std::endl; }; } }; @@ -190,7 +192,7 @@ public: std::string valFileContents() { - std::string configContents(R"rippleConfig( + std::string configContents(R"xrpldConfig( [validators] n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj @@ -204,8 +206,8 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8 nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz [validator_list_sites] -recommendedripplevalidators.com -moreripplevalidators.net +recommendedxrplvalidators.com +morexrplvalidators.net [validator_list_keys] 03E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D @@ -213,7 +215,7 @@ moreripplevalidators.net [validator_list_threshold] 2 -)rippleConfig"); +)xrpldConfig"); return configContents; } @@ -270,7 +272,7 @@ public: Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [server] port_rpc port_peer @@ -278,7 +280,7 @@ port_wss_admin [ssl_verify] 0 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); @@ -291,6 +293,126 @@ port_wss_admin BEAST_EXPECT(c.legacy("not_in_file") == "new_value"); } void + testConfigFile() + { + testcase("config_file"); + + using namespace boost::filesystem; + auto const cwd = current_path(); + + // Test both config file names. + char const* configFiles[] = { + Config::configFileName, Config::configLegacyName}; + + // Config file in current directory. + for (auto const& configFile : configFiles) + { + // Use a temporary directory for testing. + beast::temp_dir td; + current_path(td.path()); + path const f = td.file(configFile); + std::ofstream o(f.string()); + o << detail::configContents("", ""); + o.close(); + + // Load the config file from the current directory and verify it. + Config c; + c.setup("", true, false, true); + BEAST_EXPECT(c.section(SECTION_DEBUG_LOGFILE).values().size() == 1); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values()[0] == + "/Users/dummy/xrpld/config/log/debug.log"); + } + + // Config file in HOME or XDG_CONFIG_HOME directory. +#if BOOST_OS_LINUX || BOOST_OS_MACOS + for (auto const& configFile : configFiles) + { + // Point the current working directory to a temporary directory, so + // we don't pick up an actual config file from the repository root. + beast::temp_dir td; + current_path(td.path()); + + // The XDG config directory is set: the config file must be in a + // subdirectory named after the system. + { + beast::temp_dir tc; + + // Set the HOME and XDG_CONFIG_HOME environment variables. The + // HOME variable is not used when XDG_CONFIG_HOME is set, but + // must be set. + char const* h = getenv("HOME"); + setenv("HOME", tc.path().c_str(), 1); + char const* x = getenv("XDG_CONFIG_HOME"); + setenv("XDG_CONFIG_HOME", tc.path().c_str(), 1); + + // Create the config file in '${XDG_CONFIG_HOME}/[systemName]'. + path p = tc.file(systemName()); + create_directory(p); + p = tc.file(systemName() + "/" + configFile); + std::ofstream o(p.string()); + o << detail::configContents("", ""); + o.close(); + + // Load the config file from the config directory and verify it. + Config c; + c.setup("", true, false, true); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values().size() == 1); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values()[0] == + "/Users/dummy/xrpld/config/log/debug.log"); + + // Restore the environment variables. + h ? setenv("HOME", h, 1) : unsetenv("HOME"); + x ? setenv("XDG_CONFIG_HOME", x, 1) + : unsetenv("XDG_CONFIG_HOME"); + } + + // The XDG config directory is not set: the config file must be in a + // subdirectory named .config followed by the system name. + { + beast::temp_dir tc; + + // Set only the HOME environment variable. + char const* h = getenv("HOME"); + setenv("HOME", tc.path().c_str(), 1); + char const* x = getenv("XDG_CONFIG_HOME"); + unsetenv("XDG_CONFIG_HOME"); + + // Create the config file in '${HOME}/.config/[systemName]'. + std::string s = ".config"; + path p = tc.file(s); + create_directory(p); + s += "/" + systemName(); + p = tc.file(s); + create_directory(p); + p = tc.file(s + "/" + configFile); + std::ofstream o(p.string()); + o << detail::configContents("", ""); + o.close(); + + // Load the config file from the config directory and verify it. + Config c; + c.setup("", true, false, true); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values().size() == 1); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values()[0] == + "/Users/dummy/xrpld/config/log/debug.log"); + + // Restore the environment variables. + h ? setenv("HOME", h, 1) : unsetenv("HOME"); + if (x) + setenv("XDG_CONFIG_HOME", x, 1); + } + } +#endif + + // Restore the current working directory. + current_path(cwd); + } + void testDbPath() { testcase("database_path"); @@ -326,11 +448,16 @@ port_wss_admin { // read from file absolute path auto const cwd = current_path(); - xrpl::detail::DirGuard const g0(*this, "test_db"); + detail::DirGuard const g0(*this, "test_db"); path const dataDirRel("test_data_dir"); path const dataDirAbs(cwd / g0.subdir() / dataDirRel); - detail::RippledCfgGuard const g( - *this, g0.subdir(), dataDirAbs, "", false); + detail::FileCfgGuard const g( + *this, + g0.subdir(), + dataDirAbs, + Config::configFileName, + "", + false); auto const& c(g.config()); BEAST_EXPECT(g.dataDirExists()); BEAST_EXPECT(g.configFileExists()); @@ -339,7 +466,8 @@ port_wss_admin { // read from file relative path std::string const dbPath("my_db"); - detail::RippledCfgGuard const g(*this, "test_db", dbPath, ""); + detail::FileCfgGuard const g( + *this, "test_db", dbPath, Config::configFileName, ""); auto const& c(g.config()); std::string const nativeDbPath = absolute(path(dbPath)).string(); BEAST_EXPECT(g.dataDirExists()); @@ -348,7 +476,8 @@ port_wss_admin } { // read from file no path - detail::RippledCfgGuard const g(*this, "test_db", "", ""); + detail::FileCfgGuard const g( + *this, "test_db", "", Config::configFileName, ""); auto const& c(g.config()); std::string const nativeDbPath = absolute(g.subdir() / path(Config::databaseDirName)).string(); @@ -378,13 +507,13 @@ port_wss_admin { Config c; - static boost::format configTemplate(R"rippleConfig( + static boost::format configTemplate(R"xrpldConfig( [validation_seed] %1% [validator_token] %2% -)rippleConfig"); +)xrpldConfig"); std::string error; auto const expectedError = "Cannot have both [validation_seed] " @@ -410,10 +539,10 @@ port_wss_admin Config c; try { - c.loadFromString(R"rippleConfig( + c.loadFromString(R"xrpldConfig( [network_id] main -)rippleConfig"); +)xrpldConfig"); } catch (std::runtime_error& e) { @@ -425,8 +554,8 @@ main try { - c.loadFromString(R"rippleConfig( -)rippleConfig"); + c.loadFromString(R"xrpldConfig( +)xrpldConfig"); } catch (std::runtime_error& e) { @@ -438,10 +567,10 @@ main try { - c.loadFromString(R"rippleConfig( + c.loadFromString(R"xrpldConfig( [network_id] 255 -)rippleConfig"); +)xrpldConfig"); } catch (std::runtime_error& e) { @@ -453,10 +582,10 @@ main try { - c.loadFromString(R"rippleConfig( + c.loadFromString(R"xrpldConfig( [network_id] 10000 -)rippleConfig"); +)xrpldConfig"); } catch (std::runtime_error& e) { @@ -516,7 +645,7 @@ main { // load validators from config into single section Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validators] n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj @@ -525,7 +654,7 @@ n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C [validator_keys] nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5 nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT(c.legacy("validators_file").empty()); BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 5); @@ -534,9 +663,9 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8 { // load validator list sites and keys from config Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -544,13 +673,13 @@ trustthesevalidators.gov [validator_list_threshold] 1 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] == - "ripplevalidators.com"); + "xrplvalidators.com"); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] == "trustthesevalidators.gov"); @@ -570,9 +699,9 @@ trustthesevalidators.gov { // load validator list sites and keys from config Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -580,13 +709,13 @@ trustthesevalidators.gov [validator_list_threshold] 0 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] == - "ripplevalidators.com"); + "xrplvalidators.com"); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] == "trustthesevalidators.gov"); @@ -607,9 +736,9 @@ trustthesevalidators.gov // load should throw if [validator_list_threshold] is greater than // the number of [validator_list_keys] Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -617,7 +746,7 @@ trustthesevalidators.gov [validator_list_threshold] 2 -)rippleConfig"); +)xrpldConfig"); std::string error; auto const expectedError = "Value in config section [validator_list_threshold] exceeds " @@ -636,9 +765,9 @@ trustthesevalidators.gov { // load should throw if [validator_list_threshold] is malformed Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -646,7 +775,7 @@ trustthesevalidators.gov [validator_list_threshold] value = 2 -)rippleConfig"); +)xrpldConfig"); std::string error; auto const expectedError = "Config section [validator_list_threshold] should contain " @@ -665,9 +794,9 @@ value = 2 { // load should throw if [validator_list_threshold] is negative Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -675,7 +804,7 @@ trustthesevalidators.gov [validator_list_threshold] -1 -)rippleConfig"); +)xrpldConfig"); bool error = false; try { @@ -692,11 +821,11 @@ trustthesevalidators.gov // load should throw if [validator_list_sites] is configured but // [validator_list_keys] is not Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov -)rippleConfig"); +)xrpldConfig"); std::string error; auto const expectedError = "[validator_list_keys] config section is missing"; @@ -736,8 +865,13 @@ trustthesevalidators.gov std::string const valFileName = "validators.txt"; detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", valFileName); - detail::RippledCfgGuard const rcg( - *this, vtg.subdir(), "", valFileName, false); + detail::FileCfgGuard const rcg( + *this, + vtg.subdir(), + "", + Config::configFileName, + valFileName, + false); BEAST_EXPECT(vtg.validatorsFileExists()); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); @@ -758,8 +892,13 @@ trustthesevalidators.gov detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", "validators.txt"); auto const valFilePath = ".." / vtg.subdir() / "validators.txt"; - detail::RippledCfgGuard const rcg( - *this, vtg.subdir(), "", valFilePath, false); + detail::FileCfgGuard const rcg( + *this, + vtg.subdir(), + "", + Config::configFileName, + valFilePath, + false); BEAST_EXPECT(vtg.validatorsFileExists()); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); @@ -778,8 +917,8 @@ trustthesevalidators.gov // load from validators file in default location detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", "validators.txt"); - detail::RippledCfgGuard const rcg( - *this, vtg.subdir(), "", "", false); + detail::FileCfgGuard const rcg( + *this, vtg.subdir(), "", Config::configFileName, "", false); BEAST_EXPECT(vtg.validatorsFileExists()); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); @@ -803,8 +942,13 @@ trustthesevalidators.gov detail::ValidatorsTxtGuard const vtgDefault( *this, vtg.subdir(), "validators.txt", false); BEAST_EXPECT(vtgDefault.validatorsFileExists()); - detail::RippledCfgGuard const rcg( - *this, vtg.subdir(), "", vtg.validatorsFile(), false); + detail::FileCfgGuard const rcg( + *this, + vtg.subdir(), + "", + Config::configFileName, + vtg.validatorsFile(), + false); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); BEAST_EXPECT(c.legacy("validators_file") == vtg.validatorsFile()); @@ -821,7 +965,7 @@ trustthesevalidators.gov { // load validators from both config and validators file - boost::format cc(R"rippleConfig( + boost::format cc(R"xrpldConfig( [validators_file] %1% @@ -837,12 +981,12 @@ nHB1X37qrniVugfQcuBTAjswphC1drx7QjFFojJPZwKHHnt8kU7v nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] 021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566 -)rippleConfig"); +)xrpldConfig"); detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", "validators.cfg"); BEAST_EXPECT(vtg.validatorsFileExists()); @@ -861,14 +1005,14 @@ trustthesevalidators.gov } { // load should throw if [validator_list_threshold] is present both - // in rippled cfg and validators file - boost::format cc(R"rippleConfig( + // in xrpld.cfg and validators file + boost::format cc(R"xrpldConfig( [validators_file] %1% [validator_list_threshold] 1 -)rippleConfig"); +)xrpldConfig"); std::string error; detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", "validators.cfg"); @@ -890,7 +1034,7 @@ trustthesevalidators.gov } { // load should throw if [validators], [validator_keys] and - // [validator_list_keys] are missing from rippled cfg and + // [validator_list_keys] are missing from xrpld.cfg and // validators file Config c; boost::format cc("[validators_file]\n%1%\n"); @@ -920,9 +1064,13 @@ trustthesevalidators.gov void testSetup(bool explicitPath) { - detail::RippledCfgGuard const cfg( - *this, "testSetup", explicitPath ? "test_db" : "", ""); - /* RippledCfgGuard has a Config object that gets loaded on + detail::FileCfgGuard const cfg( + *this, + "testSetup", + explicitPath ? "test_db" : "", + Config::configFileName, + ""); + /* FileCfgGuard has a Config object that gets loaded on construction, but Config::setup is not reentrant, so we need a fresh config for every test case, so ignore it. */ @@ -1039,7 +1187,8 @@ trustthesevalidators.gov void testPort() { - detail::RippledCfgGuard const cfg(*this, "testPort", "", ""); + detail::FileCfgGuard const cfg( + *this, "testPort", "", Config::configFileName, ""); auto const& conf = cfg.config(); if (!BEAST_EXPECT(conf.exists("port_rpc"))) return; @@ -1065,8 +1214,14 @@ trustthesevalidators.gov try { - detail::RippledCfgGuard const cfg( - *this, "testPort", "", "", true, contents); + detail::FileCfgGuard const cfg( + *this, + "testPort", + "", + Config::configFileName, + "", + true, + contents); BEAST_EXPECT(false); } catch (std::exception const& ex) @@ -1377,9 +1532,9 @@ r.ripple.com:51235 for (auto& [unit, sec, val, shouldPass] : units) { Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [amendment_majority_time] -)rippleConfig"); +)xrpldConfig"); toLoad += std::to_string(val) + space + unit; space = space == "" ? " " : ""; @@ -1480,6 +1635,7 @@ r.ripple.com:51235 run() override { testLegacy(); + testConfigFile(); testDbPath(); testValidatorKeys(); testValidatorsFile(); diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index ef5abdd800..551e67dc5e 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -30,6 +30,7 @@ enum class FieldType { CurrencyField, HashField, HashOrObjectField, + IssueField, ObjectField, StringField, TwoAccountArrayField, @@ -40,6 +41,8 @@ enum class FieldType { std::vector> mappings{ {jss::account, FieldType::AccountField}, {jss::accounts, FieldType::TwoAccountArrayField}, + {jss::asset, FieldType::IssueField}, + {jss::asset2, FieldType::IssueField}, {jss::authorize, FieldType::AccountField}, {jss::authorized, FieldType::AccountField}, {jss::credential_type, FieldType::BlobField}, @@ -74,24 +77,26 @@ getTypeName(FieldType typeID) { switch (typeID) { - case FieldType::UInt32Field: - return "number"; - case FieldType::UInt64Field: - return "number"; - case FieldType::HashField: - return "hex string"; case FieldType::AccountField: return "AccountID"; + case FieldType::ArrayField: + return "array"; case FieldType::BlobField: return "hex string"; case FieldType::CurrencyField: return "Currency"; - case FieldType::ArrayField: - return "array"; + case FieldType::HashField: + return "hex string"; case FieldType::HashOrObjectField: return "hex string or object"; + case FieldType::IssueField: + return "Issue"; case FieldType::TwoAccountArrayField: return "length-2 array of Accounts"; + case FieldType::UInt32Field: + return "number"; + case FieldType::UInt64Field: + return "number"; default: Throw( "unknown type " + std::to_string(static_cast(typeID))); @@ -192,34 +197,37 @@ class LedgerEntry_test : public beast::unit_test::suite return values; }; - static auto const& badUInt32Values = remove({2, 3}); - static auto const& badUInt64Values = remove({2, 3}); - static auto const& badHashValues = remove({2, 3, 7, 8, 16}); static auto const& badAccountValues = remove({12}); + static auto const& badArrayValues = remove({17, 20}); static auto const& badBlobValues = remove({3, 7, 8, 16}); static auto const& badCurrencyValues = remove({14}); - static auto const& badArrayValues = remove({17, 20}); + static auto const& badHashValues = remove({2, 3, 7, 8, 16}); static auto const& badIndexValues = remove({12, 16, 18, 19}); + static auto const& badUInt32Values = remove({2, 3}); + static auto const& badUInt64Values = remove({2, 3}); + static auto const& badIssueValues = remove({}); switch (fieldType) { - case FieldType::UInt32Field: - return badUInt32Values; - case FieldType::UInt64Field: - return badUInt64Values; - case FieldType::HashField: - return badHashValues; case FieldType::AccountField: return badAccountValues; + case FieldType::ArrayField: + case FieldType::TwoAccountArrayField: + return badArrayValues; case FieldType::BlobField: return badBlobValues; case FieldType::CurrencyField: return badCurrencyValues; - case FieldType::ArrayField: - case FieldType::TwoAccountArrayField: - return badArrayValues; + case FieldType::HashField: + return badHashValues; case FieldType::HashOrObjectField: return badIndexValues; + case FieldType::IssueField: + return badIssueValues; + case FieldType::UInt32Field: + return badUInt32Values; + case FieldType::UInt64Field: + return badUInt64Values; default: Throw( "unknown type " + @@ -236,30 +244,37 @@ class LedgerEntry_test : public beast::unit_test::suite arr[1u] = "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5"; return arr; }(); + static Json::Value const issueObject = []() { + Json::Value arr(Json::objectValue); + arr[jss::currency] = "XRP"; + return arr; + }(); auto const typeID = getFieldType(fieldName); switch (typeID) { - case FieldType::UInt32Field: - return 1; - case FieldType::UInt64Field: - return 1; - case FieldType::HashField: - return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6" - "B01403D6D"; case FieldType::AccountField: return "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5"; + case FieldType::ArrayField: + return Json::arrayValue; case FieldType::BlobField: return "ABCDEF"; case FieldType::CurrencyField: return "USD"; - case FieldType::ArrayField: - return Json::arrayValue; + case FieldType::HashField: + return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6" + "B01403D6D"; + case FieldType::IssueField: + return issueObject; case FieldType::HashOrObjectField: return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6" "B01403D6D"; case FieldType::TwoAccountArrayField: return twoAccountArray; + case FieldType::UInt32Field: + return 1; + case FieldType::UInt64Field: + return 1; default: Throw( "unknown type " + @@ -444,7 +459,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryInvalid() + testInvalid() { testcase("Invalid requests"); using namespace test::jtx; @@ -526,7 +541,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryAccountRoot() + testAccountRoot() { testcase("AccountRoot"); using namespace test::jtx; @@ -632,7 +647,147 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryCheck() + testAmendments() + { + testcase("Amendments"); + using namespace test::jtx; + Env env{*this}; + + // positive test + { + Keylet const keylet = keylet::amendments(); + + // easier to hack an object into the ledger than generate it + // legitimately + { + auto const amendments = [&](OpenView& view, + beast::Journal) -> bool { + auto const sle = std::make_shared(keylet); + + // Create Amendments vector (enabled amendments) + std::vector enabledAmendments; + enabledAmendments.push_back( + uint256::fromVoid("42426C4D4F1009EE67080A9B7965B44656D7" + "714D104A72F9B4369F97ABF044EE")); + enabledAmendments.push_back( + uint256::fromVoid("4C97EBA926031A7CF7D7B36FDE3ED66DDA54" + "21192D63DE53FFB46E43B9DC8373")); + enabledAmendments.push_back( + uint256::fromVoid("03BDC0099C4E14163ADA272C1B6F6FABB448" + "CC3E51F522F978041E4B57D9158C")); + enabledAmendments.push_back( + uint256::fromVoid("35291ADD2D79EB6991343BDA0912269C817D" + "0F094B02226C1C14AD2858962ED4")); + sle->setFieldV256( + sfAmendments, STVector256(enabledAmendments)); + + // Create Majorities array + STArray majorities; + + auto majority1 = STObject::makeInnerObject(sfMajority); + majority1.setFieldH256( + sfAmendment, + uint256::fromVoid("7BB62DC13EC72B775091E9C71BF8CF97E122" + "647693B50C5E87A80DFD6FCFAC50")); + majority1.setFieldU32(sfCloseTime, 779561310); + majorities.push_back(std::move(majority1)); + + auto majority2 = STObject::makeInnerObject(sfMajority); + majority2.setFieldH256( + sfAmendment, + uint256::fromVoid("755C971C29971C9F20C6F080F2ED96F87884" + "E40AD19554A5EBECDCEC8A1F77FE")); + majority2.setFieldU32(sfCloseTime, 779561310); + majorities.push_back(std::move(majority2)); + + sle->setFieldArray(sfMajorities, majorities); + + view.rawInsert(sle); + return true; + }; + env.app().openLedger().modify(amendments); + } + + Json::Value jvParams; + jvParams[jss::amendments] = to_string(keylet.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Amendments); + } + + // negative tests + runLedgerEntryTest(env, jss::amendments); + } + + void + testAMM() + { + testcase("AMM"); + using namespace test::jtx; + Env env{*this}; + + // positive test + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + AMM amm(env, alice, XRP(10), alice["USD"](1000)); + env.close(); + + { + Json::Value jvParams; + jvParams[jss::amm] = to_string(amm.ammID()); + auto const result = + env.rpc("json", "ledger_entry", to_string(jvParams)); + BEAST_EXPECT( + result.isObject() && result.isMember(jss::result) && + !result[jss::result].isMember(jss::error) && + result[jss::result].isMember(jss::node) && + result[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + result[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::AMM); + } + + { + Json::Value jvParams; + Json::Value ammParams(Json::objectValue); + { + Json::Value obj(Json::objectValue); + obj[jss::currency] = "XRP"; + ammParams[jss::asset] = obj; + } + { + Json::Value obj(Json::objectValue); + obj[jss::currency] = "USD"; + obj[jss::issuer] = alice.human(); + ammParams[jss::asset2] = obj; + } + jvParams[jss::amm] = ammParams; + auto const result = + env.rpc("json", "ledger_entry", to_string(jvParams)); + BEAST_EXPECT( + result.isObject() && result.isMember(jss::result) && + !result[jss::result].isMember(jss::error) && + result[jss::result].isMember(jss::node) && + result[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + result[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::AMM); + } + + // negative tests + runLedgerEntryTest( + env, + jss::amm, + { + {jss::asset, "malformedRequest"}, + {jss::asset2, "malformedRequest"}, + }); + } + + void + testCheck() { testcase("Check"); using namespace test::jtx; @@ -684,7 +839,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryCredentials() + testCredentials() { testcase("Credentials"); @@ -752,7 +907,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDelegate() + testDelegate() { testcase("Delegate"); @@ -807,7 +962,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDepositPreauth() + testDepositPreauth() { testcase("Deposit Preauth"); @@ -868,7 +1023,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDepositPreauthCred() + testDepositPreauthCred() { testcase("Deposit Preauth with credentials"); @@ -1149,7 +1304,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDirectory() + testDirectory() { testcase("Directory"); using namespace test::jtx; @@ -1303,7 +1458,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryEscrow() + testEscrow() { testcase("Escrow"); using namespace test::jtx; @@ -1365,7 +1520,177 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryOffer() + testFeeSettings() + { + testcase("Fee Settings"); + using namespace test::jtx; + Env env{*this}; + + // positive test + { + Keylet const keylet = keylet::fees(); + Json::Value jvParams; + jvParams[jss::fee] = to_string(keylet.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::FeeSettings); + } + + // negative tests + runLedgerEntryTest(env, jss::fee); + } + + void + testLedgerHashes() + { + testcase("Ledger Hashes"); + using namespace test::jtx; + Env env{*this}; + + // positive test + { + Keylet const keylet = keylet::skip(); + Json::Value jvParams; + jvParams[jss::hashes] = to_string(keylet.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == + jss::LedgerHashes); + } + + // negative tests + runLedgerEntryTest(env, jss::hashes); + } + + void + testNFTokenOffer() + { + testcase("NFT Offer"); + using namespace test::jtx; + Env env{*this}; + + // positive test + Account const issuer{"issuer"}; + Account const buyer{"buyer"}; + env.fund(XRP(1000), issuer, buyer); + + uint256 const nftokenID0 = + token::getNextID(env, issuer, 0, tfTransferable); + env(token::mint(issuer, 0), txflags(tfTransferable)); + env.close(); + uint256 const offerID = keylet::nftoffer(issuer, env.seq(issuer)).key; + env(token::createOffer(issuer, nftokenID0, drops(1)), + token::destination(buyer), + txflags(tfSellNFToken)); + + { + Json::Value jvParams; + jvParams[jss::nft_offer] = to_string(offerID); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == + jss::NFTokenOffer); + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == issuer.human()); + BEAST_EXPECT( + jrr[jss::node][sfNFTokenID.jsonName] == to_string(nftokenID0)); + BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "1"); + } + + // negative tests + runLedgerEntryTest(env, jss::nft_offer); + } + + void + testNFTokenPage() + { + testcase("NFT Page"); + using namespace test::jtx; + Env env{*this}; + + // positive test + Account const issuer{"issuer"}; + env.fund(XRP(1000), issuer); + + env(token::mint(issuer, 0), txflags(tfTransferable)); + env.close(); + + auto const nftpage = keylet::nftpage_max(issuer); + BEAST_EXPECT(env.le(nftpage) != nullptr); + + { + Json::Value jvParams; + jvParams[jss::nft_page] = to_string(nftpage.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NFTokenPage); + } + + // negative tests + runLedgerEntryTest(env, jss::nft_page); + } + + void + testNegativeUNL() + { + testcase("Negative UNL"); + using namespace test::jtx; + Env env{*this}; + + // positive test + { + Keylet const keylet = keylet::negativeUNL(); + + // easier to hack an object into the ledger than generate it + // legitimately + { + auto const nUNL = [&](OpenView& view, beast::Journal) -> bool { + auto const sle = std::make_shared(keylet); + + // Create DisabledValidators array + STArray disabledValidators; + auto disabledValidator = + STObject::makeInnerObject(sfDisabledValidator); + auto pubKeyBlob = strUnHex( + "ED58F6770DB5DD77E59D28CB650EC3816E2FC95021BB56E720C9A1" + "2DA79C58A3AB"); + disabledValidator.setFieldVL(sfPublicKey, *pubKeyBlob); + disabledValidator.setFieldU32( + sfFirstLedgerSequence, 91371264); + disabledValidators.push_back(std::move(disabledValidator)); + + sle->setFieldArray( + sfDisabledValidators, disabledValidators); + sle->setFieldH256( + sfPreviousTxnID, + uint256::fromVoid("8D47FFE664BE6C335108DF689537625855A6" + "A95160CC6D351341B9" + "2624D9C5E3")); + sle->setFieldU32(sfPreviousTxnLgrSeq, 91442944); + + view.rawInsert(sle); + return true; + }; + env.app().openLedger().modify(nUNL); + } + + Json::Value jvParams; + jvParams[jss::nunl] = to_string(keylet.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NegativeUNL); + } + + // negative tests + runLedgerEntryTest(env, jss::nunl); + } + + void + testOffer() { testcase("Offer"); using namespace test::jtx; @@ -1413,7 +1738,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryPayChan() + testPayChan() { testcase("Pay Chan"); using namespace test::jtx; @@ -1475,7 +1800,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryRippleState() + testRippleState() { testcase("RippleState"); using namespace test::jtx; @@ -1626,7 +1951,16 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryTicket() + testSignerList() + { + testcase("Signer List"); + using namespace test::jtx; + Env env{*this}; + runLedgerEntryTest(env, jss::signer_list); + } + + void + testTicket() { testcase("Ticket"); using namespace test::jtx; @@ -1711,7 +2045,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDID() + testDID() { testcase("DID"); using namespace test::jtx; @@ -1848,7 +2182,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryMPT() + testMPT() { testcase("MPT"); using namespace test::jtx; @@ -1931,7 +2265,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryPermissionedDomain() + testPermissionedDomain() { testcase("PermissionedDomain"); @@ -2010,7 +2344,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryCLI() + testCLI() { testcase("command-line"); using namespace test::jtx; @@ -2040,25 +2374,33 @@ public: void run() override { - testLedgerEntryInvalid(); - testLedgerEntryAccountRoot(); - testLedgerEntryCheck(); - testLedgerEntryCredentials(); - testLedgerEntryDelegate(); - testLedgerEntryDepositPreauth(); - testLedgerEntryDepositPreauthCred(); - testLedgerEntryDirectory(); - testLedgerEntryEscrow(); - testLedgerEntryOffer(); - testLedgerEntryPayChan(); - testLedgerEntryRippleState(); - testLedgerEntryTicket(); - testLedgerEntryDID(); + testInvalid(); + testAccountRoot(); + testAmendments(); + testAMM(); + testCheck(); + testCredentials(); + testDelegate(); + testDepositPreauth(); + testDepositPreauthCred(); + testDirectory(); + testEscrow(); + testFeeSettings(); + testLedgerHashes(); + testNFTokenOffer(); + testNFTokenPage(); + testNegativeUNL(); + testOffer(); + testPayChan(); + testRippleState(); + testSignerList(); + testTicket(); + testDID(); testInvalidOracleLedgerEntry(); testOracleLedgerEntry(); - testLedgerEntryMPT(); - testLedgerEntryPermissionedDomain(); - testLedgerEntryCLI(); + testMPT(); + testPermissionedDomain(); + testCLI(); } }; @@ -2086,7 +2428,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite, } void - testLedgerEntryBridge() + testBridge() { testcase("ledger_entry: bridge"); using namespace test::jtx; @@ -2177,7 +2519,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite, } void - testLedgerEntryClaimID() + testClaimID() { testcase("ledger_entry: xchain_claim_id"); using namespace test::jtx; @@ -2235,7 +2577,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite, } void - testLedgerEntryCreateAccountClaimID() + testCreateAccountClaimID() { testcase("ledger_entry: xchain_create_account_claim_id"); using namespace test::jtx; @@ -2362,9 +2704,9 @@ public: void run() override { - testLedgerEntryBridge(); - testLedgerEntryClaimID(); - testLedgerEntryCreateAccountClaimID(); + testBridge(); + testClaimID(); + testCreateAccountClaimID(); } }; diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index e5a7435b0a..aed2343a49 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -87,7 +87,7 @@ private: /// If the node is out of sync during an online_delete healthWait() /// call, sleep the thread for this time, and continue checking until /// recovery. - /// See also: "recovery_wait_seconds" in rippled-example.cfg + /// See also: "recovery_wait_seconds" in xrpld-example.cfg std::chrono::seconds recoveryWaitTime_{5}; // these do not exist upon SHAMapStore creation, but do exist diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index cfd260787c..e25148aaa9 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -68,6 +68,7 @@ class Config : public BasicConfig public: // Settings related to the configuration file location and directories static char const* const configFileName; + static char const* const configLegacyName; static char const* const databaseDirName; static char const* const validatorsFileName; diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index e6c61e1471..1eb8d5984e 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -221,11 +221,12 @@ getSingleSection( //------------------------------------------------------------------------------ // -// Config (DEPRECATED) +// Config // //------------------------------------------------------------------------------ -char const* const Config::configFileName = "rippled.cfg"; +char const* const Config::configFileName = "xrpld.cfg"; +char const* const Config::configLegacyName = "rippled.cfg"; char const* const Config::databaseDirName = "db"; char const* const Config::validatorsFileName = "validators.txt"; @@ -295,76 +296,78 @@ Config::setup( bool bSilent, bool bStandalone) { - boost::filesystem::path dataDir; - std::string strDbPath, strConfFile; + setupControl(bQuiet, bSilent, bStandalone); // Determine the config and data directories. // If the config file is found in the current working // directory, use the current working directory as the // config directory and that with "db" as the data // directory. - - setupControl(bQuiet, bSilent, bStandalone); - - strDbPath = databaseDirName; - - if (!strConf.empty()) - strConfFile = strConf; - else - strConfFile = configFileName; + boost::filesystem::path dataDir; if (!strConf.empty()) { // --conf= : everything is relative that file. - CONFIG_FILE = strConfFile; + CONFIG_FILE = strConf; CONFIG_DIR = boost::filesystem::absolute(CONFIG_FILE); CONFIG_DIR.remove_filename(); - dataDir = CONFIG_DIR / strDbPath; + dataDir = CONFIG_DIR / databaseDirName; } else { - CONFIG_DIR = boost::filesystem::current_path(); - CONFIG_FILE = CONFIG_DIR / strConfFile; - dataDir = CONFIG_DIR / strDbPath; - - // Construct XDG config and data home. - // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html - auto const strHome = getEnvVar("HOME"); - auto strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME"); - auto strXdgDataHome = getEnvVar("XDG_DATA_HOME"); - - if (boost::filesystem::exists(CONFIG_FILE) - // Can we figure out XDG dirs? - || (strHome.empty() && - (strXdgConfigHome.empty() || strXdgDataHome.empty()))) + do { - // Current working directory is fine, put dbs in a subdir. - } - else - { - if (strXdgConfigHome.empty()) + // Check if either of the config files exist in the current working + // directory, in which case the databases will be stored in a + // subdirectory. + CONFIG_DIR = boost::filesystem::current_path(); + dataDir = CONFIG_DIR / databaseDirName; + CONFIG_FILE = CONFIG_DIR / configFileName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; + CONFIG_FILE = CONFIG_DIR / configLegacyName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; + + // Check if the home directory is set, and optionally the XDG config + // and/or data directories, as the config may be there. See + // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html. + auto const strHome = getEnvVar("HOME"); + if (!strHome.empty()) { - // $XDG_CONFIG_HOME was not set, use default based on $HOME. - strXdgConfigHome = strHome + "/.config"; + auto strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME"); + auto strXdgDataHome = getEnvVar("XDG_DATA_HOME"); + if (strXdgConfigHome.empty()) + { + // $XDG_CONFIG_HOME was not set, use default based on $HOME. + strXdgConfigHome = strHome + "/.config"; + } + if (strXdgDataHome.empty()) + { + // $XDG_DATA_HOME was not set, use default based on $HOME. + strXdgDataHome = strHome + "/.local/share"; + } + + // Check if either of the config files exist in the XDG config + // dir. + dataDir = strXdgDataHome + "/" + systemName(); + CONFIG_DIR = strXdgConfigHome + "/" + systemName(); + CONFIG_FILE = CONFIG_DIR / configFileName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; + CONFIG_FILE = CONFIG_DIR / configLegacyName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; } - if (strXdgDataHome.empty()) - { - // $XDG_DATA_HOME was not set, use default based on $HOME. - strXdgDataHome = strHome + "/.local/share"; - } - - CONFIG_DIR = strXdgConfigHome + "/" + systemName(); - CONFIG_FILE = CONFIG_DIR / strConfFile; - dataDir = strXdgDataHome + "/" + systemName(); - - if (!boost::filesystem::exists(CONFIG_FILE)) - { - CONFIG_DIR = "/etc/opt/" + systemName(); - CONFIG_FILE = CONFIG_DIR / strConfFile; - dataDir = "/var/opt/" + systemName(); - } - } + // As a last resort, check the system config directory. + dataDir = "/var/opt/" + systemName(); + CONFIG_DIR = "/etc/opt/" + systemName(); + CONFIG_FILE = CONFIG_DIR / configFileName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; + CONFIG_FILE = CONFIG_DIR / configLegacyName; + } while (false); } // Update default values @@ -374,11 +377,9 @@ Config::setup( std::string const dbPath(legacy("database_path")); if (!dbPath.empty()) dataDir = boost::filesystem::path(dbPath); - else if (RUN_STANDALONE) - dataDir.clear(); } - if (!dataDir.empty()) + if (!RUN_STANDALONE) { boost::system::error_code ec; boost::filesystem::create_directories(dataDir, ec); diff --git a/src/xrpld/overlay/README.md b/src/xrpld/overlay/README.md index cd00488915..51eb96a001 100644 --- a/src/xrpld/overlay/README.md +++ b/src/xrpld/overlay/README.md @@ -373,7 +373,7 @@ command. The key is in the `pubkey_node` value, and is a text string beginning with the letter `n`. The key is maintained across runs in a database. -Cluster members are configured in the `rippled.cfg` file under +Cluster members are configured in the `xrpld.cfg` file under `[cluster_nodes]`. Each member should be configured on a line beginning with the node public key, followed optionally by a space and a friendly name. diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index e5f77021d7..7cd02c72e0 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -514,7 +514,7 @@ OverlayImpl::start() m_peerFinder->addFallbackStrings(base + name, ips); }); - // Add the ips_fixed from the rippled.cfg file + // Add the ips_fixed from the xrpld.cfg file if (!app_.config().standalone() && !app_.config().IPS_FIXED.empty()) { m_resolver.resolve( diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 5b5db72c22..2e9d5b35bf 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -71,16 +71,17 @@ parseAMM(Json::Value const& params, Json::StaticString const fieldName) return Unexpected(value.error()); } - try - { - auto const issue = issueFromJson(params[jss::asset]); - auto const issue2 = issueFromJson(params[jss::asset2]); - return keylet::amm(issue, issue2).key; - } - catch (std::runtime_error const&) - { - return LedgerEntryHelpers::malformedError("malformedRequest", ""); - } + auto const asset = LedgerEntryHelpers::requiredIssue( + params, jss::asset, "malformedRequest"); + if (!asset) + return Unexpected(asset.error()); + + auto const asset2 = LedgerEntryHelpers::requiredIssue( + params, jss::asset2, "malformedRequest"); + if (!asset2) + return Unexpected(asset2.error()); + + return keylet::amm(*asset, *asset2).key; } static Expected @@ -424,7 +425,7 @@ parseLoan(Json::Value const& params, Json::StaticString const fieldName) } auto const id = LedgerEntryHelpers::requiredUInt256( - params, jss::loan_broker_id, "malformedOwner"); + params, jss::loan_broker_id, "malformedLoanBrokerID"); if (!id) return Unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32( diff --git a/src/xrpld/rpc/handlers/LedgerEntryHelpers.h b/src/xrpld/rpc/handlers/LedgerEntryHelpers.h index 0a4453c063..4b656e6b05 100644 --- a/src/xrpld/rpc/handlers/LedgerEntryHelpers.h +++ b/src/xrpld/rpc/handlers/LedgerEntryHelpers.h @@ -218,6 +218,29 @@ requiredUInt192( return required(params, fieldName, err, "Hash192"); } +template <> +std::optional +parse(Json::Value const& param) +{ + try + { + return issueFromJson(param); + } + catch (std::runtime_error const&) + { + return std::nullopt; + } +} + +Expected +requiredIssue( + Json::Value const& params, + Json::StaticString const fieldName, + std::string const& err) +{ + return required(params, fieldName, err, "Issue"); +} + Expected parseBridgeFields(Json::Value const& params) {