diff --git a/.github/actions/build_docker_image/action.yml b/.github/actions/build_docker_image/action.yml index 831ee5402..7bfe3fd0e 100644 --- a/.github/actions/build_docker_image/action.yml +++ b/.github/actions/build_docker_image/action.yml @@ -46,7 +46,7 @@ runs: - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 with: cache-image: false - - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 id: meta diff --git a/.github/actions/generate/action.yml b/.github/actions/generate/action.yml index 79d3d5e9f..7fead90e8 100644 --- a/.github/actions/generate/action.yml +++ b/.github/actions/generate/action.yml @@ -5,8 +5,8 @@ inputs: conan_profile: description: Conan profile name required: true - conan_cache_hit: - description: Whether conan cache has been downloaded + force_conan_source_build: + description: Whether conan should build all dependencies from source required: true default: "false" build_type: @@ -25,15 +25,6 @@ inputs: description: Whether Clio is to be statically linked required: true default: "false" - sanitizer: - description: Sanitizer to use - required: true - default: "false" - choices: - - "false" - - "tsan" - - "asan" - - "ubsan" time_trace: description: Whether to enable compiler trace reports required: true @@ -49,7 +40,7 @@ runs: - name: Run conan shell: bash env: - BUILD_OPTION: "${{ inputs.conan_cache_hit == 'true' && 'missing' || '*' }}" + CONAN_BUILD_OPTION: "${{ inputs.force_conan_source_build == 'true' && '*' || 'missing' }}" CODE_COVERAGE: "${{ inputs.code_coverage == 'true' && 'True' || 'False' }}" STATIC_OPTION: "${{ inputs.static == 'true' && 'True' || 'False' }}" INTEGRATION_TESTS_OPTION: "${{ inputs.build_integration_tests == 'true' && 'True' || 'False' }}" @@ -59,7 +50,7 @@ runs: conan \ install .. \ -of . \ - -b "$BUILD_OPTION" \ + -b "$CONAN_BUILD_OPTION" \ -s "build_type=${{ inputs.build_type }}" \ -o "&:static=${STATIC_OPTION}" \ -o "&:tests=True" \ @@ -74,9 +65,9 @@ runs: env: BUILD_TYPE: "${{ inputs.build_type }}" SANITIZER_OPTION: |- - ${{ inputs.sanitizer == 'tsan' && '-Dsan=thread' || - inputs.sanitizer == 'ubsan' && '-Dsan=undefined' || - inputs.sanitizer == 'asan' && '-Dsan=address' || + ${{ endsWith(inputs.conan_profile, '.asan') && '-Dsan=address' || + endsWith(inputs.conan_profile, '.tsan') && '-Dsan=thread' || + endsWith(inputs.conan_profile, '.ubsan') && '-Dsan=undefined' || '' }} run: | cd build diff --git a/.github/actions/prepare_runner/action.yml b/.github/actions/prepare_runner/action.yml index be00a0207..b8073d992 100644 --- a/.github/actions/prepare_runner/action.yml +++ b/.github/actions/prepare_runner/action.yml @@ -13,7 +13,7 @@ runs: if: ${{ runner.os == 'macOS' }} shell: bash run: | - brew install \ + brew install --quiet \ bison \ ca-certificates \ ccache \ @@ -31,7 +31,7 @@ runs: shell: bash run: | # Uninstall any existing cmake - brew uninstall cmake --ignore-dependencies || true + brew uninstall --formula cmake --ignore-dependencies || true # Download specific cmake formula FORMULA_URL="https://raw.githubusercontent.com/Homebrew/homebrew-core/b4e46db74e74a8c1650b38b1da222284ce1ec5ce/Formula/c/cmake.rb" @@ -43,7 +43,7 @@ runs: echo "$FORMULA_EXPECTED_SHA256 /tmp/homebrew-formula/cmake.rb" | shasum -a 256 -c # Install cmake from the specific formula with force flag - brew install --formula --force /tmp/homebrew-formula/cmake.rb + brew install --formula --quiet --force /tmp/homebrew-formula/cmake.rb - name: Fix git permissions on Linux if: ${{ runner.os == 'Linux' }} diff --git a/.github/actions/restore_cache/action.yml b/.github/actions/restore_cache/action.yml index dfcd15008..1cfb68f3d 100644 --- a/.github/actions/restore_cache/action.yml +++ b/.github/actions/restore_cache/action.yml @@ -1,10 +1,7 @@ name: Restore cache -description: Find and restores conan and ccache cache +description: Find and restores ccache cache inputs: - conan_dir: - description: Path to Conan directory - required: true conan_profile: description: Conan profile name required: true @@ -19,13 +16,8 @@ inputs: description: Whether code coverage is on required: true default: "false" + outputs: - conan_hash: - description: Hash to use as a part of conan cache key - value: ${{ steps.conan_hash.outputs.hash }} - conan_cache_hit: - description: True if conan cache has been downloaded - value: ${{ steps.conan_cache.outputs.cache-hit }} ccache_cache_hit: description: True if ccache cache has been downloaded value: ${{ steps.ccache_cache.outputs.cache-hit }} @@ -37,24 +29,6 @@ runs: id: git_common_ancestor uses: ./.github/actions/git_common_ancestor - - name: Calculate conan hash - id: conan_hash - shell: bash - run: | - conan graph info . --format json --out-file info.json -o '&:tests=True' --profile:all ${{ inputs.conan_profile }} - packages_info="$(cat info.json | jq -r '.graph.nodes[]?.ref' | grep -v 'clio')" - echo "$packages_info" - hash="$(echo "$packages_info" | shasum -a 256 | cut -d ' ' -f 1)" - rm info.json - echo "hash=$hash" >> $GITHUB_OUTPUT - - - name: Restore conan cache - uses: actions/cache/restore@v4 - id: conan_cache - with: - path: ${{ inputs.conan_dir }}/p - key: clio-conan_data-${{ runner.os }}-${{ inputs.build_type }}-${{ inputs.conan_profile }}-develop-${{ steps.conan_hash.outputs.hash }} - - name: Restore ccache cache uses: actions/cache/restore@v4 id: ccache_cache diff --git a/.github/actions/save_cache/action.yml b/.github/actions/save_cache/action.yml index 9898def51..3711f8025 100644 --- a/.github/actions/save_cache/action.yml +++ b/.github/actions/save_cache/action.yml @@ -1,27 +1,13 @@ name: Save cache -description: Save conan and ccache cache for develop branch +description: Save ccache cache for develop branch inputs: - conan_dir: - description: Path to .conan directory - required: true conan_profile: description: Conan profile name required: true - conan_hash: - description: Hash to use as a part of conan cache key - required: true - conan_cache_hit: - description: Whether conan cache has been downloaded - required: true ccache_dir: description: Path to .ccache directory required: true - ccache_cache_hit: - description: Whether conan cache has been downloaded - required: true - ccache_cache_miss_rate: - description: How many cache misses happened build_type: description: Current build type (e.g. Release, Debug) required: true @@ -31,6 +17,12 @@ inputs: required: true default: "false" + ccache_cache_hit: + description: Whether ccache cache has been downloaded + required: true + ccache_cache_miss_rate: + description: How many ccache cache misses happened + runs: using: composite steps: @@ -38,19 +30,6 @@ runs: id: git_common_ancestor uses: ./.github/actions/git_common_ancestor - - name: Cleanup conan directory from extra data - if: ${{ inputs.conan_cache_hit != 'true' }} - shell: bash - run: | - conan cache clean --source --build --temp - - - name: Save conan cache - if: ${{ inputs.conan_cache_hit != 'true' }} - uses: actions/cache/save@v4 - with: - path: ${{ inputs.conan_dir }}/p - key: clio-conan_data-${{ runner.os }}-${{ inputs.build_type }}-${{ inputs.conan_profile }}-develop-${{ inputs.conan_hash }} - - name: Save ccache cache if: ${{ inputs.ccache_cache_hit != 'true' || inputs.ccache_cache_miss_rate == '100.0' }} uses: actions/cache/save@v4 diff --git a/.github/actions/setup_conan/action.yml b/.github/actions/setup_conan/action.yml deleted file mode 100644 index c34176372..000000000 --- a/.github/actions/setup_conan/action.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Setup conan -description: Setup conan profile and artifactory - -inputs: - conan_profile: - description: Conan profile name - required: true - -runs: - using: composite - steps: - - name: Create conan profile on macOS - if: ${{ runner.os == 'macOS' }} - shell: bash - run: | - conan profile detect --name "${{ inputs.conan_profile }}" --force - sed -i '' 's/compiler.cppstd=[^ ]*/compiler.cppstd=20/' "${{ env.CONAN_HOME }}/profiles/${{ inputs.conan_profile }}" - - - name: Add artifactory remote - shell: bash - run: | - conan remote add --index 0 --force ripple http://18.143.149.228:8081/artifactory/api/conan/dev diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e6d1d3873..addc7c01a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -142,16 +142,3 @@ updates: 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 - reviewers: - - XRPLF/clio-dev-team - commit-message: - prefix: "ci: [DEPENDABOT] " - target-branch: develop diff --git a/.github/scripts/conan/apple-clang.profile b/.github/scripts/conan/apple-clang.profile new file mode 100644 index 000000000..b61f5eb4d --- /dev/null +++ b/.github/scripts/conan/apple-clang.profile @@ -0,0 +1,8 @@ +[settings] +arch={{detect_api.detect_arch()}} +build_type=Release +compiler=apple-clang +compiler.cppstd=20 +compiler.libcxx=libc++ +compiler.version=16 +os=Macos diff --git a/.github/scripts/conan/generate_matrix.py b/.github/scripts/conan/generate_matrix.py new file mode 100755 index 000000000..c645d487d --- /dev/null +++ b/.github/scripts/conan/generate_matrix.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +import itertools +import json + +LINUX_OS = ["heavy", "heavy-arm64"] +LINUX_CONTAINERS = ['{ "image": "ghcr.io/xrplf/clio-ci:latest" }'] +LINUX_COMPILERS = ["gcc", "clang"] + +MACOS_OS = ["macos15"] +MACOS_CONTAINERS = [""] +MACOS_COMPILERS = ["apple-clang"] + +BUILD_TYPES = ["Release", "Debug"] +SANITIZER_EXT = [".asan", ".tsan", ".ubsan", ""] + + +def generate_matrix(): + configurations = [] + + for os, container, compiler in itertools.chain( + itertools.product(LINUX_OS, LINUX_CONTAINERS, LINUX_COMPILERS), + itertools.product(MACOS_OS, MACOS_CONTAINERS, MACOS_COMPILERS), + ): + for sanitizer_ext, build_type in itertools.product(SANITIZER_EXT, BUILD_TYPES): + # libbacktrace doesn't build on arm64 with gcc.tsan + if os == "heavy-arm64" and compiler == "gcc" and sanitizer_ext == ".tsan": + continue + configurations.append( + { + "os": os, + "container": container, + "compiler": compiler, + "sanitizer_ext": sanitizer_ext, + "build_type": build_type, + } + ) + + return {"include": configurations} + + +if __name__ == "__main__": + print(f"matrix={json.dumps(generate_matrix())}") diff --git a/.github/scripts/conan/init.sh b/.github/scripts/conan/init.sh new file mode 100755 index 000000000..6ef4a228e --- /dev/null +++ b/.github/scripts/conan/init.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -ex + +CURRENT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_DIR="$(cd "$CURRENT_DIR/../../../" && pwd)" + +CONAN_DIR="${CONAN_HOME:-$HOME/.conan2}" +PROFILES_DIR="$CONAN_DIR/profiles" + +APPLE_CLANG_PROFILE="$CURRENT_DIR/apple-clang.profile" + +GCC_PROFILE="$REPO_DIR/docker/ci/conan/gcc.profile" +CLANG_PROFILE="$REPO_DIR/docker/ci/conan/clang.profile" + +SANITIZER_TEMPLATE_FILE="$REPO_DIR/docker/ci/conan/sanitizer_template.profile" + +rm -rf "$CONAN_DIR" + +conan remote add --index 0 ripple http://18.143.149.228:8081/artifactory/api/conan/dev + +cp "$REPO_DIR/docker/ci/conan/global.conf" "$CONAN_DIR/global.conf" + +create_profile_with_sanitizers() { + profile_name="$1" + profile_source="$2" + + cp "$profile_source" "$PROFILES_DIR/$profile_name" + cp "$SANITIZER_TEMPLATE_FILE" "$PROFILES_DIR/$profile_name.asan" + cp "$SANITIZER_TEMPLATE_FILE" "$PROFILES_DIR/$profile_name.tsan" + cp "$SANITIZER_TEMPLATE_FILE" "$PROFILES_DIR/$profile_name.ubsan" +} + +mkdir -p "$PROFILES_DIR" + +if [[ "$(uname)" == "Darwin" ]]; then + create_profile_with_sanitizers "apple-clang" "$APPLE_CLANG_PROFILE" + echo "include(apple-clang)" > "$PROFILES_DIR/default" +else + create_profile_with_sanitizers "clang" "$CLANG_PROFILE" + create_profile_with_sanitizers "gcc" "$GCC_PROFILE" + echo "include(gcc)" > "$PROFILES_DIR/default" +fi diff --git a/.github/scripts/update-libxrpl-version b/.github/scripts/update-libxrpl-version deleted file mode 100755 index da2da55c7..000000000 --- a/.github/scripts/update-libxrpl-version +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# Note: This script is intended to be run from the root of the repository. -# -# This script modifies conanfile.py such that the specified version of libXRPL is used. - -if [[ -z "$1" ]]; then - cat <&1 | grep -q 'GNU' && echo true || echo false) - -echo "+ Updating required libXRPL version to $VERSION" - -if [[ "$GNU_SED" == "false" ]]; then - sed -i '' -E "s|'xrpl/[a-zA-Z0-9\\.\\-]+'|'xrpl/$VERSION'|g" conanfile.py -else - sed -i -E "s|'xrpl/[a-zA-Z0-9\\.\\-]+'|'xrpl/$VERSION'|g" conanfile.py -fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa94b84e4..b3d850c6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,7 @@ on: - CMakeLists.txt - conanfile.py + - conan.lock - "cmake/**" - "src/**" - "tests/**" @@ -46,7 +47,7 @@ jobs: include: - os: macos15 - conan_profile: default_apple_clang + conan_profile: apple-clang build_type: Release container: "" static: false @@ -76,7 +77,6 @@ jobs: static: true upload_clio_server: false targets: all - sanitizer: "false" analyze_build_time: false secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} @@ -99,23 +99,9 @@ jobs: shell: bash run: | repoConfigFile=docs/config-description.md - if ! [ -f "${repoConfigFile}" ]; then - echo "Config Description markdown file is missing in docs folder" - exit 1 - fi + configDescriptionFile=config_description_new.md chmod +x ./clio_server - configDescriptionFile=config_description_new.md ./clio_server -d "${configDescriptionFile}" - configDescriptionHash=$(sha256sum "${configDescriptionFile}" | cut -d' ' -f1) - repoConfigHash=$(sha256sum "${repoConfigFile}" | cut -d' ' -f1) - - if [ "${configDescriptionHash}" != "${repoConfigHash}" ]; then - echo "Markdown file is not up to date" - diff -u "${repoConfigFile}" "${configDescriptionFile}" - rm -f "${configDescriptionFile}" - exit 1 - fi - rm -f "${configDescriptionFile}" - exit 0 + diff -u "${repoConfigFile}" "${configDescriptionFile}" diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 6a73e62f6..d8ce3740b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -24,7 +24,7 @@ on: type: string disable_cache: - description: Whether ccache and conan cache should be disabled + description: Whether ccache should be disabled required: false type: boolean default: false @@ -57,12 +57,6 @@ on: type: string default: all - sanitizer: - description: Sanitizer to use - required: false - type: string - default: "false" - jobs: build: uses: ./.github/workflows/build_impl.yml @@ -76,7 +70,6 @@ jobs: static: ${{ inputs.static }} upload_clio_server: ${{ inputs.upload_clio_server }} targets: ${{ inputs.targets }} - sanitizer: ${{ inputs.sanitizer }} analyze_build_time: false test: @@ -89,4 +82,3 @@ jobs: build_type: ${{ inputs.build_type }} run_unit_tests: ${{ inputs.run_unit_tests }} run_integration_tests: ${{ inputs.run_integration_tests }} - sanitizer: ${{ inputs.sanitizer }} diff --git a/.github/workflows/build_impl.yml b/.github/workflows/build_impl.yml index 12493eeb2..0d8dc0ce0 100644 --- a/.github/workflows/build_impl.yml +++ b/.github/workflows/build_impl.yml @@ -24,7 +24,7 @@ on: type: string disable_cache: - description: Whether ccache and conan cache should be disabled + description: Whether ccache should be disabled required: false type: boolean @@ -48,11 +48,6 @@ on: required: true type: string - sanitizer: - description: Sanitizer to use - required: true - type: string - analyze_build_time: description: Whether to enable build time analysis required: true @@ -64,7 +59,7 @@ on: jobs: build: - name: Build ${{ inputs.container != '' && 'in container' || 'natively' }} + name: Build runs-on: ${{ inputs.runs_on }} container: ${{ inputs.container != '' && fromJson(inputs.container) || null }} @@ -82,17 +77,16 @@ jobs: with: disable_ccache: ${{ inputs.disable_cache }} - - name: Setup conan - uses: ./.github/actions/setup_conan - with: - conan_profile: ${{ inputs.conan_profile }} + - name: Setup conan on macOS + if: runner.os == 'macOS' + shell: bash + run: ./.github/scripts/conan/init.sh - name: Restore cache if: ${{ !inputs.disable_cache }} uses: ./.github/actions/restore_cache id: restore_cache with: - conan_dir: ${{ env.CONAN_HOME }} conan_profile: ${{ inputs.conan_profile }} ccache_dir: ${{ env.CCACHE_DIR }} build_type: ${{ inputs.build_type }} @@ -102,11 +96,9 @@ jobs: uses: ./.github/actions/generate with: conan_profile: ${{ inputs.conan_profile }} - conan_cache_hit: ${{ !inputs.disable_cache && steps.restore_cache.outputs.conan_cache_hit }} build_type: ${{ inputs.build_type }} code_coverage: ${{ inputs.code_coverage }} static: ${{ inputs.static }} - sanitizer: ${{ inputs.sanitizer }} time_trace: ${{ inputs.analyze_build_time }} - name: Build Clio @@ -140,11 +132,11 @@ jobs: cat /tmp/ccache.stats - name: Strip unit_tests - if: inputs.sanitizer == 'false' && !inputs.code_coverage && !inputs.analyze_build_time + if: ${{ !endsWith(inputs.conan_profile, 'san') && !inputs.code_coverage && !inputs.analyze_build_time }} run: strip build/clio_tests - name: Strip integration_tests - if: inputs.sanitizer == 'false' && !inputs.code_coverage && !inputs.analyze_build_time + if: ${{ !endsWith(inputs.conan_profile, 'san') && !inputs.code_coverage && !inputs.analyze_build_time }} run: strip build/clio_integration_tests - name: Upload clio_server @@ -172,15 +164,13 @@ jobs: if: ${{ !inputs.disable_cache && github.ref == 'refs/heads/develop' }} uses: ./.github/actions/save_cache with: - conan_dir: ${{ env.CONAN_HOME }} - conan_hash: ${{ steps.restore_cache.outputs.conan_hash }} - conan_cache_hit: ${{ steps.restore_cache.outputs.conan_cache_hit }} + conan_profile: ${{ inputs.conan_profile }} ccache_dir: ${{ env.CCACHE_DIR }} - ccache_cache_hit: ${{ steps.restore_cache.outputs.ccache_cache_hit }} - ccache_cache_miss_rate: ${{ steps.ccache_stats.outputs.miss_rate }} build_type: ${{ inputs.build_type }} code_coverage: ${{ inputs.code_coverage }} - conan_profile: ${{ inputs.conan_profile }} + + ccache_cache_hit: ${{ steps.restore_cache.outputs.ccache_cache_hit }} + ccache_cache_miss_rate: ${{ steps.ccache_stats.outputs.miss_rate }} # This is run as part of the build job, because it requires the following: # - source code diff --git a/.github/workflows/check_libxrpl.yml b/.github/workflows/check_libxrpl.yml index a3e5e8fb7..1c8cca621 100644 --- a/.github/workflows/check_libxrpl.yml +++ b/.github/workflows/check_libxrpl.yml @@ -15,7 +15,7 @@ env: jobs: build: name: Build Clio / `libXRPL ${{ github.event.client_payload.version }}` - runs-on: [self-hosted, heavy] + runs-on: heavy container: image: ghcr.io/xrplf/clio-ci:latest @@ -27,24 +27,23 @@ jobs: - name: Update libXRPL version requirement shell: bash run: | - ./.github/scripts/update-libxrpl-version ${{ github.event.client_payload.version }} + sed -i.bak -E "s|'xrpl/[a-zA-Z0-9\\.\\-]+'|'xrpl/${{ github.event.client_payload.version }}'|g" conanfile.py + rm -f conanfile.py.bak + + - name: Update conan lockfile + shell: bash + run: | + conan lock create . -o '&:tests=True' -o '&:benchmark=True' - name: Prepare runner uses: ./.github/actions/prepare_runner with: disable_ccache: true - - name: Setup conan - uses: ./.github/actions/setup_conan - with: - conan_profile: ${{ env.CONAN_PROFILE }} - - name: Run conan and cmake uses: ./.github/actions/generate with: conan_profile: ${{ env.CONAN_PROFILE }} - conan_cache_hit: ${{ steps.restore_cache.outputs.conan_cache_hit }} - build_type: Release - name: Build Clio uses: ./.github/actions/build_clio @@ -61,7 +60,7 @@ jobs: run_tests: name: Run tests needs: build - runs-on: [self-hosted, heavy] + runs-on: heavy container: image: ghcr.io/xrplf/clio-ci:latest diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 60239f1b0..e8bfe4249 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -40,25 +40,17 @@ jobs: with: disable_ccache: true - - name: Setup conan - uses: ./.github/actions/setup_conan - with: - conan_profile: ${{ env.CONAN_PROFILE }} - - name: Restore cache uses: ./.github/actions/restore_cache id: restore_cache with: - conan_dir: ${{ env.CONAN_HOME }} - ccache_dir: ${{ env.CCACHE_DIR }} conan_profile: ${{ env.CONAN_PROFILE }} + ccache_dir: ${{ env.CCACHE_DIR }} - name: Run conan and cmake uses: ./.github/actions/generate with: conan_profile: ${{ env.CONAN_PROFILE }} - conan_cache_hit: ${{ steps.restore_cache.outputs.conan_cache_hit }} - build_type: Release - name: Get number of threads uses: ./.github/actions/get_number_of_threads diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4db12c363..f4dec61a3 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -32,28 +32,24 @@ jobs: matrix: include: - os: macos15 - conan_profile: default_apple_clang + conan_profile: apple-clang build_type: Release static: false - sanitizer: "false" - os: heavy conan_profile: gcc build_type: Release static: true container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' - sanitizer: "false" - os: heavy conan_profile: gcc build_type: Debug static: true container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' - sanitizer: "false" - os: heavy conan_profile: gcc.ubsan build_type: Release static: false container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' - sanitizer: "ubsan" uses: ./.github/workflows/build_and_test.yml with: @@ -66,7 +62,6 @@ jobs: run_integration_tests: true upload_clio_server: true disable_cache: true - sanitizer: ${{ matrix.sanitizer }} analyze_build_time: name: Analyze Build Time @@ -80,7 +75,7 @@ jobs: container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' static: true - os: macos15 - conan_profile: default_apple_clang + conan_profile: apple-clang container: "" static: false uses: ./.github/workflows/build_impl.yml @@ -94,7 +89,6 @@ jobs: static: ${{ matrix.static }} upload_clio_server: false targets: all - sanitizer: "false" analyze_build_time: true nightly_release: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b351d703..eb221d539 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: matrix: include: - os: macos15 - conan_profile: default_apple_clang + conan_profile: apple-clang build_type: Release static: false - os: heavy diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 6088be3c7..f6afd5b30 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -19,6 +19,7 @@ on: - CMakeLists.txt - conanfile.py + - conan.lock - "cmake/**" # We don't run sanitizer on code change, because it takes too long # - "src/**" @@ -36,24 +37,22 @@ jobs: strategy: fail-fast: false matrix: - include: - - sanitizer: tsan - compiler: gcc - - sanitizer: asan - compiler: gcc - - sanitizer: ubsan - compiler: gcc + compiler: ["gcc", "clang"] + sanitizer_ext: [".asan", ".tsan", ".ubsan"] + exclude: + # Currently, clang.tsan unit tests hang + - compiler: clang + sanitizer_ext: .tsan uses: ./.github/workflows/build_and_test.yml with: runs_on: heavy container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' disable_cache: true - conan_profile: ${{ matrix.compiler }}.${{ matrix.sanitizer }} + conan_profile: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }} build_type: Release static: false run_unit_tests: true run_integration_tests: false upload_clio_server: false targets: clio_tests clio_integration_tests - sanitizer: ${{ matrix.sanitizer }} diff --git a/.github/workflows/test_impl.yml b/.github/workflows/test_impl.yml index 4fe50c200..72c962c92 100644 --- a/.github/workflows/test_impl.yml +++ b/.github/workflows/test_impl.yml @@ -33,22 +33,17 @@ on: required: true type: boolean - sanitizer: - description: Sanitizer to use - required: true - type: string - jobs: unit_tests: - name: Unit testing ${{ inputs.container != '' && 'in container' || 'natively' }} + name: Unit testing runs-on: ${{ inputs.runs_on }} container: ${{ inputs.container != '' && fromJson(inputs.container) || null }} if: inputs.run_unit_tests env: - # TODO: remove when we have fixed all currently existing issues from sanitizers - SANITIZER_IGNORE_ERRORS: ${{ inputs.sanitizer != 'false' && inputs.sanitizer != 'ubsan' }} + # TODO: remove completely when we have fixed all currently existing issues with sanitizers + SANITIZER_IGNORE_ERRORS: ${{ endsWith(inputs.conan_profile, '.asan') || endsWith(inputs.conan_profile, '.tsan') }} steps: - name: Clean workdir @@ -109,7 +104,7 @@ jobs: Reports are available as artifacts. integration_tests: - name: Integration testing ${{ inputs.container != '' && 'in container' || 'natively' }} + name: Integration testing runs-on: ${{ inputs.runs_on }} container: ${{ inputs.container != '' && fromJson(inputs.container) || null }} diff --git a/.github/workflows/update_docker_ci.yml b/.github/workflows/update_docker_ci.yml index a0c99cc07..d566b5d3d 100644 --- a/.github/workflows/update_docker_ci.yml +++ b/.github/workflows/update_docker_ci.yml @@ -23,14 +23,18 @@ on: workflow_dispatch: concurrency: - # Only cancel in-progress jobs or runs for the current workflow - matches against branch & tags + # Only matches runs for the current workflow - matches against branch & tags group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + # We want to execute all builds sequentially in develop + cancel-in-progress: false + +env: + GHCR_REPO: ghcr.io/${{ github.repository_owner }} jobs: - gcc: - name: Build and push GCC docker image - runs-on: [self-hosted, heavy] + gcc-amd64: + name: Build and push GCC docker image (amd64) + runs-on: heavy steps: - uses: actions/checkout@v4 @@ -42,30 +46,112 @@ jobs: files: "docker/compilers/gcc/**" - uses: ./.github/actions/build_docker_image - # Skipping this build for now, because CI environment is not stable - if: false && steps.changed-files.outputs.any_changed == 'true' + if: steps.changed-files.outputs.any_changed == 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} DOCKERHUB_PW: ${{ secrets.DOCKERHUB_PW }} with: images: | - ghcr.io/xrplf/clio-gcc + ${{ env.GHCR_REPO }}/clio-gcc rippleci/clio_gcc push_image: ${{ github.event_name != 'pull_request' }} directory: docker/compilers/gcc tags: | - type=raw,value=latest - type=raw,value=12 - type=raw,value=12.3.0 - type=raw,value=${{ github.sha }} - platforms: linux/amd64,linux/arm64 + type=raw,value=amd64-latest + type=raw,value=amd64-12 + type=raw,value=amd64-12.3.0 + type=raw,value=amd64-${{ github.sha }} + platforms: linux/amd64 dockerhub_repo: rippleci/clio_gcc dockerhub_description: GCC compiler for XRPLF/clio. + gcc-arm64: + name: Build and push GCC docker image (arm64) + runs-on: heavy-arm64 + + steps: + - uses: actions/checkout@v4 + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5 + with: + files: "docker/compilers/gcc/**" + + - uses: ./.github/actions/build_docker_image + if: steps.changed-files.outputs.any_changed == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} + DOCKERHUB_PW: ${{ secrets.DOCKERHUB_PW }} + with: + images: | + ${{ env.GHCR_REPO }}/clio-gcc + rippleci/clio_gcc + push_image: ${{ github.event_name != 'pull_request' }} + directory: docker/compilers/gcc + tags: | + type=raw,value=arm64-latest + type=raw,value=arm64-12 + type=raw,value=arm64-12.3.0 + type=raw,value=arm64-${{ github.sha }} + platforms: linux/arm64 + dockerhub_repo: rippleci/clio_gcc + dockerhub_description: GCC compiler for XRPLF/clio. + + gcc-merge: + name: Merge and push multi-arch GCC docker image + runs-on: heavy + needs: [gcc-amd64, gcc-arm64] + + steps: + - uses: actions/checkout@v4 + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5 + with: + files: "docker/compilers/gcc/**" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_PW }} + + - name: Make GHCR_REPO lowercase + run: | + echo "GHCR_REPO_LC=$(echo ${{env.GHCR_REPO}} | tr '[:upper:]' '[:lower:]')" >> ${GITHUB_ENV} + + - name: Create and push multi-arch manifest + if: github.event_name != 'pull_request' && steps.changed-files.outputs.any_changed == 'true' + run: | + for image in ${{ env.GHCR_REPO_LC }}/clio-gcc rippleci/clio_gcc; do + docker buildx imagetools create \ + -t $image:latest \ + -t $image:12 \ + -t $image:12.3.0 \ + -t $image:${{ github.sha }} \ + $image:arm64-latest \ + $image:amd64-latest + done + clang: name: Build and push Clang docker image - runs-on: [self-hosted, heavy] + runs-on: heavy steps: - uses: actions/checkout@v4 @@ -84,7 +170,7 @@ jobs: DOCKERHUB_PW: ${{ secrets.DOCKERHUB_PW }} with: images: | - ghcr.io/xrplf/clio-clang + ${{ env.GHCR_REPO }}/clio-clang rippleci/clio_clang push_image: ${{ github.event_name != 'pull_request' }} directory: docker/compilers/clang @@ -98,7 +184,7 @@ jobs: tools: name: Build and push tools docker image - runs-on: [self-hosted, heavy] + runs-on: heavy steps: - uses: actions/checkout@v4 @@ -115,7 +201,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: images: | - ghcr.io/xrplf/clio-tools + ${{ env.GHCR_REPO }}/clio-tools push_image: ${{ github.event_name != 'pull_request' }} directory: docker/tools tags: | @@ -125,8 +211,8 @@ jobs: ci: name: Build and push CI docker image - runs-on: [self-hosted, heavy] - needs: [gcc, clang, tools] + runs-on: heavy + needs: [gcc-merge, clang, tools] steps: - uses: actions/checkout@v4 @@ -137,8 +223,8 @@ jobs: DOCKERHUB_PW: ${{ secrets.DOCKERHUB_PW }} with: images: | + ${{ env.GHCR_REPO }}/clio-ci rippleci/clio_ci - ghcr.io/xrplf/clio-ci push_image: ${{ github.event_name != 'pull_request' }} directory: docker/ci tags: | diff --git a/.github/workflows/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml new file mode 100644 index 000000000..9c0fdd8b6 --- /dev/null +++ b/.github/workflows/upload_conan_deps.yml @@ -0,0 +1,102 @@ +name: Upload Conan Dependencies + +on: + schedule: + - cron: "0 9 * * 1-5" + workflow_dispatch: + inputs: + force_source_build: + description: "Force source build of all dependencies" + required: false + default: false + type: boolean + pull_request: + branches: + - develop + paths: + - .github/workflows/upload_conan_deps.yml + + - .github/actions/generate/action.yml + - .github/actions/prepare_runner/action.yml + - .github/scripts/conan/generate_matrix.py + - .github/scripts/conan/init.sh + + - conanfile.py + - conan.lock + push: + branches: + - develop + paths: + - .github/workflows/upload_conan_deps.yml + + - .github/actions/generate/action.yml + - .github/actions/prepare_runner/action.yml + - .github/scripts/conan/generate_matrix.py + - .github/scripts/conan/init.sh + + - conanfile.py + - conan.lock + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + generate-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + + - name: Calculate conan matrix + id: set-matrix + run: .github/scripts/conan/generate_matrix.py >> "${GITHUB_OUTPUT}" + + upload-conan-deps: + name: Build ${{ matrix.compiler }}${{ matrix.sanitizer_ext }} ${{ matrix.build_type }} + + needs: generate-matrix + + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + + runs-on: ${{ matrix.os }} + container: ${{ matrix.container != '' && fromJson(matrix.container) || null }} + + env: + CONAN_PROFILE: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }} + + steps: + - uses: actions/checkout@v4 + + - name: Prepare runner + uses: ./.github/actions/prepare_runner + with: + disable_ccache: true + + - name: Setup conan on macOS + if: runner.os == 'macOS' + shell: bash + run: ./.github/scripts/conan/init.sh + + - name: Show conan profile + run: conan profile show --profile:all ${{ env.CONAN_PROFILE }} + + - name: Run conan and cmake + uses: ./.github/actions/generate + with: + conan_profile: ${{ env.CONAN_PROFILE }} + # We check that everything builds fine from source on scheduled runs + # But we do build and upload packages with build=missing by default + force_conan_source_build: ${{ github.event_name == 'schedule' || github.event.inputs.force_source_build == 'true' }} + build_type: ${{ matrix.build_type }} + + - name: Login to Conan + if: github.event_name != 'pull_request' + run: conan remote login -p ${{ secrets.CONAN_PASSWORD }} ripple ${{ secrets.CONAN_USERNAME }} + + - name: Upload Conan packages + if: github.event_name != 'pull_request' && github.event_name != 'schedule' + run: conan upload "*" -r=ripple --confirm diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df477e58c..f2411898f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ # # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -exclude: ^docs/doxygen-awesome-theme/ +exclude: ^(docs/doxygen-awesome-theme/|conan\.lock$) repos: # `pre-commit sample-config` default hooks diff --git a/cmake/Settings.cmake b/cmake/Settings.cmake index 85fe7df93..2754db733 100644 --- a/cmake/Settings.cmake +++ b/cmake/Settings.cmake @@ -1,21 +1,22 @@ set(COMPILER_FLAGS + -pedantic -Wall -Wcast-align -Wdouble-promotion - -Wextra -Werror + -Wextra -Wformat=2 -Wimplicit-fallthrough -Wmisleading-indentation - -Wno-narrowing - -Wno-deprecated-declarations -Wno-dangling-else + -Wno-deprecated-declarations + -Wno-narrowing -Wno-unused-but-set-variable -Wnon-virtual-dtor -Wnull-dereference -Wold-style-cast - -pedantic -Wpedantic + -Wunreachable-code -Wunused # FIXME: The following bunch are needed for gcc12 atm. -Wno-missing-requires diff --git a/conan.lock b/conan.lock new file mode 100644 index 000000000..35b23e246 --- /dev/null +++ b/conan.lock @@ -0,0 +1,57 @@ +{ + "version": "0.5", + "requires": [ + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1750263732.782", + "xxhash/0.8.2#7856c968c985b2981b707ee8f2413b2b%1750263730.908", + "xrpl/2.5.0#7880d1696f11fceb1d498570f1a184c8%1751035095.524809", + "sqlite3/3.47.0#7a0904fd061f5f8a2366c294f9387830%1750263721.79", + "soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1750263717.455", + "re2/20230301#dfd6e2bf050eb90ddd8729cfb4c844a4%1750263715.145", + "rapidjson/cci.20220822#1b9d8c2256876a154172dc5cfbe447c6%1750263713.526", + "protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1750263698.841", + "openssl/1.1.1v#216374e4fb5b2e0f5ab1fb6f27b5b434%1750263685.885", + "nudb/2.0.8#63990d3e517038e04bf529eb8167f69f%1750263683.814", + "minizip/1.2.13#9e87d57804bd372d6d1e32b1871517a3%1750263681.745", + "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1750263679.891", + "libuv/1.46.0#78565d142ac7102776256328a26cdf60%1750263677.819", + "libiconv/1.17#1ae2f60ab5d08de1643a22a81b360c59%1750257497.552", + "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1750263675.748", + "libarchive/3.7.6#e0453864b2a4d225f06b3304903cb2b7%1750263671.05", + "http_parser/2.9.4#98d91690d6fd021e9e624218a85d9d97%1750263668.751", + "gtest/1.14.0#f8f0757a574a8dd747d16af62d6eb1b7%1750263666.833", + "grpc/1.50.1#02291451d1e17200293a409410d1c4e1%1750263646.614", + "fmt/10.1.1#021e170cf81db57da82b5f737b6906c1%1750263644.741", + "date/3.0.3#cf28fe9c0aab99fe12da08aa42df65e1%1750263643.099", + "cassandra-cpp-driver/2.17.0#e50919efac8418c26be6671fd702540a%1750263632.157", + "c-ares/1.34.5#b78b91e7cfb1f11ce777a285bbf169c6%1750263630.06", + "bzip2/1.0.8#00b4a4658791c1f06914e087f0e792f5%1750263627.95", + "boost/1.83.0#8eb22f36ddfb61f54bbc412c4555bd66%1750263616.444", + "benchmark/1.8.3#1a2ce62c99e2b3feaa57b1f0c15a8c46%1724323740.181", + "abseil/20230802.1#f0f91485b111dc9837a68972cb19ca7b%1750263609.776" + ], + "build_requires": [ + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1750263732.782", + "protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1750263698.841", + "protobuf/3.21.9#64ce20e1d9ea24f3d6c504015d5f6fa8%1750263690.822", + "cmake/3.31.7#57c3e118bcf267552c0ea3f8bee1e7d5%1749863707.208", + "b2/5.3.2#7b5fabfe7088ae933fb3e78302343ea0%1750263614.565" + ], + "python_requires": [], + "overrides": { + "boost/1.83.0": [ + null, + "boost/1.83.0#8eb22f36ddfb61f54bbc412c4555bd66" + ], + "protobuf/3.21.9": [ + null, + "protobuf/3.21.12" + ], + "lz4/1.9.4": [ + "lz4/1.10.0" + ], + "sqlite3/3.44.2": [ + "sqlite3/3.47.0" + ] + }, + "config_requires": [] +} \ No newline at end of file diff --git a/conanfile.py b/conanfile.py index 5e346baf8..039a6d2bd 100644 --- a/conanfile.py +++ b/conanfile.py @@ -11,7 +11,6 @@ class ClioConan(ConanFile): settings = 'os', 'compiler', 'build_type', 'arch' options = { 'static': [True, False], # static linkage - 'fPIC': [True, False], # unused? 'verbose': [True, False], 'tests': [True, False], # build unit tests; create `clio_tests` binary 'integration_tests': [True, False], # build integration tests; create `clio_integration_tests` binary @@ -31,14 +30,13 @@ class ClioConan(ConanFile): 'protobuf/3.21.12', 'grpc/1.50.1', 'openssl/1.1.1v', - 'xrpl/2.5.0-rc1', + 'xrpl/2.5.0', 'zlib/1.3.1', 'libbacktrace/cci.20210118' ] default_options = { 'static': False, - 'fPIC': True, 'verbose': False, 'tests': False, 'integration_tests': False, @@ -89,21 +87,8 @@ class ClioConan(ConanFile): def generate(self): tc = CMakeToolchain(self) - tc.variables['verbose'] = self.options.verbose - tc.variables['static'] = self.options.static - tc.variables['tests'] = self.options.tests - tc.variables['integration_tests'] = self.options.integration_tests - tc.variables['coverage'] = self.options.coverage - tc.variables['lint'] = self.options.lint - tc.variables['docs'] = self.options.docs - tc.variables['packaging'] = self.options.packaging - tc.variables['benchmark'] = self.options.benchmark - tc.variables['snapshot'] = self.options.snapshot - tc.variables['time_trace'] = self.options.time_trace - - if self.settings.compiler == 'clang' and self.settings.compiler.version == 16: - tc.extra_cxxflags = ["-DBOOST_ASIO_DISABLE_CONCEPTS"] - + for option_name, option_value in self.options.items(): + tc.variables[option_name] = option_value tc.generate() def build(self): diff --git a/docker/ci/Dockerfile b/docker/ci/Dockerfile index ecca54396..503e07501 100644 --- a/docker/ci/Dockerfile +++ b/docker/ci/Dockerfile @@ -1,5 +1,4 @@ -# TODO: change this when we are able to push gcc image to ghcr.io -FROM rippleci/clio_gcc:12.3.0 AS clio-gcc +FROM ghcr.io/xrplf/clio-gcc:12.3.0 AS clio-gcc FROM ghcr.io/xrplf/clio-tools:latest AS clio-tools FROM ghcr.io/xrplf/clio-clang:16 @@ -33,10 +32,8 @@ RUN apt-get update \ # Install packages RUN apt-get update \ && apt-get install -y --no-install-recommends --no-install-suggests \ - bison \ clang-tidy-${LLVM_TOOLS_VERSION} \ clang-tools-${LLVM_TOOLS_VERSION} \ - flex \ git \ git-lfs \ graphviz \ @@ -48,6 +45,10 @@ RUN apt-get update \ zip \ && pip3 install -q --upgrade --no-cache-dir pip \ && pip3 install -q --no-cache-dir \ + # TODO: Remove this once we switch to newer Ubuntu base image + # lxml 6.0.0 is not compatible with our image + 'lxml<6.0.0' \ + \ cmake==3.31.6 \ conan==2.17.0 \ gcovr \ @@ -90,6 +91,9 @@ WORKDIR /root # Setup conan RUN conan remote add --index 0 ripple http://18.143.149.228:8081/artifactory/api/conan/dev +WORKDIR /root/.conan2 +COPY conan/global.conf ./global.conf + WORKDIR /root/.conan2/profiles COPY conan/clang.profile ./clang diff --git a/docker/ci/conan/clang.profile b/docker/ci/conan/clang.profile index 647ca3709..2d8691bc9 100644 --- a/docker/ci/conan/clang.profile +++ b/docker/ci/conan/clang.profile @@ -1,5 +1,5 @@ [settings] -arch=x86_64 +arch={{detect_api.detect_arch()}} build_type=Release compiler=clang compiler.cppstd=20 diff --git a/docker/ci/conan/gcc.profile b/docker/ci/conan/gcc.profile index 1575376f5..97ef51d9c 100644 --- a/docker/ci/conan/gcc.profile +++ b/docker/ci/conan/gcc.profile @@ -1,5 +1,5 @@ [settings] -arch=x86_64 +arch={{detect_api.detect_arch()}} build_type=Release compiler=gcc compiler.cppstd=20 diff --git a/docker/ci/conan/global.conf b/docker/ci/conan/global.conf new file mode 100644 index 000000000..d2ae941c8 --- /dev/null +++ b/docker/ci/conan/global.conf @@ -0,0 +1,3 @@ +core.download:parallel={{os.cpu_count()}} +core.upload:parallel={{os.cpu_count()}} +tools.info.package_id:confs = ["tools.build:cflags", "tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags"] diff --git a/docker/ci/conan/sanitizer_template.profile b/docker/ci/conan/sanitizer_template.profile index ff601481c..5f015335f 100644 --- a/docker/ci/conan/sanitizer_template.profile +++ b/docker/ci/conan/sanitizer_template.profile @@ -2,14 +2,19 @@ {% set sanitizer_opt_map = {'asan': 'address', 'tsan': 'thread', 'ubsan': 'undefined'} %} {% set sanitizer = sanitizer_opt_map[sani] %} +{% set sanitizer_build_flags_str = "-fsanitize=" ~ sanitizer ~ " -g -O1 -fno-omit-frame-pointer" %} +{% set sanitizer_build_flags = sanitizer_build_flags_str.split(' ') %} +{% set sanitizer_link_flags_str = "-fsanitize=" ~ sanitizer %} +{% set sanitizer_link_flags = sanitizer_link_flags_str.split(' ') %} include({{ compiler }}) [options] -boost/*:extra_b2_flags="cxxflags=\"-fsanitize={{ sanitizer }}\" linkflags=\"-fsanitize={{ sanitizer }}\"" -boost/*:without_stacktrace=True +boost/*:extra_b2_flags = "cxxflags=\"{{ sanitizer_build_flags_str }}\" linkflags=\"{{ sanitizer_link_flags_str }}\"" +boost/*:without_stacktrace = True [conf] -tools.build:cflags+=["-fsanitize={{ sanitizer }}"] -tools.build:cxxflags+=["-fsanitize={{ sanitizer }}"] -tools.build:exelinkflags+=["-fsanitize={{ sanitizer }}"] +tools.build:cflags += {{ sanitizer_build_flags }} +tools.build:cxxflags += {{ sanitizer_build_flags }} +tools.build:exelinkflags += {{ sanitizer_link_flags }} +tools.build:sharedlinkflags += {{ sanitizer_link_flags }} diff --git a/docker/compilers/gcc/Dockerfile b/docker/compilers/gcc/Dockerfile index f4e763880..94091b7d2 100644 --- a/docker/compilers/gcc/Dockerfile +++ b/docker/compilers/gcc/Dockerfile @@ -1,10 +1,19 @@ -FROM ubuntu:20.04 AS build +ARG UBUNTU_VERSION=20.04 + +ARG GCC_MAJOR_VERSION=12 + +FROM ubuntu:$UBUNTU_VERSION AS build + +ARG UBUNTU_VERSION + +ARG GCC_MAJOR_VERSION +ARG GCC_MINOR_VERSION=3 +ARG GCC_PATCH_VERSION=0 +ARG GCC_VERSION=${GCC_MAJOR_VERSION}.${GCC_MINOR_VERSION}.${GCC_PATCH_VERSION} +ARG BUILD_VERSION=6 ARG DEBIAN_FRONTEND=noninteractive ARG TARGETARCH -ARG UBUNTU_VERSION=20.04 -ARG GCC_VERSION=12.3.0 -ARG BUILD_VERSION=2 RUN apt-get update \ && apt-get install -y --no-install-recommends --no-install-suggests \ @@ -18,18 +27,21 @@ RUN apt-get update \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +WORKDIR / RUN wget --progress=dot:giga https://gcc.gnu.org/pub/gcc/releases/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.gz \ - && tar xf gcc-$GCC_VERSION.tar.gz \ - && cd /gcc-$GCC_VERSION && ./contrib/download_prerequisites + && tar xf gcc-$GCC_VERSION.tar.gz -RUN mkdir /${TARGETARCH}-gcc-12 -WORKDIR /${TARGETARCH}-gcc-12 +WORKDIR /gcc-$GCC_VERSION +RUN ./contrib/download_prerequisites + +RUN mkdir /gcc-build +WORKDIR /gcc-build RUN /gcc-$GCC_VERSION/configure \ --with-pkgversion="clio-build-$BUILD_VERSION https://github.com/XRPLF/clio" \ --enable-languages=c,c++ \ --prefix=/usr \ --with-gcc-major-version-only \ - --program-suffix=-12 \ + --program-suffix=-${GCC_MAJOR_VERSION} \ --enable-shared \ --enable-linker-build-id \ --libexecdir=/usr/lib \ @@ -53,38 +65,54 @@ RUN /gcc-$GCC_VERSION/configure \ --enable-cet \ --disable-multilib \ --without-cuda-driver \ - --enable-checking=release \ - && make -j "$(nproc)" \ - && make install-strip DESTDIR=/gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION \ + --enable-checking=release + +RUN make -j "$(nproc)" + +RUN make install-strip DESTDIR=/gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION \ && mkdir -p /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/usr/share/gdb/auto-load/usr/lib64 \ - && mv /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/usr/lib64/libstdc++.so.6.0.30-gdb.py /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/usr/share/gdb/auto-load/usr/lib64/libstdc++.so.6.0.30-gdb.py + && mv \ + /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/usr/lib64/libstdc++.so.6.0.30-gdb.py \ + /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/usr/share/gdb/auto-load/usr/lib64/libstdc++.so.6.0.30-gdb.py # Generate deb WORKDIR / COPY control.m4 / -COPY ld.so.conf /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/etc/ld.so.conf.d/1-gcc-12.conf +COPY ld.so.conf /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/etc/ld.so.conf.d/1-gcc-${GCC_MAJOR_VERSION}.conf RUN mkdir /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/DEBIAN \ - && m4 -P -DUBUNTU_VERSION=$UBUNTU_VERSION -DVERSION=$GCC_VERSION-$BUILD_VERSION -DTARGETARCH=$TARGETARCH control.m4 > /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/DEBIAN/control \ - && dpkg-deb --build --root-owner-group /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION /gcc12.deb + && m4 \ + -P \ + -DUBUNTU_VERSION=$UBUNTU_VERSION \ + -DVERSION=$GCC_VERSION-$BUILD_VERSION \ + -DTARGETARCH=$TARGETARCH \ + control.m4 > /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION/DEBIAN/control \ + && dpkg-deb \ + --build \ + --root-owner-group \ + /gcc-$GCC_VERSION-$BUILD_VERSION-ubuntu-$UBUNTU_VERSION \ + /gcc${GCC_MAJOR_VERSION}.deb # Create final image -FROM ubuntu:20.04 -COPY --from=build /gcc12.deb / +FROM ubuntu:$UBUNTU_VERSION -# Make gcc-12 available but also leave gcc12.deb for others to copy if needed +ARG GCC_MAJOR_VERSION + +COPY --from=build /gcc${GCC_MAJOR_VERSION}.deb / + +# Install gcc-${GCC_MAJOR_VERSION}, but also leave gcc${GCC_MAJOR_VERSION}.deb for others to copy if needed RUN apt-get update \ && apt-get install -y --no-install-recommends --no-install-suggests \ binutils \ libc6-dev \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ - && dpkg -i /gcc12.deb + && dpkg -i /gcc${GCC_MAJOR_VERSION}.deb -RUN update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100 \ - && update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-12 100 \ - && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100 \ - && update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-12 100 \ - && update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-12 100 \ - && update-alternatives --install /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-12 100 \ - && update-alternatives --install /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-12 100 +RUN update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-${GCC_MAJOR_VERSION} 100 \ + && update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-${GCC_MAJOR_VERSION} 100 \ + && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${GCC_MAJOR_VERSION} 100 \ + && update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-${GCC_MAJOR_VERSION} 100 \ + && update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-${GCC_MAJOR_VERSION} 100 \ + && update-alternatives --install /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-${GCC_MAJOR_VERSION} 100 \ + && update-alternatives --install /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-${GCC_MAJOR_VERSION} 100 diff --git a/docker/compilers/gcc/README.md b/docker/compilers/gcc/README.md index 44261c936..5cc3a9cbf 100644 --- a/docker/compilers/gcc/README.md +++ b/docker/compilers/gcc/README.md @@ -1,3 +1,3 @@ -# gcc compiler +# GCC compiler -This image contains gcc compiler to build . +This image contains GCC compiler to build . diff --git a/docker/compilers/gcc/control.m4 b/docker/compilers/gcc/control.m4 index 878ea9359..050fec61f 100644 --- a/docker/compilers/gcc/control.m4 +++ b/docker/compilers/gcc/control.m4 @@ -2,5 +2,6 @@ Package: gcc-12-ubuntu-UBUNTUVERSION Version: VERSION Architecture: TARGETARCH Maintainer: Alex Kremer -Description: Gcc VERSION build for ubuntu UBUNTUVERSION +Uploaders: Ayaz Salikhov +Description: GCC VERSION build for ubuntu UBUNTUVERSION Depends: binutils, libc6-dev diff --git a/docker/develop/compose.yaml b/docker/develop/compose.yaml index 4fb23b7c2..8526a970e 100644 --- a/docker/develop/compose.yaml +++ b/docker/develop/compose.yaml @@ -2,7 +2,7 @@ services: clio_develop: image: ghcr.io/xrplf/clio-ci:latest volumes: - - clio_develop_conan_data:/root/.conan/data + - clio_develop_conan_data:/root/.conan2/p - clio_develop_ccache:/root/.ccache - ../../:/root/clio - clio_develop_build:/root/clio/build_docker diff --git a/docs/build-clio.md b/docs/build-clio.md index a8c07bfdf..5e07c2daa 100644 --- a/docs/build-clio.md +++ b/docs/build-clio.md @@ -6,7 +6,7 @@ ## Minimum Requirements - [Python 3.7](https://www.python.org/downloads/) -- [Conan 1.55, <2.0](https://conan.io/downloads.html) +- [Conan 2.17.0](https://conan.io/downloads.html) - [CMake 3.20, <4.0](https://cmake.org/download/) - [**Optional**] [GCovr](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html): needed for code coverage generation - [**Optional**] [CCache](https://ccache.dev/): speeds up compilation if you are going to compile Clio often @@ -19,16 +19,27 @@ ### Conan Configuration -Clio requires `compiler.cppstd=20` in your Conan profile (`~/.conan/profiles/default`). +By default, Conan uses `~/.conan2` as it's home folder. +You can change it by using `$CONAN_HOME` env variable. +[More info about Conan home](https://docs.conan.io/2/reference/environment.html#conan-home). -> [!NOTE] -> Although Clio is built using C++23, it's required to set `compiler.cppstd=20` for the time being as some of Clio's dependencies are not yet capable of building under C++23. +> [!TIP] +> To setup Conan automatically, you can run `.github/scripts/conan/init.sh`. +> This will delete Conan home directory (if it exists), set up profiles and add Artifactory remote. + +The instruction below assumes that `$CONAN_HOME` is not set. + +#### Profiles + +The default profile is the file in `~/.conan2/profiles/default`. + +Here are some examples of possible profiles: **Mac apple-clang 16 example**: ```text [settings] -arch=armv8 +arch={{detect_api.detect_arch()}} build_type=Release compiler=apple-clang compiler.cppstd=20 @@ -37,14 +48,14 @@ compiler.version=16 os=Macos [conf] -tools.build:cxxflags+=["-Wno-missing-template-arg-list-after-template-kw"] +grpc/1.50.1:tools.build:cxxflags+=["-Wno-missing-template-arg-list-after-template-kw"] ``` **Linux gcc-12 example**: ```text [settings] -arch=x86_64 +arch={{detect_api.detect_arch()}} build_type=Release compiler=gcc compiler.cppstd=20 @@ -56,6 +67,19 @@ os=Linux tools.build:compiler_executables={'c': '/usr/bin/gcc-12', 'cpp': '/usr/bin/g++-12'} ``` +> [!NOTE] +> Although Clio is built using C++23, it's required to set `compiler.cppstd=20` in your profile for the time being as some of Clio's dependencies are not yet capable of building under C++23. + +#### global.conf file + +Add the following to the `~/.conan2/global.conf` file: + +```text +core.download:parallel={{os.cpu_count()}} +core.upload:parallel={{os.cpu_count()}} +tools.info.package_id:confs = ["tools.build:cflags", "tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags"] +``` + #### Artifactory Make sure artifactory is setup with Conan. @@ -64,15 +88,21 @@ Make sure artifactory is setup with Conan. conan remote add --index 0 ripple http://18.143.149.228:8081/artifactory/api/conan/dev ``` -Now you should be able to download the prebuilt `xrpl` package on some platforms. +Now you should be able to download the prebuilt dependencies (including `xrpl` package) on supported platforms. -> [!NOTE] -> You may need to edit the `~/.conan/remotes.json` file to ensure that this newly added artifactory is listed last. Otherwise, you could see compilation errors when building the project with gcc version 13 (or newer). +#### Conan lockfile -Remove old packages you may have cached. +To achieve reproducible dependencies, we use [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html). -```sh -conan remove -f xrpl +The `conan.lock` file in the repository contains a "snapshot" of the current dependencies. +It is implicitly used when running `conan` commands, you don't need to specify it. + +You have to update this file every time you add a new dependency or change a revision or version of an existing dependency. + +To do that, run the following command in the repository root: + +```bash +conan lock create . -o '&:tests=True' -o '&:benchmark=True' ``` ## Building Clio @@ -81,6 +111,7 @@ Navigate to Clio's root directory and run: ```sh mkdir build && cd build +# You can also specify profile explicitly by adding `--profile:all ` conan install .. --output-folder . --build missing --settings build_type=Release -o '&:tests=True' # You can also add -GNinja to use Ninja build system instead of Make cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release .. @@ -152,24 +183,24 @@ If you wish to develop against a `rippled` instance running in standalone mode t Sometimes, during development, you need to build against a custom version of `libxrpl`. (For example, you may be developing compatibility for a proposed amendment that is not yet merged to the main `rippled` codebase.) To build Clio with compatibility for a custom fork or branch of `rippled`, follow these steps: -1. First, pull/clone the appropriate `rippled` fork and switch to the branch you want to build. - The following example uses an in-development build with [XLS-33d Multi-Purpose Tokens](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033d-multi-purpose-tokens): +1. First, pull/clone the appropriate `rippled` version and switch to the branch you want to build. + The following example uses a `2.5.0-rc1` tag of rippled in the main branch: ```sh - git clone https://github.com/shawnxie999/rippled/ + git clone https://github.com/XRPLF/rippled/ cd rippled - git switch mpt-1.1 + git checkout 2.5.0-rc1 ``` 2. Export a custom package to your local Conan store using a user/channel: ```sh - conan export . my/feature + conan export . --user=my --channel=feature ``` 3. Patch your local Clio build to use the right package. - Edit `conanfile.py` (from the Clio repository root). Replace the `xrpl` requirement with the custom package version from the previous step. This must also include the current version number from your `rippled` branch. For example: + Edit `conanfile.py` in the Clio repository root. Replace the `xrpl` requirement with the custom package version from the previous step. This must also include the current version number from your `rippled` branch. For example: ```py # ... (excerpt from conanfile.py) @@ -180,7 +211,7 @@ Sometimes, during development, you need to build against a custom version of `li 'protobuf/3.21.9', 'grpc/1.50.1', 'openssl/1.1.1v', - 'xrpl/2.3.0-b1@my/feature', # Update this line + 'xrpl/2.5.0-rc1@my/feature', # Use your exported version here 'zlib/1.3.1', 'libbacktrace/cci.20210118' ] diff --git a/src/etl/ETLService.cpp b/src/etl/ETLService.cpp index 63ea45914..2db7b05a6 100644 --- a/src/etl/ETLService.cpp +++ b/src/etl/ETLService.cpp @@ -22,7 +22,6 @@ #include "data/BackendInterface.hpp" #include "etl/CacheLoader.hpp" #include "etl/CorruptionDetector.hpp" -#include "etl/ETLState.hpp" #include "etl/LoadBalancer.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" #include "etl/SystemState.hpp" @@ -38,6 +37,7 @@ #include "etlng/LoadBalancer.hpp" #include "etlng/LoadBalancerInterface.hpp" #include "etlng/impl/LedgerPublisher.hpp" +#include "etlng/impl/MonitorProvider.hpp" #include "etlng/impl/TaskManagerProvider.hpp" #include "etlng/impl/ext/Cache.hpp" #include "etlng/impl/ext/Core.hpp" @@ -86,6 +86,7 @@ ETLService::makeETLService( ); auto state = std::make_shared(); + state->isStrictReadonly = config.get("read_only"); auto fetcher = std::make_shared(backend, balancer); auto extractor = std::make_shared(fetcher); @@ -93,6 +94,9 @@ ETLService::makeETLService( auto cacheLoader = std::make_shared>(config, backend, backend->cache()); auto cacheUpdater = std::make_shared(backend->cache()); auto amendmentBlockHandler = std::make_shared(ctx, *state); + auto monitorProvider = std::make_shared(); + + backend->setCorruptionDetector(CorruptionDetector{*state, backend->cache()}); auto loader = std::make_shared( backend, @@ -104,7 +108,8 @@ ETLService::makeETLService( etlng::impl::NFTExt{backend}, etlng::impl::MPTExt{backend} ), - amendmentBlockHandler + amendmentBlockHandler, + state ); auto taskManagerProvider = std::make_shared(*ledgers, extractor, loader); @@ -122,6 +127,7 @@ ETLService::makeETLService( loader, // loader itself loader, // initial load observer taskManagerProvider, + monitorProvider, state ); } else { @@ -346,7 +352,7 @@ ETLService::doWork() worker_ = std::thread([this]() { beast::setCurrentThreadName("ETLService worker"); - if (state_.isReadOnly) { + if (state_.isStrictReadonly) { monitorReadOnly(); } else { monitor(); @@ -373,7 +379,7 @@ ETLService::ETLService( { startSequence_ = config.maybeValue("start_sequence"); finishSequence_ = config.maybeValue("finish_sequence"); - state_.isReadOnly = config.get("read_only"); + state_.isStrictReadonly = config.get("read_only"); extractorThreads_ = config.get("extractor_threads"); // This should probably be done in the backend factory but we don't have state available until here diff --git a/src/etl/ETLService.hpp b/src/etl/ETLService.hpp index af9a34429..09f5478ef 100644 --- a/src/etl/ETLService.hpp +++ b/src/etl/ETLService.hpp @@ -239,7 +239,7 @@ public: result["etl_sources"] = loadBalancer_->toJson(); result["is_writer"] = static_cast(state_.isWriting); - result["read_only"] = static_cast(state_.isReadOnly); + result["read_only"] = static_cast(state_.isStrictReadonly); auto last = ledgerPublisher_.getLastPublish(); if (last.time_since_epoch().count() != 0) result["last_publish_age_seconds"] = std::to_string(ledgerPublisher_.lastPublishAgeSeconds()); diff --git a/src/etl/LoadBalancer.hpp b/src/etl/LoadBalancer.hpp index b6eba6802..8edb01bb2 100644 --- a/src/etl/LoadBalancer.hpp +++ b/src/etl/LoadBalancer.hpp @@ -177,14 +177,14 @@ public: /** * @brief Load the initial ledger, writing data to the queue. - * @note This function will retry indefinitely until the ledger is downloaded. + * @note This function will retry indefinitely until the ledger is downloaded or the download is cancelled. * * @param sequence Sequence of ledger to download * @param observer The observer to notify of progress * @param retryAfter Time to wait between retries (2 seconds by default) - * @return A std::vector The ledger data + * @return A std::expected with ledger edge keys on success, or InitialLedgerLoadError on failure */ - std::vector + etlng::InitialLedgerLoadResult loadInitialLedger( [[maybe_unused]] uint32_t sequence, [[maybe_unused]] etlng::InitialLoadObserverInterface& observer, diff --git a/src/etl/SystemState.hpp b/src/etl/SystemState.hpp index fdfc6c6e7..7f841665f 100644 --- a/src/etl/SystemState.hpp +++ b/src/etl/SystemState.hpp @@ -37,7 +37,7 @@ struct SystemState { * In strict read-only mode, the process will never attempt to become the ETL writer, and will only publish ledgers * as they are written to the database. */ - util::prometheus::Bool isReadOnly = PrometheusService::boolMetric( + util::prometheus::Bool isStrictReadonly = PrometheusService::boolMetric( "read_only", util::prometheus::Labels{}, "Whether the process is in strict read-only mode" diff --git a/src/etl/impl/LedgerLoader.hpp b/src/etl/impl/LedgerLoader.hpp index 8eeb7553d..2ca7bdf18 100644 --- a/src/etl/impl/LedgerLoader.hpp +++ b/src/etl/impl/LedgerLoader.hpp @@ -242,8 +242,8 @@ public: } prev = cur->key; - static constexpr std::size_t kLOG_INTERVAL = 100000; - if (numWrites % kLOG_INTERVAL == 0 && numWrites != 0) + static constexpr std::size_t kLOG_STRIDE = 100000; + if (numWrites % kLOG_STRIDE == 0 && numWrites != 0) LOG(log_.info()) << "Wrote " << numWrites << " book successors"; } diff --git a/src/etlng/ETLService.cpp b/src/etlng/ETLService.cpp index 0d0eb7e51..64da45713 100644 --- a/src/etlng/ETLService.cpp +++ b/src/etlng/ETLService.cpp @@ -35,13 +35,13 @@ #include "etlng/LoadBalancerInterface.hpp" #include "etlng/LoaderInterface.hpp" #include "etlng/MonitorInterface.hpp" +#include "etlng/MonitorProviderInterface.hpp" #include "etlng/TaskManagerProviderInterface.hpp" #include "etlng/impl/AmendmentBlockHandler.hpp" #include "etlng/impl/CacheUpdater.hpp" #include "etlng/impl/Extraction.hpp" #include "etlng/impl/LedgerPublisher.hpp" #include "etlng/impl/Loading.hpp" -#include "etlng/impl/Monitor.hpp" #include "etlng/impl/Registry.hpp" #include "etlng/impl/Scheduling.hpp" #include "etlng/impl/TaskManager.hpp" @@ -57,6 +57,7 @@ #include #include #include +#include #include #include @@ -82,6 +83,7 @@ ETLService::ETLService( std::shared_ptr loader, std::shared_ptr initialLoadObserver, std::shared_ptr taskManagerProvider, + std::shared_ptr monitorProvider, std::shared_ptr state ) : ctx_(std::move(ctx)) @@ -96,9 +98,20 @@ ETLService::ETLService( , loader_(std::move(loader)) , initialLoadObserver_(std::move(initialLoadObserver)) , taskManagerProvider_(std::move(taskManagerProvider)) + , monitorProvider_(std::move(monitorProvider)) , state_(std::move(state)) + , startSequence_(config.get().maybeValue("start_sequence")) + , finishSequence_(config.get().maybeValue("finish_sequence")) { - LOG(log_.info()) << "Creating ETLng..."; + ASSERT(not state_->isWriting, "ETL should never start in writer mode"); + + if (startSequence_.has_value()) + LOG(log_.info()) << "Start sequence: " << *startSequence_; + + if (finishSequence_.has_value()) + LOG(log_.info()) << "Finish sequence: " << *finishSequence_; + + LOG(log_.info()) << "Starting in " << (state_->isStrictReadonly ? "STRICT READONLY MODE" : "WRITE MODE"); } ETLService::~ETLService() @@ -112,12 +125,7 @@ ETLService::run() { LOG(log_.info()) << "Running ETLng..."; - // TODO: write-enabled node should start in readonly and do the 10 second dance to become a writer mainLoop_.emplace(ctx_.execute([this] { - state_->isWriting = - not state_->isReadOnly; // TODO: this is now needed because we don't have a mechanism for readonly or - // ETL writer node. remove later in favor of real mechanism - auto const rng = loadInitialLedgerIfNeeded(); LOG(log_.info()) << "Waiting for next ledger to be validated by network..."; @@ -129,15 +137,18 @@ ETLService::run() return; } - ASSERT(rng.has_value(), "Ledger range can't be null"); + if (not rng.has_value()) { + LOG(log_.warn()) << "Initial ledger download got cancelled - stopping ETL service"; + return; + } + auto const nextSequence = rng->maxSequence + 1; LOG(log_.debug()) << "Database is populated. Starting monitor loop. sequence = " << nextSequence; startMonitor(nextSequence); - // TODO: we only want to run the full ETL task man if we are POSSIBLY a write node - // but definitely not in strict readonly - if (not state_->isReadOnly) + // If we are a writer as the result of loading the initial ledger - start loading + if (state_->isWriting) startLoading(nextSequence); })); } @@ -147,6 +158,8 @@ ETLService::stop() { LOG(log_.info()) << "Stop called"; + if (mainLoop_) + mainLoop_->wait(); if (taskMan_) taskMan_->stop(); if (monitor_) @@ -160,7 +173,7 @@ ETLService::getInfo() const result["etl_sources"] = balancer_->toJson(); result["is_writer"] = static_cast(state_->isWriting); - result["read_only"] = static_cast(state_->isReadOnly); + result["read_only"] = static_cast(state_->isStrictReadonly); auto last = publisher_->getLastPublish(); if (last.time_since_epoch().count() != 0) result["last_publish_age_seconds"] = std::to_string(publisher_->lastPublishAgeSeconds()); @@ -196,21 +209,40 @@ ETLService::loadInitialLedgerIfNeeded() { auto rng = backend_->hardFetchLedgerRangeNoThrow(); if (not rng.has_value()) { - LOG(log_.info()) << "Database is empty. Will download a ledger from the network."; + ASSERT( + not state_->isStrictReadonly, + "Database is empty but this node is in strict readonly mode. Can't write initial ledger." + ); - LOG(log_.info()) << "Waiting for next ledger to be validated by network..."; - if (auto const mostRecentValidated = ledgers_->getMostRecent(); mostRecentValidated.has_value()) { - auto const seq = *mostRecentValidated; - LOG(log_.info()) << "Ledger " << seq << " has been validated. Downloading... "; + LOG(log_.info()) << "Database is empty. Will download a ledger from the network."; + state_->isWriting = true; // immediately become writer as the db is empty + + auto const getMostRecent = [this]() { + LOG(log_.info()) << "Waiting for next ledger to be validated by network..."; + return ledgers_->getMostRecent(); + }; + + if (auto const maybeSeq = startSequence_.or_else(getMostRecent); maybeSeq.has_value()) { + auto const seq = *maybeSeq; + LOG(log_.info()) << "Starting from sequence " << seq + << ". Initial ledger download and extraction can take a while..."; auto [ledger, timeDiff] = ::util::timed>([this, seq]() { - return extractor_->extractLedgerOnly(seq).and_then([this, seq](auto&& data) { - // TODO: loadInitialLedger in balancer should be called fetchEdgeKeys or similar - data.edgeKeys = balancer_->loadInitialLedger(seq, *initialLoadObserver_); + return extractor_->extractLedgerOnly(seq).and_then( + [this, seq](auto&& data) -> std::optional { + // TODO: loadInitialLedger in balancer should be called fetchEdgeKeys or similar + auto res = balancer_->loadInitialLedger(seq, *initialLoadObserver_); + if (not res.has_value() and res.error() == InitialLedgerLoadError::Cancelled) { + LOG(log_.debug()) << "Initial ledger load got cancelled"; + return std::nullopt; + } - // TODO: this should be interruptible for graceful shutdown - return loader_->loadInitialLedger(data); - }); + ASSERT(res.has_value(), "Initial ledger retry logic failed"); + data.edgeKeys = std::move(res).value(); + + return loader_->loadInitialLedger(data); + } + ); }); if (not ledger.has_value()) { @@ -238,28 +270,64 @@ ETLService::loadInitialLedgerIfNeeded() void ETLService::startMonitor(uint32_t seq) { - monitor_ = std::make_unique(ctx_, backend_, ledgers_, seq); - monitorSubscription_ = monitor_->subscribe([this](uint32_t seq) { - log_.info() << "MONITOR got new seq from db: " << seq; + monitor_ = monitorProvider_->make(ctx_, backend_, ledgers_, seq); + + monitorNewSeqSubscription_ = monitor_->subscribeToNewSequence([this](uint32_t seq) { + LOG(log_.info()) << "ETLService (via Monitor) got new seq from db: " << seq; + + if (state_->writeConflict) { + LOG(log_.info()) << "Got a write conflict; Giving up writer seat immediately"; + giveUpWriter(); + } - // FIXME: is this the best way? if (not state_->isWriting) { auto const diff = data::synchronousAndRetryOnTimeout([this, seq](auto yield) { return backend_->fetchLedgerDiff(seq, yield); }); + cacheUpdater_->update(seq, diff); + backend_->updateRange(seq); } publisher_->publish(seq, {}); }); + + monitorDbStalledSubscription_ = monitor_->subscribeToDbStalled([this]() { + LOG(log_.warn()) << "ETLService received DbStalled signal from Monitor"; + if (not state_->isStrictReadonly and not state_->isWriting) + attemptTakeoverWriter(); + }); + monitor_->run(); } void ETLService::startLoading(uint32_t seq) { - taskMan_ = taskManagerProvider_->make(ctx_, *monitor_, seq); + ASSERT(not state_->isStrictReadonly, "This should only happen on writer nodes"); + taskMan_ = taskManagerProvider_->make(ctx_, *monitor_, seq, finishSequence_); taskMan_->run(config_.get().get("extractor_threads")); } +void +ETLService::attemptTakeoverWriter() +{ + ASSERT(not state_->isStrictReadonly, "This should only happen on writer nodes"); + auto rng = backend_->hardFetchLedgerRangeNoThrow(); + ASSERT(rng.has_value(), "Ledger range can't be null"); + + state_->isWriting = true; // switch to writer + LOG(log_.info()) << "Taking over the ETL writer seat"; + startLoading(rng->maxSequence + 1); +} + +void +ETLService::giveUpWriter() +{ + ASSERT(not state_->isStrictReadonly, "This should only happen on writer nodes"); + state_->isWriting = false; + state_->writeConflict = false; + taskMan_ = nullptr; +} + } // namespace etlng diff --git a/src/etlng/ETLService.hpp b/src/etlng/ETLService.hpp index 168db6f90..4dc061e1b 100644 --- a/src/etlng/ETLService.hpp +++ b/src/etlng/ETLService.hpp @@ -35,6 +35,7 @@ #include "etlng/LoadBalancerInterface.hpp" #include "etlng/LoaderInterface.hpp" #include "etlng/MonitorInterface.hpp" +#include "etlng/MonitorProviderInterface.hpp" #include "etlng/TaskManagerInterface.hpp" #include "etlng/TaskManagerProviderInterface.hpp" #include "etlng/impl/AmendmentBlockHandler.hpp" @@ -42,7 +43,6 @@ #include "etlng/impl/Extraction.hpp" #include "etlng/impl/LedgerPublisher.hpp" #include "etlng/impl/Loading.hpp" -#include "etlng/impl/Monitor.hpp" #include "etlng/impl/Registry.hpp" #include "etlng/impl/Scheduling.hpp" #include "etlng/impl/TaskManager.hpp" @@ -106,12 +106,17 @@ class ETLService : public ETLServiceInterface { std::shared_ptr loader_; std::shared_ptr initialLoadObserver_; std::shared_ptr taskManagerProvider_; + std::shared_ptr monitorProvider_; std::shared_ptr state_; + std::optional startSequence_; + std::optional finishSequence_; + std::unique_ptr monitor_; std::unique_ptr taskMan_; - boost::signals2::scoped_connection monitorSubscription_; + boost::signals2::scoped_connection monitorNewSeqSubscription_; + boost::signals2::scoped_connection monitorDbStalledSubscription_; std::optional> mainLoop_; @@ -131,6 +136,7 @@ public: * @param loader Interface for loading data * @param initialLoadObserver The observer for initial data loading * @param taskManagerProvider The provider of the task manager instance + * @param monitorProvider The provider of the monitor instance * @param state System state tracking object */ ETLService( @@ -146,6 +152,7 @@ public: std::shared_ptr loader, std::shared_ptr initialLoadObserver, std::shared_ptr taskManagerProvider, + std::shared_ptr monitorProvider, std::shared_ptr state ); @@ -173,7 +180,6 @@ public: lastCloseAgeSeconds() const override; private: - // TODO: this better be std::expected std::optional loadInitialLedgerIfNeeded(); @@ -182,6 +188,12 @@ private: void startLoading(uint32_t seq); + + void + attemptTakeoverWriter(); + + void + giveUpWriter(); }; } // namespace etlng diff --git a/src/etlng/LoadBalancer.cpp b/src/etlng/LoadBalancer.cpp index ff66bb7a4..50b86fe9d 100644 --- a/src/etlng/LoadBalancer.cpp +++ b/src/etlng/LoadBalancer.cpp @@ -210,30 +210,32 @@ LoadBalancer::LoadBalancer( } } -std::vector +InitialLedgerLoadResult LoadBalancer::loadInitialLedger( uint32_t sequence, etlng::InitialLoadObserverInterface& loadObserver, std::chrono::steady_clock::duration retryAfter ) { - std::vector response; + InitialLedgerLoadResult response; + execute( [this, &response, &sequence, &loadObserver](auto& source) { - auto [data, res] = source->loadInitialLedger(sequence, downloadRanges_, loadObserver); + auto res = source->loadInitialLedger(sequence, downloadRanges_, loadObserver); - if (!res) { + if (not res.has_value() and res.error() == InitialLedgerLoadError::Errored) { LOG(log_.error()) << "Failed to download initial ledger." << " Sequence = " << sequence << " source = " << source->toString(); - } else { - response = std::move(data); + return false; // should retry on error } - return res; + response = std::move(res); // cancelled or data received + return true; }, sequence, retryAfter ); + return response; } diff --git a/src/etlng/LoadBalancer.hpp b/src/etlng/LoadBalancer.hpp index 10c48e2ff..ebbb302b0 100644 --- a/src/etlng/LoadBalancer.hpp +++ b/src/etlng/LoadBalancer.hpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -183,14 +184,14 @@ public: /** * @brief Load the initial ledger, writing data to the queue. - * @note This function will retry indefinitely until the ledger is downloaded. + * @note This function will retry indefinitely until the ledger is downloaded or the download is cancelled. * * @param sequence Sequence of ledger to download * @param observer The observer to notify of progress * @param retryAfter Time to wait between retries (2 seconds by default) - * @return A std::vector The ledger data + * @return A std::expected with ledger edge keys on success, or InitialLedgerLoadError on failure */ - std::vector + InitialLedgerLoadResult loadInitialLedger( uint32_t sequence, etlng::InitialLoadObserverInterface& observer, diff --git a/src/etlng/LoadBalancerInterface.hpp b/src/etlng/LoadBalancerInterface.hpp index 200bbb3f3..eeb4bd3da 100644 --- a/src/etlng/LoadBalancerInterface.hpp +++ b/src/etlng/LoadBalancerInterface.hpp @@ -39,6 +39,20 @@ namespace etlng { +/** + * @brief Represents possible errors for initial ledger load + */ +enum class InitialLedgerLoadError { + Cancelled, /*< Indicating the initial load got cancelled by user */ + Errored, /*< Indicating some error happened during initial ledger load */ +}; + +/** + * @brief The result type of the initial ledger load + * @note The successful value represents edge keys + */ +using InitialLedgerLoadResult = std::expected, InitialLedgerLoadError>; + /** * @brief An interface for LoadBalancer */ @@ -52,14 +66,14 @@ public: /** * @brief Load the initial ledger, writing data to the queue. - * @note This function will retry indefinitely until the ledger is downloaded. + * @note This function will retry indefinitely until the ledger is downloaded or the download is cancelled. * * @param sequence Sequence of ledger to download * @param loader InitialLoadObserverInterface implementation * @param retryAfter Time to wait between retries (2 seconds by default) - * @return A std::vector The ledger data + * @return A std::expected with ledger edge keys on success, or InitialLedgerLoadError on failure */ - virtual std::vector + [[nodiscard]] virtual InitialLedgerLoadResult loadInitialLedger( uint32_t sequence, etlng::InitialLoadObserverInterface& loader, @@ -74,7 +88,7 @@ public: * @param retryAfter Time to wait between retries (2 seconds by default) * @return A std::vector The ledger data */ - virtual std::vector + [[nodiscard]] virtual std::vector loadInitialLedger(uint32_t sequence, std::chrono::steady_clock::duration retryAfter = std::chrono::seconds{2}) = 0; /** @@ -90,7 +104,7 @@ public: * @return The extracted data, if extraction was successful. If the ledger was found * in the database or the server is shutting down, the optional will be empty */ - virtual OptionalGetLedgerResponseType + [[nodiscard]] virtual OptionalGetLedgerResponseType fetchLedger( uint32_t ledgerSequence, bool getObjects, @@ -103,7 +117,7 @@ public: * * @return JSON representation of the state of this load balancer. */ - virtual boost::json::value + [[nodiscard]] virtual boost::json::value toJson() const = 0; /** @@ -115,7 +129,7 @@ public: * @param yield The coroutine context * @return Response received from rippled node as JSON object on success or error on failure */ - virtual std::expected + [[nodiscard]] virtual std::expected forwardToRippled( boost::json::object const& request, std::optional const& clientIp, @@ -127,7 +141,7 @@ public: * @brief Return state of ETL nodes. * @return ETL state, nullopt if etl nodes not available */ - virtual std::optional + [[nodiscard]] virtual std::optional getETLState() noexcept = 0; /** diff --git a/src/etlng/LoaderInterface.hpp b/src/etlng/LoaderInterface.hpp index 72cad3192..599982eab 100644 --- a/src/etlng/LoaderInterface.hpp +++ b/src/etlng/LoaderInterface.hpp @@ -23,10 +23,19 @@ #include +#include #include namespace etlng { +/** + * @brief Enumeration of possible errors that can occur during loading operations + */ +enum class LoaderError { + AmendmentBlocked, /*< Error indicating that an operation is blocked by an amendment */ + WriteConflict, /*< Error indicating that a write operation resulted in a conflict */ +}; + /** * @brief An interface for a ETL Loader */ @@ -36,8 +45,9 @@ struct LoaderInterface { /** * @brief Load ledger data * @param data The data to load + * @return Nothing or error as std::expected */ - virtual void + [[nodiscard]] virtual std::expected load(model::LedgerData const& data) = 0; /** diff --git a/src/etlng/MonitorInterface.hpp b/src/etlng/MonitorInterface.hpp index dfae93447..136455345 100644 --- a/src/etlng/MonitorInterface.hpp +++ b/src/etlng/MonitorInterface.hpp @@ -36,7 +36,8 @@ namespace etlng { class MonitorInterface { public: static constexpr auto kDEFAULT_REPEAT_INTERVAL = std::chrono::seconds{1}; - using SignalType = boost::signals2::signal; + using NewSequenceSignalType = boost::signals2::signal; + using DbStalledSignalType = boost::signals2::signal; virtual ~MonitorInterface() = default; @@ -45,7 +46,14 @@ public: * @param seq The ledger sequence loaded */ virtual void - notifyLedgerLoaded(uint32_t seq) = 0; + notifySequenceLoaded(uint32_t seq) = 0; + + /** + * @brief Notifies the monitor of a write conflict + * @param seq The sequence number of the ledger that encountered a write conflict + */ + virtual void + notifyWriteConflict(uint32_t seq) = 0; /** * @brief Allows clients to get notified when a new ledger becomes available in Clio's database @@ -54,7 +62,16 @@ public: * @return A connection object that automatically disconnects the subscription once destroyed */ [[nodiscard]] virtual boost::signals2::scoped_connection - subscribe(SignalType::slot_type const& subscriber) = 0; + subscribeToNewSequence(NewSequenceSignalType::slot_type const& subscriber) = 0; + + /** + * @brief Allows clients to get notified when no database update is detected for a configured period. + * + * @param subscriber The slot to connect + * @return A connection object that automatically disconnects the subscription once destroyed + */ + [[nodiscard]] virtual boost::signals2::scoped_connection + subscribeToDbStalled(DbStalledSignalType::slot_type const& subscriber) = 0; /** * @brief Run the monitor service diff --git a/src/etlng/MonitorProviderInterface.hpp b/src/etlng/MonitorProviderInterface.hpp new file mode 100644 index 000000000..f92b1a43a --- /dev/null +++ b/src/etlng/MonitorProviderInterface.hpp @@ -0,0 +1,64 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2025, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include "data/BackendInterface.hpp" +#include "etl/NetworkValidatedLedgersInterface.hpp" +#include "etlng/MonitorInterface.hpp" +#include "util/async/AnyExecutionContext.hpp" + +#include +#include +#include + +namespace etlng { + +/** + * @brief An interface for providing Monitor instances + */ +struct MonitorProviderInterface { + /** + * @brief The time Monitor should wait before reporting absence of updates to the database + */ + static constexpr auto kDEFAULT_DB_STALLED_REPORT_DELAY = std::chrono::seconds{10}; + + virtual ~MonitorProviderInterface() = default; + + /** + * @brief Create a new Monitor instance + * + * @param ctx The execution context for asynchronous operations + * @param backend Interface to the backend database + * @param validatedLedgers Interface for accessing network validated ledgers + * @param startSequence The sequence number to start monitoring from + * @param dbStalledReportDelay The timeout duration after which to signal no database updates + * @return A unique pointer to a Monitor implementation + */ + [[nodiscard]] virtual std::unique_ptr + make( + util::async::AnyExecutionContext ctx, + std::shared_ptr backend, + std::shared_ptr validatedLedgers, + uint32_t startSequence, + std::chrono::steady_clock::duration dbStalledReportDelay = kDEFAULT_DB_STALLED_REPORT_DELAY + ) = 0; +}; + +} // namespace etlng diff --git a/src/etlng/Source.cpp b/src/etlng/Source.cpp index fb6fa6df9..6f40d3ffa 100644 --- a/src/etlng/Source.cpp +++ b/src/etlng/Source.cpp @@ -20,8 +20,8 @@ #include "etlng/Source.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" -#include "etl/impl/ForwardingSource.hpp" #include "etl/impl/SubscriptionSource.hpp" +#include "etlng/impl/ForwardingSource.hpp" #include "etlng/impl/GrpcSource.hpp" #include "etlng/impl/SourceImpl.hpp" #include "feed/SubscriptionManagerInterface.hpp" @@ -52,7 +52,7 @@ makeSource( auto const wsPort = config.get("ws_port"); auto const grpcPort = config.get("grpc_port"); - etl::impl::ForwardingSource forwardingSource{ip, wsPort, forwardingTimeout}; + etlng::impl::ForwardingSource forwardingSource{ip, wsPort, forwardingTimeout}; impl::GrpcSource grpcSource{ip, grpcPort}; auto subscriptionSource = std::make_unique( ioc, diff --git a/src/etlng/Source.hpp b/src/etlng/Source.hpp index 3e84ce568..ce75a37f4 100644 --- a/src/etlng/Source.hpp +++ b/src/etlng/Source.hpp @@ -19,9 +19,9 @@ #pragma once -#include "data/BackendInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" #include "etlng/InitialLoadObserverInterface.hpp" +#include "etlng/LoadBalancerInterface.hpp" #include "feed/SubscriptionManagerInterface.hpp" #include "rpc/Errors.hpp" #include "util/config/ObjectView.hpp" @@ -131,7 +131,7 @@ public: * @param loader InitialLoadObserverInterface implementation * @return A std::pair of the data and a bool indicating whether the download was successful */ - virtual std::pair, bool> + virtual InitialLedgerLoadResult loadInitialLedger(uint32_t sequence, std::uint32_t numMarkers, etlng::InitialLoadObserverInterface& loader) = 0; /** diff --git a/src/etlng/TaskManagerProviderInterface.hpp b/src/etlng/TaskManagerProviderInterface.hpp index 2894170bb..1cf9843db 100644 --- a/src/etlng/TaskManagerProviderInterface.hpp +++ b/src/etlng/TaskManagerProviderInterface.hpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace etlng { @@ -41,11 +42,17 @@ struct TaskManagerProviderInterface { * * @param ctx The async context to associate the task manager instance with * @param monitor The monitor to notify when ledger is loaded - * @param seq The sequence to start at + * @param startSeq The sequence to start at + * @param finishSeq The sequence to stop at if specified * @return A unique pointer to a TaskManager implementation */ - virtual std::unique_ptr - make(util::async::AnyExecutionContext ctx, std::reference_wrapper monitor, uint32_t seq) = 0; + [[nodiscard]] virtual std::unique_ptr + make( + util::async::AnyExecutionContext ctx, + std::reference_wrapper monitor, + uint32_t startSeq, + std::optional finishSeq = std::nullopt + ) = 0; }; } // namespace etlng diff --git a/src/etlng/impl/GrpcSource.cpp b/src/etlng/impl/GrpcSource.cpp index a537bcd08..5b121687e 100644 --- a/src/etlng/impl/GrpcSource.cpp +++ b/src/etlng/impl/GrpcSource.cpp @@ -20,11 +20,13 @@ #include "etlng/impl/GrpcSource.hpp" #include "etlng/InitialLoadObserverInterface.hpp" +#include "etlng/LoadBalancerInterface.hpp" #include "etlng/impl/AsyncGrpcCall.hpp" #include "util/Assert.hpp" #include "util/log/Logger.hpp" #include "web/Resolver.hpp" +#include #include #include #include @@ -33,9 +35,12 @@ #include #include +#include #include #include #include +#include +#include #include #include #include @@ -60,6 +65,7 @@ namespace etlng::impl { GrpcSource::GrpcSource(std::string const& ip, std::string const& grpcPort) : log_(fmt::format("ETL_Grpc[{}:{}]", ip, grpcPort)) + , initialLoadShouldStop_(std::make_unique(false)) { try { grpc::ChannelArguments chArgs; @@ -103,15 +109,18 @@ GrpcSource::fetchLedger(uint32_t sequence, bool getObjects, bool getObjectNeighb return {status, std::move(response)}; } -std::pair, bool> +InitialLedgerLoadResult GrpcSource::loadInitialLedger( uint32_t const sequence, uint32_t const numMarkers, etlng::InitialLoadObserverInterface& observer ) { + if (*initialLoadShouldStop_) + return std::unexpected{InitialLedgerLoadError::Cancelled}; + if (!stub_) - return {{}, false}; + return std::unexpected{InitialLedgerLoadError::Errored}; std::vector calls = AsyncGrpcCall::makeAsyncCalls(sequence, numMarkers); @@ -131,9 +140,9 @@ GrpcSource::loadInitialLedger( ASSERT(tag != nullptr, "Tag can't be null."); auto ptr = static_cast(tag); - if (!ok) { - LOG(log_.error()) << "loadInitialLedger - ok is false"; - return {{}, false}; // cancelled + if (not ok or *initialLoadShouldStop_) { + LOG(log_.error()) << "loadInitialLedger cancelled"; + return std::unexpected{InitialLedgerLoadError::Cancelled}; } LOG(log_.trace()) << "Marker prefix = " << ptr->getMarkerPrefix(); @@ -151,7 +160,16 @@ GrpcSource::loadInitialLedger( abort = true; } - return {std::move(edgeKeys), !abort}; + if (abort) + return std::unexpected{InitialLedgerLoadError::Errored}; + + return edgeKeys; +} + +void +GrpcSource::stop(boost::asio::yield_context) +{ + initialLoadShouldStop_->store(true); } } // namespace etlng::impl diff --git a/src/etlng/impl/GrpcSource.hpp b/src/etlng/impl/GrpcSource.hpp index 0111f3330..3f177e656 100644 --- a/src/etlng/impl/GrpcSource.hpp +++ b/src/etlng/impl/GrpcSource.hpp @@ -20,23 +20,26 @@ #pragma once #include "etlng/InitialLoadObserverInterface.hpp" +#include "etlng/LoadBalancerInterface.hpp" #include "util/log/Logger.hpp" +#include #include #include #include +#include #include #include #include #include -#include namespace etlng::impl { class GrpcSource { util::Logger log_; std::unique_ptr stub_; + std::unique_ptr initialLoadShouldStop_; public: GrpcSource(std::string const& ip, std::string const& grpcPort); @@ -61,10 +64,18 @@ public: * @param sequence Sequence of the ledger to download * @param numMarkers Number of markers to generate for async calls * @param observer InitialLoadObserverInterface implementation - * @return A std::pair of the data and a bool indicating whether the download was successful + * @return Downloaded data or an indication of error or cancellation */ - std::pair, bool> + InitialLedgerLoadResult loadInitialLedger(uint32_t sequence, uint32_t numMarkers, etlng::InitialLoadObserverInterface& observer); + + /** + * @brief Stop any ongoing operations + * @note This is used to cancel any ongoing initial ledger downloads + * @param yield The coroutine context + */ + void + stop(boost::asio::yield_context yield); }; } // namespace etlng::impl diff --git a/src/etlng/impl/LedgerPublisher.hpp b/src/etlng/impl/LedgerPublisher.hpp index 2c0d9ed7f..efd51ba80 100644 --- a/src/etlng/impl/LedgerPublisher.hpp +++ b/src/etlng/impl/LedgerPublisher.hpp @@ -21,7 +21,6 @@ #include "data/BackendInterface.hpp" #include "data/DBHelpers.hpp" -#include "data/Types.hpp" #include "etl/SystemState.hpp" #include "etlng/LedgerPublisherInterface.hpp" #include "etlng/impl/Loading.hpp" @@ -35,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -164,10 +164,6 @@ public: boost::asio::post(publishStrand_, [this, lgrInfo = lgrInfo]() { LOG(log_.info()) << "Publishing ledger " << std::to_string(lgrInfo.seq); - // TODO: This should probably not be part of publisher in the future - if (not state_.get().isWriting) - backend_->updateRange(lgrInfo.seq); // This can't be unit tested atm. - setLastClose(lgrInfo.closeTime); auto age = lastCloseAgeSeconds(); diff --git a/src/etlng/impl/Loading.cpp b/src/etlng/impl/Loading.cpp index 701fe3597..4914d7a41 100644 --- a/src/etlng/impl/Loading.cpp +++ b/src/etlng/impl/Loading.cpp @@ -20,11 +20,14 @@ #include "etlng/impl/Loading.hpp" #include "data/BackendInterface.hpp" +#include "etl/SystemState.hpp" #include "etl/impl/LedgerLoader.hpp" #include "etlng/AmendmentBlockHandlerInterface.hpp" +#include "etlng/LoaderInterface.hpp" #include "etlng/Models.hpp" #include "etlng/RegistryInterface.hpp" #include "util/Assert.hpp" +#include "util/Constants.hpp" #include "util/LedgerUtils.hpp" #include "util/Profiler.hpp" #include "util/log/Logger.hpp" @@ -46,29 +49,45 @@ namespace etlng::impl { Loader::Loader( std::shared_ptr backend, std::shared_ptr registry, - std::shared_ptr amendmentBlockHandler + std::shared_ptr amendmentBlockHandler, + std::shared_ptr state ) : backend_(std::move(backend)) , registry_(std::move(registry)) , amendmentBlockHandler_(std::move(amendmentBlockHandler)) + , state_(std::move(state)) { } -void +std::expected Loader::load(model::LedgerData const& data) { try { - // perform cache updates and all writes from extensions + // Perform cache updates and all writes from extensions + // TODO: maybe this readonly logic should be removed? registry_->dispatch(data); - auto [success, duration] = - ::util::timed>([&]() { return backend_->finishWrites(data.seq); }); - LOG(log_.info()) << "Finished writes to DB for " << data.seq << ": " << (success ? "YES" : "NO") << "; took " - << duration; + // Only a writer should attempt to commit to DB + // This is also where conflicts with other writer nodes will be detected + if (state_->isWriting) { + auto [success, duration] = + ::util::timed([&]() { return backend_->finishWrites(data.seq); }); + LOG(log_.info()) << "Finished writes to DB for " << data.seq << ": " << (success ? "YES" : "NO") + << "; took " << duration << "ms"; + + if (not success) { + state_->writeConflict = true; + LOG(log_.warn()) << "Another node wrote a ledger into the DB - we have a write conflict"; + return std::unexpected(LoaderError::WriteConflict); + } + } } catch (std::runtime_error const& e) { LOG(log_.fatal()) << "Failed to load " << data.seq << ": " << e.what(); amendmentBlockHandler_->notifyAmendmentBlocked(); + return std::unexpected(LoaderError::AmendmentBlocked); } + + return {}; }; void @@ -78,13 +97,32 @@ Loader::onInitialLoadGotMoreObjects( std::optional lastKey ) { + static constexpr std::size_t kLOG_STRIDE = 1000u; + static auto kINITIAL_LOAD_START_TIME = std::chrono::steady_clock::now(); + try { - LOG(log_.debug()) << "On initial load: got more objects for seq " << seq << ". size = " << data.size(); + LOG(log_.trace()) << "On initial load: got more objects for seq " << seq << ". size = " << data.size(); registry_->dispatchInitialObjects( seq, data, std::move(lastKey).value_or(std::string{}) // TODO: perhaps use optional all the way to extensions? ); + + initialLoadWrittenObjects_ += data.size(); + ++initialLoadWrites_; + if (initialLoadWrites_ % kLOG_STRIDE == 0u && initialLoadWrites_ != 0u) { + auto elapsedSinceStart = std::chrono::duration_cast( + std::chrono::steady_clock::now() - kINITIAL_LOAD_START_TIME + ); + auto elapsedSeconds = elapsedSinceStart.count() / static_cast(util::kMILLISECONDS_PER_SECOND); + auto objectsPerSecond = + elapsedSeconds > 0.0 ? static_cast(initialLoadWrittenObjects_) / elapsedSeconds : 0.0; + + LOG(log_.info()) << "Wrote " << initialLoadWrittenObjects_ + << " initial ledger objects so far with average rate of " << objectsPerSecond + << " objects per second"; + } + } catch (std::runtime_error const& e) { LOG(log_.fatal()) << "Failed to load initial objects for " << seq << ": " << e.what(); amendmentBlockHandler_->notifyAmendmentBlocked(); @@ -95,9 +133,7 @@ std::optional Loader::loadInitialLedger(model::LedgerData const& data) { try { - // check that database is actually empty - auto rng = backend_->hardFetchLedgerRangeNoThrow(); - if (rng) { + if (auto const rng = backend_->hardFetchLedgerRangeNoThrow(); rng.has_value()) { ASSERT(false, "Database is not empty"); return std::nullopt; } diff --git a/src/etlng/impl/Loading.hpp b/src/etlng/impl/Loading.hpp index 39a1e150a..19b1c381b 100644 --- a/src/etlng/impl/Loading.hpp +++ b/src/etlng/impl/Loading.hpp @@ -20,7 +20,7 @@ #pragma once #include "data/BackendInterface.hpp" -#include "etl/LedgerFetcherInterface.hpp" +#include "etl/SystemState.hpp" #include "etl/impl/LedgerLoader.hpp" #include "etlng/AmendmentBlockHandlerInterface.hpp" #include "etlng/InitialLoadObserverInterface.hpp" @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -51,7 +52,10 @@ class Loader : public LoaderInterface, public InitialLoadObserverInterface { std::shared_ptr backend_; std::shared_ptr registry_; std::shared_ptr amendmentBlockHandler_; + std::shared_ptr state_; + std::size_t initialLoadWrittenObjects_{0u}; + std::size_t initialLoadWrites_{0u}; util::Logger log_{"ETL"}; public: @@ -62,7 +66,8 @@ public: Loader( std::shared_ptr backend, std::shared_ptr registry, - std::shared_ptr amendmentBlockHandler + std::shared_ptr amendmentBlockHandler, + std::shared_ptr state ); Loader(Loader const&) = delete; @@ -72,7 +77,7 @@ public: Loader& operator=(Loader&&) = delete; - void + std::expected load(model::LedgerData const& data) override; void diff --git a/src/etlng/impl/Monitor.cpp b/src/etlng/impl/Monitor.cpp index e55eb3456..241eaa8d9 100644 --- a/src/etlng/impl/Monitor.cpp +++ b/src/etlng/impl/Monitor.cpp @@ -23,11 +23,11 @@ #include "etl/NetworkValidatedLedgersInterface.hpp" #include "util/Assert.hpp" #include "util/async/AnyExecutionContext.hpp" -#include "util/async/AnyOperation.hpp" #include "util/log/Logger.hpp" #include +#include #include #include #include @@ -41,12 +41,18 @@ Monitor::Monitor( util::async::AnyExecutionContext ctx, std::shared_ptr backend, std::shared_ptr validatedLedgers, - uint32_t startSequence + uint32_t startSequence, + std::chrono::steady_clock::duration dbStalledReportDelay ) : strand_(ctx.makeStrand()) , backend_(std::move(backend)) , validatedLedgers_(std::move(validatedLedgers)) , nextSequence_(startSequence) + , updateData_({ + .dbStalledReportDelay = dbStalledReportDelay, + .lastDbCheckTime = std::chrono::steady_clock::now(), + .lastSeenMaxSeqInDb = startSequence > 0 ? startSequence - 1 : 0, + }) { } @@ -55,20 +61,37 @@ Monitor::~Monitor() stop(); } -// TODO: think about using signals perhaps? maybe combining with onNextSequence? -// also, how do we not double invoke or does it not matter void -Monitor::notifyLedgerLoaded(uint32_t seq) +Monitor::notifySequenceLoaded(uint32_t seq) { - LOG(log_.debug()) << "Loader notified about newly committed ledger " << seq; - repeatedTask_->invoke(); // force-invoke immediately + LOG(log_.debug()) << "Loader notified Monitor about newly committed ledger " << seq; + { + auto lck = updateData_.lock(); + lck->lastSeenMaxSeqInDb = std::max(seq, lck->lastSeenMaxSeqInDb); + lck->lastDbCheckTime = std::chrono::steady_clock::now(); + } + repeatedTask_->invoke(); // force-invoke doWork immediately }; +void +Monitor::notifyWriteConflict(uint32_t seq) +{ + LOG(log_.warn()) << "Loader notified Monitor about write conflict at " << seq; + nextSequence_ = seq + 1; // we already loaded the cache for seq just before we detected conflict + LOG(log_.warn()) << "Resume monitoring from " << nextSequence_; +} + void Monitor::run(std::chrono::steady_clock::duration repeatInterval) { ASSERT(not repeatedTask_.has_value(), "Monitor attempted to run more than once"); - LOG(log_.debug()) << "Starting monitor"; + { + auto lck = updateData_.lock(); + LOG(log_.debug()) << "Starting monitor with repeat interval: " + << std::chrono::duration_cast(repeatInterval).count() + << "s and dbStalledReportDelay: " + << std::chrono::duration_cast(lck->dbStalledReportDelay).count() << "s"; + } repeatedTask_ = strand_.executeRepeatedly(repeatInterval, std::bind_front(&Monitor::doWork, this)); subscription_ = validatedLedgers_->subscribe(std::bind_front(&Monitor::onNextSequence, this)); @@ -80,28 +103,65 @@ Monitor::stop() if (repeatedTask_.has_value()) repeatedTask_->abort(); + subscription_ = std::nullopt; repeatedTask_ = std::nullopt; } boost::signals2::scoped_connection -Monitor::subscribe(SignalType::slot_type const& subscriber) +Monitor::subscribeToNewSequence(NewSequenceSignalType::slot_type const& subscriber) { return notificationChannel_.connect(subscriber); } +boost::signals2::scoped_connection +Monitor::subscribeToDbStalled(DbStalledSignalType::slot_type const& subscriber) +{ + return dbStalledChannel_.connect(subscriber); +} + void Monitor::onNextSequence(uint32_t seq) { - LOG(log_.debug()) << "rippled published sequence " << seq; + ASSERT(repeatedTask_.has_value(), "Ledger subscription without repeated task is a logic error"); + LOG(log_.debug()) << "Notified about new sequence on the network: " << seq; repeatedTask_->invoke(); // force-invoke immediately } void Monitor::doWork() { - if (auto rng = backend_->hardFetchLedgerRangeNoThrow(); rng) { - while (rng->maxSequence >= nextSequence_) + auto rng = backend_->hardFetchLedgerRangeNoThrow(); + bool dbProgressedThisCycle = false; + auto lck = updateData_.lock(); + + if (rng.has_value()) { + if (rng->maxSequence > lck->lastSeenMaxSeqInDb) { + LOG(log_.trace()) << "DB progressed. Old max seq = " << lck->lastSeenMaxSeqInDb + << ", new max seq = " << rng->maxSequence; + lck->lastSeenMaxSeqInDb = rng->maxSequence; + dbProgressedThisCycle = true; + } + + while (lck->lastSeenMaxSeqInDb >= nextSequence_) { + LOG(log_.trace()) << "Publishing from Monitor::doWork. nextSequence_ = " << nextSequence_ + << ", lastSeenMaxSeqInDb_ = " << lck->lastSeenMaxSeqInDb; notificationChannel_(nextSequence_++); + dbProgressedThisCycle = true; + } + } else { + LOG(log_.trace()) << "DB range is not available or empty. lastSeenMaxSeqInDb_ = " << lck->lastSeenMaxSeqInDb + << ", nextSequence_ = " << nextSequence_; + } + + if (dbProgressedThisCycle) { + lck->lastDbCheckTime = std::chrono::steady_clock::now(); + } else if (std::chrono::steady_clock::now() - lck->lastDbCheckTime > lck->dbStalledReportDelay) { + LOG(log_.info()) << "No DB update detected for " + << std::chrono::duration_cast(lck->dbStalledReportDelay).count() + << " seconds. Firing dbStalledChannel. Last seen max seq in DB: " << lck->lastSeenMaxSeqInDb + << ". Expecting next: " << nextSequence_; + dbStalledChannel_(); + lck->lastDbCheckTime = std::chrono::steady_clock::now(); } } diff --git a/src/etlng/impl/Monitor.hpp b/src/etlng/impl/Monitor.hpp index b8971bc84..c59e60639 100644 --- a/src/etlng/impl/Monitor.hpp +++ b/src/etlng/impl/Monitor.hpp @@ -22,6 +22,7 @@ #include "data/BackendInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" #include "etlng/MonitorInterface.hpp" +#include "util/Mutex.hpp" #include "util/async/AnyExecutionContext.hpp" #include "util/async/AnyOperation.hpp" #include "util/async/AnyStrand.hpp" @@ -30,6 +31,7 @@ #include #include +#include #include #include #include @@ -43,11 +45,20 @@ class Monitor : public MonitorInterface { std::shared_ptr backend_; std::shared_ptr validatedLedgers_; - uint32_t nextSequence_; + std::atomic_uint32_t nextSequence_; std::optional> repeatedTask_; std::optional subscription_; // network validated ledgers subscription - SignalType notificationChannel_; + NewSequenceSignalType notificationChannel_; + DbStalledSignalType dbStalledChannel_; + + struct UpdateData { + std::chrono::steady_clock::duration dbStalledReportDelay; + std::chrono::steady_clock::time_point lastDbCheckTime; + uint32_t lastSeenMaxSeqInDb = 0u; + }; + + util::Mutex updateData_; util::Logger log_{"ETL"}; @@ -56,12 +67,16 @@ public: util::async::AnyExecutionContext ctx, std::shared_ptr backend, std::shared_ptr validatedLedgers, - uint32_t startSequence + uint32_t startSequence, + std::chrono::steady_clock::duration dbStalledReportDelay ); ~Monitor() override; void - notifyLedgerLoaded(uint32_t seq) override; + notifySequenceLoaded(uint32_t seq) override; + + void + notifyWriteConflict(uint32_t seq) override; void run(std::chrono::steady_clock::duration repeatInterval) override; @@ -70,7 +85,10 @@ public: stop() override; boost::signals2::scoped_connection - subscribe(SignalType::slot_type const& subscriber) override; + subscribeToNewSequence(NewSequenceSignalType::slot_type const& subscriber) override; + + boost::signals2::scoped_connection + subscribeToDbStalled(DbStalledSignalType::slot_type const& subscriber) override; private: void diff --git a/src/etlng/impl/MonitorProvider.hpp b/src/etlng/impl/MonitorProvider.hpp new file mode 100644 index 000000000..f449fe780 --- /dev/null +++ b/src/etlng/impl/MonitorProvider.hpp @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2025, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include "data/BackendInterface.hpp" +#include "etl/NetworkValidatedLedgersInterface.hpp" +#include "etlng/MonitorInterface.hpp" +#include "etlng/MonitorProviderInterface.hpp" +#include "etlng/impl/Monitor.hpp" +#include "util/async/AnyExecutionContext.hpp" + +#include +#include +#include +#include + +namespace etlng::impl { + +class MonitorProvider : public MonitorProviderInterface { +public: + std::unique_ptr + make( + util::async::AnyExecutionContext ctx, + std::shared_ptr backend, + std::shared_ptr validatedLedgers, + uint32_t startSequence, + std::chrono::steady_clock::duration dbStalledReportDelay + ) override + { + return std::make_unique( + std::move(ctx), std::move(backend), std::move(validatedLedgers), startSequence, dbStalledReportDelay + ); + } +}; + +} // namespace etlng::impl diff --git a/src/etlng/impl/SourceImpl.hpp b/src/etlng/impl/SourceImpl.hpp index 1c99d973b..ffde6a80c 100644 --- a/src/etlng/impl/SourceImpl.hpp +++ b/src/etlng/impl/SourceImpl.hpp @@ -19,10 +19,11 @@ #pragma once -#include "etl/impl/ForwardingSource.hpp" #include "etl/impl/SubscriptionSource.hpp" #include "etlng/InitialLoadObserverInterface.hpp" +#include "etlng/LoadBalancerInterface.hpp" #include "etlng/Source.hpp" +#include "etlng/impl/ForwardingSource.hpp" #include "etlng/impl/GrpcSource.hpp" #include "rpc/Errors.hpp" @@ -53,7 +54,7 @@ namespace etlng::impl { template < typename GrpcSourceType = GrpcSource, typename SubscriptionSourceTypePtr = std::unique_ptr, - typename ForwardingSourceType = etl::impl::ForwardingSource> + typename ForwardingSourceType = etlng::impl::ForwardingSource> class SourceImpl : public SourceBase { std::string ip_; std::string wsPort_; @@ -107,6 +108,7 @@ public: stop(boost::asio::yield_context yield) final { subscriptionSource_->stop(yield); + grpcSource_.stop(yield); } /** @@ -202,7 +204,7 @@ public: * @param loader InitialLoadObserverInterface implementation * @return A std::pair of the data and a bool indicating whether the download was successful */ - std::pair, bool> + InitialLedgerLoadResult loadInitialLedger(uint32_t sequence, std::uint32_t numMarkers, etlng::InitialLoadObserverInterface& loader) final { return grpcSource_.loadInitialLedger(sequence, numMarkers, loader); diff --git a/src/etlng/impl/TaskManager.cpp b/src/etlng/impl/TaskManager.cpp index 8f61dc445..33f75a132 100644 --- a/src/etlng/impl/TaskManager.cpp +++ b/src/etlng/impl/TaskManager.cpp @@ -26,6 +26,7 @@ #include "etlng/SchedulerInterface.hpp" #include "etlng/impl/Monitor.hpp" #include "etlng/impl/TaskQueue.hpp" +#include "util/Constants.hpp" #include "util/LedgerUtils.hpp" #include "util/Profiler.hpp" #include "util/async/AnyExecutionContext.hpp" @@ -102,29 +103,49 @@ TaskManager::spawnExtractor(TaskQueue& queue) if (stopRequested) break; } - } else { - // TODO: how do we signal to the loaders that it's time to shutdown? some special task? - break; // TODO: handle server shutdown or other node took over ETL } } else { // TODO (https://github.com/XRPLF/clio/issues/1852) std::this_thread::sleep_for(kDELAY_BETWEEN_ATTEMPTS); } } + + LOG(log_.info()) << "Extractor (one of) coroutine stopped"; }); } util::async::AnyOperation TaskManager::spawnLoader(TaskQueue& queue) { - static constexpr auto kNANO_TO_SECOND = 1.0e9; - return ctx_.execute([this, &queue](auto stopRequested) { while (not stopRequested) { // TODO (https://github.com/XRPLF/clio/issues/66): does not tell the loader whether it's out of order or not if (auto data = queue.dequeue(); data.has_value()) { - auto nanos = util::timed([this, data = *data] { loader_.get().load(data); }); - auto const seconds = nanos / kNANO_TO_SECOND; + auto [expectedSuccess, nanos] = + util::timed([&] { return loader_.get().load(*data); }); + + auto const shouldExitOnError = [&] { + if (expectedSuccess.has_value()) + return false; + + switch (expectedSuccess.error()) { + case LoaderError::WriteConflict: + LOG(log_.warn()) << "Immediately stopping loader on write conflict" + << "; latest ledger cache loaded for " << data->seq; + monitor_.get().notifyWriteConflict(data->seq); + return true; + case LoaderError::AmendmentBlocked: + LOG(log_.warn()) << "Immediately stopping loader on amendment block"; + return true; + } + + std::unreachable(); + }(); + + if (shouldExitOnError) + break; + + auto const seconds = nanos / util::kNANO_PER_SECOND; auto const txnCount = data->transactions.size(); auto const objCount = data->objects.size(); @@ -133,9 +154,11 @@ TaskManager::spawnLoader(TaskQueue& queue) << " seconds;" << " tps[" << txnCount / seconds << "], ops[" << objCount / seconds << "]"; - monitor_.get().notifyLedgerLoaded(data->seq); + monitor_.get().notifySequenceLoaded(data->seq); } } + + LOG(log_.info()) << "Loader coroutine stopped"; }); } diff --git a/src/etlng/impl/TaskManagerProvider.hpp b/src/etlng/impl/TaskManagerProvider.hpp index f242d7ce8..2a20964d6 100644 --- a/src/etlng/impl/TaskManagerProvider.hpp +++ b/src/etlng/impl/TaskManagerProvider.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace etlng::impl { @@ -62,12 +63,19 @@ public: } std::unique_ptr - make(util::async::AnyExecutionContext ctx, std::reference_wrapper monitor, uint32_t seq) override + make( + util::async::AnyExecutionContext ctx, + std::reference_wrapper monitor, + uint32_t startSeq, + std::optional finishSeq + ) override { - auto scheduler = impl::makeScheduler(impl::ForwardScheduler{ledgers_, seq}); - // TODO: add impl::BackfillScheduler{seq - 1, seq - 1000}, + auto scheduler = impl::makeScheduler(impl::ForwardScheduler{ledgers_, startSeq, finishSeq}); + // TODO: add impl::BackfillScheduler{startSeq - 1, startSeq - ...}, - return std::make_unique(std::move(ctx), std::move(scheduler), *extractor_, *loader_, monitor, seq); + return std::make_unique( + std::move(ctx), std::move(scheduler), *extractor_, *loader_, monitor, startSeq + ); } }; diff --git a/src/rpc/BookChangesHelper.hpp b/src/rpc/BookChangesHelper.hpp index f802b076e..ec4198567 100644 --- a/src/rpc/BookChangesHelper.hpp +++ b/src/rpc/BookChangesHelper.hpp @@ -265,6 +265,9 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, BookChange const {JS(open), to_string(change.openRate.iou())}, {JS(close), to_string(change.closeRate.iou())}, }; + + if (change.domain.has_value()) + jv.as_object()[JS(domain)] = ripple::to_string(*change.domain); } /** diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index bce9f8475..b0f53dc0b 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -51,6 +51,7 @@ target_sources( handlers/Subscribe.cpp handlers/TransactionEntry.cpp handlers/Unsubscribe.cpp + handlers/VaultInfo.cpp ) target_link_libraries(clio_rpc PRIVATE clio_util) diff --git a/src/rpc/Errors.cpp b/src/rpc/Errors.cpp index b57f85bf7..a7ad6a136 100644 --- a/src/rpc/Errors.cpp +++ b/src/rpc/Errors.cpp @@ -74,7 +74,7 @@ makeWarning(WarningCode code) ClioErrorInfo const& getErrorInfo(ClioError code) { - constexpr static ClioErrorInfo kINFOS[]{ + static constexpr ClioErrorInfo kINFOS[]{ {.code = ClioError::RpcMalformedCurrency, .error = "malformedCurrency", .message = "Malformed currency."}, {.code = ClioError::RpcMalformedRequest, .error = "malformedRequest", .message = "Malformed request."}, {.code = ClioError::RpcMalformedOwner, .error = "malformedOwner", .message = "Malformed owner."}, @@ -89,7 +89,7 @@ getErrorInfo(ClioError code) {.code = ClioError::RpcMalformedAuthorizedCredentials, .error = "malformedAuthorizedCredentials", .message = "Malformed authorized credentials."}, - + {.code = ClioError::RpcEntryNotFound, .error = "entryNotFound", .message = "Entry Not Found."}, // special system errors {.code = ClioError::RpcInvalidApiVersion, .error = JS(invalid_API_version), .message = "Invalid API version."}, {.code = ClioError::RpcCommandIsMissing, diff --git a/src/rpc/Errors.hpp b/src/rpc/Errors.hpp index e3735a86c..0bff5d26e 100644 --- a/src/rpc/Errors.hpp +++ b/src/rpc/Errors.hpp @@ -43,6 +43,7 @@ enum class ClioError { RpcFieldNotFoundTransaction = 5006, RpcMalformedOracleDocumentId = 5007, RpcMalformedAuthorizedCredentials = 5008, + RpcEntryNotFound = 5009, // special system errors start with 6000 RpcInvalidApiVersion = 6000, diff --git a/src/rpc/RPCCenter.cpp b/src/rpc/RPCCenter.cpp index f74f689bb..e7cba54c9 100644 --- a/src/rpc/RPCCenter.cpp +++ b/src/rpc/RPCCenter.cpp @@ -30,6 +30,7 @@ std::unordered_set const& handledRpcs() { static std::unordered_set const kHANDLED_RPCS = { + // clang-format off "account_channels", "account_currencies", "account_info", @@ -64,7 +65,9 @@ handledRpcs() "tx", "subscribe", "unsubscribe", + "vault_info", "version", + // clang-format on }; return kHANDLED_RPCS; } diff --git a/src/rpc/RPCHelpers.hpp b/src/rpc/RPCHelpers.hpp index 975bad5d6..e27a55e21 100644 --- a/src/rpc/RPCHelpers.hpp +++ b/src/rpc/RPCHelpers.hpp @@ -28,6 +28,7 @@ #include "data/BackendInterface.hpp" #include "data/Types.hpp" #include "rpc/Errors.hpp" +#include "rpc/JS.hpp" #include "rpc/common/Types.hpp" #include "util/JsonUtils.hpp" #include "util/Taggable.hpp" @@ -42,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -50,9 +52,12 @@ #include #include #include +#include #include +#include #include #include +#include #include #include #include @@ -60,9 +65,11 @@ #include #include #include +#include #include #include #include +#include #include #include diff --git a/src/rpc/common/ValidationHelpers.hpp b/src/rpc/common/ValidationHelpers.hpp index 08f0ffa83..495419024 100644 --- a/src/rpc/common/ValidationHelpers.hpp +++ b/src/rpc/common/ValidationHelpers.hpp @@ -36,7 +36,8 @@ namespace rpc::validation { * @return true if convertible; false otherwise */ template -[[nodiscard]] bool static checkType(boost::json::value const& value) +[[nodiscard]] static bool +checkType(boost::json::value const& value) { auto hasError = false; if constexpr (std::is_same_v) { diff --git a/src/rpc/common/impl/HandlerProvider.cpp b/src/rpc/common/impl/HandlerProvider.cpp index 83f93e99a..126dd7fc8 100644 --- a/src/rpc/common/impl/HandlerProvider.cpp +++ b/src/rpc/common/impl/HandlerProvider.cpp @@ -60,6 +60,7 @@ #include "rpc/handlers/TransactionEntry.hpp" #include "rpc/handlers/Tx.hpp" #include "rpc/handlers/Unsubscribe.hpp" +#include "rpc/handlers/VaultInfo.hpp" #include "rpc/handlers/VersionHandler.hpp" #include "util/config/ConfigDefinition.hpp" @@ -114,6 +115,7 @@ ProductionHandlerProvider::ProductionHandlerProvider( {"tx", {.handler = TxHandler{backend, etl}}}, {"subscribe", {.handler = SubscribeHandler{backend, amendmentCenter, subscriptionManager}}}, {"unsubscribe", {.handler = UnsubscribeHandler{subscriptionManager}}}, + {"vault_info", {.handler = VaultInfoHandler{backend}}}, {"version", {.handler = VersionHandler{config}}}, } { diff --git a/src/rpc/handlers/BookOffers.hpp b/src/rpc/handlers/BookOffers.hpp index 36bedbad2..12ac27fc2 100644 --- a/src/rpc/handlers/BookOffers.hpp +++ b/src/rpc/handlers/BookOffers.hpp @@ -148,11 +148,14 @@ public: validation::CustomValidators::accountValidator, Status(RippledError::rpcINVALID_PARAMS, "Invalid field 'taker'.") }}, - { - JS(domain), - validation::Type{}, - validation::CustomValidators::uint256HexStringValidator, - }, + {JS(domain), + meta::WithCustomError{ + validation::Type{}, Status(RippledError::rpcDOMAIN_MALFORMED, "Unable to parse domain.") + }, + meta::WithCustomError{ + validation::CustomValidators::uint256HexStringValidator, + Status(RippledError::rpcDOMAIN_MALFORMED, "Unable to parse domain.") + }}, {JS(limit), validation::Type{}, validation::Min(1u), diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index a312aedc1..41996fabf 100644 --- a/src/rpc/handlers/GatewayBalances.cpp +++ b/src/rpc/handlers/GatewayBalances.cpp @@ -79,7 +79,32 @@ GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context con auto output = GatewayBalancesHandler::Output{}; + auto addEscrow = [&](ripple::SLE const& sle) { + if (sle.getType() == ripple::ltESCROW) { + auto const& escrow = sle.getFieldAmount(ripple::sfAmount); + auto& lockedBalance = output.locked[escrow.getCurrency()]; + if (lockedBalance == beast::zero) { + // This is needed to set the currency code correctly + lockedBalance = escrow; + } else { + try { + lockedBalance += escrow; + } catch (std::runtime_error const&) { + // Presumably the exception was caused by overflow. + // On overflow return the largest valid STAmount. + // Very large sums of STAmount are approximations + // anyway. + lockedBalance = ripple::STAmount( + lockedBalance.issue(), ripple::STAmount::cMaxValue, ripple::STAmount::cMaxOffset + ); + } + } + } + }; + auto const addToResponse = [&](ripple::SLE const sle) { + addEscrow(sle); + if (sle.getType() == ripple::ltRIPPLE_STATE) { ripple::STAmount balance = sle.getFieldAmount(ripple::sfBalance); auto const lowLimit = sle.getFieldAmount(ripple::sfLowLimit); @@ -194,6 +219,14 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, GatewayBalancesH if (auto balances = toJson(output.assets); !balances.empty()) obj[JS(assets)] = balances; + if (!output.locked.empty()) { + boost::json::object lockedObj; + for (auto const& [currency, amount] : output.locked) { + lockedObj[ripple::to_string(currency)] = amount.getText(); + } + obj[JS(locked)] = std::move(lockedObj); + } + obj[JS(account)] = output.accountID; obj[JS(ledger_index)] = output.ledgerIndex; obj[JS(ledger_hash)] = output.ledgerHash; diff --git a/src/rpc/handlers/GatewayBalances.hpp b/src/rpc/handlers/GatewayBalances.hpp index 08a9e97d2..0a791bd49 100644 --- a/src/rpc/handlers/GatewayBalances.hpp +++ b/src/rpc/handlers/GatewayBalances.hpp @@ -73,6 +73,7 @@ public: std::map> hotBalances; std::map> assets; std::map> frozenBalances; + std::map locked; // validated should be sent via framework bool validated = true; }; @@ -147,9 +148,9 @@ public: {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator} }; - auto static const kSPEC_V1 = + static auto const kSPEC_V1 = RpcSpec{kSPEC_COMMON, {{JS(hotwallet), getHotWalletValidator(ripple::rpcINVALID_HOTWALLET)}}}; - auto static const kSPEC_V2 = + static auto const kSPEC_V2 = RpcSpec{kSPEC_COMMON, {{JS(hotwallet), getHotWalletValidator(ripple::rpcINVALID_PARAMS)}}}; return apiVersion == 1 ? kSPEC_V1 : kSPEC_V2; diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index 2b39dc3ab..c7b3812ec 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -184,6 +184,11 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) ); auto const seq = input.permissionedDomain->at(JS(seq)).as_int64(); key = ripple::keylet::permissionedDomain(*account, seq).key; + } else if (input.vault) { + auto const account = + ripple::parseBase58(boost::json::value_to(input.vault->at(JS(owner)))); + auto const seq = input.vault->at(JS(seq)).as_int64(); + key = ripple::keylet::vault(*account, seq).key; } else if (input.delegate) { auto const account = ripple::parseBase58(boost::json::value_to(input.delegate->at(JS(account)))); @@ -214,13 +219,13 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx) if (!ledgerObject || ledgerObject->empty()) { if (not input.includeDeleted) - return Error{Status{"entryNotFound"}}; + return Error{Status{ClioError::RpcEntryNotFound}}; auto const deletedSeq = sharedPtrBackend_->fetchLedgerObjectSeq(key, lgrInfo.seq, ctx.yield); if (!deletedSeq) - return Error{Status{"entryNotFound"}}; + return Error{Status{ClioError::RpcEntryNotFound}}; ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, deletedSeq.value() - 1, ctx.yield); if (!ledgerObject || ledgerObject->empty()) - return Error{Status{"entryNotFound"}}; + return Error{Status{ClioError::RpcEntryNotFound}}; output.deletedLedgerIndex = deletedSeq; } @@ -326,6 +331,7 @@ tag_invoke(boost::json::value_to_tag, boost::json::va {JS(credential), ripple::ltCREDENTIAL}, {JS(mptoken), ripple::ltMPTOKEN}, {JS(permissioned_domain), ripple::ltPERMISSIONED_DOMAIN}, + {JS(vault), ripple::ltVAULT}, {JS(delegate), ripple::ltDELEGATE} }; @@ -415,6 +421,8 @@ tag_invoke(boost::json::value_to_tag, boost::json::va input.mptoken = jv.at(JS(mptoken)).as_object(); } else if (jsonObject.contains(JS(permissioned_domain))) { input.permissionedDomain = jv.at(JS(permissioned_domain)).as_object(); + } else if (jsonObject.contains(JS(vault))) { + input.vault = jv.at(JS(vault)).as_object(); } else if (jsonObject.contains(JS(delegate))) { input.delegate = jv.at(JS(delegate)).as_object(); } diff --git a/src/rpc/handlers/LedgerEntry.hpp b/src/rpc/handlers/LedgerEntry.hpp index a7f261869..970d4f7b0 100644 --- a/src/rpc/handlers/LedgerEntry.hpp +++ b/src/rpc/handlers/LedgerEntry.hpp @@ -104,6 +104,7 @@ public: std::optional amm; std::optional mptoken; std::optional permissionedDomain; + std::optional vault; std::optional bridge; std::optional bridgeAccount; std::optional chainClaimId; @@ -393,6 +394,23 @@ public: }, }, }}}, + {JS(vault), + meta::WithCustomError{ + validation::Type{}, Status(ClioError::RpcMalformedRequest) + }, + meta::IfType{kMALFORMED_REQUEST_HEX_STRING_VALIDATOR}, + meta::IfType{meta::Section{ + {JS(seq), + meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)}, + meta::WithCustomError{validation::Type{}, Status(ClioError::RpcMalformedRequest)}}, + { + JS(owner), + meta::WithCustomError{validation::Required{}, Status(ClioError::RpcMalformedRequest)}, + meta::WithCustomError{ + validation::CustomValidators::accountBase58Validator, Status(ClioError::RpcMalformedOwner) + }, + }, + }}}, {JS(delegate), meta::WithCustomError{ validation::Type{}, Status(ClioError::RpcMalformedRequest) diff --git a/src/rpc/handlers/VaultInfo.cpp b/src/rpc/handlers/VaultInfo.cpp new file mode 100644 index 000000000..1f50722ad --- /dev/null +++ b/src/rpc/handlers/VaultInfo.cpp @@ -0,0 +1,191 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2025, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "rpc/handlers/VaultInfo.hpp" + +#include "data/BackendInterface.hpp" +#include "rpc/Errors.hpp" +#include "rpc/JS.hpp" +#include "rpc/RPCHelpers.hpp" +#include "rpc/common/Types.hpp" +#include "util/Assert.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace rpc { + +namespace { + +/** + * @brief Ensures that the input contains either a `vaultID` alone, or both `owner` and `tnxSequence`. + * Any other combination is considered malformed. + * + * @param input The input object containing optional fields for the vault request. + * @return Returns true if the input is valid, false otherwise. + */ +bool +validate(VaultInfoHandler::Input const& input) +{ + bool const hasVaultId = input.vaultID.has_value(); + bool const hasOwner = input.owner.has_value(); + bool const hasSeq = input.tnxSequence.has_value(); + + // Only valid combinations: (vaultID) or (owner + ledgerIndex) + // NOLINTNEXTLINE(readability-simplify-boolean-expr) + return (hasVaultId && !hasOwner && !hasSeq) || (!hasVaultId && hasOwner && hasSeq); +} + +} // namespace + +VaultInfoHandler::VaultInfoHandler(std::shared_ptr const& sharedPtrBackend) + : sharedPtrBackend_{sharedPtrBackend} +{ +} + +VaultInfoHandler::Result +VaultInfoHandler::process(VaultInfoHandler::Input input, Context const& ctx) const +{ + // vault info input must either have owner and sequence, or vault_id only. + if (not validate(input)) + return Error{ClioError::RpcMalformedRequest}; + + auto const range = sharedPtrBackend_->fetchLedgerRange(); + ASSERT(range.has_value(), "VaultInfo's ledger range must be available"); + + auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, std::nullopt, input.ledgerIndex, range->maxSequence + ); + + if (not expectedLgrInfo.has_value()) + return Error{expectedLgrInfo.error()}; + + auto const& lgrInfo = *expectedLgrInfo; + + // Extract the vault keylet based on input + auto const vaultKeylet = [&]() -> std::expected { + if (input.owner && input.tnxSequence) { + auto const accountStr = *input.owner; + auto const accountID = accountFromStringStrict(accountStr); + + // checks that account exists + { + auto const accountKeylet = ripple::keylet::account(*accountID); + auto const accountLedgerObject = + sharedPtrBackend_->fetchLedgerObject(accountKeylet.key, lgrInfo.seq, ctx.yield); + + if (!accountLedgerObject) + return std::unexpected{Status{ClioError::RpcEntryNotFound}}; + } + + return ripple::keylet::vault(*accountID, *input.tnxSequence); + } + ripple::uint256 nodeIndex; + if (nodeIndex.parseHex(*input.vaultID)) + return ripple::keylet::vault(nodeIndex); + + return std::unexpected{Status{ClioError::RpcEntryNotFound}}; + }(); + + if (not vaultKeylet.has_value()) + return Error{vaultKeylet.error()}; + + // Fetch the vault object and it's associated issuance ID + auto const vaultLedgerObject = + sharedPtrBackend_->fetchLedgerObject(vaultKeylet.value().key, lgrInfo.seq, ctx.yield); + + if (not vaultLedgerObject) + return Error{Status{ClioError::RpcEntryNotFound, "vault object not found."}}; + + ripple::STLedgerEntry const vaultSle{ + ripple::SerialIter{vaultLedgerObject->data(), vaultLedgerObject->size()}, vaultKeylet.value().key + }; + + auto const issuanceKeylet = ripple::keylet::mptIssuance(vaultSle[ripple::sfShareMPTID]).key; + auto const issuanceObject = sharedPtrBackend_->fetchLedgerObject(issuanceKeylet, lgrInfo.seq, ctx.yield); + + if (not issuanceObject) + return Error{Status{ClioError::RpcEntryNotFound, "issuance object not found."}}; + + ripple::STLedgerEntry const issuanceSle{ + ripple::SerialIter{issuanceObject->data(), issuanceObject->size()}, issuanceKeylet + }; + + // put issuance object into "shares" field of vault object + // follows same logic as rippled: + // https://github.com/XRPLF/rippled/pull/5224/files#diff-6cb544622c7942261f097d628f61f1c1fcf34a1bcfd954aedbada4238fc28f69R107 + Output response; + response.vault = toBoostJson(vaultSle.getJson(ripple::JsonOptions::none)); + response.vault.as_object()[JS(shares)] = toBoostJson(issuanceSle.getJson(ripple::JsonOptions::none)); + response.ledgerIndex = lgrInfo.seq; + + return response; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, VaultInfoHandler::Output const& output) +{ + jv = boost::json::object{ + {JS(ledger_index), output.ledgerIndex}, {JS(validated), output.validated}, {JS(vault), output.vault} + }; +} + +VaultInfoHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = VaultInfoHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + if (jsonObject.contains(JS(owner))) + input.owner = jsonObject.at(JS(owner)).as_string(); + + if (jsonObject.contains(JS(seq))) + input.tnxSequence = static_cast(jsonObject.at(JS(seq)).as_int64()); + + if (jsonObject.contains(JS(vault_id))) + input.vaultID = jsonObject.at(JS(vault_id)).as_string(); + + if (jsonObject.contains(JS(ledger_index))) { + if (not jsonObject.at(JS(ledger_index)).is_string()) { + input.ledgerIndex = jsonObject.at(JS(ledger_index)).as_int64(); + } else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") { + input.ledgerIndex = std::stoi(jsonObject.at(JS(ledger_index)).as_string().c_str()); + } + } + + return input; +} + +} // namespace rpc diff --git a/src/rpc/handlers/VaultInfo.hpp b/src/rpc/handlers/VaultInfo.hpp new file mode 100644 index 000000000..d5e1b1acd --- /dev/null +++ b/src/rpc/handlers/VaultInfo.hpp @@ -0,0 +1,134 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2025, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include "data/BackendInterface.hpp" +#include "rpc/Errors.hpp" +#include "rpc/JS.hpp" +#include "rpc/common/MetaProcessors.hpp" +#include "rpc/common/Specs.hpp" +#include "rpc/common/Types.hpp" +#include "rpc/common/Validators.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace rpc { + +/** + * @brief The vault_info command retrieves information about a vault, currency, shares etc. + */ +class VaultInfoHandler { + std::shared_ptr sharedPtrBackend_; + +public: + /** + * @brief Construct a new VaultInfo object + * + * @param sharedPtrBackend The backend to use + */ + VaultInfoHandler(std::shared_ptr const& sharedPtrBackend); + + /** + * @brief A struct to hold the input data for the command + */ + struct Input { + std::optional vaultID; + std::optional owner; + std::optional tnxSequence; + std::optional ledgerIndex; + }; + + /** + * @brief A struct to hold the output data for the command + */ + struct Output { + boost::json::value vault; + uint32_t ledgerIndex{}; + bool validated = true; + }; + + using Result = HandlerReturnType; + + /** + * @brief Returns the API specification for the command + * + * @param apiVersion The api version to return the spec for + * @return The spec for the given apiVersion + */ + static RpcSpecConstRef + spec([[maybe_unused]] uint32_t apiVersion) + { + static auto const kRPC_SPEC = RpcSpec{ + {JS(vault_id), + meta::WithCustomError{ + validation::CustomValidators::uint256HexStringValidator, Status(ClioError::RpcMalformedRequest) + }}, + {JS(owner), + meta::WithCustomError{ + validation::CustomValidators::accountBase58Validator, + Status(ClioError::RpcMalformedRequest, "OwnerNotHexString") + }}, + {JS(seq), meta::WithCustomError{validation::Type{}, Status(ClioError::RpcMalformedRequest)}}, + {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, + }; + + return kRPC_SPEC; + } + + /** + * @brief Process the VaultInfo command + * + * @param input The input data for the command + * @param ctx The context of the request + * @return The result of the operation + */ + Result + process(Input input, Context const& ctx) const; + +private: + /** + * @brief Convert the Output to a JSON object + * + * @param jv The JSON object to convert to + * @param output The output to convert + */ + friend void + tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); + + /** + * @brief Convert a JSON object to Input type + * + * @param jv The JSON object to convert + * @return Input parsed from the JSON object + */ + friend Input + tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); +}; + +} // namespace rpc diff --git a/src/util/Constants.hpp b/src/util/Constants.hpp index e148ea58e..e16e5b33d 100644 --- a/src/util/Constants.hpp +++ b/src/util/Constants.hpp @@ -23,4 +23,5 @@ namespace util { static constexpr std::size_t kMILLISECONDS_PER_SECOND = 1000; +static constexpr double kNANO_PER_SECOND = 1.0e9; } // namespace util diff --git a/src/util/LedgerUtils.hpp b/src/util/LedgerUtils.hpp index 84a5606ba..ac3eb3006 100644 --- a/src/util/LedgerUtils.hpp +++ b/src/util/LedgerUtils.hpp @@ -114,6 +114,7 @@ class LedgerTypes { LedgerTypeAttribute::accountOwnedLedgerType(JS(did), ripple::ltDID), LedgerTypeAttribute::accountOwnedLedgerType(JS(oracle), ripple::ltORACLE), LedgerTypeAttribute::accountOwnedLedgerType(JS(credential), ripple::ltCREDENTIAL), + LedgerTypeAttribute::accountOwnedLedgerType(JS(vault), ripple::ltVAULT), LedgerTypeAttribute::chainLedgerType(JS(nunl), ripple::ltNEGATIVE_UNL), LedgerTypeAttribute::deletionBlockerLedgerType(JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE), LedgerTypeAttribute::deletionBlockerLedgerType(JS(mptoken), ripple::ltMPTOKEN), diff --git a/src/util/async/context/impl/Cancellation.hpp b/src/util/async/context/impl/Cancellation.hpp index 9dfd57d7e..e148aa99f 100644 --- a/src/util/async/context/impl/Cancellation.hpp +++ b/src/util/async/context/impl/Cancellation.hpp @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include diff --git a/src/util/prometheus/MetricBuilder.hpp b/src/util/prometheus/MetricBuilder.hpp index 0011bc3c4..30da77147 100644 --- a/src/util/prometheus/MetricBuilder.hpp +++ b/src/util/prometheus/MetricBuilder.hpp @@ -82,7 +82,8 @@ public: override; private: - std::unique_ptr static makeMetric(std::string name, std::string labelsString, MetricType type); + static std::unique_ptr + makeMetric(std::string name, std::string labelsString, MetricType type); template requires std::same_as || std::same_as diff --git a/src/util/prometheus/Prometheus.hpp b/src/util/prometheus/Prometheus.hpp index 15615c0ba..923c15d03 100644 --- a/src/util/prometheus/Prometheus.hpp +++ b/src/util/prometheus/Prometheus.hpp @@ -257,7 +257,8 @@ public: * * @param config The configuration to use */ - void static init(util::config::ClioConfigDefinition const& config); + static void + init(util::config::ClioConfigDefinition const& config); /** * @brief Get a bool based metric. It will be created if it doesn't exist diff --git a/src/web/impl/ErrorHandling.hpp b/src/web/impl/ErrorHandling.hpp index d200810c6..42fe6b6aa 100644 --- a/src/web/impl/ErrorHandling.hpp +++ b/src/web/impl/ErrorHandling.hpp @@ -91,6 +91,7 @@ public: case rpc::ClioError::RpcFieldNotFoundTransaction: case rpc::ClioError::RpcMalformedOracleDocumentId: case rpc::ClioError::RpcMalformedAuthorizedCredentials: + case rpc::ClioError::RpcEntryNotFound: case rpc::ClioError::EtlConnectionError: case rpc::ClioError::EtlRequestError: case rpc::ClioError::EtlRequestTimeout: diff --git a/src/web/ng/impl/ErrorHandling.cpp b/src/web/ng/impl/ErrorHandling.cpp index 6e9a0540f..23836191e 100644 --- a/src/web/ng/impl/ErrorHandling.cpp +++ b/src/web/ng/impl/ErrorHandling.cpp @@ -105,6 +105,7 @@ ErrorHelper::makeError(rpc::Status const& err) const case rpc::ClioError::RpcFieldNotFoundTransaction: case rpc::ClioError::RpcMalformedOracleDocumentId: case rpc::ClioError::RpcMalformedAuthorizedCredentials: + case rpc::ClioError::RpcEntryNotFound: case rpc::ClioError::EtlConnectionError: case rpc::ClioError::EtlRequestError: case rpc::ClioError::EtlRequestTimeout: diff --git a/tests/common/util/MockLoadBalancer.hpp b/tests/common/util/MockLoadBalancer.hpp index b9989252c..a279a6af9 100644 --- a/tests/common/util/MockLoadBalancer.hpp +++ b/tests/common/util/MockLoadBalancer.hpp @@ -42,7 +42,7 @@ struct MockNgLoadBalancer : etlng::LoadBalancerInterface { using RawLedgerObjectType = FakeLedgerObject; MOCK_METHOD( - std::vector, + etlng::InitialLedgerLoadResult, loadInitialLedger, (uint32_t, etlng::InitialLoadObserverInterface&, std::chrono::steady_clock::duration), (override) diff --git a/tests/common/util/MockSourceNg.hpp b/tests/common/util/MockSourceNg.hpp index 6c59c5136..7fce7545b 100644 --- a/tests/common/util/MockSourceNg.hpp +++ b/tests/common/util/MockSourceNg.hpp @@ -20,6 +20,7 @@ #include "etl/NetworkValidatedLedgersInterface.hpp" #include "etlng/InitialLoadObserverInterface.hpp" +#include "etlng/LoadBalancerInterface.hpp" #include "etlng/Source.hpp" #include "feed/SubscriptionManagerInterface.hpp" #include "rpc/Errors.hpp" @@ -61,7 +62,7 @@ struct MockSourceNg : etlng::SourceBase { (override) ); MOCK_METHOD( - (std::pair, bool>), + etlng::InitialLedgerLoadResult, loadInitialLedger, (uint32_t, uint32_t, etlng::InitialLoadObserverInterface&), (override) @@ -136,7 +137,7 @@ public: return mock_->fetchLedger(sequence, getObjects, getObjectNeighbors); } - std::pair, bool> + etlng::InitialLedgerLoadResult loadInitialLedger(uint32_t sequence, uint32_t maxLedger, etlng::InitialLoadObserverInterface& observer) override { return mock_->loadInitialLedger(sequence, maxLedger, observer); diff --git a/tests/common/util/TestObject.cpp b/tests/common/util/TestObject.cpp index 7d2152025..459da15a1 100644 --- a/tests/common/util/TestObject.cpp +++ b/tests/common/util/TestObject.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -328,18 +329,21 @@ createMetaDataForBookChange( std::string_view issueId, uint32_t transactionIndex, int finalTakerGets, - int perviousTakerGets, + int previousTakerGets, int finalTakerPays, - int perviousTakerPays + int previousTakerPays, + std::optional domain ) { ripple::STObject finalFields(ripple::sfFinalFields); ripple::Issue const issue1 = getIssue(currency, issueId); finalFields.setFieldAmount(ripple::sfTakerPays, ripple::STAmount(issue1, finalTakerPays)); finalFields.setFieldAmount(ripple::sfTakerGets, ripple::STAmount(finalTakerGets, false)); + if (domain.has_value()) + finalFields.setFieldH256(ripple::sfDomainID, ripple::uint256{*domain}); ripple::STObject previousFields(ripple::sfPreviousFields); - previousFields.setFieldAmount(ripple::sfTakerPays, ripple::STAmount(issue1, perviousTakerPays)); - previousFields.setFieldAmount(ripple::sfTakerGets, ripple::STAmount(perviousTakerGets, false)); + previousFields.setFieldAmount(ripple::sfTakerPays, ripple::STAmount(issue1, previousTakerPays)); + previousFields.setFieldAmount(ripple::sfTakerGets, ripple::STAmount(previousTakerGets, false)); ripple::STObject metaObj(ripple::sfTransactionMetaData); ripple::STArray metaArray{1}; ripple::STObject node(ripple::sfModifiedNode); @@ -484,7 +488,7 @@ createOfferLedgerObject( std::string_view getsIssueId, std::string_view paysIssueId, std::string_view dirId, - std::optional const& domain + std::optional domain ) { ripple::STObject offer(ripple::sfLedgerEntry); @@ -1639,3 +1643,37 @@ createAuthCredentialArray(std::vector issuer, std::vector domain = std::nullopt ); /* @@ -258,7 +259,7 @@ createOfferLedgerObject( std::string_view getsIssueId, std::string_view paysIssueId, std::string_view bookDirId, - std::optional const& domain = std::nullopt + std::optional domain = std::nullopt ); [[nodiscard]] ripple::STObject @@ -523,3 +524,16 @@ createCredentialObject( [[nodiscard]] ripple::STArray createAuthCredentialArray(std::vector issuer, std::vector credType); + +[[nodiscard]] ripple::STObject +createVault( + std::string_view owner, + std::string_view account, + ripple::LedgerIndex seq, + std::string_view assetCurrency, + std::string_view assetIssuer, + ripple::uint192 shareMPTID, + uint64_t ownerNode, + ripple::uint256 previousTxId, + uint32_t previousTxSeq +); diff --git a/tests/common/util/config/FakeConfigData.hpp b/tests/common/util/config/FakeConfigData.hpp index a796b9a91..06dc4708f 100644 --- a/tests/common/util/config/FakeConfigData.hpp +++ b/tests/common/util/config/FakeConfigData.hpp @@ -85,7 +85,7 @@ generateConfig() } } ], - "dosguard": { + "dosguard": { "whitelist": [ // mandatory for user to include ], @@ -132,16 +132,16 @@ static constexpr auto kJSON_DATA = R"JSON({ } } ], - "dosguard": { + "dosguard": { "whitelist": [ "125.5.5.1", "204.2.2.1" ], - "port" : 44444 + "port": 44444 }, - "optional" : { - "withDefault" : 0.0 + "optional": { + "withDefault": 0.0 }, - "requireValue" : "required" + "requireValue": "required" })JSON"; /* After parsing jsonValue and populating it into ClioConfig, It will look like this below in json format; @@ -177,7 +177,7 @@ static constexpr auto kJSON_DATA = R"JSON({ } } ], - "dosguard": { + "dosguard": { "whitelist": [ "125.5.5.1", "204.2.2.1" ], @@ -203,8 +203,8 @@ static constexpr auto kINVALID_JSON_DATA = R"JSON({ ] }, "idk": true, - "requireValue" : "required", - "optional" : { - "withDefault" : "0.0" + "requireValue": "required", + "optional": { + "withDefault": "0.0" } })JSON"; diff --git a/tests/integration/data/BackendFactoryTests.cpp b/tests/integration/data/BackendFactoryTests.cpp index 27befa960..4c59217d5 100644 --- a/tests/integration/data/BackendFactoryTests.cpp +++ b/tests/integration/data/BackendFactoryTests.cpp @@ -43,7 +43,7 @@ using namespace util::config; struct BackendCassandraFactoryTest : SyncAsioContextTest, util::prometheus::WithPrometheus { - constexpr static auto kKEYSPACE = "factory_test"; + static constexpr auto kKEYSPACE = "factory_test"; protected: ClioConfigDefinition cfg_{ diff --git a/tests/integration/data/cassandra/BackendTests.cpp b/tests/integration/data/cassandra/BackendTests.cpp index 9fcc16d3a..e0f87cb1f 100644 --- a/tests/integration/data/cassandra/BackendTests.cpp +++ b/tests/integration/data/cassandra/BackendTests.cpp @@ -1424,7 +1424,7 @@ TEST_F(BackendCassandraNodeMessageTest, UpdatingMessageKeepsItAlive) { #if defined(__APPLE__) GTEST_SKIP() << "Skipping test on Apple platform due to slow DB"; -#endif +#else static boost::uuids::uuid const kUUID = generateUuid(); static std::string const kUPDATED_MESSAGE = "updated message"; @@ -1442,4 +1442,5 @@ TEST_F(BackendCassandraNodeMessageTest, UpdatingMessageKeepsItAlive) EXPECT_EQ(uuid, kUUID); EXPECT_EQ(message, kUPDATED_MESSAGE); }); +#endif } diff --git a/tests/integration/migration/cassandra/CassandraMigrationTestBackend.hpp b/tests/integration/migration/cassandra/CassandraMigrationTestBackend.hpp index 6894d6bb7..a342f0fae 100644 --- a/tests/integration/migration/cassandra/CassandraMigrationTestBackend.hpp +++ b/tests/integration/migration/cassandra/CassandraMigrationTestBackend.hpp @@ -72,7 +72,7 @@ public: void writeTxIndexExample(std::string const& hash, std::string const& txType) { - auto static kINSERT_TX_INDEX_EXAMPLE = [this]() { + static auto kINSERT_TX_INDEX_EXAMPLE = [this]() { return handle_.prepare(fmt::format( R"( INSERT INTO {} @@ -96,7 +96,7 @@ public: std::optional fetchTxTypeViaID(std::string const& hash, boost::asio::yield_context ctx) { - auto static kFETCH_TX_TYPE = [this]() { + static auto kFETCH_TX_TYPE = [this]() { return handle_.prepare(fmt::format( R"( SELECT tx_type FROM {} WHERE hash = ? @@ -129,7 +129,7 @@ public: std::optional fetchTxIndexTableSize(boost::asio::yield_context ctx) { - auto static kINSERT_TX_INDEX_EXAMPLE = [this]() { + static auto kINSERT_TX_INDEX_EXAMPLE = [this]() { return handle_.prepare(fmt::format( R"( SELECT COUNT(*) FROM {} @@ -168,7 +168,7 @@ public: void writeLedgerAccountHash(std::uint64_t sequence, std::string const& accountHash) { - auto static kINSERT_LEDGER_EXAMPLE = [this]() { + static auto kINSERT_LEDGER_EXAMPLE = [this]() { return handle_.prepare(fmt::format( R"( INSERT INTO {} @@ -192,7 +192,7 @@ public: std::optional fetchAccountHashViaSequence(std::uint64_t sequence, boost::asio::yield_context ctx) { - auto static kFETCH_ACCOUNT_HASH = [this]() { + static auto kFETCH_ACCOUNT_HASH = [this]() { return handle_.prepare(fmt::format( R"( SELECT account_hash FROM {} WHERE sequence = ? @@ -225,7 +225,7 @@ public: std::optional fetchLedgerTableSize(boost::asio::yield_context ctx) { - auto static kINSERT_LEDGER_EXAMPLE = [this]() { + static auto kINSERT_LEDGER_EXAMPLE = [this]() { return handle_.prepare(fmt::format( R"( SELECT COUNT(*) FROM {} @@ -280,7 +280,7 @@ public: std::optional fetchDiffTableSize(boost::asio::yield_context ctx) { - auto static kCOUNT_DIFF = [this]() { + static auto kCOUNT_DIFF = [this]() { return handle_.prepare(fmt::format( R"( SELECT COUNT(*) FROM {} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index aa8b42849..28e2a5c50 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -132,6 +132,7 @@ target_sources( rpc/handlers/TxTests.cpp rpc/handlers/UnsubscribeTests.cpp rpc/handlers/VersionHandlerTests.cpp + rpc/handlers/VaultInfoTests.cpp rpc/JsonBoolTests.cpp rpc/RPCEngineTests.cpp rpc/RPCHelpersTests.cpp diff --git a/tests/unit/etl/ExtractorTests.cpp b/tests/unit/etl/ExtractorTests.cpp index 43de87f5a..bb87a7e71 100644 --- a/tests/unit/etl/ExtractorTests.cpp +++ b/tests/unit/etl/ExtractorTests.cpp @@ -43,7 +43,7 @@ struct ETLExtractorTest : util::prometheus::WithPrometheus, NoLoggerFixture { { state_.isStopping = false; state_.writeConflict = false; - state_.isReadOnly = false; + state_.isStrictReadonly = false; state_.isWriting = false; } diff --git a/tests/unit/etl/LoadBalancerTests.cpp b/tests/unit/etl/LoadBalancerTests.cpp index 533317235..f527357d0 100644 --- a/tests/unit/etl/LoadBalancerTests.cpp +++ b/tests/unit/etl/LoadBalancerTests.cpp @@ -62,7 +62,7 @@ using namespace util::config; using testing::Return; using namespace util::prometheus; -constexpr static auto const kTWO_SOURCES_LEDGER_RESPONSE = R"JSON({ +static constexpr auto kTWO_SOURCES_LEDGER_RESPONSE = R"JSON({ "etl_sources": [ { "ip": "127.0.0.1", @@ -77,7 +77,7 @@ constexpr static auto const kTWO_SOURCES_LEDGER_RESPONSE = R"JSON({ ] })JSON"; -constexpr static auto const kTHREE_SOURCES_LEDGER_RESPONSE = R"JSON({ +static constexpr auto kTHREE_SOURCES_LEDGER_RESPONSE = R"JSON({ "etl_sources": [ { "ip": "127.0.0.1", diff --git a/tests/unit/etl/TransformerTests.cpp b/tests/unit/etl/TransformerTests.cpp index 0b0aba113..00bebbe85 100644 --- a/tests/unit/etl/TransformerTests.cpp +++ b/tests/unit/etl/TransformerTests.cpp @@ -64,7 +64,7 @@ struct ETLTransformerTest : util::prometheus::WithPrometheus, MockBackendTest { { state_.isStopping = false; state_.writeConflict = false; - state_.isReadOnly = false; + state_.isStrictReadonly = false; state_.isWriting = false; } diff --git a/tests/unit/etlng/AmendmentBlockHandlerTests.cpp b/tests/unit/etlng/AmendmentBlockHandlerTests.cpp index 13813ec45..c66ac84bc 100644 --- a/tests/unit/etlng/AmendmentBlockHandlerTests.cpp +++ b/tests/unit/etlng/AmendmentBlockHandlerTests.cpp @@ -42,7 +42,7 @@ protected: TEST_F(AmendmentBlockHandlerNgTests, CallTonotifyAmendmentBlockedSetsStateAndRepeatedlyCallsAction) { - constexpr static auto kMAX_ITERATIONS = 10uz; + static constexpr auto kMAX_ITERATIONS = 10uz; etlng::impl::AmendmentBlockHandler handler{ctx_, state_, std::chrono::nanoseconds{1}, actionMock_.AsStdFunction()}; auto counter = 0uz; std::binary_semaphore stop{0}; diff --git a/tests/unit/etlng/ETLServiceTests.cpp b/tests/unit/etlng/ETLServiceTests.cpp index dfcffadeb..ec1b04502 100644 --- a/tests/unit/etlng/ETLServiceTests.cpp +++ b/tests/unit/etlng/ETLServiceTests.cpp @@ -17,21 +17,24 @@ */ //============================================================================== +#include "data/BackendInterface.hpp" #include "data/Types.hpp" #include "etl/ETLState.hpp" +#include "etl/NetworkValidatedLedgersInterface.hpp" #include "etl/SystemState.hpp" #include "etlng/CacheLoaderInterface.hpp" #include "etlng/CacheUpdaterInterface.hpp" #include "etlng/ETLService.hpp" #include "etlng/ExtractorInterface.hpp" #include "etlng/InitialLoadObserverInterface.hpp" +#include "etlng/LoadBalancerInterface.hpp" #include "etlng/LoaderInterface.hpp" #include "etlng/Models.hpp" #include "etlng/MonitorInterface.hpp" +#include "etlng/MonitorProviderInterface.hpp" #include "etlng/TaskManagerInterface.hpp" #include "etlng/TaskManagerProviderInterface.hpp" #include "util/BinaryTestObject.hpp" -#include "util/MockAssert.hpp" #include "util/MockBackendTestFixture.hpp" #include "util/MockLedgerPublisher.hpp" #include "util/MockLoadBalancer.hpp" @@ -43,6 +46,7 @@ #include "util/async/context/BasicExecutionContext.hpp" #include "util/async/context/SyncExecutionContext.hpp" #include "util/async/impl/ErasedOperation.hpp" +#include "util/config/ConfigConstraints.hpp" #include "util/config/ConfigDefinition.hpp" #include "util/config/ConfigValue.hpp" #include "util/config/Types.hpp" @@ -62,6 +66,7 @@ #include #include #include +#include #include using namespace util::config; @@ -71,8 +76,20 @@ constinit auto const kSEQ = 100; constinit auto const kLEDGER_HASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; struct MockMonitor : public etlng::MonitorInterface { - MOCK_METHOD(void, notifyLedgerLoaded, (uint32_t), (override)); - MOCK_METHOD(boost::signals2::scoped_connection, subscribe, (SignalType::slot_type const&), (override)); + MOCK_METHOD(void, notifySequenceLoaded, (uint32_t), (override)); + MOCK_METHOD(void, notifyWriteConflict, (uint32_t), (override)); + MOCK_METHOD( + boost::signals2::scoped_connection, + subscribeToNewSequence, + (NewSequenceSignalType::slot_type const&), + (override) + ); + MOCK_METHOD( + boost::signals2::scoped_connection, + subscribeToDbStalled, + (DbStalledSignalType::slot_type const&), + (override) + ); MOCK_METHOD(void, run, (std::chrono::steady_clock::duration), (override)); MOCK_METHOD(void, stop, (), (override)); }; @@ -83,7 +100,8 @@ struct MockExtractor : etlng::ExtractorInterface { }; struct MockLoader : etlng::LoaderInterface { - MOCK_METHOD(void, load, (etlng::model::LedgerData const&), (override)); + using ExpectedType = std::expected; + MOCK_METHOD(ExpectedType, load, (etlng::model::LedgerData const&), (override)); MOCK_METHOD(std::optional, loadInitialLedger, (etlng::model::LedgerData const&), (override)); }; @@ -118,7 +136,23 @@ struct MockTaskManagerProvider : etlng::TaskManagerProviderInterface { MOCK_METHOD( std::unique_ptr, make, - (util::async::AnyExecutionContext, std::reference_wrapper, uint32_t), + (util::async::AnyExecutionContext, + std::reference_wrapper, + uint32_t, + std::optional), + (override) + ); +}; + +struct MockMonitorProvider : etlng::MonitorProviderInterface { + MOCK_METHOD( + std::unique_ptr, + make, + (util::async::AnyExecutionContext, + std::shared_ptr, + std::shared_ptr, + uint32_t, + std::chrono::steady_clock::duration), (override) ); }; @@ -134,7 +168,7 @@ createTestData(uint32_t seq) .edgeKeys = {}, .header = header, .rawHeader = {}, - .seq = seq + .seq = seq, }; } } // namespace @@ -150,6 +184,9 @@ struct ETLServiceTests : util::prometheus::WithPrometheus, MockBackendTest { protected: SameThreadTestContext ctx_; util::config::ClioConfigDefinition config_{ + {"read_only", ConfigValue{ConfigType::Boolean}.defaultValue(false)}, + {"start_sequence", ConfigValue{ConfigType::Integer}.optional().withConstraint(gValidateUint32)}, + {"finish_sequence", ConfigValue{ConfigType::Integer}.optional().withConstraint(gValidateUint32)}, {"extractor_threads", ConfigValue{ConfigType::Integer}.defaultValue(4)}, {"io_threads", ConfigValue{ConfigType::Integer}.defaultValue(2)}, {"cache.num_diffs", ConfigValue{ConfigType::Integer}.defaultValue(32)}, @@ -159,7 +196,7 @@ protected: {"cache.page_fetch_size", ConfigValue{ConfigType::Integer}.defaultValue(512)}, {"cache.load", ConfigValue{ConfigType::String}.defaultValue("async")} }; - StrictMockSubscriptionManagerSharedPtr subscriptions_; + MockSubscriptionManagerSharedPtr subscriptions_; std::shared_ptr> balancer_ = std::make_shared>(); std::shared_ptr> ledgers_ = @@ -176,6 +213,8 @@ protected: std::make_shared>(); std::shared_ptr> taskManagerProvider_ = std::make_shared>(); + std::shared_ptr> monitorProvider_ = + std::make_shared>(); std::shared_ptr systemState_ = std::make_shared(); etlng::ETLService service_{ @@ -191,6 +230,7 @@ protected: loader_, initialLoadObserver_, taskManagerProvider_, + monitorProvider_, systemState_ }; }; @@ -258,82 +298,244 @@ TEST_F(ETLServiceTests, LastCloseAgeSeconds) TEST_F(ETLServiceTests, RunWithEmptyDatabase) { auto mockTaskManager = std::make_unique>(); + auto& mockTaskManagerRef = *mockTaskManager; auto ledgerData = createTestData(kSEQ); testing::Sequence const s; - EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).InSequence(s).WillOnce(testing::Return(std::nullopt)); + EXPECT_CALL(*backend_, hardFetchLedgerRange).InSequence(s).WillOnce(testing::Return(std::nullopt)); EXPECT_CALL(*ledgers_, getMostRecent()).WillRepeatedly(testing::Return(kSEQ)); EXPECT_CALL(*extractor_, extractLedgerOnly(kSEQ)).WillOnce(testing::Return(ledgerData)); EXPECT_CALL(*balancer_, loadInitialLedger(kSEQ, testing::_, testing::_)) .WillOnce(testing::Return(std::vector{})); - EXPECT_CALL(*loader_, loadInitialLedger(testing::_)).WillOnce(testing::Return(ripple::LedgerHeader{})); - EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)) + EXPECT_CALL(*loader_, loadInitialLedger).WillOnce(testing::Return(ripple::LedgerHeader{})); + EXPECT_CALL(*backend_, hardFetchLedgerRange) .InSequence(s) .WillOnce(testing::Return(data::LedgerRange{.minSequence = 1, .maxSequence = kSEQ})); - EXPECT_CALL(*mockTaskManager, run(testing::_)); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, kSEQ + 1)) + EXPECT_CALL(mockTaskManagerRef, run); + EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, kSEQ + 1, testing::_)) .WillOnce(testing::Return(std::unique_ptr(mockTaskManager.release()))); + EXPECT_CALL(*monitorProvider_, make(testing::_, testing::_, testing::_, testing::_, testing::_)) + .WillOnce([](auto, auto, auto, auto, auto) { return std::make_unique>(); }); service_.run(); } TEST_F(ETLServiceTests, RunWithPopulatedDatabase) { - auto mockTaskManager = std::make_unique>(); - - EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)) + EXPECT_CALL(*backend_, hardFetchLedgerRange) .WillRepeatedly(testing::Return(data::LedgerRange{.minSequence = 1, .maxSequence = kSEQ})); + EXPECT_CALL(*monitorProvider_, make).WillOnce([](auto, auto, auto, auto, auto) { + return std::make_unique>(); + }); EXPECT_CALL(*ledgers_, getMostRecent()).WillRepeatedly(testing::Return(kSEQ)); EXPECT_CALL(*cacheLoader_, load(kSEQ)); - EXPECT_CALL(*mockTaskManager, run(testing::_)); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, kSEQ + 1)) - .WillOnce(testing::Return(std::unique_ptr(mockTaskManager.release()))); service_.run(); } TEST_F(ETLServiceTests, WaitForValidatedLedgerIsAborted) { - EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(std::nullopt)); + EXPECT_CALL(*backend_, hardFetchLedgerRange).WillOnce(testing::Return(std::nullopt)); EXPECT_CALL(*ledgers_, getMostRecent()).Times(2).WillRepeatedly(testing::Return(std::nullopt)); // No other calls should happen because we exit early - EXPECT_CALL(*extractor_, extractLedgerOnly(testing::_)).Times(0); + EXPECT_CALL(*extractor_, extractLedgerOnly).Times(0); EXPECT_CALL(*balancer_, loadInitialLedger(testing::_, testing::_, testing::_)).Times(0); - EXPECT_CALL(*loader_, loadInitialLedger(testing::_)).Times(0); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, testing::_)).Times(0); + EXPECT_CALL(*loader_, loadInitialLedger).Times(0); + EXPECT_CALL(*taskManagerProvider_, make).Times(0); service_.run(); } -struct ETLServiceAssertTests : common::util::WithMockAssert, ETLServiceTests {}; - -TEST_F(ETLServiceAssertTests, FailToLoadInitialLedger) +TEST_F(ETLServiceTests, HandlesWriteConflictInMonitorSubscription) { - EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(std::nullopt)); + auto mockMonitor = std::make_unique>(); + auto& mockMonitorRef = *mockMonitor; + std::function capturedCallback; + + EXPECT_CALL(*monitorProvider_, make).WillOnce([&mockMonitor](auto, auto, auto, auto, auto) { + return std::move(mockMonitor); + }); + + EXPECT_CALL(mockMonitorRef, subscribeToNewSequence).WillOnce([&capturedCallback](auto&& callback) { + capturedCallback = callback; + return boost::signals2::scoped_connection{}; + }); + EXPECT_CALL(mockMonitorRef, subscribeToDbStalled); + EXPECT_CALL(mockMonitorRef, run); + + EXPECT_CALL(*backend_, hardFetchLedgerRange) + .WillOnce(testing::Return(data::LedgerRange{.minSequence = 1, .maxSequence = kSEQ})); + EXPECT_CALL(*ledgers_, getMostRecent()).WillOnce(testing::Return(kSEQ)); + EXPECT_CALL(*cacheLoader_, load(kSEQ)); + + service_.run(); + systemState_->writeConflict = true; + + EXPECT_CALL(*publisher_, publish(kSEQ + 1, testing::_, testing::_)); + ASSERT_TRUE(capturedCallback); + capturedCallback(kSEQ + 1); + + EXPECT_FALSE(systemState_->writeConflict); + EXPECT_FALSE(systemState_->isWriting); +} + +TEST_F(ETLServiceTests, NormalFlowInMonitorSubscription) +{ + auto mockMonitor = std::make_unique>(); + auto& mockMonitorRef = *mockMonitor; + std::function capturedCallback; + + EXPECT_CALL(*monitorProvider_, make).WillOnce([&mockMonitor](auto, auto, auto, auto, auto) { + return std::move(mockMonitor); + }); + + EXPECT_CALL(mockMonitorRef, subscribeToNewSequence).WillOnce([&capturedCallback](auto callback) { + capturedCallback = callback; + return boost::signals2::scoped_connection{}; + }); + EXPECT_CALL(mockMonitorRef, subscribeToDbStalled); + EXPECT_CALL(mockMonitorRef, run); + + EXPECT_CALL(*backend_, hardFetchLedgerRange) + .WillOnce(testing::Return(data::LedgerRange{.minSequence = 1, .maxSequence = kSEQ})); + EXPECT_CALL(*ledgers_, getMostRecent()).WillOnce(testing::Return(kSEQ)); + EXPECT_CALL(*cacheLoader_, load(kSEQ)); + + service_.run(); + systemState_->isWriting = false; + std::vector const dummyDiff = {}; + + EXPECT_CALL(*backend_, fetchLedgerDiff(kSEQ + 1, testing::_)).WillOnce(testing::Return(dummyDiff)); + EXPECT_CALL(*cacheUpdater_, update(kSEQ + 1, testing::A const&>())); + EXPECT_CALL(*publisher_, publish(kSEQ + 1, testing::_, testing::_)); + + ASSERT_TRUE(capturedCallback); + capturedCallback(kSEQ + 1); +} + +TEST_F(ETLServiceTests, AttemptTakeoverWriter) +{ + auto mockMonitor = std::make_unique>(); + auto& mockMonitorRef = *mockMonitor; + std::function capturedDbStalledCallback; + + EXPECT_CALL(*monitorProvider_, make).WillOnce([&mockMonitor](auto, auto, auto, auto, auto) { + return std::move(mockMonitor); + }); + + EXPECT_CALL(mockMonitorRef, subscribeToNewSequence); + EXPECT_CALL(mockMonitorRef, subscribeToDbStalled).WillOnce([&capturedDbStalledCallback](auto callback) { + capturedDbStalledCallback = callback; + return boost::signals2::scoped_connection{}; + }); + EXPECT_CALL(mockMonitorRef, run); + + EXPECT_CALL(*backend_, hardFetchLedgerRange) + .WillRepeatedly(testing::Return(data::LedgerRange{.minSequence = 1, .maxSequence = kSEQ})); + EXPECT_CALL(*ledgers_, getMostRecent()).WillOnce(testing::Return(kSEQ)); + EXPECT_CALL(*cacheLoader_, load(kSEQ)); + + service_.run(); + systemState_->isStrictReadonly = false; // writer node + systemState_->isWriting = false; // but starts in readonly as usual + + auto mockTaskManager = std::make_unique>(); + auto& mockTaskManagerRef = *mockTaskManager; + EXPECT_CALL(mockTaskManagerRef, run); + + EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, kSEQ + 1, testing::_)) + .WillOnce(testing::Return(std::move(mockTaskManager))); + + ASSERT_TRUE(capturedDbStalledCallback); + capturedDbStalledCallback(); + + EXPECT_TRUE(systemState_->isWriting); // should attempt to become writer +} + +TEST_F(ETLServiceTests, GiveUpWriterAfterWriteConflict) +{ + auto mockMonitor = std::make_unique>(); + auto& mockMonitorRef = *mockMonitor; + + std::function capturedCallback; + + EXPECT_CALL(*monitorProvider_, make).WillOnce([&mockMonitor](auto, auto, auto, auto, auto) { + return std::move(mockMonitor); + }); + EXPECT_CALL(mockMonitorRef, subscribeToNewSequence).WillOnce([&capturedCallback](auto callback) { + capturedCallback = callback; + return boost::signals2::scoped_connection{}; + }); + EXPECT_CALL(mockMonitorRef, subscribeToDbStalled); + EXPECT_CALL(mockMonitorRef, run); + + EXPECT_CALL(*backend_, hardFetchLedgerRange) + .WillOnce(testing::Return(data::LedgerRange{.minSequence = 1, .maxSequence = kSEQ})); + EXPECT_CALL(*ledgers_, getMostRecent()).WillOnce(testing::Return(kSEQ)); + EXPECT_CALL(*cacheLoader_, load(kSEQ)); + + service_.run(); + systemState_->isWriting = true; + systemState_->writeConflict = true; // got a write conflict along the way + + EXPECT_CALL(*publisher_, publish(kSEQ + 1, testing::_, testing::_)); + + ASSERT_TRUE(capturedCallback); + capturedCallback(kSEQ + 1); + + EXPECT_FALSE(systemState_->isWriting); // gives up writing + EXPECT_FALSE(systemState_->writeConflict); // and removes write conflict flag +} + +TEST_F(ETLServiceTests, CancelledLoadInitialLedger) +{ + EXPECT_CALL(*backend_, hardFetchLedgerRange).WillOnce(testing::Return(std::nullopt)); EXPECT_CALL(*ledgers_, getMostRecent()).WillRepeatedly(testing::Return(kSEQ)); EXPECT_CALL(*extractor_, extractLedgerOnly(kSEQ)).WillOnce(testing::Return(std::nullopt)); // These calls should not happen because loading the initial ledger fails EXPECT_CALL(*balancer_, loadInitialLedger(testing::_, testing::_, testing::_)).Times(0); - EXPECT_CALL(*loader_, loadInitialLedger(testing::_)).Times(0); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, testing::_)).Times(0); + EXPECT_CALL(*loader_, loadInitialLedger).Times(0); + EXPECT_CALL(*taskManagerProvider_, make).Times(0); - EXPECT_CLIO_ASSERT_FAIL({ service_.run(); }); + service_.run(); } -TEST_F(ETLServiceAssertTests, WaitForValidatedLedgerIsAbortedLeadToFailToLoadInitialLedger) +TEST_F(ETLServiceTests, WaitForValidatedLedgerIsAbortedLeadToFailToLoadInitialLedger) { testing::Sequence const s; - EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(std::nullopt)); + EXPECT_CALL(*backend_, hardFetchLedgerRange).WillOnce(testing::Return(std::nullopt)); EXPECT_CALL(*ledgers_, getMostRecent()).InSequence(s).WillOnce(testing::Return(std::nullopt)); EXPECT_CALL(*ledgers_, getMostRecent()).InSequence(s).WillOnce(testing::Return(kSEQ)); // No other calls should happen because we exit early - EXPECT_CALL(*extractor_, extractLedgerOnly(testing::_)).Times(0); + EXPECT_CALL(*extractor_, extractLedgerOnly).Times(0); EXPECT_CALL(*balancer_, loadInitialLedger(testing::_, testing::_, testing::_)).Times(0); - EXPECT_CALL(*loader_, loadInitialLedger(testing::_)).Times(0); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, testing::_)).Times(0); + EXPECT_CALL(*loader_, loadInitialLedger).Times(0); + EXPECT_CALL(*taskManagerProvider_, make).Times(0); - EXPECT_CLIO_ASSERT_FAIL({ service_.run(); }); + service_.run(); +} + +TEST_F(ETLServiceTests, RunStopsIfInitialLoadIsCancelledByBalancer) +{ + constexpr uint32_t kMOCK_START_SEQUENCE = 123u; + systemState_->isStrictReadonly = false; + + testing::Sequence const s; + EXPECT_CALL(*backend_, hardFetchLedgerRange).WillOnce(testing::Return(std::nullopt)); + EXPECT_CALL(*ledgers_, getMostRecent).InSequence(s).WillOnce(testing::Return(kMOCK_START_SEQUENCE)); + EXPECT_CALL(*ledgers_, getMostRecent).InSequence(s).WillOnce(testing::Return(kMOCK_START_SEQUENCE + 10)); + + auto const dummyLedgerData = createTestData(kMOCK_START_SEQUENCE); + EXPECT_CALL(*extractor_, extractLedgerOnly(kMOCK_START_SEQUENCE)).WillOnce(testing::Return(dummyLedgerData)); + EXPECT_CALL(*balancer_, loadInitialLedger(testing::_, testing::_, testing::_)) + .WillOnce(testing::Return(std::unexpected{etlng::InitialLedgerLoadError::Cancelled})); + + service_.run(); + + EXPECT_TRUE(systemState_->isWriting); + EXPECT_FALSE(service_.isAmendmentBlocked()); + EXPECT_FALSE(service_.isCorruptionDetected()); } diff --git a/tests/unit/etlng/GrpcSourceTests.cpp b/tests/unit/etlng/GrpcSourceTests.cpp index cb8f1f767..cdc7390b9 100644 --- a/tests/unit/etlng/GrpcSourceTests.cpp +++ b/tests/unit/etlng/GrpcSourceTests.cpp @@ -21,14 +21,17 @@ #include "etl/ETLHelpers.hpp" #include "etl/impl/GrpcSource.hpp" #include "etlng/InitialLoadObserverInterface.hpp" +#include "etlng/LoadBalancerInterface.hpp" #include "etlng/Models.hpp" #include "etlng/impl/GrpcSource.hpp" +#include "util/AsioContextTestFixture.hpp" #include "util/Assert.hpp" #include "util/LoggerFixtures.hpp" #include "util/MockXrpLedgerAPIService.hpp" #include "util/Mutex.hpp" #include "util/TestObject.hpp" +#include #include #include #include @@ -39,9 +42,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -62,7 +67,7 @@ struct MockLoadObserver : etlng::InitialLoadObserverInterface { ); }; -struct GrpcSourceNgTests : NoLoggerFixture, tests::util::WithMockXrpLedgerAPIService { +struct GrpcSourceNgTests : virtual NoLoggerFixture, tests::util::WithMockXrpLedgerAPIService { GrpcSourceNgTests() : WithMockXrpLedgerAPIService("localhost:0"), grpcSource_("localhost", std::to_string(getXRPLMockPort())) { @@ -184,9 +189,8 @@ TEST_F(GrpcSourceNgLoadInitialLedgerTests, GetLedgerDataNotFound) return grpc::Status{grpc::StatusCode::NOT_FOUND, "Not found"}; }); - auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_); - EXPECT_TRUE(data.empty()); - EXPECT_FALSE(success); + auto const res = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_); + EXPECT_FALSE(res.has_value()); } TEST_F(GrpcSourceNgLoadInitialLedgerTests, ObserverCalledCorrectly) @@ -219,12 +223,12 @@ TEST_F(GrpcSourceNgLoadInitialLedgerTests, ObserverCalledCorrectly) EXPECT_EQ(data.size(), 1); }); - auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_); + auto const res = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_); - EXPECT_TRUE(success); - EXPECT_EQ(data.size(), numMarkers_); + EXPECT_TRUE(res.has_value()); + EXPECT_EQ(res.value().size(), numMarkers_); - EXPECT_EQ(data, std::vector(4, keyStr)); + EXPECT_EQ(res.value(), std::vector(4, keyStr)); } TEST_F(GrpcSourceNgLoadInitialLedgerTests, DataTransferredAndObserverCalledCorrectly) @@ -284,12 +288,73 @@ TEST_F(GrpcSourceNgLoadInitialLedgerTests, DataTransferredAndObserverCalledCorre total += data.size(); }); - auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_); + auto const res = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_); - EXPECT_TRUE(success); - EXPECT_EQ(data.size(), numMarkers_); + EXPECT_TRUE(res.has_value()); + EXPECT_EQ(res.value().size(), numMarkers_); EXPECT_EQ(total, totalKeys); EXPECT_EQ(totalWithLastKey + totalWithoutLastKey, numMarkers_ * batchesPerMarker); EXPECT_EQ(totalWithoutLastKey, numMarkers_); EXPECT_EQ(totalWithLastKey, (numMarkers_ - 1) * batchesPerMarker); } + +struct GrpcSourceStopTests : GrpcSourceNgTests, SyncAsioContextTest {}; + +TEST_F(GrpcSourceStopTests, LoadInitialLedgerStopsWhenRequested) +{ + uint32_t const sequence = 123u; + uint32_t const numMarkers = 1; + + std::mutex mtx; + std::condition_variable cvGrpcCallActive; + std::condition_variable cvStopCalled; + bool grpcCallIsActive = false; + bool stopHasBeenCalled = false; + + EXPECT_CALL(mockXrpLedgerAPIService, GetLedgerData) + .WillOnce([&](grpc::ServerContext*, + org::xrpl::rpc::v1::GetLedgerDataRequest const* request, + org::xrpl::rpc::v1::GetLedgerDataResponse* response) { + EXPECT_EQ(request->ledger().sequence(), sequence); + EXPECT_EQ(request->user(), "ETL"); + + { + std::unique_lock const lk(mtx); + grpcCallIsActive = true; + } + cvGrpcCallActive.notify_one(); + + { + std::unique_lock lk(mtx); + cvStopCalled.wait(lk, [&] { return stopHasBeenCalled; }); + } + + response->set_is_unlimited(true); + return grpc::Status::OK; + }); + + EXPECT_CALL(observer_, onInitialLoadGotMoreObjects).Times(0); + + auto loadTask = std::async(std::launch::async, [&]() { + return grpcSource_.loadInitialLedger(sequence, numMarkers, observer_); + }); + + { + std::unique_lock lk(mtx); + cvGrpcCallActive.wait(lk, [&] { return grpcCallIsActive; }); + } + + runSyncOperation([&](boost::asio::yield_context yield) { + grpcSource_.stop(yield); + { + std::unique_lock const lk(mtx); + stopHasBeenCalled = true; + } + cvStopCalled.notify_one(); + }); + + auto const res = loadTask.get(); + + ASSERT_FALSE(res.has_value()); + EXPECT_EQ(res.error(), etlng::InitialLedgerLoadError::Cancelled); +} diff --git a/tests/unit/etlng/LedgerPublisherTests.cpp b/tests/unit/etlng/LedgerPublisherTests.cpp index fdd32e36e..220c3d113 100644 --- a/tests/unit/etlng/LedgerPublisherTests.cpp +++ b/tests/unit/etlng/LedgerPublisherTests.cpp @@ -69,52 +69,64 @@ struct ETLLedgerPublisherNgTest : util::prometheus::WithPrometheus, MockBackendT StrictMockSubscriptionManagerSharedPtr mockSubscriptionManagerPtr; }; -TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderIsWritingFalseAndCacheDisabled) +TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderSkipDueToAge) { - etl::SystemState dummyState; - dummyState.isWriting = false; + // Use kAGE (800) which is > MAX_LEDGER_AGE_SECONDS (600) to test skipping auto const dummyLedgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ, kAGE); - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); - publisher.publish(dummyLedgerHeader); - EXPECT_CALL(*backend_, fetchLedgerDiff(kSEQ, _)).Times(0); + auto dummyState = etl::SystemState{}; + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); - // setLastPublishedSequence not in strand, should verify before run + backend_->setRange(kSEQ - 1, kSEQ); + publisher.publish(dummyLedgerHeader); + + // Verify last published sequence is set immediately EXPECT_TRUE(publisher.getLastPublishedSequence()); EXPECT_EQ(publisher.getLastPublishedSequence().value(), kSEQ); + // Since age > MAX_LEDGER_AGE_SECONDS, these should not be called + EXPECT_CALL(*backend_, doFetchLedgerObject).Times(0); + EXPECT_CALL(*backend_, fetchAllTransactionsInLedger).Times(0); + EXPECT_CALL(*mockSubscriptionManagerPtr, pubLedger).Times(0); + EXPECT_CALL(*mockSubscriptionManagerPtr, pubBookChanges).Times(0); + EXPECT_CALL(*mockSubscriptionManagerPtr, pubTransaction).Times(0); + ctx_.run(); - EXPECT_TRUE(backend_->fetchLedgerRange()); - EXPECT_EQ(backend_->fetchLedgerRange().value().minSequence, kSEQ); - EXPECT_EQ(backend_->fetchLedgerRange().value().maxSequence, kSEQ); } -TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderIsWritingFalseAndCacheEnabled) +TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderWithinAgeLimit) { - etl::SystemState dummyState; - dummyState.isWriting = false; - auto const dummyLedgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ, kAGE); - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + // Use age 0 which is < MAX_LEDGER_AGE_SECONDS to ensure publishing happens + auto const dummyLedgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ, 0); + auto dummyState = etl::SystemState{}; + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + + backend_->setRange(kSEQ - 1, kSEQ); publisher.publish(dummyLedgerHeader); - // setLastPublishedSequence not in strand, should verify before run + // Verify last published sequence is set immediately EXPECT_TRUE(publisher.getLastPublishedSequence()); EXPECT_EQ(publisher.getLastPublishedSequence().value(), kSEQ); + EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::keylet::fees().key, kSEQ, _)) + .WillOnce(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0))); + EXPECT_CALL(*backend_, fetchAllTransactionsInLedger(kSEQ, _)) + .WillOnce(Return(std::vector{})); + + EXPECT_CALL(*mockSubscriptionManagerPtr, pubLedger(_, _, fmt::format("{}-{}", kSEQ - 1, kSEQ), 0)); + EXPECT_CALL(*mockSubscriptionManagerPtr, pubBookChanges); + ctx_.run(); - EXPECT_TRUE(backend_->fetchLedgerRange()); - EXPECT_EQ(backend_->fetchLedgerRange().value().minSequence, kSEQ); - EXPECT_EQ(backend_->fetchLedgerRange().value().maxSequence, kSEQ); + EXPECT_TRUE(publisher.lastPublishAgeSeconds() <= 1); } TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderIsWritingTrue) { - etl::SystemState dummyState; + auto dummyState = etl::SystemState{}; dummyState.isWriting = true; auto const dummyLedgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ, kAGE); - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); publisher.publish(dummyLedgerHeader); - // setLastPublishedSequence not in strand, should verify before run EXPECT_TRUE(publisher.getLastPublishedSequence()); EXPECT_EQ(publisher.getLastPublishedSequence().value(), kSEQ); @@ -124,16 +136,15 @@ TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderIsWritingTrue) TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderInRange) { - etl::SystemState dummyState; + auto dummyState = etl::SystemState{}; dummyState.isWriting = true; auto const dummyLedgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ, 0); // age is 0 - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); backend_->setRange(kSEQ - 1, kSEQ); publisher.publish(dummyLedgerHeader); - // mock fetch fee EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::keylet::fees().key, kSEQ, _)) .WillOnce(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0))); @@ -145,10 +156,8 @@ TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderInRange) .peekData(); t1.ledgerSequence = kSEQ; - // mock fetch transactions EXPECT_CALL(*backend_, fetchAllTransactionsInLedger).WillOnce(Return(std::vector{t1})); - // setLastPublishedSequence not in strand, should verify before run EXPECT_TRUE(publisher.getLastPublishedSequence()); EXPECT_EQ(publisher.getLastPublishedSequence().value(), kSEQ); @@ -158,26 +167,24 @@ TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderInRange) EXPECT_CALL(*mockSubscriptionManagerPtr, pubTransaction); ctx_.run(); - // last publish time should be set EXPECT_TRUE(publisher.lastPublishAgeSeconds() <= 1); } TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderCloseTimeGreaterThanNow) { - etl::SystemState dummyState; + auto dummyState = etl::SystemState{}; dummyState.isWriting = true; - ripple::LedgerHeader dummyLedgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ, 0); + auto dummyLedgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ, 0); auto const nowPlus10 = system_clock::now() + seconds(10); auto const closeTime = duration_cast(nowPlus10.time_since_epoch()).count() - kRIPPLE_EPOCH_START; dummyLedgerHeader.closeTime = ripple::NetClock::time_point{seconds{closeTime}}; backend_->setRange(kSEQ - 1, kSEQ); - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); publisher.publish(dummyLedgerHeader); - // mock fetch fee EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::keylet::fees().key, kSEQ, _)) .WillOnce(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0))); @@ -189,37 +196,33 @@ TEST_F(ETLLedgerPublisherNgTest, PublishLedgerHeaderCloseTimeGreaterThanNow) .peekData(); t1.ledgerSequence = kSEQ; - // mock fetch transactions EXPECT_CALL(*backend_, fetchAllTransactionsInLedger(kSEQ, _)) .WillOnce(Return(std::vector{t1})); - // setLastPublishedSequence not in strand, should verify before run EXPECT_TRUE(publisher.getLastPublishedSequence()); EXPECT_EQ(publisher.getLastPublishedSequence().value(), kSEQ); EXPECT_CALL(*mockSubscriptionManagerPtr, pubLedger(_, _, fmt::format("{}-{}", kSEQ - 1, kSEQ), 1)); EXPECT_CALL(*mockSubscriptionManagerPtr, pubBookChanges); - // mock 1 transaction EXPECT_CALL(*mockSubscriptionManagerPtr, pubTransaction); ctx_.run(); - // last publish time should be set EXPECT_TRUE(publisher.lastPublishAgeSeconds() <= 1); } TEST_F(ETLLedgerPublisherNgTest, PublishLedgerSeqStopIsTrue) { - etl::SystemState dummyState; + auto dummyState = etl::SystemState{}; dummyState.isStopping = true; - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); EXPECT_FALSE(publisher.publish(kSEQ, {})); } TEST_F(ETLLedgerPublisherNgTest, PublishLedgerSeqMaxAttempt) { - etl::SystemState dummyState; + auto dummyState = etl::SystemState{}; dummyState.isStopping = false; - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); static constexpr auto kMAX_ATTEMPT = 2; @@ -231,9 +234,9 @@ TEST_F(ETLLedgerPublisherNgTest, PublishLedgerSeqMaxAttempt) TEST_F(ETLLedgerPublisherNgTest, PublishLedgerSeqStopIsFalse) { - etl::SystemState dummyState; + auto dummyState = etl::SystemState{}; dummyState.isStopping = false; - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); LedgerRange const range{.minSequence = kSEQ, .maxSequence = kSEQ}; EXPECT_CALL(*backend_, hardFetchLedgerRange).WillOnce(Return(range)); @@ -247,16 +250,15 @@ TEST_F(ETLLedgerPublisherNgTest, PublishLedgerSeqStopIsFalse) TEST_F(ETLLedgerPublisherNgTest, PublishMultipleTxInOrder) { - etl::SystemState dummyState; + auto dummyState = etl::SystemState{}; dummyState.isWriting = true; auto const dummyLedgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ, 0); // age is 0 - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); backend_->setRange(kSEQ - 1, kSEQ); publisher.publish(dummyLedgerHeader); - // mock fetch fee EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::keylet::fees().key, kSEQ, _)) .WillOnce(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0))); @@ -278,34 +280,31 @@ TEST_F(ETLLedgerPublisherNgTest, PublishMultipleTxInOrder) t2.ledgerSequence = kSEQ; t2.date = 2; - // mock fetch transactions EXPECT_CALL(*backend_, fetchAllTransactionsInLedger(kSEQ, _)) .WillOnce(Return(std::vector{t1, t2})); - // setLastPublishedSequence not in strand, should verify before run EXPECT_TRUE(publisher.getLastPublishedSequence()); EXPECT_EQ(publisher.getLastPublishedSequence().value(), kSEQ); EXPECT_CALL(*mockSubscriptionManagerPtr, pubLedger(_, _, fmt::format("{}-{}", kSEQ - 1, kSEQ), 2)); EXPECT_CALL(*mockSubscriptionManagerPtr, pubBookChanges); - // should call pubTransaction t2 first (greater tx index) + Sequence const s; EXPECT_CALL(*mockSubscriptionManagerPtr, pubTransaction(t2, _)).InSequence(s); EXPECT_CALL(*mockSubscriptionManagerPtr, pubTransaction(t1, _)).InSequence(s); ctx_.run(); - // last publish time should be set EXPECT_TRUE(publisher.lastPublishAgeSeconds() <= 1); } TEST_F(ETLLedgerPublisherNgTest, PublishVeryOldLedgerShouldSkip) { - etl::SystemState dummyState; + auto dummyState = etl::SystemState{}; dummyState.isWriting = true; // Create a ledger header with age (800) greater than MAX_LEDGER_AGE_SECONDS (600) auto const dummyLedgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ, 800); - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); backend_->setRange(kSEQ - 1, kSEQ); publisher.publish(dummyLedgerHeader); @@ -322,12 +321,12 @@ TEST_F(ETLLedgerPublisherNgTest, PublishVeryOldLedgerShouldSkip) TEST_F(ETLLedgerPublisherNgTest, PublishMultipleLedgersInQuickSuccession) { - etl::SystemState dummyState; + auto dummyState = etl::SystemState{}; dummyState.isWriting = true; auto const dummyLedgerHeader1 = createLedgerHeader(kLEDGER_HASH, kSEQ, 0); auto const dummyLedgerHeader2 = createLedgerHeader(kLEDGER_HASH, kSEQ + 1, 0); - impl::LedgerPublisher publisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); + auto publisher = impl::LedgerPublisher(ctx_, backend_, mockSubscriptionManagerPtr, dummyState); backend_->setRange(kSEQ - 1, kSEQ + 1); // Publish two ledgers in quick succession diff --git a/tests/unit/etlng/LoadBalancerTests.cpp b/tests/unit/etlng/LoadBalancerTests.cpp index 715aa7ec6..97f704959 100644 --- a/tests/unit/etlng/LoadBalancerTests.cpp +++ b/tests/unit/etlng/LoadBalancerTests.cpp @@ -19,6 +19,7 @@ #include "etlng/InitialLoadObserverInterface.hpp" #include "etlng/LoadBalancer.hpp" +#include "etlng/LoadBalancerInterface.hpp" #include "etlng/Models.hpp" #include "etlng/Source.hpp" #include "rpc/Errors.hpp" @@ -459,7 +460,7 @@ struct LoadBalancerLoadInitialLedgerNgTests : LoadBalancerOnConnectHookNgTests { protected: uint32_t const sequence_ = 123; uint32_t const numMarkers_ = 16; - std::pair, bool> const response_ = {{"1", "2", "3"}, true}; + InitialLedgerLoadResult const response_{std::vector{"1", "2", "3"}}; testing::StrictMock observer_; }; @@ -469,7 +470,7 @@ TEST_F(LoadBalancerLoadInitialLedgerNgTests, load) EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, testing::_)) .WillOnce(Return(response_)); - EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first); + EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value()); } TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_source0DoesntHaveLedger) @@ -479,7 +480,7 @@ TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_source0DoesntHaveLedger) EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, testing::_)) .WillOnce(Return(response_)); - EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first); + EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value()); } TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_bothSourcesDontHaveLedger) @@ -489,26 +490,26 @@ TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_bothSourcesDontHaveLedger) EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, testing::_)) .WillOnce(Return(response_)); - EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first); + EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value()); } TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_source0ReturnsStatusFalse) { EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true)); EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, testing::_)) - .WillOnce(Return(std::make_pair(std::vector{}, false))); + .WillOnce(Return(std::unexpected{InitialLedgerLoadError::Errored})); EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(true)); EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, testing::_)) .WillOnce(Return(response_)); - EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first); + EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value()); } struct LoadBalancerLoadInitialLedgerCustomNumMarkersNgTests : LoadBalancerConstructorNgTests { protected: uint32_t const numMarkers_ = 16; uint32_t const sequence_ = 123; - std::pair, bool> const response_ = {{"1", "2", "3"}, true}; + InitialLedgerLoadResult const response_{std::vector{"1", "2", "3"}}; testing::StrictMock observer_; }; @@ -527,7 +528,7 @@ TEST_F(LoadBalancerLoadInitialLedgerCustomNumMarkersNgTests, loadInitialLedger) EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, testing::_)) .WillOnce(Return(response_)); - EXPECT_EQ(loadBalancer->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first); + EXPECT_EQ(loadBalancer->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value()); } struct LoadBalancerFetchLegerNgTests : LoadBalancerOnConnectHookNgTests { diff --git a/tests/unit/etlng/LoadingTests.cpp b/tests/unit/etlng/LoadingTests.cpp index 512c8968d..086e523fe 100644 --- a/tests/unit/etlng/LoadingTests.cpp +++ b/tests/unit/etlng/LoadingTests.cpp @@ -18,6 +18,7 @@ //============================================================================== #include "data/Types.hpp" +#include "etl/SystemState.hpp" #include "etlng/InitialLoadObserverInterface.hpp" #include "etlng/Models.hpp" #include "etlng/RegistryInterface.hpp" @@ -67,7 +68,8 @@ struct MockLoadObserver : etlng::InitialLoadObserverInterface { struct LoadingTests : util::prometheus::WithPrometheus, MockBackendTest, MockAmendmentBlockHandlerTest { protected: std::shared_ptr mockRegistryPtr_ = std::make_shared(); - Loader loader_{backend_, mockRegistryPtr_, mockAmendmentBlockHandlerPtr_}; + std::shared_ptr state_ = std::make_shared(); + Loader loader_{backend_, mockRegistryPtr_, mockAmendmentBlockHandlerPtr_, state_}; }; struct LoadingAssertTest : common::util::WithMockAssert, LoadingTests {}; @@ -104,6 +106,7 @@ TEST_F(LoadingTests, LoadInitialLedger) TEST_F(LoadingTests, LoadSuccess) { + state_->isWriting = true; // writer is active auto const data = createTestData(); EXPECT_CALL(*backend_, doFinishWrites()); @@ -114,6 +117,7 @@ TEST_F(LoadingTests, LoadSuccess) TEST_F(LoadingTests, LoadFailure) { + state_->isWriting = true; // writer is active auto const data = createTestData(); EXPECT_CALL(*backend_, doFinishWrites()).Times(0); diff --git a/tests/unit/etlng/MonitorTests.cpp b/tests/unit/etlng/MonitorTests.cpp index e719a1718..68c5574f9 100644 --- a/tests/unit/etlng/MonitorTests.cpp +++ b/tests/unit/etlng/MonitorTests.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include using namespace etlng::impl; @@ -40,6 +41,7 @@ using namespace data; namespace { constexpr auto kSTART_SEQ = 123u; +constexpr auto kNO_NEW_LEDGER_REPORT_DELAY = std::chrono::milliseconds(1u); } // namespace struct MonitorTests : util::prometheus::WithPrometheus, MockBackendTest { @@ -47,8 +49,10 @@ protected: util::async::CoroExecutionContext ctx_; StrictMockNetworkValidatedLedgersPtr ledgers_; testing::StrictMock> actionMock_; + testing::StrictMock> dbStalledMock_; - etlng::impl::Monitor monitor_ = etlng::impl::Monitor(ctx_, backend_, ledgers_, kSTART_SEQ); + etlng::impl::Monitor monitor_ = + etlng::impl::Monitor(ctx_, backend_, ledgers_, kSTART_SEQ, kNO_NEW_LEDGER_REPORT_DELAY); }; TEST_F(MonitorTests, ConsumesAndNotifiesForAllOutstandingSequencesAtOnce) @@ -65,7 +69,7 @@ TEST_F(MonitorTests, ConsumesAndNotifiesForAllOutstandingSequencesAtOnce) unblock.release(); }); - auto subscription = monitor_.subscribe(actionMock_.AsStdFunction()); + auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction()); monitor_.run(std::chrono::milliseconds{10}); unblock.acquire(); } @@ -88,7 +92,7 @@ TEST_F(MonitorTests, NotifiesForEachSequence) unblock.release(); }); - auto subscription = monitor_.subscribe(actionMock_.AsStdFunction()); + auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction()); monitor_.run(std::chrono::milliseconds{1}); unblock.acquire(); } @@ -106,7 +110,7 @@ TEST_F(MonitorTests, NotifiesWhenForcedByNewSequenceAvailableFromNetwork) EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(range)); EXPECT_CALL(actionMock_, Call).WillOnce([&] { unblock.release(); }); - auto subscription = monitor_.subscribe(actionMock_.AsStdFunction()); + auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction()); monitor_.run(std::chrono::seconds{10}); // expected to be force-invoked sooner than in 10 sec pusher(kSTART_SEQ); // pretend network validated a new ledger unblock.acquire(); @@ -121,8 +125,49 @@ TEST_F(MonitorTests, NotifiesWhenForcedByLedgerLoaded) EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(range)); EXPECT_CALL(actionMock_, Call).WillOnce([&] { unblock.release(); }); - auto subscription = monitor_.subscribe(actionMock_.AsStdFunction()); - monitor_.run(std::chrono::seconds{10}); // expected to be force-invoked sooner than in 10 sec - monitor_.notifyLedgerLoaded(kSTART_SEQ); // notify about newly committed ledger + auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction()); + monitor_.run(std::chrono::seconds{10}); // expected to be force-invoked sooner than in 10 sec + monitor_.notifySequenceLoaded(kSTART_SEQ); // notify about newly committed ledger + unblock.acquire(); +} + +TEST_F(MonitorTests, ResumesMonitoringFromNextSequenceAfterWriteConflict) +{ + constexpr uint32_t kCONFLICT_SEQ = 456u; + constexpr uint32_t kEXPECTED_NEXT_SEQ = kCONFLICT_SEQ + 1; + + LedgerRange const rangeBeforeConflict(kSTART_SEQ, kSTART_SEQ); + LedgerRange const rangeAfterConflict(kEXPECTED_NEXT_SEQ, kEXPECTED_NEXT_SEQ); + std::binary_semaphore unblock(0); + + EXPECT_CALL(*ledgers_, subscribe(testing::_)); + + { + testing::InSequence const seq; // second call will produce conflict + EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(rangeBeforeConflict)); + EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillRepeatedly(testing::Return(rangeAfterConflict)); + } + + EXPECT_CALL(actionMock_, Call(kEXPECTED_NEXT_SEQ)).WillOnce([&](uint32_t seq) { + EXPECT_EQ(seq, kEXPECTED_NEXT_SEQ); + unblock.release(); + }); + + auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction()); + monitor_.run(std::chrono::nanoseconds{100}); + monitor_.notifyWriteConflict(kCONFLICT_SEQ); + unblock.acquire(); +} + +TEST_F(MonitorTests, DbStalledChannelTriggeredWhenTimeoutExceeded) +{ + std::binary_semaphore unblock(0); + + EXPECT_CALL(*ledgers_, subscribe(testing::_)); + EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillRepeatedly(testing::Return(std::nullopt)); + EXPECT_CALL(dbStalledMock_, Call()).WillOnce([&]() { unblock.release(); }); + + auto subscription = monitor_.subscribeToDbStalled(dbStalledMock_.AsStdFunction()); + monitor_.run(std::chrono::nanoseconds{100}); unblock.acquire(); } diff --git a/tests/unit/etlng/RegistryTests.cpp b/tests/unit/etlng/RegistryTests.cpp index 5e40a6c2a..b75b044d2 100644 --- a/tests/unit/etlng/RegistryTests.cpp +++ b/tests/unit/etlng/RegistryTests.cpp @@ -672,16 +672,28 @@ TEST_F(RegistryTest, MixedReadonlyAndRegularExtensions) TEST_F(RegistryTest, MonitorInterfaceExecution) { struct MockMonitor : etlng::MonitorInterface { - MOCK_METHOD(void, notifyLedgerLoaded, (uint32_t), (override)); - MOCK_METHOD(boost::signals2::scoped_connection, subscribe, (SignalType::slot_type const&), (override)); + MOCK_METHOD(void, notifySequenceLoaded, (uint32_t), (override)); + MOCK_METHOD(void, notifyWriteConflict, (uint32_t), (override)); + MOCK_METHOD( + boost::signals2::scoped_connection, + subscribeToNewSequence, + (NewSequenceSignalType::slot_type const&), + (override) + ); + MOCK_METHOD( + boost::signals2::scoped_connection, + subscribeToDbStalled, + (DbStalledSignalType::slot_type const&), + (override) + ); MOCK_METHOD(void, run, (std::chrono::steady_clock::duration), (override)); MOCK_METHOD(void, stop, (), (override)); }; auto monitor = MockMonitor{}; - EXPECT_CALL(monitor, notifyLedgerLoaded(kSEQ)).Times(1); + EXPECT_CALL(monitor, notifySequenceLoaded(kSEQ)).Times(1); - monitor.notifyLedgerLoaded(kSEQ); + monitor.notifySequenceLoaded(kSEQ); } TEST_F(RegistryTest, ReadonlyModeWithAllowInReadonlyTest) diff --git a/tests/unit/etlng/SourceImplTests.cpp b/tests/unit/etlng/SourceImplTests.cpp index d9a01f1b7..17d4d1b50 100644 --- a/tests/unit/etlng/SourceImplTests.cpp +++ b/tests/unit/etlng/SourceImplTests.cpp @@ -18,6 +18,7 @@ //============================================================================== #include "etlng/InitialLoadObserverInterface.hpp" +#include "etlng/LoadBalancerInterface.hpp" #include "etlng/Models.hpp" #include "etlng/impl/SourceImpl.hpp" #include "rpc/Errors.hpp" @@ -33,6 +34,7 @@ #include #include +#include #include #include #include @@ -51,8 +53,10 @@ struct GrpcSourceMock { using FetchLedgerReturnType = std::pair; MOCK_METHOD(FetchLedgerReturnType, fetchLedger, (uint32_t, bool, bool)); - using LoadLedgerReturnType = std::pair, bool>; + using LoadLedgerReturnType = etlng::InitialLedgerLoadResult; MOCK_METHOD(LoadLedgerReturnType, loadInitialLedger, (uint32_t, uint32_t, etlng::InitialLoadObserverInterface&)); + + MOCK_METHOD(void, stop, (boost::asio::yield_context), ()); }; struct SubscriptionSourceMock { @@ -127,6 +131,7 @@ TEST_F(SourceImplNgTest, run) TEST_F(SourceImplNgTest, stop) { EXPECT_CALL(*subscriptionSourceMock_, stop); + EXPECT_CALL(grpcSourceMock_, stop); boost::asio::io_context ctx; boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) { source_.stop(yield); }); ctx.run(); @@ -190,7 +195,7 @@ TEST_F(SourceImplNgTest, fetchLedger) EXPECT_EQ(actualStatus.error_code(), grpc::StatusCode::OK); } -TEST_F(SourceImplNgTest, loadInitialLedger) +TEST_F(SourceImplNgTest, loadInitialLedgerErrorPath) { uint32_t const ledgerSeq = 123; uint32_t const numMarkers = 3; @@ -198,11 +203,25 @@ TEST_F(SourceImplNgTest, loadInitialLedger) auto observerMock = testing::StrictMock(); EXPECT_CALL(grpcSourceMock_, loadInitialLedger(ledgerSeq, numMarkers, testing::_)) - .WillOnce(Return(std::make_pair(std::vector{}, true))); - auto const [actualLedgers, actualSuccess] = source_.loadInitialLedger(ledgerSeq, numMarkers, observerMock); + .WillOnce(Return(std::unexpected{etlng::InitialLedgerLoadError::Errored})); + auto const res = source_.loadInitialLedger(ledgerSeq, numMarkers, observerMock); - EXPECT_TRUE(actualLedgers.empty()); - EXPECT_TRUE(actualSuccess); + EXPECT_FALSE(res.has_value()); +} + +TEST_F(SourceImplNgTest, loadInitialLedgerSuccessPath) +{ + uint32_t const ledgerSeq = 123; + uint32_t const numMarkers = 3; + auto response = etlng::InitialLedgerLoadResult{{"1", "2", "3"}}; + + auto observerMock = testing::StrictMock(); + + EXPECT_CALL(grpcSourceMock_, loadInitialLedger(ledgerSeq, numMarkers, testing::_)).WillOnce(Return(response)); + auto const res = source_.loadInitialLedger(ledgerSeq, numMarkers, observerMock); + + EXPECT_TRUE(res.has_value()); + EXPECT_EQ(res, response); } TEST_F(SourceImplNgTest, forwardToRippled) diff --git a/tests/unit/etlng/TaskManagerTests.cpp b/tests/unit/etlng/TaskManagerTests.cpp index d94655a0b..5931dcfbd 100644 --- a/tests/unit/etlng/TaskManagerTests.cpp +++ b/tests/unit/etlng/TaskManagerTests.cpp @@ -62,13 +62,26 @@ struct MockExtractor : etlng::ExtractorInterface { }; struct MockLoader : etlng::LoaderInterface { - MOCK_METHOD(void, load, (LedgerData const&), (override)); + using ExpectedType = std::expected; + MOCK_METHOD(ExpectedType, load, (LedgerData const&), (override)); MOCK_METHOD(std::optional, loadInitialLedger, (LedgerData const&), (override)); }; struct MockMonitor : etlng::MonitorInterface { - MOCK_METHOD(void, notifyLedgerLoaded, (uint32_t), (override)); - MOCK_METHOD(boost::signals2::scoped_connection, subscribe, (SignalType::slot_type const&), (override)); + MOCK_METHOD(void, notifySequenceLoaded, (uint32_t), (override)); + MOCK_METHOD(void, notifyWriteConflict, (uint32_t), (override)); + MOCK_METHOD( + boost::signals2::scoped_connection, + subscribeToNewSequence, + (NewSequenceSignalType::slot_type const&), + (override) + ); + MOCK_METHOD( + boost::signals2::scoped_connection, + subscribeToDbStalled, + (DbStalledSignalType::slot_type const&), + (override) + ); MOCK_METHOD(void, run, (std::chrono::steady_clock::duration), (override)); MOCK_METHOD(void, stop, (), (override)); }; @@ -127,21 +140,130 @@ TEST_F(TaskManagerTests, LoaderGetsDataIfNextSequenceIsExtracted) return createTestData(seq); }); - EXPECT_CALL(*mockLoaderPtr_, load(testing::_)).Times(kTOTAL).WillRepeatedly([&](LedgerData data) { - loaded.push_back(data.seq); - if (loaded.size() == kTOTAL) { - done.release(); - } - }); + EXPECT_CALL(*mockLoaderPtr_, load(testing::_)) + .Times(kTOTAL) + .WillRepeatedly([&](LedgerData data) -> std::expected { + loaded.push_back(data.seq); + if (loaded.size() == kTOTAL) + done.release(); - EXPECT_CALL(*mockMonitorPtr_, notifyLedgerLoaded(testing::_)).Times(kTOTAL); + return {}; + }); + + EXPECT_CALL(*mockMonitorPtr_, notifySequenceLoaded(testing::_)).Times(kTOTAL); taskManager_.run(kEXTRACTORS); done.acquire(); taskManager_.stop(); EXPECT_EQ(loaded.size(), kTOTAL); - for (std::size_t i = 0; i < loaded.size(); ++i) { + for (std::size_t i = 0; i < loaded.size(); ++i) + EXPECT_EQ(loaded[i], kSEQ + i); +} + +TEST_F(TaskManagerTests, WriteConflictHandling) +{ + static constexpr auto kTOTAL = 64uz; + static constexpr auto kCONFLICT_AFTER = 32uz; // Conflict after 32 ledgers + static constexpr auto kEXTRACTORS = 4uz; + + std::atomic_uint32_t seq = kSEQ; + std::vector loaded; + std::binary_semaphore done{0}; + bool conflictOccurred = false; + + EXPECT_CALL(*mockSchedulerPtr_, next()).WillRepeatedly([&]() { + return Task{.priority = Task::Priority::Higher, .seq = seq++}; + }); + + EXPECT_CALL(*mockExtractorPtr_, extractLedgerWithDiff(testing::_)) + .WillRepeatedly([](uint32_t seq) -> std::optional { + if (seq > kSEQ + kTOTAL - 1) + return std::nullopt; + + return createTestData(seq); + }); + + // First kCONFLICT_AFTER calls succeed, then we get a write conflict + EXPECT_CALL(*mockLoaderPtr_, load(testing::_)) + .WillRepeatedly([&](LedgerData data) -> std::expected { + loaded.push_back(data.seq); + + if (loaded.size() == kCONFLICT_AFTER) { + conflictOccurred = true; + done.release(); + return std::unexpected(etlng::LoaderError::WriteConflict); + } + + if (loaded.size() == kTOTAL) + done.release(); + + return {}; + }); + + EXPECT_CALL(*mockMonitorPtr_, notifySequenceLoaded(testing::_)).Times(kCONFLICT_AFTER - 1); + EXPECT_CALL(*mockMonitorPtr_, notifyWriteConflict(kSEQ + kCONFLICT_AFTER - 1)); + + taskManager_.run(kEXTRACTORS); + done.acquire(); + taskManager_.stop(); + + EXPECT_EQ(loaded.size(), kCONFLICT_AFTER); + EXPECT_TRUE(conflictOccurred); + + for (std::size_t i = 0; i < loaded.size(); ++i) + EXPECT_EQ(loaded[i], kSEQ + i); +} + +TEST_F(TaskManagerTests, AmendmentBlockedHandling) +{ + static constexpr auto kTOTAL = 64uz; + static constexpr auto kAMENDMENT_BLOCKED_AFTER = 20uz; // Amendment block after 20 ledgers + static constexpr auto kEXTRACTORS = 2uz; + + std::atomic_uint32_t seq = kSEQ; + std::vector loaded; + std::binary_semaphore done{0}; + bool amendmentBlockedOccurred = false; + + EXPECT_CALL(*mockSchedulerPtr_, next()).WillRepeatedly([&]() { + return Task{.priority = Task::Priority::Higher, .seq = seq++}; + }); + + EXPECT_CALL(*mockExtractorPtr_, extractLedgerWithDiff(testing::_)) + .WillRepeatedly([](uint32_t seq) -> std::optional { + if (seq > kSEQ + kTOTAL - 1) + return std::nullopt; + + return createTestData(seq); + }); + + EXPECT_CALL(*mockLoaderPtr_, load(testing::_)) + .WillRepeatedly([&](LedgerData data) -> std::expected { + loaded.push_back(data.seq); + + if (loaded.size() == kAMENDMENT_BLOCKED_AFTER) { + amendmentBlockedOccurred = true; + done.release(); + return std::unexpected(etlng::LoaderError::AmendmentBlocked); + } + + if (loaded.size() == kTOTAL) + done.release(); + + return {}; + }); + + EXPECT_CALL(*mockMonitorPtr_, notifySequenceLoaded(testing::_)).Times(kAMENDMENT_BLOCKED_AFTER - 1); + EXPECT_CALL(*mockMonitorPtr_, notifyWriteConflict(testing::_)).Times(0); + + taskManager_.run(kEXTRACTORS); + done.acquire(); + taskManager_.stop(); + + EXPECT_EQ(loaded.size(), kAMENDMENT_BLOCKED_AFTER); + EXPECT_TRUE(amendmentBlockedOccurred); + + for (std::size_t i = 0; i < loaded.size(); ++i) EXPECT_EQ(loaded[i], kSEQ + i); - } } diff --git a/tests/unit/feed/BookChangesFeedTests.cpp b/tests/unit/feed/BookChangesFeedTests.cpp index c7480b3de..3bad15a80 100644 --- a/tests/unit/feed/BookChangesFeedTests.cpp +++ b/tests/unit/feed/BookChangesFeedTests.cpp @@ -62,21 +62,21 @@ TEST_F(FeedBookChangeTest, Pub) static constexpr auto kBOOK_CHANGE_PUBLISH = R"JSON({ - "type":"bookChanges", - "ledger_index":32, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_time":0, + "type": "bookChanges", + "ledger_index": 32, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_time": 0, "changes": [ { - "currency_a":"XRP_drops", - "currency_b":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD/0158415500000000C1F76FF6ECB0BAC600000000", - "volume_a":"2", - "volume_b":"2", - "high":"-1", - "low":"-1", - "open":"-1", - "close":"-1" + "currency_a": "XRP_drops", + "currency_b": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD/0158415500000000C1F76FF6ECB0BAC600000000", + "volume_a": "2", + "volume_b": "2", + "high": "-1", + "low": "-1", + "open": "-1", + "close": "-1" } ] })JSON"; diff --git a/tests/unit/feed/ForwardFeedTests.cpp b/tests/unit/feed/ForwardFeedTests.cpp index 7bbf4fe88..a1056cf96 100644 --- a/tests/unit/feed/ForwardFeedTests.cpp +++ b/tests/unit/feed/ForwardFeedTests.cpp @@ -34,7 +34,7 @@ using namespace util::prometheus; namespace { -constexpr auto kFEED = R"JSON({"test":"test"})JSON"; +constexpr auto kFEED = R"JSON({"test": "test"})JSON"; } // namespace diff --git a/tests/unit/feed/LedgerFeedTests.cpp b/tests/unit/feed/LedgerFeedTests.cpp index 007a91da8..cc9762a78 100644 --- a/tests/unit/feed/LedgerFeedTests.cpp +++ b/tests/unit/feed/LedgerFeedTests.cpp @@ -53,13 +53,13 @@ TEST_F(FeedLedgerTest, SubPub) // the type and txn_count fields static constexpr auto kLEDGER_RESPONSE = R"JSON({ - "validated_ledgers":"10-30", - "ledger_index":30, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_time":0, - "fee_base":1, - "reserve_base":3, - "reserve_inc":2 + "validated_ledgers": "10-30", + "ledger_index": 30, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_time": 0, + "fee_base": 1, + "reserve_base": 3, + "reserve_inc": 2 })JSON"; boost::asio::io_context ioContext; boost::asio::spawn(ioContext, [this](boost::asio::yield_context yield) { @@ -73,15 +73,15 @@ TEST_F(FeedLedgerTest, SubPub) static constexpr auto kLEDGER_PUB = R"JSON({ - "type":"ledgerClosed", - "ledger_index":31, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_time":0, - "fee_base":0, - "reserve_base":10, - "reserve_inc":0, - "validated_ledgers":"10-31", - "txn_count":8 + "type": "ledgerClosed", + "ledger_index": 31, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_time": 0, + "fee_base": 0, + "reserve_base": 10, + "reserve_inc": 0, + "validated_ledgers": "10-31", + "txn_count": 8 })JSON"; // test publish @@ -108,13 +108,13 @@ TEST_F(FeedLedgerTest, AutoDisconnect) EXPECT_CALL(*backend_, doFetchLedgerObject).WillOnce(testing::Return(feeBlob)); static constexpr auto kLEDGER_RESPONSE = R"JSON({ - "validated_ledgers":"10-30", - "ledger_index":30, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_time":0, - "fee_base":1, - "reserve_base":3, - "reserve_inc":2 + "validated_ledgers": "10-30", + "ledger_index": 30, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_time": 0, + "fee_base": 1, + "reserve_base": 3, + "reserve_inc": 2 })JSON"; web::SubscriptionContextInterface::OnDisconnectSlot slot; diff --git a/tests/unit/feed/ProposedTransactionFeedTests.cpp b/tests/unit/feed/ProposedTransactionFeedTests.cpp index 3126c5b3a..da2f580de 100644 --- a/tests/unit/feed/ProposedTransactionFeedTests.cpp +++ b/tests/unit/feed/ProposedTransactionFeedTests.cpp @@ -43,16 +43,16 @@ constexpr auto kDUMMY_TRANSACTION = R"JSON({ "transaction": { - "Account":"rh1HPuRVsYYvThxG2Bs1MfjmrVC73S16Fb", - "Amount":"40000000", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"20", - "Flags":2147483648, - "Sequence":13767283, - "SigningPubKey":"036F3CFFE1EA77C1EEC5DCCA38C83E62E3AC068F8A16369620AF1D609BA5A620B2", - "TransactionType":"Payment", - "TxnSignature":"30450221009BD0D563B24E50B26A42F30455AD21C3D5CD4D80174C41F7B54969FFC08DE94C02201FC35320B56D56D1E34D1D281D48AC68CBEDDD6EE9DFA639CCB08BB251453A87", - "hash":"F44393295DB860C6860769C16F5B23887762F09F87A8D1174E0FCFF9E7247F07" + "Account": "rh1HPuRVsYYvThxG2Bs1MfjmrVC73S16Fb", + "Amount": "40000000", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "20", + "Flags": 2147483648, + "Sequence": 13767283, + "SigningPubKey": "036F3CFFE1EA77C1EEC5DCCA38C83E62E3AC068F8A16369620AF1D609BA5A620B2", + "TransactionType": "Payment", + "TxnSignature": "30450221009BD0D563B24E50B26A42F30455AD21C3D5CD4D80174C41F7B54969FFC08DE94C02201FC35320B56D56D1E34D1D281D48AC68CBEDDD6EE9DFA639CCB08BB251453A87", + "hash": "F44393295DB860C6860769C16F5B23887762F09F87A8D1174E0FCFF9E7247F07" } })JSON"; diff --git a/tests/unit/feed/SingleFeedBaseTests.cpp b/tests/unit/feed/SingleFeedBaseTests.cpp index 185660750..40405c998 100644 --- a/tests/unit/feed/SingleFeedBaseTests.cpp +++ b/tests/unit/feed/SingleFeedBaseTests.cpp @@ -32,7 +32,7 @@ #include namespace { -constexpr auto kFEED = R"JSON({"test":"test"})JSON"; +constexpr auto kFEED = R"JSON({"test": "test"})JSON"; } // namespace using namespace feed::impl; diff --git a/tests/unit/feed/SubscriptionManagerTests.cpp b/tests/unit/feed/SubscriptionManagerTests.cpp index 522c597a3..b657ae545 100644 --- a/tests/unit/feed/SubscriptionManagerTests.cpp +++ b/tests/unit/feed/SubscriptionManagerTests.cpp @@ -93,8 +93,8 @@ TEST_F(SubscriptionManagerAsyncTest, MultipleThreadCtx) EXPECT_CALL(*sessionPtr_, onDisconnect); subscriptionManagerPtr_->subValidation(session_); - static constexpr auto kJSON_MANIFEST = R"JSON({"manifest":"test"})JSON"; - static constexpr auto kJSON_VALIDATION = R"JSON({"validation":"test"})JSON"; + static constexpr auto kJSON_MANIFEST = R"JSON({"manifest": "test"})JSON"; + static constexpr auto kJSON_VALIDATION = R"JSON({"validation": "test"})JSON"; EXPECT_CALL(*sessionPtr_, send(testing::_)).Times(testing::AtMost(2)); @@ -112,23 +112,23 @@ TEST_F(SubscriptionManagerAsyncTest, MultipleThreadCtxSessionDieEarly) EXPECT_CALL(*sessionPtr_, send(testing::_)).Times(0); session_.reset(); - subscriptionManagerPtr_->forwardManifest(json::parse(R"JSON({"manifest":"test"})JSON").get_object()); - subscriptionManagerPtr_->forwardValidation(json::parse(R"JSON({"validation":"test"})JSON").get_object()); + subscriptionManagerPtr_->forwardManifest(json::parse(R"JSON({"manifest": "test"})JSON").get_object()); + subscriptionManagerPtr_->forwardValidation(json::parse(R"JSON({"validation": "test"})JSON").get_object()); } TEST_F(SubscriptionManagerTest, ReportCurrentSubscriber) { static constexpr auto kREPORT_RETURN = R"JSON({ - "ledger":0, - "transactions":2, - "transactions_proposed":2, - "manifests":2, - "validations":2, - "account":2, - "accounts_proposed":2, - "books":2, - "book_changes":2 + "ledger": 0, + "transactions": 2, + "transactions_proposed": 2, + "manifests": 2, + "validations": 2, + "account": 2, + "accounts_proposed": 2, + "books": 2, + "book_changes": 2 })JSON"; web::SubscriptionContextPtr const session1 = std::make_shared(); MockSession* mockSession1 = dynamic_cast(session1.get()); @@ -201,7 +201,7 @@ TEST_F(SubscriptionManagerTest, ReportCurrentSubscriber) TEST_F(SubscriptionManagerTest, ManifestTest) { - static constexpr auto kDUMMY_MANIFEST = R"JSON({"manifest":"test"})JSON"; + static constexpr auto kDUMMY_MANIFEST = R"JSON({"manifest": "test"})JSON"; EXPECT_CALL(*sessionPtr_, onDisconnect); EXPECT_CALL(*sessionPtr_, send(sharedStringJsonEq(kDUMMY_MANIFEST))); subscriptionManagerPtr_->subManifest(session_); @@ -214,7 +214,7 @@ TEST_F(SubscriptionManagerTest, ManifestTest) TEST_F(SubscriptionManagerTest, ValidationTest) { - static constexpr auto kDUMMY = R"JSON({"validation":"test"})JSON"; + static constexpr auto kDUMMY = R"JSON({"validation": "test"})JSON"; EXPECT_CALL(*sessionPtr_, onDisconnect); EXPECT_CALL(*sessionPtr_, send(sharedStringJsonEq(kDUMMY))); subscriptionManagerPtr_->subValidation(session_); @@ -242,21 +242,21 @@ TEST_F(SubscriptionManagerTest, BookChangesTest) transactions.push_back(trans1); static constexpr auto kBOOK_CHANGE_PUBLISH = R"JSON({ - "type":"bookChanges", - "ledger_index":32, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_time":0, + "type": "bookChanges", + "ledger_index": 32, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_time": 0, "changes": [ { - "currency_a":"XRP_drops", - "currency_b":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD/0158415500000000C1F76FF6ECB0BAC600000000", - "volume_a":"2", - "volume_b":"2", - "high":"-1", - "low":"-1", - "open":"-1", - "close":"-1" + "currency_a": "XRP_drops", + "currency_b": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD/0158415500000000C1F76FF6ECB0BAC600000000", + "volume_a": "2", + "volume_b": "2", + "high": "-1", + "low": "-1", + "open": "-1", + "close": "-1" } ] })JSON"; @@ -282,13 +282,13 @@ TEST_F(SubscriptionManagerTest, LedgerTest) // the type and txn_count fields static constexpr auto kLEDGER_RESPONSE = R"JSON({ - "validated_ledgers":"10-30", - "ledger_index":30, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_time":0, - "fee_base":1, - "reserve_base":3, - "reserve_inc":2 + "validated_ledgers": "10-30", + "ledger_index": 30, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_time": 0, + "fee_base": 1, + "reserve_base": 3, + "reserve_inc": 2 })JSON"; boost::asio::io_context ctx; boost::asio::spawn(ctx, [this](boost::asio::yield_context yield) { @@ -306,15 +306,15 @@ TEST_F(SubscriptionManagerTest, LedgerTest) fee2.reserve = 10; static constexpr auto kLEDGER_PUB = R"JSON({ - "type":"ledgerClosed", - "ledger_index":31, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_time":0, - "fee_base":0, - "reserve_base":10, - "reserve_inc":0, - "validated_ledgers":"10-31", - "txn_count":8 + "type": "ledgerClosed", + "ledger_index": 31, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_time": 0, + "fee_base": 0, + "reserve_base": 10, + "reserve_inc": 0, + "validated_ledgers": "10-31", + "txn_count": 8 })JSON"; EXPECT_CALL(*sessionPtr_, send(sharedStringJsonEq(kLEDGER_PUB))); subscriptionManagerPtr_->pubLedger(ledgerHeader2, fee2, "10-31", 8); @@ -349,16 +349,16 @@ TEST_F(SubscriptionManagerTest, TransactionTest) R"JSON({ "transaction": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Amount":"1", - "DeliverMax":"1", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", - "date":0 + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Amount": "1", + "DeliverMax": "1", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "hash": "51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", + "date": 0 }, "meta": { @@ -369,42 +369,42 @@ TEST_F(SubscriptionManagerTest, TransactionTest) { "FinalFields": { - "TakerGets":"3", + "TakerGets": "3", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"1" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "1" } }, - "LedgerEntryType":"Offer", + "LedgerEntryType": "Offer", "PreviousFields": { - "TakerGets":"1", + "TakerGets": "1", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"3" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "3" } } } } ], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "engine_result_code":0, - "engine_result":"tesSUCCESS", + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "engine_result_code": 0, + "engine_result": "tesSUCCESS", "close_time_iso": "2000-01-01T00:00:00Z", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; EXPECT_CALL(*sessionPtr_, send(sharedStringJsonEq(kORDERBOOK_PUBLISH))).Times(3); EXPECT_CALL(*sessionPtr_, apiSubversion).Times(3).WillRepeatedly(testing::Return(1)); @@ -431,24 +431,24 @@ TEST_F(SubscriptionManagerTest, ProposedTransactionTest) R"JSON({ "transaction": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun" + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun" } })JSON"; static constexpr auto kORDERBOOK_PUBLISH = R"JSON({ "transaction": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Amount":"1", - "DeliverMax":"1", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", - "date":0 + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Amount": "1", + "DeliverMax": "1", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "hash": "51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", + "date": 0 }, "meta": { @@ -459,42 +459,42 @@ TEST_F(SubscriptionManagerTest, ProposedTransactionTest) { "FinalFields": { - "TakerGets":"3", + "TakerGets": "3", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"1" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "1" } }, - "LedgerEntryType":"Offer", + "LedgerEntryType": "Offer", "PreviousFields": { - "TakerGets":"1", + "TakerGets": "1", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"3" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "3" } } } } ], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "engine_result_code":0, - "engine_result":"tesSUCCESS", + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "engine_result_code": 0, + "engine_result": "tesSUCCESS", "close_time_iso": "2000-01-01T00:00:00Z", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; EXPECT_CALL(*sessionPtr_, send(sharedStringJsonEq(kDUMMY_TRANSACTION))).Times(2); EXPECT_CALL(*sessionPtr_, send(sharedStringJsonEq(kORDERBOOK_PUBLISH))).Times(2); diff --git a/tests/unit/feed/TransactionFeedTests.cpp b/tests/unit/feed/TransactionFeedTests.cpp index b095949dd..100c1dcdb 100644 --- a/tests/unit/feed/TransactionFeedTests.cpp +++ b/tests/unit/feed/TransactionFeedTests.cpp @@ -64,16 +64,16 @@ constexpr auto kTRAN_V1 = R"JSON({ "transaction": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Amount":"1", - "DeliverMax":"1", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", - "date":0 + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Amount": "1", + "DeliverMax": "1", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "hash": "51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", + "date": 0 }, "meta": { @@ -84,10 +84,10 @@ constexpr auto kTRAN_V1 = { "FinalFields": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Balance":"110" + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Balance": "110" }, - "LedgerEntryType":"AccountRoot" + "LedgerEntryType": "AccountRoot" } }, { @@ -95,80 +95,80 @@ constexpr auto kTRAN_V1 = { "FinalFields": { - "Account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Balance":"30" + "Account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Balance": "30" }, - "LedgerEntryType":"AccountRoot" + "LedgerEntryType": "AccountRoot" } } ], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, "close_time_iso": "2000-01-01T00:00:00Z", - "ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "engine_result_code":0, - "engine_result":"tesSUCCESS", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "ledger_hash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "engine_result_code": 0, + "engine_result": "tesSUCCESS", + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; constexpr auto kTRAN_V2 = R"JSON({ "tx_json": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "DeliverMax":"1", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "date":0 + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "DeliverMax": "1", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "date": 0 }, "meta": { "AffectedNodes": [ { - "ModifiedNode":{ - "FinalFields":{ - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Balance":"110" + "ModifiedNode": { + "FinalFields": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Balance": "110" }, - "LedgerEntryType":"AccountRoot" + "LedgerEntryType": "AccountRoot" } }, { - "ModifiedNode":{ - "FinalFields":{ - "Account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Balance":"30" + "ModifiedNode": { + "FinalFields": { + "Account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Balance": "30" }, - "LedgerEntryType":"AccountRoot" + "LedgerEntryType": "AccountRoot" } } ], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, "close_time_iso": "2000-01-01T00:00:00Z", - "ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", - "engine_result_code":0, - "engine_result":"tesSUCCESS", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "ledger_hash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "hash": "51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", + "engine_result_code": 0, + "engine_result": "tesSUCCESS", + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; } // namespace @@ -370,16 +370,16 @@ TEST_F(FeedTransactionTest, SubBookV1) R"JSON({ "transaction": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Amount":"1", - "DeliverMax":"1", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", - "date":0 + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Amount": "1", + "DeliverMax": "1", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "hash": "51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", + "date": 0 }, "meta": { @@ -390,41 +390,41 @@ TEST_F(FeedTransactionTest, SubBookV1) { "FinalFields": { - "TakerGets":"3", + "TakerGets": "3", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"1" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "1" } }, - "LedgerEntryType":"Offer", - "PreviousFields":{ - "TakerGets":"1", + "LedgerEntryType": "Offer", + "PreviousFields": { + "TakerGets": "1", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"3" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "3" } } } } ], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, - "ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "engine_result_code":0, - "engine_result":"tesSUCCESS", + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, + "ledger_hash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "engine_result_code": 0, + "engine_result": "tesSUCCESS", "close_time_iso": "2000-01-01T00:00:00Z", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1)); @@ -437,19 +437,19 @@ TEST_F(FeedTransactionTest, SubBookV1) static constexpr auto kORDERBOOK_CANCEL_PUBLISH = R"JSON({ - "transaction":{ - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Amount":"1", - "DeliverMax":"1", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", - "date":0 + "transaction": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Amount": "1", + "DeliverMax": "1", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "hash": "51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", + "date": 0 }, - "meta":{ + "meta": { "AffectedNodes": [ { @@ -457,31 +457,31 @@ TEST_F(FeedTransactionTest, SubBookV1) { "FinalFields": { - "TakerGets":"3", - "TakerPays":{ - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"1" + "TakerGets": "3", + "TakerPays": { + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "1" } }, - "LedgerEntryType":"Offer" + "LedgerEntryType": "Offer" } } ], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, - "ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "engine_result_code":0, - "engine_result":"tesSUCCESS", + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, + "ledger_hash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "engine_result_code": 0, + "engine_result": "tesSUCCESS", "close_time_iso": "2000-01-01T00:00:00Z", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1)); @@ -493,16 +493,16 @@ TEST_F(FeedTransactionTest, SubBookV1) R"JSON({ "transaction": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Amount":"1", - "DeliverMax":"1", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", - "date":0 + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Amount": "1", + "DeliverMax": "1", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "hash": "51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", + "date": 0 }, "meta": { @@ -511,33 +511,33 @@ TEST_F(FeedTransactionTest, SubBookV1) { "CreatedNode": { - "NewFields":{ - "TakerGets":"3", + "NewFields": { + "TakerGets": "3", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"1" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "1" } }, - "LedgerEntryType":"Offer" + "LedgerEntryType": "Offer" } } ], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, - "ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "engine_result_code":0, - "engine_result":"tesSUCCESS", + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, + "ledger_hash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "engine_result_code": 0, + "engine_result": "tesSUCCESS", "close_time_iso": "2000-01-01T00:00:00Z", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; metaObj = createMetaDataForCreateOffer(kCURRENCY, kISSUER, 22, 3, 1); trans1.metadata = metaObj.getSerializer().peekData(); @@ -574,14 +574,14 @@ TEST_F(FeedTransactionTest, SubBookV2) R"JSON({ "tx_json": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "DeliverMax":"1", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "date":0 + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "DeliverMax": "1", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "date": 0 }, "meta": { @@ -592,43 +592,43 @@ TEST_F(FeedTransactionTest, SubBookV2) { "FinalFields": { - "TakerGets":"3", + "TakerGets": "3", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"1" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "1" } }, - "LedgerEntryType":"Offer", + "LedgerEntryType": "Offer", "PreviousFields": { - "TakerGets":"1", + "TakerGets": "1", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"3" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "3" } } } } ], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, - "ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "engine_result_code":0, - "engine_result":"tesSUCCESS", + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, + "ledger_hash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "engine_result_code": 0, + "engine_result": "tesSUCCESS", "close_time_iso": "2000-01-01T00:00:00Z", - "hash":"51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "hash": "51D2AAA6B8E4E16EF22F6424854283D8391B56875858A711B8CE4D5B9A422CC2", + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(2)); @@ -844,38 +844,38 @@ TEST_F(FeedTransactionTest, PubTransactionWithOwnerFund) R"JSON({ "transaction": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", "TakerGets": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"1" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "1" }, - "TakerPays":"3", - "TransactionType":"OfferCreate", - "hash":"EE8775B43A67F4803DECEC5E918E0EA9C56D8ED93E512EBE9F2891846509AAAB", - "date":0, - "owner_funds":"100" + "TakerPays": "3", + "TransactionType": "OfferCreate", + "hash": "EE8775B43A67F4803DECEC5E918E0EA9C56D8ED93E512EBE9F2891846509AAAB", + "date": 0, + "owner_funds": "100" }, "meta": { - "AffectedNodes":[], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS" + "AffectedNodes": [], + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, - "ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "engine_result_code":0, + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, + "ledger_hash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "engine_result_code": 0, "close_time_iso": "2000-01-01T00:00:00Z", - "engine_result":"tesSUCCESS", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "engine_result": "tesSUCCESS", + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1)); @@ -890,37 +890,37 @@ static constexpr auto kTRAN_FROZEN = R"JSON({ "transaction": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", "TakerGets": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", - "value":"1" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD", + "value": "1" }, - "TakerPays":"3", - "TransactionType":"OfferCreate", - "hash":"EE8775B43A67F4803DECEC5E918E0EA9C56D8ED93E512EBE9F2891846509AAAB", - "date":0, - "owner_funds":"0" + "TakerPays": "3", + "TransactionType": "OfferCreate", + "hash": "EE8775B43A67F4803DECEC5E918E0EA9C56D8ED93E512EBE9F2891846509AAAB", + "date": 0, + "owner_funds": "0" }, - "meta":{ - "AffectedNodes":[], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS" + "meta": { + "AffectedNodes": [], + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, "close_time_iso": "2000-01-01T00:00:00Z", - "ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "engine_result_code":0, - "engine_result":"tesSUCCESS", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "ledger_hash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "engine_result_code": 0, + "engine_result": "tesSUCCESS", + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; TEST_F(FeedTransactionTest, PubTransactionOfferCreationFrozenLine) @@ -1150,38 +1150,38 @@ TEST_F(FeedTransactionTest, PubTransactionWithOwnerFundFrozenLPToken) R"JSON({ "transaction": { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Fee":"1", - "Sequence":32, - "SigningPubKey":"74657374", + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Fee": "1", + "Sequence": 32, + "SigningPubKey": "74657374", "TakerGets": { - "currency":"037C35306B24AAB7FF90848206E003279AA47090", - "issuer":"rnW8FAPgpQgA6VoESnVrUVJHBdq9QAtRZs", - "value":"1" + "currency": "037C35306B24AAB7FF90848206E003279AA47090", + "issuer": "rnW8FAPgpQgA6VoESnVrUVJHBdq9QAtRZs", + "value": "1" }, - "TakerPays":"3", - "TransactionType":"OfferCreate", - "hash":"9CA8BBF209DC4505F593A1EA0DC2135A5FA2C6541AF19D128B046873E0CEB695", - "date":0, - "owner_funds":"0" + "TakerPays": "3", + "TransactionType": "OfferCreate", + "hash": "9CA8BBF209DC4505F593A1EA0DC2135A5FA2C6541AF19D128B046873E0CEB695", + "date": 0, + "owner_funds": "0" }, "meta": { - "AffectedNodes":[], - "TransactionIndex":22, - "TransactionResult":"tesSUCCESS" + "AffectedNodes": [], + "TransactionIndex": 22, + "TransactionResult": "tesSUCCESS" }, - "ctid":"C000002100160000", - "type":"transaction", - "validated":true, - "status":"closed", - "ledger_index":33, - "ledger_hash":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "engine_result_code":0, + "ctid": "C000002100160000", + "type": "transaction", + "validated": true, + "status": "closed", + "ledger_index": 33, + "ledger_hash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "engine_result_code": 0, "close_time_iso": "2000-01-01T00:00:00Z", - "engine_result":"tesSUCCESS", - "engine_result_message":"The transaction was applied. Only final in a validated ledger." + "engine_result": "tesSUCCESS", + "engine_result_message": "The transaction was applied. Only final in a validated ledger." })JSON"; EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1)); diff --git a/tests/unit/rpc/BaseTests.cpp b/tests/unit/rpc/BaseTests.cpp index 51d6fe3c5..a0a696ef9 100644 --- a/tests/unit/rpc/BaseTests.cpp +++ b/tests/unit/rpc/BaseTests.cpp @@ -277,7 +277,7 @@ TEST_F(RPCBaseTest, ArrayAtValidator) auto failingInput = json::parse(R"JSON({ "arr": [{"limit": "not int"}] })JSON"); ASSERT_FALSE(spec.process(failingInput)); - failingInput = json::parse(R"JSON({ "arr": [{"limit": 42}] ,"arr2": "not array type" })JSON"); + failingInput = json::parse(R"JSON({ "arr": [{"limit": 42}], "arr2": "not array type" })JSON"); ASSERT_FALSE(spec.process(failingInput)); failingInput = json::parse(R"JSON({ "arr": [] })JSON"); @@ -323,7 +323,7 @@ TEST_F(RPCBaseTest, IfTypeValidator) failingInput = json::parse(R"JSON({ "mix": 1213 })JSON"); ASSERT_FALSE(spec.process(failingInput)); - failingInput = json::parse(R"JSON({ "mix": {"limit": 42, "limit2": 22} , "mix2": 1213 })JSON"); + failingInput = json::parse(R"JSON({ "mix": {"limit": 42, "limit2": 22}, "mix2": 1213 })JSON"); ASSERT_FALSE(spec.process(failingInput)); } @@ -585,7 +585,7 @@ TEST_F(RPCBaseTest, CurrencyValidator) ASSERT_TRUE(spec.process(passingInput)); for (auto const& currency : {"[]<", ">()", "{}|", "?!@", "#$%", "^&*"}) { - passingInput = json::parse(fmt::format(R"JSON({{ "currency" : "{}" }})JSON", currency)); + passingInput = json::parse(fmt::format(R"JSON({{ "currency": "{}" }})JSON", currency)); ASSERT_TRUE(spec.process(passingInput)); } @@ -652,7 +652,7 @@ TEST_F(RPCBaseTest, SubscribeAccountsValidator) { auto const spec = RpcSpec{{"accounts", CustomValidators::subscribeAccountsValidator}}; auto passingInput = json::parse( - R"JSON({ "accounts": ["rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"]})JSON" + R"JSON({ "accounts": ["rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"]})JSON" ); ASSERT_TRUE(spec.process(passingInput)); diff --git a/tests/unit/rpc/RPCEngineTests.cpp b/tests/unit/rpc/RPCEngineTests.cpp index d924fedc6..30192fe57 100644 --- a/tests/unit/rpc/RPCEngineTests.cpp +++ b/tests/unit/rpc/RPCEngineTests.cpp @@ -485,7 +485,7 @@ TEST_F(RPCEngineTest, NotCacheIfErrorHappen) yield, method, 1, - boost::json::parse(R"JSON({"hello": "world","limit": 50})JSON").as_object(), + boost::json::parse(R"JSON({"hello": "world", "limit": 50})JSON").as_object(), nullptr, tagFactory, LedgerRange{.minSequence = 0, .maxSequence = 30}, diff --git a/tests/unit/rpc/RPCHelpersTests.cpp b/tests/unit/rpc/RPCHelpersTests.cpp index b1837eaf9..b8308cd42 100644 --- a/tests/unit/rpc/RPCHelpersTests.cpp +++ b/tests/unit/rpc/RPCHelpersTests.cpp @@ -49,7 +49,9 @@ #include #include #include +#include #include +#include #include #include diff --git a/tests/unit/rpc/handlers/AMMInfoTests.cpp b/tests/unit/rpc/handlers/AMMInfoTests.cpp index 85e32b4ca..8e34c238a 100644 --- a/tests/unit/rpc/handlers/AMMInfoTests.cpp +++ b/tests/unit/rpc/handlers/AMMInfoTests.cpp @@ -179,7 +179,7 @@ TEST_F(RPCAMMInfoHandlerTest, AccountNotFound) ON_CALL(*backend_, doFetchLedgerObject(accountKey, testing::_, testing::_)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}", "account": "{}" @@ -205,7 +205,7 @@ TEST_F(RPCAMMInfoHandlerTest, AMMAccountNotExist) ON_CALL(*backend_, fetchLedgerBySequence).WillByDefault(Return(lgrInfo)); ON_CALL(*backend_, doFetchLedgerObject).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -228,7 +228,7 @@ TEST_F(RPCAMMInfoHandlerTest, AMMAccountNotInDBIsMalformed) ON_CALL(*backend_, fetchLedgerBySequence).WillByDefault(Return(lgrInfo)); ON_CALL(*backend_, doFetchLedgerObject).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -254,7 +254,7 @@ TEST_F(RPCAMMInfoHandlerTest, AMMAccountNotFoundMissingAmmField) ON_CALL(*backend_, fetchLedgerBySequence).WillByDefault(Return(lgrInfo)); ON_CALL(*backend_, doFetchLedgerObject).WillByDefault(Return(accountRoot.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -289,7 +289,7 @@ TEST_F(RPCAMMInfoHandlerTest, AMMAccountAmmBlobNotFound) ON_CALL(*backend_, doFetchLedgerObject(ammKeylet.key, testing::_, testing::_)) .WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -328,7 +328,7 @@ TEST_F(RPCAMMInfoHandlerTest, AMMAccountAccBlobNotFound) ON_CALL(*backend_, doFetchLedgerObject(account2Key, testing::_, testing::_)) .WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -373,7 +373,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathMinimalFirstXRPNoTrustline) ON_CALL(*backend_, doFetchLedgerObject(feesKey, kSEQ, _)).WillByDefault(Return(feesObj)); ON_CALL(*backend_, doFetchLedgerObject(issue2LineKey, kSEQ, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -453,7 +453,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathWithAccount) ON_CALL(*backend_, doFetchLedgerObject(accountHoldsKeylet.key, kSEQ, _)) .WillByDefault(Return(trustline.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}", "account": "{}" @@ -527,7 +527,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathMinimalSecondXRPNoTrustline) ON_CALL(*backend_, doFetchLedgerObject(feesKey, kSEQ, _)).WillByDefault(Return(feesObj)); ON_CALL(*backend_, doFetchLedgerObject(issue2LineKey, kSEQ, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -597,7 +597,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathNonXRPNoTrustlines) ON_CALL(*backend_, doFetchLedgerObject(feesKey, kSEQ, _)).WillByDefault(Return(feesObj)); ON_CALL(*backend_, doFetchLedgerObject(issue2LineKey, kSEQ, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -686,7 +686,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathFrozen) ON_CALL(*backend_, doFetchLedgerObject(issue2LineKey, kSEQ, _)) .WillByDefault(Return(trustline2BalanceFrozen.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -776,7 +776,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathFrozenIssuer) ON_CALL(*backend_, doFetchLedgerObject(issue2LineKey, kSEQ, _)) .WillByDefault(Return(trustline2BalanceFrozen.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -858,7 +858,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathWithTrustline) ON_CALL(*backend_, doFetchLedgerObject(issue2LineKey, kSEQ, _)) .WillByDefault(Return(trustlineBalance.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -935,7 +935,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathWithVoteSlots) ON_CALL(*backend_, doFetchLedgerObject(issue2LineKey, kSEQ, _)) .WillByDefault(Return(trustlineBalance.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -1028,7 +1028,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathWithAuctionSlot) ON_CALL(*backend_, doFetchLedgerObject(issue2LineKey, kSEQ, _)) .WillByDefault(Return(trustlineBalance.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "amm_account": "{}" }})JSON", @@ -1116,7 +1116,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathWithAssetsMatchingInputOrder) ON_CALL(*backend_, doFetchLedgerObject(ammKeylet.key, testing::_, testing::_)) .WillByDefault(Return(ammObj.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "asset": {{ "currency": "JPY", @@ -1226,7 +1226,7 @@ TEST_F(RPCAMMInfoHandlerTest, HappyPathWithAssetsPreservesInputOrder) ON_CALL(*backend_, doFetchLedgerObject(ammKeylet.key, testing::_, testing::_)) .WillByDefault(Return(ammObj.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "asset": {{ "currency": "USD", diff --git a/tests/unit/rpc/handlers/AccountChannelsTests.cpp b/tests/unit/rpc/handlers/AccountChannelsTests.cpp index fc50abf61..9bc84a17e 100644 --- a/tests/unit/rpc/handlers/AccountChannelsTests.cpp +++ b/tests/unit/rpc/handlers/AccountChannelsTests.cpp @@ -427,31 +427,31 @@ TEST_F(RPCAccountChannelsHandlerTest, NonExistAccount) TEST_F(RPCAccountChannelsHandlerTest, DefaultParameterTest) { static constexpr auto kCORRECT_OUTPUT = R"JSON({ - "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "limit":200, - "channels":[ + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "limit": 200, + "channels": [ { - "channel_id":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "destination_account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "amount":"100", - "balance":"10", - "settle_delay":32, - "public_key":"aBMxWrnPUnvwZPfsmTyVizxEGsGheAu3Tsn6oPRgyjgvd2NggFxz", - "public_key_hex":"020000000000000000000000000000000000000000000000000000000000000000" + "channel_id": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "amount": "100", + "balance": "10", + "settle_delay": 32, + "public_key": "aBMxWrnPUnvwZPfsmTyVizxEGsGheAu3Tsn6oPRgyjgvd2NggFxz", + "public_key_hex": "020000000000000000000000000000000000000000000000000000000000000000" }, { - "channel_id":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC322", - "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "destination_account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "amount":"100", - "balance":"10", - "settle_delay":32, - "public_key":"aBMxWrnPUnvwZPfsmTyVizxEGsGheAu3Tsn6oPRgyjgvd2NggFxz", - "public_key_hex":"020000000000000000000000000000000000000000000000000000000000000000" + "channel_id": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC322", + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "amount": "100", + "balance": "10", + "settle_delay": 32, + "public_key": "aBMxWrnPUnvwZPfsmTyVizxEGsGheAu3Tsn6oPRgyjgvd2NggFxz", + "public_key_hex": "020000000000000000000000000000000000000000000000000000000000000000" } ] })JSON"; @@ -623,7 +623,7 @@ TEST_F(RPCAccountChannelsHandlerTest, UseDestination) R"JSON({{ "account": "{}", "limit": 30, - "destination_account":"{}" + "destination_account": "{}" }})JSON", kACCOUNT, kACCOUNT3 @@ -674,35 +674,35 @@ TEST_F(RPCAccountChannelsHandlerTest, EmptyChannel) TEST_F(RPCAccountChannelsHandlerTest, OptionalResponseField) { static constexpr auto kCORRECT_OUTPUT = R"JSON({ - "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "limit":200, - "channels":[ + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "limit": 200, + "channels": [ { - "channel_id":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "destination_account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "amount":"100", - "balance":"10", - "settle_delay":32, - "public_key":"aBMxWrnPUnvwZPfsmTyVizxEGsGheAu3Tsn6oPRgyjgvd2NggFxz", - "public_key_hex":"020000000000000000000000000000000000000000000000000000000000000000", + "channel_id": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "amount": "100", + "balance": "10", + "settle_delay": 32, + "public_key": "aBMxWrnPUnvwZPfsmTyVizxEGsGheAu3Tsn6oPRgyjgvd2NggFxz", + "public_key_hex": "020000000000000000000000000000000000000000000000000000000000000000", "expiration": 100, "cancel_after": 200, "source_tag": 300, "destination_tag": 400 }, { - "channel_id":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC322", - "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "destination_account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "amount":"100", - "balance":"10", - "settle_delay":32, - "public_key":"aBMxWrnPUnvwZPfsmTyVizxEGsGheAu3Tsn6oPRgyjgvd2NggFxz", - "public_key_hex":"020000000000000000000000000000000000000000000000000000000000000000", + "channel_id": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC322", + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "amount": "100", + "balance": "10", + "settle_delay": 32, + "public_key": "aBMxWrnPUnvwZPfsmTyVizxEGsGheAu3Tsn6oPRgyjgvd2NggFxz", + "public_key_hex": "020000000000000000000000000000000000000000000000000000000000000000", "expiration": 100, "cancel_after": 200, "source_tag": 300, diff --git a/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp b/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp index dedb02945..f27c80ef8 100644 --- a/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp +++ b/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp @@ -73,9 +73,9 @@ TEST_F(RPCAccountCurrenciesHandlerTest, AccountNotExist) ON_CALL(*backend_, doFetchLedgerObject).WillByDefault(Return(std::optional{})); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -95,9 +95,9 @@ TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaIntSequence) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(30, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -119,10 +119,10 @@ TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaStringSequence) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(12, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":"{}" + "account": "{}", + "ledger_index": "{}" }})JSON", kACCOUNT, kSEQ @@ -144,10 +144,10 @@ TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaHash) ON_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) .WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})JSON", kACCOUNT, kLEDGER_HASH @@ -165,14 +165,14 @@ TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaHash) TEST_F(RPCAccountCurrenciesHandlerTest, DefaultParameter) { static constexpr auto kOUTPUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "receive_currencies":[ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "receive_currencies": [ "EUR", "JPY" ], - "send_currencies":[ + "send_currencies": [ "EUR", "USD" ] @@ -210,9 +210,9 @@ TEST_F(RPCAccountCurrenciesHandlerTest, DefaultParameter) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -245,10 +245,10 @@ TEST_F(RPCAccountCurrenciesHandlerTest, RequestViaLegderHash) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})JSON", kACCOUNT, kLEDGER_HASH @@ -282,10 +282,10 @@ TEST_F(RPCAccountCurrenciesHandlerTest, RequestViaLegderSeq) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":{} + "account": "{}", + "ledger_index": {} }})JSON", kACCOUNT, ledgerSeq diff --git a/tests/unit/rpc/handlers/AccountInfoTests.cpp b/tests/unit/rpc/handlers/AccountInfoTests.cpp index d67b1169c..fef1517aa 100644 --- a/tests/unit/rpc/handlers/AccountInfoTests.cpp +++ b/tests/unit/rpc/handlers/AccountInfoTests.cpp @@ -90,49 +90,49 @@ generateTestValuesForParametersTest() }, AccountInfoParamTestCaseBundle{ .testName = "AccountNotString", - .testJson = R"JSON({"account":1})JSON", + .testJson = R"JSON({"account": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "accountNotString" }, AccountInfoParamTestCaseBundle{ .testName = "AccountInvalid", - .testJson = R"JSON({"account":"xxx"})JSON", + .testJson = R"JSON({"account": "xxx"})JSON", .expectedError = "actMalformed", .expectedErrorMessage = "accountMalformed" }, AccountInfoParamTestCaseBundle{ .testName = "IdentNotString", - .testJson = R"JSON({"ident":1})JSON", + .testJson = R"JSON({"ident": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "identNotString" }, AccountInfoParamTestCaseBundle{ .testName = "IdentInvalid", - .testJson = R"JSON({"ident":"xxx"})JSON", + .testJson = R"JSON({"ident": "xxx"})JSON", .expectedError = "actMalformed", .expectedErrorMessage = "identMalformed" }, AccountInfoParamTestCaseBundle{ .testName = "SignerListsInvalid", - .testJson = R"JSON({"ident":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "signer_lists":1})JSON", + .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "signer_lists": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, AccountInfoParamTestCaseBundle{ .testName = "LedgerHashInvalid", - .testJson = R"JSON({"ident":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash":"1"})JSON", + .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash": "1"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashMalformed" }, AccountInfoParamTestCaseBundle{ .testName = "LedgerHashNotString", - .testJson = R"JSON({"ident":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash":1})JSON", + .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashNotString" }, AccountInfoParamTestCaseBundle{ .testName = "LedgerIndexInvalid", - .testJson = R"JSON({"ident":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_index":"a"})JSON", + .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_index": "a"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledgerIndexMalformed" }, @@ -164,7 +164,7 @@ TEST_P(AccountInfoParameterTest, InvalidParams) TEST_F(AccountInfoParameterTest, ApiV1SignerListIsNotBool) { static constexpr auto kREQ_JSON = R"JSON( - {"ident":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "signer_lists":1} + {"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "signer_lists": 1} )JSON"; EXPECT_CALL(*backend_, fetchLedgerBySequence); @@ -187,7 +187,7 @@ TEST_F(RPCAccountInfoHandlerTest, LedgerNonExistViaIntSequence) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(30, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index": 30 @@ -210,7 +210,7 @@ TEST_F(RPCAccountInfoHandlerTest, LedgerNonExistViaStringSequence) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(30, _)).WillByDefault(Return(std::nullopt)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index": "30" @@ -234,7 +234,7 @@ TEST_F(RPCAccountInfoHandlerTest, LedgerNonExistViaHash) ON_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) .WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_hash": "{}" @@ -261,7 +261,7 @@ TEST_F(RPCAccountInfoHandlerTest, AccountNotExist) ON_CALL(*backend_, doFetchLedgerObject).WillByDefault(Return(std::optional{})); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}" }})JSON", @@ -287,7 +287,7 @@ TEST_F(RPCAccountInfoHandlerTest, AccountInvalid) ON_CALL(*backend_, doFetchLedgerObject).WillByDefault(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0))); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}" }})JSON", @@ -321,7 +321,7 @@ TEST_F(RPCAccountInfoHandlerTest, SignerListsInvalid) EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)).WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "signer_lists": true @@ -424,7 +424,7 @@ TEST_F(RPCAccountInfoHandlerTest, SignerListsTrueV2) EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)).WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "signer_lists": true @@ -525,7 +525,7 @@ TEST_F(RPCAccountInfoHandlerTest, SignerListsTrueV1) EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)).WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "signer_lists": true @@ -599,7 +599,7 @@ TEST_F(RPCAccountInfoHandlerTest, Flags) EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)).WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}" }})JSON", @@ -628,7 +628,7 @@ TEST_F(RPCAccountInfoHandlerTest, IdentAndSignerListsFalse) EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)).WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "ident": "{}" }})JSON", @@ -706,7 +706,7 @@ TEST_F(RPCAccountInfoHandlerTest, DisallowIncoming) EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)).WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}" }})JSON", @@ -780,7 +780,7 @@ TEST_F(RPCAccountInfoHandlerTest, Clawback) EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)).WillOnce(Return(true)); EXPECT_CALL(*backend_, doFetchLedgerObject); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}" }})JSON", diff --git a/tests/unit/rpc/handlers/AccountNFTsTests.cpp b/tests/unit/rpc/handlers/AccountNFTsTests.cpp index ea9db3d4b..757593ebb 100644 --- a/tests/unit/rpc/handlers/AccountNFTsTests.cpp +++ b/tests/unit/rpc/handlers/AccountNFTsTests.cpp @@ -181,10 +181,10 @@ TEST_F(RPCAccountNFTsHandlerTest, LedgerNotFoundViaHash) ON_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) .WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})JSON", kACCOUNT, kLEDGER_HASH @@ -207,10 +207,10 @@ TEST_F(RPCAccountNFTsHandlerTest, LedgerNotFoundViaStringIndex) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(kSEQ, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":"{}" + "account": "{}", + "ledger_index": "{}" }})JSON", kACCOUNT, kSEQ @@ -233,10 +233,10 @@ TEST_F(RPCAccountNFTsHandlerTest, LedgerNotFoundViaIntIndex) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(kSEQ, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":{} + "account": "{}", + "ledger_index": {} }})JSON", kACCOUNT, kSEQ @@ -260,9 +260,9 @@ TEST_F(RPCAccountNFTsHandlerTest, AccountNotFound) ON_CALL(*backend_, doFetchLedgerObject).WillByDefault(Return(std::optional{})); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -280,22 +280,22 @@ TEST_F(RPCAccountNFTsHandlerTest, NormalPath) { static auto const kEXPECTED_OUTPUT = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":30, - "validated":true, - "account":"{}", - "account_nfts":[ + "ledger_hash": "{}", + "ledger_index": 30, + "validated": true, + "account": "{}", + "account_nfts": [ {{ - "NFTokenID":"{}", - "URI":"7777772E6F6B2E636F6D", - "Flags":{}, - "Issuer":"{}", - "NFTokenTaxon":{}, - "nft_serial":{}, - "TransferFee":10000 + "NFTokenID": "{}", + "URI": "7777772E6F6B2E636F6D", + "Flags": {}, + "Issuer": "{}", + "NFTokenTaxon": {}, + "nft_serial": {}, + "TransferFee": 10000 }} ], - "limit":100 + "limit": 100 }})JSON", kLEDGER_HASH, kACCOUNT, @@ -323,9 +323,9 @@ TEST_F(RPCAccountNFTsHandlerTest, NormalPath) .WillByDefault(Return(pageObject.getSerializer().peekData())); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -357,10 +357,10 @@ TEST_F(RPCAccountNFTsHandlerTest, Limit) .WillByDefault(Return(pageObject.getSerializer().peekData())); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(1 + kLIMIT); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, kLIMIT @@ -392,10 +392,10 @@ TEST_F(RPCAccountNFTsHandlerTest, Marker) .WillByDefault(Return(pageObject.getSerializer().peekData())); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{}" + "account": "{}", + "marker": "{}" }})JSON", kACCOUNT, kPAGE @@ -419,10 +419,10 @@ TEST_F(RPCAccountNFTsHandlerTest, InvalidMarker) ON_CALL(*backend_, doFetchLedgerObject(ripple::keylet::account(accountID).key, 30, _)) .WillByDefault(Return(accountObject.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{}" + "account": "{}", + "marker": "{}" }})JSON", kACCOUNT, kINVALID_PAGE @@ -448,9 +448,9 @@ TEST_F(RPCAccountNFTsHandlerTest, AccountWithNoNFT) ON_CALL(*backend_, doFetchLedgerObject(ripple::keylet::account(accountID).key, 30, _)) .WillByDefault(Return(accountObject.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -480,10 +480,10 @@ TEST_F(RPCAccountNFTsHandlerTest, invalidPage) .WillByDefault(Return(accountObject.getSerializer().peekData())); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{}" + "account": "{}", + "marker": "{}" }})JSON", kACCOUNT, kPAGE @@ -502,22 +502,22 @@ TEST_F(RPCAccountNFTsHandlerTest, LimitLessThanMin) { static auto const kEXPECTED_OUTPUT = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":30, - "validated":true, - "account":"{}", - "account_nfts":[ + "ledger_hash": "{}", + "ledger_index": 30, + "validated": true, + "account": "{}", + "account_nfts": [ {{ - "NFTokenID":"{}", - "URI":"7777772E6F6B2E636F6D", - "Flags":{}, - "Issuer":"{}", - "NFTokenTaxon":{}, - "nft_serial":{}, - "TransferFee":10000 + "NFTokenID": "{}", + "URI": "7777772E6F6B2E636F6D", + "Flags": {}, + "Issuer": "{}", + "NFTokenTaxon": {}, + "nft_serial": {}, + "TransferFee": 10000 }} ], - "limit":{} + "limit": {} }})JSON", kLEDGER_HASH, kACCOUNT, @@ -546,10 +546,10 @@ TEST_F(RPCAccountNFTsHandlerTest, LimitLessThanMin) .WillByDefault(Return(pageObject.getSerializer().peekData())); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, AccountNFTsHandler::kLIMIT_MIN - 1 @@ -566,22 +566,22 @@ TEST_F(RPCAccountNFTsHandlerTest, LimitMoreThanMax) { static auto const kEXPECTED_OUTPUT = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":30, - "validated":true, - "account":"{}", - "account_nfts":[ + "ledger_hash": "{}", + "ledger_index": 30, + "validated": true, + "account": "{}", + "account_nfts": [ {{ - "NFTokenID":"{}", - "URI":"7777772E6F6B2E636F6D", - "Flags":{}, - "Issuer":"{}", - "NFTokenTaxon":{}, - "nft_serial":{}, - "TransferFee":10000 + "NFTokenID": "{}", + "URI": "7777772E6F6B2E636F6D", + "Flags": {}, + "Issuer": "{}", + "NFTokenTaxon": {}, + "nft_serial": {}, + "TransferFee": 10000 }} ], - "limit":{} + "limit": {} }})JSON", kLEDGER_HASH, kACCOUNT, @@ -610,10 +610,10 @@ TEST_F(RPCAccountNFTsHandlerTest, LimitMoreThanMax) .WillByDefault(Return(pageObject.getSerializer().peekData())); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, AccountNFTsHandler::kLIMIT_MAX + 1 diff --git a/tests/unit/rpc/handlers/AccountObjectsTests.cpp b/tests/unit/rpc/handlers/AccountObjectsTests.cpp index a9b0cd398..d158bf68d 100644 --- a/tests/unit/rpc/handlers/AccountObjectsTests.cpp +++ b/tests/unit/rpc/handlers/AccountObjectsTests.cpp @@ -96,86 +96,86 @@ generateTestValuesForParametersTest() }, AccountObjectsParamTestCaseBundle{ .testName = "AccountNotString", - .testJson = R"JSON({"account":1})JSON", + .testJson = R"JSON({"account": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "accountNotString" }, AccountObjectsParamTestCaseBundle{ .testName = "AccountInvalid", - .testJson = R"JSON({"account":"xxx"})JSON", + .testJson = R"JSON({"account": "xxx"})JSON", .expectedError = "actMalformed", .expectedErrorMessage = "accountMalformed" }, AccountObjectsParamTestCaseBundle{ .testName = "TypeNotString", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "type":1})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "type": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, AccountObjectsParamTestCaseBundle{ .testName = "TypeInvalid", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "type":"wrong"})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "type": "wrong"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid field 'type'." }, AccountObjectsParamTestCaseBundle{ .testName = "TypeNotAccountOwned", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "type":"amendments"})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "type": "amendments"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid field 'type'." }, AccountObjectsParamTestCaseBundle{ .testName = "LedgerHashInvalid", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash":"1"})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash": "1"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashMalformed" }, AccountObjectsParamTestCaseBundle{ .testName = "LedgerHashNotString", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash":1})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashNotString" }, AccountObjectsParamTestCaseBundle{ .testName = "LedgerIndexInvalid", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_index":"a"})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_index": "a"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledgerIndexMalformed" }, AccountObjectsParamTestCaseBundle{ .testName = "LimitNotInt", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "limit":"1"})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "limit": "1"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, AccountObjectsParamTestCaseBundle{ .testName = "LimitNegative", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "limit":-1})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "limit":-1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, AccountObjectsParamTestCaseBundle{ .testName = "LimitZero", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "limit":0})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "limit": 0})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, AccountObjectsParamTestCaseBundle{ .testName = "MarkerNotString", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "marker":9})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "marker": 9})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "markerNotString" }, AccountObjectsParamTestCaseBundle{ .testName = "MarkerInvalid", - .testJson = R"JSON({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "marker":"xxxx"})JSON", + .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "marker": "xxxx"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Malformed cursor." }, AccountObjectsParamTestCaseBundle{ .testName = "NFTMarkerInvalid", .testJson = fmt::format( - R"JSON({{"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "marker":"wronghex256,{}"}})JSON", + R"JSON({{"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "marker": "wronghex256,{}"}})JSON", std::numeric_limits::max() ), .expectedError = "invalidParams", @@ -223,10 +223,10 @@ TEST_F(RPCAccountObjectsHandlerTest, LedgerNonExistViaIntSequence) // return empty ledgerHeader EXPECT_CALL(*backend_, fetchLedgerBySequence(kMAX_SEQ, _)).WillOnce(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":30 + "account": "{}", + "ledger_index": 30 }})JSON", kACCOUNT )); @@ -245,10 +245,10 @@ TEST_F(RPCAccountObjectsHandlerTest, LedgerNonExistViaStringSequence) // return empty ledgerHeader EXPECT_CALL(*backend_, fetchLedgerBySequence(kMAX_SEQ, _)).WillOnce(Return(std::nullopt)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":"30" + "account": "{}", + "ledger_index": "30" }})JSON", kACCOUNT )); @@ -268,10 +268,10 @@ TEST_F(RPCAccountObjectsHandlerTest, LedgerNonExistViaHash) EXPECT_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) .WillOnce(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})JSON", kACCOUNT, kLEDGER_HASH @@ -293,9 +293,9 @@ TEST_F(RPCAccountObjectsHandlerTest, AccountNotExist) EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); EXPECT_CALL(*backend_, doFetchLedgerObject).WillOnce(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -312,33 +312,33 @@ TEST_F(RPCAccountObjectsHandlerTest, AccountNotExist) TEST_F(RPCAccountObjectsHandlerTest, DefaultParameterNoNFTFound) { static constexpr auto kEXPECTED_OUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 200, - "account_objects":[ + "account_objects": [ { - "Balance":{ - "currency":"USD", - "issuer":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", - "value":"100" + "Balance": { + "currency": "USD", + "issuer": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", + "value": "100" }, - "Flags":0, - "HighLimit":{ - "currency":"USD", - "issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "value":"20" + "Flags": 0, + "HighLimit": { + "currency": "USD", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "value": "20" }, - "LedgerEntryType":"RippleState", - "LowLimit":{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "LedgerEntryType": "RippleState", + "LowLimit": { + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }, - "PreviousTxnID":"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879", - "PreviousTxnLgrSeq":123, - "index":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" + "PreviousTxnID": "E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879", + "PreviousTxnLgrSeq": 123, + "index": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" } ] })JSON"; @@ -365,9 +365,9 @@ TEST_F(RPCAccountObjectsHandlerTest, DefaultParameterNoNFTFound) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -409,10 +409,10 @@ TEST_F(RPCAccountObjectsHandlerTest, Limit) } EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, kLIMIT @@ -453,10 +453,10 @@ TEST_F(RPCAccountObjectsHandlerTest, Marker) } EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, kINDEX1, @@ -507,10 +507,10 @@ TEST_F(RPCAccountObjectsHandlerTest, MultipleDirNoNFT) } EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, 2 * kCOUNT @@ -561,10 +561,10 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilter) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "type":"offer" + "account": "{}", + "type": "offer" }})JSON", kACCOUNT )); @@ -605,7 +605,7 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterAmmType) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "type": "amm" @@ -658,9 +658,9 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterReturnEmpty) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", + "account": "{}", "type": "check" }})JSON", kACCOUNT @@ -713,7 +713,7 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilter) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "deletion_blockers_only": true @@ -756,7 +756,7 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilterWithTypeFilter) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "deletion_blockers_only": true, @@ -818,7 +818,7 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilterEmptyResult) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "deletion_blockers_only": true @@ -878,7 +878,7 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilterWithIncompatibleT EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "deletion_blockers_only": true, @@ -898,64 +898,64 @@ TEST_F(RPCAccountObjectsHandlerTest, DeletionBlockersOnlyFilterWithIncompatibleT TEST_F(RPCAccountObjectsHandlerTest, NFTMixOtherObjects) { static constexpr auto kEXPECTED_OUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 200, - "account_objects":[ + "account_objects": [ { - "Flags":0, - "LedgerEntryType":"NFTokenPage", - "NFTokens":[ + "Flags": 0, + "LedgerEntryType": "NFTokenPage", + "NFTokens": [ { - "NFToken":{ - "NFTokenID":"000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA", - "URI":"7777772E6F6B2E636F6D" + "NFToken": { + "NFTokenID": "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA", + "URI": "7777772E6F6B2E636F6D" } } ], - "PreviousPageMin":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "index":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9FFFFFFFFFFFFFFFFFFFFFFFF" + "PreviousPageMin": "4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "index": "4B4E9C06F24296074F7BC48F92A97916C6DC5EA9FFFFFFFFFFFFFFFFFFFFFFFF" }, { - "Flags":0, - "LedgerEntryType":"NFTokenPage", - "NFTokens":[ + "Flags": 0, + "LedgerEntryType": "NFTokenPage", + "NFTokens": [ { - "NFToken":{ - "NFTokenID":"000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA", - "URI":"7777772E6F6B2E636F6D" + "NFToken": { + "NFTokenID": "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA", + "URI": "7777772E6F6B2E636F6D" } } ], - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "index":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC" + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "index": "4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC" }, { - "Balance":{ - "currency":"USD", - "issuer":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", - "value":"100" + "Balance": { + "currency": "USD", + "issuer": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", + "value": "100" }, - "Flags":0, - "HighLimit":{ - "currency":"USD", - "issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "value":"20" + "Flags": 0, + "HighLimit": { + "currency": "USD", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "value": "20" }, - "LedgerEntryType":"RippleState", - "LowLimit":{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "LedgerEntryType": "RippleState", + "LowLimit": { + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }, - "PreviousTxnID":"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879", - "PreviousTxnLgrSeq":123, - "index":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" + "PreviousTxnID": "E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879", + "PreviousTxnLgrSeq": 123, + "index": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" } ] })JSON"; @@ -992,9 +992,9 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMixOtherObjects) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -1031,10 +1031,10 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTReachLimitReturnMarker) current = previous; } - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, 10 @@ -1080,10 +1080,10 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTReachLimitNoMarker) ); EXPECT_CALL(*backend_, doFetchLedgerObject(current, 30, _)).WillOnce(Return(nftpage11.getSerializer().peekData())); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, 11 @@ -1158,10 +1158,10 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMarker) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, ripple::strHex(marker), @@ -1214,10 +1214,10 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMarkerNoMoreNFT) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, ripple::strHex(ripple::uint256{beast::zero}), @@ -1242,10 +1242,10 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMarkerNotInRange) auto const accountKk = ripple::keylet::account(account).key; EXPECT_CALL(*backend_, doFetchLedgerObject(accountKk, kMAX_SEQ, _)).WillOnce(Return(Blob{'f', 'a', 'k', 'e'})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", - "marker" : "{},{}" + "marker": "{},{}" }})JSON", kACCOUNT, kINDEX1, @@ -1275,10 +1275,10 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMarkerNotExist) auto const accountNftMax = ripple::keylet::nftpage_max(account).key; EXPECT_CALL(*backend_, doFetchLedgerObject(accountNftMax, kMAX_SEQ, _)).WillOnce(Return(std::nullopt)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", - "marker" : "{},{}" + "marker": "{},{}" }})JSON", kACCOUNT, ripple::strHex(accountNftMax), @@ -1349,10 +1349,10 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTLimitAdjust) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}", + "account": "{}", + "marker": "{},{}", "limit": 12 }})JSON", kACCOUNT, @@ -1373,42 +1373,42 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTLimitAdjust) TEST_F(RPCAccountObjectsHandlerTest, FilterNFT) { static constexpr auto kEXPECTED_OUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 200, - "account_objects":[ + "account_objects": [ { - "Flags":0, - "LedgerEntryType":"NFTokenPage", - "NFTokens":[ + "Flags": 0, + "LedgerEntryType": "NFTokenPage", + "NFTokens": [ { - "NFToken":{ - "NFTokenID":"000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA", - "URI":"7777772E6F6B2E636F6D" + "NFToken": { + "NFTokenID": "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA", + "URI": "7777772E6F6B2E636F6D" } } ], - "PreviousPageMin":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "index":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9FFFFFFFFFFFFFFFFFFFFFFFF" + "PreviousPageMin": "4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "index": "4B4E9C06F24296074F7BC48F92A97916C6DC5EA9FFFFFFFFFFFFFFFFFFFFFFFF" }, { - "Flags":0, - "LedgerEntryType":"NFTokenPage", - "NFTokens":[ + "Flags": 0, + "LedgerEntryType": "NFTokenPage", + "NFTokens": [ { - "NFToken":{ - "NFTokenID":"000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA", - "URI":"7777772E6F6B2E636F6D" + "NFToken": { + "NFTokenID": "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA", + "URI": "7777772E6F6B2E636F6D" } } ], - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "index":"4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC" + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "index": "4B4E9C06F24296074F7BC48F92A97916C6DC5EA9659B25014D08E1BC983515BC" } ] })JSON"; @@ -1445,9 +1445,9 @@ TEST_F(RPCAccountObjectsHandlerTest, FilterNFT) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", + "account": "{}", "type": "nft_page" }})JSON", kACCOUNT @@ -1486,10 +1486,10 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTZeroMarkerNotAffectOtherMarker) } EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{}, + "account": "{}", + "limit": {}, "marker": "{},{}" }})JSON", kACCOUNT, @@ -1511,33 +1511,33 @@ TEST_F(RPCAccountObjectsHandlerTest, LimitLessThanMin) { static auto const kEXPECTED_OUT = fmt::format( R"JSON({{ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": {}, - "account_objects":[ + "account_objects": [ {{ - "Balance":{{ - "currency":"USD", - "issuer":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", - "value":"100" + "Balance": {{ + "currency": "USD", + "issuer": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", + "value": "100" }}, - "Flags":0, - "HighLimit":{{ - "currency":"USD", - "issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "value":"20" + "Flags": 0, + "HighLimit": {{ + "currency": "USD", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "value": "20" }}, - "LedgerEntryType":"RippleState", - "LowLimit":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "LedgerEntryType": "RippleState", + "LowLimit": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "PreviousTxnID":"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879", - "PreviousTxnLgrSeq":123, - "index":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" + "PreviousTxnID": "E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879", + "PreviousTxnLgrSeq": 123, + "index": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" }} ] }})JSON", @@ -1566,9 +1566,9 @@ TEST_F(RPCAccountObjectsHandlerTest, LimitLessThanMin) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", + "account": "{}", "limit": {} }})JSON", kACCOUNT, @@ -1587,33 +1587,33 @@ TEST_F(RPCAccountObjectsHandlerTest, LimitMoreThanMax) { static auto const kEXPECTED_OUT = fmt::format( R"JSON({{ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": {}, - "account_objects":[ + "account_objects": [ {{ - "Balance":{{ - "currency":"USD", - "issuer":"rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", - "value":"100" + "Balance": {{ + "currency": "USD", + "issuer": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", + "value": "100" }}, - "Flags":0, - "HighLimit":{{ - "currency":"USD", - "issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "value":"20" + "Flags": 0, + "HighLimit": {{ + "currency": "USD", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "value": "20" }}, - "LedgerEntryType":"RippleState", - "LowLimit":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "LedgerEntryType": "RippleState", + "LowLimit": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "PreviousTxnID":"E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879", - "PreviousTxnLgrSeq":123, - "index":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" + "PreviousTxnID": "E3FE6EA3D48F0C2B639448020EA4F03D4F4F8FFDB243A852A0F59177921B4879", + "PreviousTxnLgrSeq": 123, + "index": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" }} ] }})JSON", @@ -1642,9 +1642,9 @@ TEST_F(RPCAccountObjectsHandlerTest, LimitMoreThanMax) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", + "account": "{}", "limit": {} }})JSON", kACCOUNT, @@ -1684,7 +1684,7 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTIssuanceType) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "type": "mpt_issuance" @@ -1733,7 +1733,7 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTokenType) EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "type": "mptoken" diff --git a/tests/unit/rpc/handlers/AccountOffersTests.cpp b/tests/unit/rpc/handlers/AccountOffersTests.cpp index 8dbb267d4..e3ac62a08 100644 --- a/tests/unit/rpc/handlers/AccountOffersTests.cpp +++ b/tests/unit/rpc/handlers/AccountOffersTests.cpp @@ -176,10 +176,10 @@ TEST_F(RPCAccountOffersHandlerTest, LedgerNotFoundViaHash) ON_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) .WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})JSON", kACCOUNT, kLEDGER_HASH @@ -202,10 +202,10 @@ TEST_F(RPCAccountOffersHandlerTest, LedgerNotFoundViaStringIndex) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(kSEQ, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":"{}" + "account": "{}", + "ledger_index": "{}" }})JSON", kACCOUNT, kSEQ @@ -228,10 +228,10 @@ TEST_F(RPCAccountOffersHandlerTest, LedgerNotFoundViaIntIndex) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(kSEQ, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":{} + "account": "{}", + "ledger_index": {} }})JSON", kACCOUNT, kSEQ @@ -255,9 +255,9 @@ TEST_F(RPCAccountOffersHandlerTest, AccountNotFound) ON_CALL(*backend_, doFetchLedgerObject).WillByDefault(Return(std::optional{})); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -275,24 +275,24 @@ TEST_F(RPCAccountOffersHandlerTest, DefaultParams) { auto const expectedOutput = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":30, - "validated":true, - "account":"{}", + "ledger_hash": "{}", + "ledger_index": 30, + "validated": true, + "account": "{}", "offers": [ {{ - "seq":0, - "flags":0, - "quality":"0.000000024999999374023", - "taker_pays":"20", + "seq": 0, + "flags": 0, + "quality": "0.000000024999999374023", + "taker_pays": "20", "taker_gets": {{ - "currency":"USD", - "issuer":"{}", - "value":"10" + "currency": "USD", + "issuer": "{}", + "value": "10" }}, - "expiration":123 + "expiration": 123 }} ] }})JSON", @@ -332,9 +332,9 @@ TEST_F(RPCAccountOffersHandlerTest, DefaultParams) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -380,10 +380,10 @@ TEST_F(RPCAccountOffersHandlerTest, Limit) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":10 + "account": "{}", + "limit": 10 }})JSON", kACCOUNT )); @@ -433,10 +433,10 @@ TEST_F(RPCAccountOffersHandlerTest, Marker) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, kINDEX1, @@ -469,10 +469,10 @@ TEST_F(RPCAccountOffersHandlerTest, MarkerNotExists) ON_CALL(*backend_, doFetchLedgerObject(hintIndex, kLEDGER_SEQ, _)).WillByDefault(Return(std::nullopt)); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, kINDEX1, @@ -527,10 +527,10 @@ TEST_F(RPCAccountOffersHandlerTest, LimitLessThanMin) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, AccountOffersHandler::kLIMIT_MIN - 1 @@ -582,10 +582,10 @@ TEST_F(RPCAccountOffersHandlerTest, LimitMoreThanMax) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, AccountOffersHandler::kLIMIT_MAX + 1 diff --git a/tests/unit/rpc/handlers/AccountTxTests.cpp b/tests/unit/rpc/handlers/AccountTxTests.cpp index a4314d1ee..e51df9bc1 100644 --- a/tests/unit/rpc/handlers/AccountTxTests.cpp +++ b/tests/unit/rpc/handlers/AccountTxTests.cpp @@ -512,7 +512,7 @@ TEST_F(RPCAccountTxHandlerTest, IndexSpecificForwardTrue) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -554,7 +554,7 @@ TEST_F(RPCAccountTxHandlerTest, IndexSpecificForwardFalse) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -596,7 +596,7 @@ TEST_F(RPCAccountTxHandlerTest, IndexNotSpecificForwardTrue) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -638,7 +638,7 @@ TEST_F(RPCAccountTxHandlerTest, IndexNotSpecificForwardFalse) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -678,7 +678,7 @@ TEST_F(RPCAccountTxHandlerTest, BinaryTrue) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -732,7 +732,7 @@ TEST_F(RPCAccountTxHandlerTest, BinaryTrueV2) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -784,14 +784,14 @@ TEST_F(RPCAccountTxHandlerTest, LimitAndMarker) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "limit": 2, "forward": false, - "marker": {{"ledger":10,"seq":11}} + "marker": {{"ledger": 10, "seq": 11}} }})JSON", kACCOUNT, -1, @@ -818,7 +818,7 @@ TEST_F(RPCAccountTxHandlerTest, LimitIsCapped) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -850,7 +850,7 @@ TEST_F(RPCAccountTxHandlerTest, LimitAllowedUpToCap) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -898,7 +898,7 @@ TEST_F(RPCAccountTxHandlerTest, SpecificLedgerIndex) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index": {} @@ -924,7 +924,7 @@ TEST_F(RPCAccountTxHandlerTest, SpecificNonexistLedgerIntIndex) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index": {} @@ -947,7 +947,7 @@ TEST_F(RPCAccountTxHandlerTest, SpecificNonexistLedgerStringIndex) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index": "{}" @@ -989,7 +989,7 @@ TEST_F(RPCAccountTxHandlerTest, SpecificLedgerHash) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_hash": "{}" @@ -1033,7 +1033,7 @@ TEST_F(RPCAccountTxHandlerTest, SpecificLedgerIndexValidated) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index": "validated" @@ -1071,7 +1071,7 @@ TEST_F(RPCAccountTxHandlerTest, TxLessThanMinSeq) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -1113,7 +1113,7 @@ TEST_F(RPCAccountTxHandlerTest, TxLargerThanMaxSeq) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -1355,7 +1355,7 @@ TEST_F(RPCAccountTxHandlerTest, NFTTxs_API_v1) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, @@ -1603,7 +1603,7 @@ TEST_F(RPCAccountTxHandlerTest, NFTTxs_API_v2) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountTxHandler{backend_, mockETLServicePtr_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "ledger_index_min": {}, diff --git a/tests/unit/rpc/handlers/AllHandlerTests.cpp b/tests/unit/rpc/handlers/AllHandlerTests.cpp index 41f04669d..94b4c7198 100644 --- a/tests/unit/rpc/handlers/AllHandlerTests.cpp +++ b/tests/unit/rpc/handlers/AllHandlerTests.cpp @@ -47,6 +47,7 @@ #include "rpc/handlers/ServerInfo.hpp" #include "rpc/handlers/Subscribe.hpp" #include "rpc/handlers/TransactionEntry.hpp" +#include "rpc/handlers/VaultInfo.hpp" #include "util/Assert.hpp" #include "util/HandlerBaseTestFixture.hpp" #include "util/MockAmendmentCenter.hpp" @@ -74,11 +75,12 @@ using ::testing::Types; using namespace rpc; using TestServerInfoHandler = BaseServerInfoHandler; -constexpr static auto kINDEX1 = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"; -constexpr static auto kAMM_ACCOUNT = "rLcS7XL6nxRAi7JcbJcn1Na179oF3vdfbh"; -constexpr static auto kACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"; -constexpr static auto kNFT_ID = "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004"; -constexpr static auto kCURRENCY = "0158415500000000C1F76FF6ECB0BAC600000000"; +static constexpr auto kINDEX1 = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"; +static constexpr auto kAMM_ACCOUNT = "rLcS7XL6nxRAi7JcbJcn1Na179oF3vdfbh"; +static constexpr auto kACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"; +static constexpr auto kNFT_ID = "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004"; +static constexpr auto kCURRENCY = "0158415500000000C1F76FF6ECB0BAC600000000"; +static constexpr auto kVAULT_ID = "61B03A6F8CEBD3AF9D8F696C3D0A9A9F0493B34BF6B5D93CF0BC009E6BA75303"; using AnyHandlerType = Types< AccountChannelsHandler, @@ -109,7 +111,8 @@ using AnyHandlerType = Types< NoRippleCheckHandler, TestServerInfoHandler, SubscribeHandler, - TransactionEntryHandler>; + TransactionEntryHandler, + VaultInfoHandler>; template struct AllHandlersAssertTest : common::util::WithMockAssert, @@ -255,6 +258,16 @@ createInput() return input; } +template <> +VaultInfoHandler::Input +createInput() +{ + VaultInfoHandler::Input input{}; + input.vaultID = kVAULT_ID; + + return input; +} + TYPED_TEST_CASE(AllHandlersAssertTest, AnyHandlerType); TYPED_TEST(AllHandlersAssertTest, NoRangeAvailable) diff --git a/tests/unit/rpc/handlers/BookChangesTests.cpp b/tests/unit/rpc/handlers/BookChangesTests.cpp index 8006d2b84..c6fa2a70b 100644 --- a/tests/unit/rpc/handlers/BookChangesTests.cpp +++ b/tests/unit/rpc/handlers/BookChangesTests.cpp @@ -50,6 +50,7 @@ constexpr auto kISSUER = "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD"; constexpr auto kACCOUNT1 = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"; constexpr auto kACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"; constexpr auto kLEDGER_HASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; +constexpr auto kDOMAIN = "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E3370F3649CE134E5"; constexpr auto kMAX_SEQ = 30; constexpr auto kMIN_SEQ = 10; @@ -79,19 +80,19 @@ generateTestValuesForParametersTest() return std::vector{ BookChangesParamTestCaseBundle{ .testName = "LedgerHashInvalid", - .testJson = R"JSON({"ledger_hash":"1"})JSON", + .testJson = R"JSON({"ledger_hash": "1"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashMalformed" }, BookChangesParamTestCaseBundle{ .testName = "LedgerHashNotString", - .testJson = R"JSON({"ledger_hash":1})JSON", + .testJson = R"JSON({"ledger_hash": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashNotString" }, BookChangesParamTestCaseBundle{ .testName = "LedgerIndexInvalid", - .testJson = R"JSON({"ledger_index":"a"})JSON", + .testJson = R"JSON({"ledger_index": "a"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledgerIndexMalformed" }, @@ -121,11 +122,11 @@ TEST_P(BookChangesParameterTest, InvalidParams) TEST_F(RPCBookChangesHandlerTest, LedgerNonExistViaIntSequence) { - EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); + EXPECT_CALL(*backend_, fetchLedgerBySequence); // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(kMAX_SEQ, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(R"JSON({"ledger_index":30})JSON"); + static auto const kINPUT = json::parse(R"JSON({"ledger_index": 30})JSON"); auto const handler = AnyHandler{BookChangesHandler{backend_}}; runSpawn([&](auto yield) { auto const output = handler.process(kINPUT, Context{yield}); @@ -138,11 +139,11 @@ TEST_F(RPCBookChangesHandlerTest, LedgerNonExistViaIntSequence) TEST_F(RPCBookChangesHandlerTest, LedgerNonExistViaStringSequence) { - EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); + EXPECT_CALL(*backend_, fetchLedgerBySequence); // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(kMAX_SEQ, _)).WillByDefault(Return(std::nullopt)); - auto static const kINPUT = json::parse(R"JSON({"ledger_index":"30"})JSON"); + static auto const kINPUT = json::parse(R"JSON({"ledger_index": "30"})JSON"); auto const handler = AnyHandler{BookChangesHandler{backend_}}; runSpawn([&](auto yield) { auto const output = handler.process(kINPUT, Context{yield}); @@ -155,14 +156,13 @@ TEST_F(RPCBookChangesHandlerTest, LedgerNonExistViaStringSequence) TEST_F(RPCBookChangesHandlerTest, LedgerNonExistViaHash) { - EXPECT_CALL(*backend_, fetchLedgerByHash).Times(1); // return empty ledgerHeader - ON_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) - .WillByDefault(Return(std::optional{})); + EXPECT_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) + .WillOnce(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "ledger_hash":"{}" + "ledger_hash": "{}" }})JSON", kLEDGER_HASH )); @@ -180,28 +180,27 @@ TEST_F(RPCBookChangesHandlerTest, NormalPath) { static constexpr auto kEXPECTED_OUT = R"JSON({ - "type":"bookChanges", - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "ledger_time":0, - "validated":true, - "changes":[ + "type": "bookChanges", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "ledger_time": 0, + "validated": true, + "changes": [ { - "currency_a":"XRP_drops", - "currency_b":"rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD/0158415500000000C1F76FF6ECB0BAC600000000", - "volume_a":"2", - "volume_b":"2", - "high":"-1", - "low":"-1", - "open":"-1", - "close":"-1" + "currency_a": "XRP_drops", + "currency_b": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD/0158415500000000C1F76FF6ECB0BAC600000000", + "volume_a": "2", + "volume_b": "2", + "high": "-1", + "low": "-1", + "open": "-1", + "close": "-1" } ] })JSON"; - EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); - ON_CALL(*backend_, fetchLedgerBySequence(kMAX_SEQ, _)) - .WillByDefault(Return(createLedgerHeader(kLEDGER_HASH, kMAX_SEQ))); + EXPECT_CALL(*backend_, fetchLedgerBySequence(kMAX_SEQ, _)) + .WillOnce(Return(createLedgerHeader(kLEDGER_HASH, kMAX_SEQ))); auto transactions = std::vector{}; auto trans1 = TransactionAndMetadata(); @@ -212,8 +211,53 @@ TEST_F(RPCBookChangesHandlerTest, NormalPath) trans1.metadata = metaObj.getSerializer().peekData(); transactions.push_back(trans1); - EXPECT_CALL(*backend_, fetchAllTransactionsInLedger).Times(1); - ON_CALL(*backend_, fetchAllTransactionsInLedger(kMAX_SEQ, _)).WillByDefault(Return(transactions)); + EXPECT_CALL(*backend_, fetchAllTransactionsInLedger(kMAX_SEQ, _)).WillOnce(Return(transactions)); + + auto const handler = AnyHandler{BookChangesHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(json::parse("{}"), Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(*output.result, json::parse(kEXPECTED_OUT)); + }); +} + +TEST_F(RPCBookChangesHandlerTest, NormalPathWithDomain) +{ + static constexpr auto kEXPECTED_OUT = + R"JSON({ + "type": "bookChanges", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "ledger_time": 0, + "validated": true, + "changes": [ + { + "currency_a": "XRP_drops", + "currency_b": "rK9DrarGKnVEo2nYp5MfVRXRYf5yRX3mwD/0158415500000000C1F76FF6ECB0BAC600000000", + "volume_a": "2", + "volume_b": "2", + "high": "-1", + "low": "-1", + "open": "-1", + "close": "-1", + "domain": "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E3370F3649CE134E5" + } + ] + })JSON"; + + EXPECT_CALL(*backend_, fetchLedgerBySequence(kMAX_SEQ, _)) + .WillOnce(Return(createLedgerHeader(kLEDGER_HASH, kMAX_SEQ))); + + auto transactions = std::vector{}; + auto trans1 = TransactionAndMetadata(); + ripple::STObject const obj = createPaymentTransactionObject(kACCOUNT1, kACCOUNT2, 1, 1, 32); + trans1.transaction = obj.getSerializer().peekData(); + trans1.ledgerSequence = 32; + ripple::STObject const metaObj = createMetaDataForBookChange(kCURRENCY, kISSUER, 22, 1, 3, 3, 1, kDOMAIN); + trans1.metadata = metaObj.getSerializer().peekData(); + transactions.push_back(trans1); + + EXPECT_CALL(*backend_, fetchAllTransactionsInLedger(kMAX_SEQ, _)).WillOnce(Return(transactions)); auto const handler = AnyHandler{BookChangesHandler{backend_}}; runSpawn([&](auto yield) { diff --git a/tests/unit/rpc/handlers/BookOffersTests.cpp b/tests/unit/rpc/handlers/BookOffersTests.cpp index 765ec30b3..1752586e3 100644 --- a/tests/unit/rpc/handlers/BookOffersTests.cpp +++ b/tests/unit/rpc/handlers/BookOffersTests.cpp @@ -114,10 +114,10 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "MissingTakerGets", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "USD", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" } })JSON", .expectedError = "invalidParams", @@ -126,10 +126,10 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "MissingTakerPays", .testJson = R"JSON({ - "taker_gets" : + "taker_gets": { - "currency" : "USD", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" } })JSON", .expectedError = "invalidParams", @@ -138,10 +138,10 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "WrongTypeTakerPays", .testJson = R"JSON({ - "taker_pays" : "wrong", - "taker_gets" : + "taker_pays": "wrong", + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "invalidParams", @@ -150,10 +150,10 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "WrongTypeTakerGets", .testJson = R"JSON({ - "taker_gets" : "wrong", - "taker_pays" : + "taker_gets": "wrong", + "taker_pays": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "invalidParams", @@ -162,10 +162,10 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "TakerPaysMissingCurrency", .testJson = R"JSON({ - "taker_pays" : {}, - "taker_gets" : + "taker_pays": {}, + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "invalidParams", @@ -174,10 +174,10 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "TakerGetsMissingCurrency", .testJson = R"JSON({ - "taker_gets" : {}, - "taker_pays" : + "taker_gets": {}, + "taker_pays": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "invalidParams", @@ -186,14 +186,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "TakerGetsWrongCurrency", .testJson = R"JSON({ - "taker_gets" : + "taker_gets": { - "currency" : "CNYY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNYY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_pays" : + "taker_pays": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "dstAmtMalformed", @@ -202,14 +202,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "TakerPaysWrongCurrency", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNYY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNYY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "srcCurMalformed", @@ -218,14 +218,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "TakerGetsCurrencyNotString", .testJson = R"JSON({ - "taker_gets" : + "taker_gets": { - "currency" : 123, - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": 123, + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_pays" : + "taker_pays": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "dstAmtMalformed", @@ -234,14 +234,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "TakerPaysCurrencyNotString", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : 123, - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": 123, + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "srcCurMalformed", @@ -250,14 +250,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "TakerGetsWrongIssuer", .testJson = R"JSON({ - "taker_gets" : + "taker_gets": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs5" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs5" }, - "taker_pays" : + "taker_pays": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "dstIsrMalformed", @@ -266,14 +266,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "TakerPaysWrongIssuer", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs5" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs5" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" } })JSON", .expectedError = "srcIsrMalformed", @@ -282,14 +282,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "InvalidTaker", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "taker": "123" })JSON", @@ -299,14 +299,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "TakerNotString", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "taker": 123 })JSON", @@ -316,65 +316,65 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "Domain_InvalidType", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "domain": 0 })JSON", - .expectedError = "invalidParams", - .expectedErrorMessage = "Invalid parameters." + .expectedError = "domainMalformed", + .expectedErrorMessage = "Unable to parse domain." }, ParameterTestBundle{ .testName = "Domain_InvalidInt", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "domain": "123" })JSON", - .expectedError = "invalidParams", - .expectedErrorMessage = "domainMalformed" + .expectedError = "domainMalformed", + .expectedErrorMessage = "Unable to parse domain." }, ParameterTestBundle{ .testName = "Domain_InvalidObject", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "domain": {} })JSON", - .expectedError = "invalidParams", - .expectedErrorMessage = "Invalid parameters." + .expectedError = "domainMalformed", + .expectedErrorMessage = "Unable to parse domain." }, ParameterTestBundle{ .testName = "LimitNotInt", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "limit": "123" })JSON", @@ -384,14 +384,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "LimitNegative", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "limit": -1 })JSON", @@ -401,14 +401,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "LimitZero", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "limit": 0 })JSON", @@ -418,14 +418,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "LedgerIndexInvalid", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "ledger_index": "xxx" })JSON", @@ -435,14 +435,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "LedgerHashInvalid", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "ledger_hash": "xxx" })JSON", @@ -452,14 +452,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "LedgerHashNotString", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP" + "currency": "XRP" }, "ledger_hash": 123 })JSON", @@ -469,15 +469,15 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "GetsPaysXRPWithIssuer", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "XRP", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "XRP", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "CNY", - "issuer" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" + "currency": "CNY", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" } })JSON", .expectedError = "srcIsrMalformed", @@ -486,14 +486,14 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "PaysCurrencyWithXRPIssuer", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "JPY" + "currency": "JPY" }, - "taker_gets" : + "taker_gets": { - "currency" : "CNY", - "issuer" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" + "currency": "CNY", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" } })JSON", .expectedError = "srcIsrMalformed", @@ -502,13 +502,13 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "GetsCurrencyWithXRPIssuer", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "XRP" + "currency": "XRP" }, - "taker_gets" : + "taker_gets": { - "currency" : "CNY" + "currency": "CNY" } })JSON", .expectedError = "dstIsrMalformed", @@ -517,15 +517,15 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "GetsXRPWithIssuer", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "XRP", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "XRP", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" } })JSON", .expectedError = "dstIsrMalformed", @@ -534,15 +534,15 @@ generateParameterBookOffersTestBundles() ParameterTestBundle{ .testName = "BadMarket", .testJson = R"JSON({ - "taker_pays" : + "taker_pays": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" }, - "taker_gets" : + "taker_gets": { - "currency" : "CNY", - "issuer" : "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + "currency": "CNY", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" } })JSON", .expectedError = "badMarket", @@ -770,28 +770,28 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10XRPPays20USDOffer}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, - "offers":[ + "ledger_hash": "{}", + "ledger_index": 300, + "offers": [ {{ - "Account":"{}", - "BookDirectory":"43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerGets":"10", - "TakerPays":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"20" + "Account": "{}", + "BookDirectory": "43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerGets": "10", + "TakerPays": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "20" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}" }} ] }})JSON", @@ -830,34 +830,34 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10XRPPays20USDOffer}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerGets":"10", - "TakerPays":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"20" + "Account": "{}", + "BookDirectory": "43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerGets": "10", + "TakerPays": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "20" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}", - "taker_gets_funded":"5", - "taker_pays_funded":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}", + "taker_gets_funded": "5", + "taker_pays_funded": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }} }} ] @@ -892,34 +892,34 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10XRPPays20USDOffer}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerGets":"10", - "TakerPays":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"20" + "Account": "{}", + "BookDirectory": "43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerGets": "10", + "TakerPays": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "20" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}", - "taker_gets_funded":"0", - "taker_pays_funded":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"0" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}", + "taker_gets_funded": "0", + "taker_pays_funded": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "0" }} }} ] @@ -954,35 +954,35 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10XRPPays20USDOfferWithDomain}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000", - "BookNode":"0", + "Account": "{}", + "BookDirectory": "43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000", + "BookNode": "0", "DomainID": "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E3370F3649CE134E5", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerGets":"10", - "TakerPays":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"20" + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerGets": "10", + "TakerPays": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "20" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}", - "taker_gets_funded":"0", - "taker_pays_funded":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"0" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}", + "taker_gets_funded": "0", + "taker_pays_funded": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "0" }} }} ] @@ -1019,34 +1019,34 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10USDPays20XRPOffer}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerPays":"20", - "TakerGets":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerPays": "20", + "TakerGets": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}", - "taker_pays_funded":"0", - "taker_gets_funded":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"0" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}", + "taker_pays_funded": "0", + "taker_gets_funded": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "0" }} }} ] @@ -1085,35 +1085,35 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10USDPays20XRPOffer}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerPays":"20", - "TakerGets":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerPays": "20", + "TakerGets": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}", - "taker_gets_funded":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"4" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}", + "taker_gets_funded": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "4" }}, - "taker_pays_funded":"8" + "taker_pays_funded": "8" }} ] }})JSON", @@ -1160,54 +1160,54 @@ generateNormalPathBookOffersTestBundles() }, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerPays":"20", - "TakerGets":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerPays": "20", + "TakerGets": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}" }}, {{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerPays":"20", - "TakerGets":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerPays": "20", + "TakerGets": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "taker_gets_funded":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"5" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "taker_gets_funded": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "5" }}, - "taker_pays_funded":"10", - "quality":"{}" + "taker_pays_funded": "10", + "quality": "{}" }} ] }})JSON", @@ -1246,29 +1246,29 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10USDPays20XRPOwnerOffer}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerPays":"20", - "TakerGets":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerPays": "20", + "TakerGets": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}" }} ] }})JSON", @@ -1306,35 +1306,35 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10USDPays20XRPOffer}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerPays":"20", - "TakerGets":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerPays": "20", + "TakerGets": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}", - "taker_gets_funded":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"0" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}", + "taker_gets_funded": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "0" }}, - "taker_pays_funded":"0" + "taker_pays_funded": "0" }} ] }})JSON", @@ -1371,35 +1371,35 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10USDPays20XRPOffer}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerPays":"20", - "TakerGets":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerPays": "20", + "TakerGets": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}", - "taker_gets_funded":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"0" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}", + "taker_gets_funded": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "0" }}, - "taker_pays_funded":"0" + "taker_pays_funded": "0" }} ] }})JSON", @@ -1439,35 +1439,35 @@ generateNormalPathBookOffersTestBundles() .mockedOffers = std::vector{gets10USDPays20XRPOffer}, .expectedJson = fmt::format( R"JSON({{ - "ledger_hash":"{}", - "ledger_index":300, + "ledger_hash": "{}", + "ledger_index": 300, "offers": [ {{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerPays":"20", - "TakerGets":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"10" + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerPays": "20", + "TakerGets": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "10" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"{}", - "quality":"{}", - "taker_gets_funded":{{ - "currency":"USD", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"0" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "{}", + "quality": "{}", + "taker_gets_funded": {{ + "currency": "USD", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "0" }}, - "taker_pays_funded":"0" + "taker_pays_funded": "0" }} ] }})JSON", @@ -1495,7 +1495,7 @@ TEST_F(RPCBookOffersHandlerTest, LedgerNonExistViaIntSequence) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(30, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "ledger_index": 30, "taker_gets": @@ -1526,7 +1526,7 @@ TEST_F(RPCBookOffersHandlerTest, LedgerNonExistViaSequence) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(30, _)).WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "ledger_index": "30", "taker_gets": @@ -1558,7 +1558,7 @@ TEST_F(RPCBookOffersHandlerTest, LedgerNonExistViaHash) ON_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) .WillByDefault(Return(std::optional{})); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "ledger_hash": "{}", "taker_gets": @@ -1635,7 +1635,7 @@ TEST_F(RPCBookOffersHandlerTest, Limit) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "taker_gets": {{ @@ -1709,7 +1709,7 @@ TEST_F(RPCBookOffersHandlerTest, LimitMoreThanMax) ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "taker_gets": {{ diff --git a/tests/unit/rpc/handlers/GatewayBalancesTests.cpp b/tests/unit/rpc/handlers/GatewayBalancesTests.cpp index c5eaba04a..7f37b1d78 100644 --- a/tests/unit/rpc/handlers/GatewayBalancesTests.cpp +++ b/tests/unit/rpc/handlers/GatewayBalancesTests.cpp @@ -270,9 +270,7 @@ TEST_F(RPCGatewayBalancesHandlerTest, LedgerNotFoundViaStringIndex) { auto const seq = 123; - EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); - // return empty ledgerHeader - ON_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillByDefault(Return(std::optional{})); + EXPECT_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillOnce(Return(std::optional{})); auto const handler = AnyHandler{GatewayBalancesHandler{backend_}}; runSpawn([&](auto yield) { @@ -298,9 +296,7 @@ TEST_F(RPCGatewayBalancesHandlerTest, LedgerNotFoundViaIntIndex) { auto const seq = 123; - EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); - // return empty ledgerHeader - ON_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillByDefault(Return(std::optional{})); + EXPECT_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillOnce(Return(std::optional{})); auto const handler = AnyHandler{GatewayBalancesHandler{backend_}}; runSpawn([&](auto yield) { @@ -324,10 +320,8 @@ TEST_F(RPCGatewayBalancesHandlerTest, LedgerNotFoundViaIntIndex) TEST_F(RPCGatewayBalancesHandlerTest, LedgerNotFoundViaHash) { - EXPECT_CALL(*backend_, fetchLedgerByHash).Times(1); - // return empty ledgerHeader - ON_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) - .WillByDefault(Return(std::optional{})); + EXPECT_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)) + .WillOnce(Return(std::optional{})); auto const handler = AnyHandler{GatewayBalancesHandler{backend_}}; runSpawn([&](auto yield) { @@ -353,15 +347,11 @@ TEST_F(RPCGatewayBalancesHandlerTest, AccountNotFound) { auto const seq = 300; - EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); - // return valid ledgerHeader auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, seq); - ON_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillByDefault(Return(ledgerHeader)); + EXPECT_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillOnce(Return(ledgerHeader)); - // return empty account auto const accountKk = ripple::keylet::account(getAccountIdWithString(kACCOUNT)).key; - ON_CALL(*backend_, doFetchLedgerObject(accountKk, seq, _)).WillByDefault(Return(std::optional{})); - EXPECT_CALL(*backend_, doFetchLedgerObject).Times(1); + EXPECT_CALL(*backend_, doFetchLedgerObject(accountKk, seq, _)).WillOnce(Return(std::optional{})); auto const handler = AnyHandler{GatewayBalancesHandler{backend_}}; runSpawn([&](auto yield) { @@ -396,31 +386,25 @@ TEST_P(NormalPathTest, CheckOutput) auto const& bundle = GetParam(); auto const seq = 300; - EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); // return valid ledgerHeader auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, seq); - ON_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillByDefault(Return(ledgerHeader)); + EXPECT_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillOnce(Return(ledgerHeader)); // return valid account auto const accountKk = ripple::keylet::account(getAccountIdWithString(kACCOUNT)).key; - ON_CALL(*backend_, doFetchLedgerObject(accountKk, seq, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'})); + EXPECT_CALL(*backend_, doFetchLedgerObject(accountKk, seq, _)).WillOnce(Return(Blob{'f', 'a', 'k', 'e'})); // return valid owner dir auto const ownerDir = createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1); auto const ownerDirKk = ripple::keylet::ownerDir(getAccountIdWithString(kACCOUNT)).key; - ON_CALL(*backend_, doFetchLedgerObject(ownerDirKk, seq, _)) - .WillByDefault(Return(bundle.mockedDir.getSerializer().peekData())); - EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); + EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, seq, _)) + .WillOnce(Return(bundle.mockedDir.getSerializer().peekData())); std::vector bbs; - std::ranges::transform( - bundle.mockedObjects, - - std::back_inserter(bbs), - [](auto const& obj) { return obj.getSerializer().peekData(); } - ); - ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); - EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); + std::ranges::transform(bundle.mockedObjects, std::back_inserter(bbs), [](auto const& obj) { + return obj.getSerializer().peekData(); + }); + EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); auto const handler = AnyHandler{GatewayBalancesHandler{backend_}}; runSpawn([&](auto yield) { @@ -478,44 +462,44 @@ generateNormalPathTestBundles() }, .expectedJson = fmt::format( R"JSON({{ - "obligations":{{ - "JPY":"50" + "obligations": {{ + "JPY": "50" }}, - "balances":{{ - "{}":[ + "balances": {{ + "{}": [ {{ - "currency":"USD", - "value":"10" + "currency": "USD", + "value": "10" }}, {{ - "currency":"CNY", - "value":"20" + "currency": "CNY", + "value": "20" }} ] }}, - "frozen_balances":{{ - "{}":[ + "frozen_balances": {{ + "{}": [ {{ - "currency":"JPY", - "value":"50" + "currency": "JPY", + "value": "50" }} ] }}, - "assets":{{ - "{}":[ + "assets": {{ + "{}": [ {{ - "currency":"EUR", - "value":"30" + "currency": "EUR", + "value": "30" }}, {{ - "currency":"JPY", - "value":"40" + "currency": "JPY", + "value": "40" }} ] }}, - "account":"{}", - "ledger_index":300, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + "account": "{}", + "ledger_index": 300, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" }})JSON", kACCOUNT2, kACCOUNT3, @@ -532,12 +516,12 @@ generateNormalPathTestBundles() )}, .expectedJson = fmt::format( R"JSON({{ - "obligations":{{ - "JPY":"50" + "obligations": {{ + "JPY": "50" }}, - "account":"{}", - "ledger_index":300, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + "account": "{}", + "ledger_index": 300, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" }})JSON", kACCOUNT ), @@ -549,12 +533,12 @@ generateNormalPathTestBundles() .mockedObjects = std::vector{overflowState, overflowState}, .expectedJson = fmt::format( R"JSON({{ - "obligations":{{ - "JPY":"9999999999999999e80" + "obligations": {{ + "JPY": "9999999999999999e80" }}, - "account":"{}", - "ledger_index":300, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + "account": "{}", + "ledger_index": 300, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" }})JSON", kACCOUNT ), @@ -578,32 +562,32 @@ generateNormalPathTestBundles() }, .expectedJson = fmt::format( R"JSON({{ - "obligations":{{ - "EUR":"30" + "obligations": {{ + "EUR": "30" }}, - "balances":{{ - "{}":[ + "balances": {{ + "{}": [ {{ - "currency":"USD", - "value":"10" + "currency": "USD", + "value": "10" }}, {{ - "currency":"CNY", - "value":"20" + "currency": "CNY", + "value": "20" }} ] }}, - "assets":{{ - "{}":[ + "assets": {{ + "{}": [ {{ - "currency":"JPY", - "value":"50" + "currency": "JPY", + "value": "50" }} ] }}, - "account":"{}", - "ledger_index":300, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + "account": "{}", + "ledger_index": 300, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" }})JSON", kACCOUNT2, kACCOUNT3, @@ -625,27 +609,27 @@ generateNormalPathTestBundles() }, .expectedJson = fmt::format( R"JSON({{ - "balances":{{ - "{}":[ + "balances": {{ + "{}": [ {{ - "currency":"EUR", - "value":"30" + "currency": "EUR", + "value": "30" }} ], - "{}":[ + "{}": [ {{ - "currency":"USD", - "value":"10" + "currency": "USD", + "value": "10" }}, {{ - "currency":"CNY", - "value":"20" + "currency": "CNY", + "value": "20" }} ] }}, - "account":"{}", - "ledger_index":300, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + "account": "{}", + "ledger_index": 300, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" }})JSON", kACCOUNT3, kACCOUNT2, @@ -662,3 +646,137 @@ INSTANTIATE_TEST_SUITE_P( testing::ValuesIn(generateNormalPathTestBundles()), tests::util::kNAME_GENERATOR ); + +struct EscrowTestBundle { + std::string testName; + ripple::STObject mockedDir; + std::vector mockedObjects; + std::string expectedJson; +}; + +struct EscrowTest : public RPCGatewayBalancesHandlerTest, public WithParamInterface {}; + +TEST_P(EscrowTest, CheckEscrowOutput) +{ + auto const& bundle = GetParam(); + auto const seq = 300; + + auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, seq); + EXPECT_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillOnce(Return(ledgerHeader)); + + auto const accountKk = ripple::keylet::account(getAccountIdWithString(kACCOUNT)).key; + EXPECT_CALL(*backend_, doFetchLedgerObject(accountKk, seq, _)).WillOnce(Return(Blob{'f', 'a', 'k', 'e'})); + + auto const ownerDirKk = ripple::keylet::ownerDir(getAccountIdWithString(kACCOUNT)).key; + EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, seq, _)) + .WillOnce(Return(bundle.mockedDir.getSerializer().peekData())); + + std::vector bbs; + std::ranges::transform(bundle.mockedObjects, std::back_inserter(bbs), [](auto const& obj) { + return obj.getSerializer().peekData(); + }); + EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs)); + + auto const handler = AnyHandler{GatewayBalancesHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process( + json::parse(fmt::format( + R"JSON({{ + "account": "{}" + }})JSON", + kACCOUNT + )), + Context{yield} + ); + ASSERT_TRUE(output); + EXPECT_EQ(output.result.value(), json::parse(bundle.expectedJson)); + }); +} + +static auto +generateEscrowTestBundles() +{ + // Escrow with 100 XRP + auto escrow1 = createEscrowLedgerObject(kACCOUNT, kACCOUNT2); + escrow1.setFieldAmount(ripple::sfAmount, ripple::STAmount(100, false)); + + // Escrow with 200 XRP + auto escrow2 = createEscrowLedgerObject(kACCOUNT, kACCOUNT3); + escrow2.setFieldAmount(ripple::sfAmount, ripple::STAmount(200, false)); + + // Escrow with a non-XRP currency + auto escrow3 = createEscrowLedgerObject(kACCOUNT, kACCOUNT2); + escrow3.setFieldAmount(ripple::sfAmount, ripple::STAmount(getIssue("USD", kISSUER), 50)); + + return std::vector{ + EscrowTestBundle{ + .testName = "SingleEscrowXRP", + .mockedDir = createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1), + .mockedObjects = std::vector{escrow1}, + .expectedJson = fmt::format( + R"JSON({{ + "locked": {{"XRP": "100"}}, + "account": "{}", + "ledger_index": 300, + "ledger_hash": "{}" + }})JSON", + kACCOUNT, + kLEDGER_HASH + ) + }, + EscrowTestBundle{ + .testName = "MultipleEscrowXRP", + .mockedDir = createOwnerDirLedgerObject({ripple::uint256{kINDEX2}, ripple::uint256{kINDEX2}}, kINDEX1), + .mockedObjects = std::vector{escrow1, escrow2}, + .expectedJson = fmt::format( + R"JSON({{ + "locked": {{"XRP": "300"}}, + "account": "{}", + "ledger_index": 300, + "ledger_hash": "{}" + }})JSON", + kACCOUNT, + kLEDGER_HASH + ) + }, + EscrowTestBundle{ + .testName = "EscrowNonXRP", + .mockedDir = createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1), + .mockedObjects = std::vector{escrow3}, + .expectedJson = fmt::format( + R"JSON({{ + "locked": {{"USD": "50"}}, + "account": "{}", + "ledger_index": 300, + "ledger_hash": "{}" + }})JSON", + kACCOUNT, + kLEDGER_HASH + ) + }, + EscrowTestBundle{ + .testName = "EscrowMixedCurrencies", + .mockedDir = createOwnerDirLedgerObject( + {ripple::uint256{kINDEX2}, ripple::uint256{kINDEX2}, ripple::uint256{kINDEX2}}, kINDEX1 + ), + .mockedObjects = std::vector{escrow1, escrow2, escrow3}, + .expectedJson = fmt::format( + R"JSON({{ + "locked": {{"XRP": "300", "USD": "50"}}, + "account": "{}", + "ledger_index": 300, + "ledger_hash": "{}" + }})JSON", + kACCOUNT, + kLEDGER_HASH + ) + } + }; +} + +INSTANTIATE_TEST_SUITE_P( + RPCGatewayBalancesHandler, + EscrowTest, + testing::ValuesIn(generateEscrowTestBundles()), + tests::util::kNAME_GENERATOR +); diff --git a/tests/unit/rpc/handlers/GetAggregatePriceTests.cpp b/tests/unit/rpc/handlers/GetAggregatePriceTests.cpp index dafb1501b..a54f60d34 100644 --- a/tests/unit/rpc/handlers/GetAggregatePriceTests.cpp +++ b/tests/unit/rpc/handlers/GetAggregatePriceTests.cpp @@ -155,7 +155,7 @@ generateTestValuesForParametersTest() GetAggregatePriceParamTestCaseBundle{ .testName = "invalid_base_asset", .testJson = R"JSON({ - "quote_asset" : "USD", + "quote_asset": "USD", "base_asset": "asdf", "oracles": [ @@ -171,7 +171,7 @@ generateTestValuesForParametersTest() GetAggregatePriceParamTestCaseBundle{ .testName = "empty_base_asset", .testJson = R"JSON({ - "quote_asset" : "USD", + "quote_asset": "USD", "base_asset": "", "oracles": [ @@ -187,7 +187,7 @@ generateTestValuesForParametersTest() GetAggregatePriceParamTestCaseBundle{ .testName = "invalid_base_asset2", .testJson = R"JSON({ - "quote_asset" : "USD", + "quote_asset": "USD", "base_asset": "+aa", "oracles": [ @@ -218,7 +218,7 @@ generateTestValuesForParametersTest() GetAggregatePriceParamTestCaseBundle{ .testName = "invalid_quote_asset", .testJson = R"JSON({ - "quote_asset" : "asdf", + "quote_asset": "asdf", "base_asset": "USD", "oracles": [ @@ -234,7 +234,7 @@ generateTestValuesForParametersTest() GetAggregatePriceParamTestCaseBundle{ .testName = "empty_quote_asset", .testJson = R"JSON({ - "quote_asset" : "", + "quote_asset": "", "base_asset": "USD", "oracles": [ @@ -250,7 +250,7 @@ generateTestValuesForParametersTest() GetAggregatePriceParamTestCaseBundle{ .testName = "invalid_quote_asset2", .testJson = R"JSON({ - "quote_asset" : "+aa", + "quote_asset": "+aa", "base_asset": "USD", "oracles": [ diff --git a/tests/unit/rpc/handlers/LedgerDataTests.cpp b/tests/unit/rpc/handlers/LedgerDataTests.cpp index d02ce7d4a..8b6b0a08b 100644 --- a/tests/unit/rpc/handlers/LedgerDataTests.cpp +++ b/tests/unit/rpc/handlers/LedgerDataTests.cpp @@ -271,18 +271,18 @@ TEST_F(RPCLedgerDataHandlerTest, MarkerNotExist) TEST_F(RPCLedgerDataHandlerTest, NoMarker) { static auto const kLEDGER_EXPECTED = R"JSON({ - "account_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "close_flags":0, - "close_time":0, - "close_time_resolution":0, - "close_time_iso":"2000-01-01T00:00:00Z", - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":"30", - "parent_close_time":0, - "parent_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "total_coins":"0", - "transaction_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "closed":true + "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "close_flags": 0, + "close_time": 0, + "close_time_resolution": 0, + "close_time_iso": "2000-01-01T00:00:00Z", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": "30", + "parent_close_time": 0, + "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "total_coins": "0", + "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "closed": true })JSON"; EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(createLedgerHeader(kLEDGER_HASH, kRANGE_MAX))); @@ -310,7 +310,7 @@ TEST_F(RPCLedgerDataHandlerTest, NoMarker) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerDataHandler{backend_}}; - auto const req = json::parse(R"JSON({"limit":10})JSON"); + auto const req = json::parse(R"JSON({"limit": 10})JSON"); auto output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_TRUE(output.result->as_object().contains("ledger")); @@ -368,7 +368,7 @@ TEST_F(RPCLedgerDataHandlerTest, Version2) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerDataHandler{backend_}}; - auto const req = json::parse(R"JSON({"limit":10})JSON"); + auto const req = json::parse(R"JSON({"limit": 10})JSON"); auto output = handler.process(req, Context{.yield = yield, .apiVersion = 2}); ASSERT_TRUE(output); EXPECT_TRUE(output.result->as_object().contains("ledger")); @@ -383,18 +383,18 @@ TEST_F(RPCLedgerDataHandlerTest, Version2) TEST_F(RPCLedgerDataHandlerTest, TypeFilter) { static auto const kLEDGER_EXPECTED = R"JSON({ - "account_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "close_flags":0, - "close_time":0, - "close_time_resolution":0, - "close_time_iso":"2000-01-01T00:00:00Z", - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":"30", - "parent_close_time":0, - "parent_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "total_coins":"0", - "transaction_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "closed":true + "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "close_flags": 0, + "close_time": 0, + "close_time_resolution": 0, + "close_time_iso": "2000-01-01T00:00:00Z", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": "30", + "parent_close_time": 0, + "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "total_coins": "0", + "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "closed": true })JSON"; EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); @@ -425,8 +425,8 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilter) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerDataHandler{backend_}}; auto const req = json::parse(R"JSON({ - "limit":10, - "type":"state" + "limit": 10, + "type": "state" })JSON"); auto output = handler.process(req, Context{yield}); @@ -447,18 +447,18 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilter) TEST_F(RPCLedgerDataHandlerTest, TypeFilterAMM) { static auto const kLEDGER_EXPECTED = R"JSON({ - "account_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "close_flags":0, - "close_time":0, - "close_time_resolution":0, - "close_time_iso":"2000-01-01T00:00:00Z", - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":"30", - "parent_close_time":0, - "parent_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "total_coins":"0", - "transaction_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "closed":true + "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "close_flags": 0, + "close_time": 0, + "close_time_resolution": 0, + "close_time_iso": "2000-01-01T00:00:00Z", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": "30", + "parent_close_time": 0, + "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "total_coins": "0", + "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "closed": true })JSON"; EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); @@ -486,8 +486,8 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterAMM) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerDataHandler{backend_}}; auto const req = json::parse(R"JSON({ - "limit":6, - "type":"amm" + "limit": 6, + "type": "amm" })JSON"); auto output = handler.process(req, Context{yield}); @@ -508,18 +508,18 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterAMM) TEST_F(RPCLedgerDataHandlerTest, OutOfOrder) { static auto const kLEDGER_EXPECTED = R"JSON({ - "account_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "close_flags":0, - "close_time":0, - "close_time_resolution":0, - "close_time_iso":"2000-01-01T00:00:00Z", - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":"30", - "parent_close_time":0, - "parent_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "total_coins":"0", - "transaction_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "closed":true + "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "close_flags": 0, + "close_time": 0, + "close_time_resolution": 0, + "close_time_iso": "2000-01-01T00:00:00Z", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": "30", + "parent_close_time": 0, + "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "total_coins": "0", + "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "closed": true })JSON"; EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); @@ -542,7 +542,7 @@ TEST_F(RPCLedgerDataHandlerTest, OutOfOrder) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerDataHandler{backend_}}; - auto const req = json::parse(R"JSON({"limit":10, "out_of_order":true})JSON"); + auto const req = json::parse(R"JSON({"limit": 10, "out_of_order": true})JSON"); auto output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_TRUE(output.result->as_object().contains("ledger")); @@ -590,7 +590,7 @@ TEST_F(RPCLedgerDataHandlerTest, Marker) auto const handler = AnyHandler{LedgerDataHandler{backend_}}; auto const req = json::parse(fmt::format( R"JSON({{ - "limit":10, + "limit": 10, "marker": "{}" }})JSON", kINDEX1 @@ -633,7 +633,7 @@ TEST_F(RPCLedgerDataHandlerTest, DiffMarker) auto const handler = AnyHandler{LedgerDataHandler{backend_}}; auto const req = json::parse(fmt::format( R"JSON({{ - "limit":10, + "limit": 10, "marker": {}, "out_of_order": true }})JSON", @@ -674,7 +674,7 @@ TEST_F(RPCLedgerDataHandlerTest, Binary) auto const handler = AnyHandler{LedgerDataHandler{backend_}}; auto const req = json::parse( R"JSON({ - "limit":10, + "limit": 10, "binary": true })JSON" ); @@ -714,7 +714,7 @@ TEST_F(RPCLedgerDataHandlerTest, BinaryLimitMoreThanMax) auto const handler = AnyHandler{LedgerDataHandler{backend_}}; auto const req = json::parse(fmt::format( R"JSON({{ - "limit":{}, + "limit": {}, "binary": true }})JSON", LedgerDataHandler::kLIMIT_BINARY + 1 @@ -755,7 +755,7 @@ TEST_F(RPCLedgerDataHandlerTest, JsonLimitMoreThanMax) auto const handler = AnyHandler{LedgerDataHandler{backend_}}; auto const req = json::parse(fmt::format( R"JSON({{ - "limit":{}, + "limit": {}, "binary": false }})JSON", LedgerDataHandler::kLIMIT_JSON + 1 @@ -789,8 +789,8 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPTIssuance) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerDataHandler{backend_}}; auto const req = json::parse(R"JSON({ - "limit":1, - "type":"mpt_issuance" + "limit": 1, + "type": "mpt_issuance" })JSON"); auto output = handler.process(req, Context{yield}); @@ -831,8 +831,8 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPToken) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerDataHandler{backend_}}; auto const req = json::parse(R"JSON({ - "limit":1, - "type":"mptoken" + "limit": 1, + "type": "mptoken" })JSON"); auto output = handler.process(req, Context{yield}); diff --git a/tests/unit/rpc/handlers/LedgerEntryTests.cpp b/tests/unit/rpc/handlers/LedgerEntryTests.cpp index 3067b4760..90e747d0c 100644 --- a/tests/unit/rpc/handlers/LedgerEntryTests.cpp +++ b/tests/unit/rpc/handlers/LedgerEntryTests.cpp @@ -44,8 +44,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -687,7 +689,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}"] + "accounts": ["{}"] }} }})JSON", kACCOUNT @@ -701,7 +703,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}","{}"], + "accounts": ["{}", "{}"], "currency": "USD" }} }})JSON", @@ -717,7 +719,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}",123], + "accounts": ["{}",123], "currency": "USD" }} }})JSON", @@ -732,7 +734,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}","123"], + "accounts": ["{}", "123"], "currency": "USD" }} }})JSON", @@ -747,7 +749,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}","{}"], + "accounts": ["{}", "{}"], "currency": "XXXX" }} }})JSON", @@ -763,7 +765,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}","{}"], + "accounts": ["{}", "{}"], "currency": 123 }} }})JSON", @@ -912,11 +914,11 @@ generateTestValuesForParametersTest() R"JSON({{ "amm": {{ - "asset":{{}}, + "asset": {{}}, "asset2": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -932,11 +934,11 @@ generateTestValuesForParametersTest() R"JSON({{ "amm": {{ - "asset2":{{}}, + "asset2": {{}}, "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -954,8 +956,8 @@ generateTestValuesForParametersTest() {{ "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -973,8 +975,8 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -993,8 +995,8 @@ generateTestValuesForParametersTest() "asset": "invalid", "asset2": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -1013,8 +1015,8 @@ generateTestValuesForParametersTest() "asset2": "invalid", "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -1032,12 +1034,12 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"XRP" + "currency": "XRP" }}, "asset": {{ - "currency" : "USD2", - "issuer" : "{}" + "currency": "USD2", + "issuer": "{}" }} }} }})JSON", @@ -1055,12 +1057,12 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"XRP" + "currency": "XRP" }}, "asset": {{ - "currency" : "USD", - "issuer" : "aa{}" + "currency": "USD", + "issuer": "aa{}" }} }} }})JSON", @@ -1078,12 +1080,12 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"JPY" + "currency": "JPY" }}, "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -1101,13 +1103,13 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"XRP", - "issuer":"{}" + "currency": "XRP", + "issuer": "{}" }}, "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -1126,11 +1128,11 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"XRP" + "currency": "XRP" }}, "asset": {{ - "issuer" : "{}" + "issuer": "{}" }} }} }})JSON", @@ -2193,6 +2195,76 @@ generateTestValuesForParametersTest() .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, + ParamTestCaseBundle{ + .testName = "InvalidVault_Type", + .testJson = + R"JSON({ + "vault": 0 + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request.", + }, + ParamTestCaseBundle{ + .testName = "InvalidVault_NotHex", + .testJson = + R"JSON({ + "vault": "invalid_hex" + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request.", + }, + ParamTestCaseBundle{ + .testName = "MissingOwner", + .testJson = + R"JSON({ + "vault": { "seq": 1 } + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request.", + }, + + ParamTestCaseBundle{ + .testName = "MissingSeq", + .testJson = + R"JSON({ + "vault": { "owner": "abcd" } + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request.", + }, + ParamTestCaseBundle{ + .testName = "SeqNotInteger", + .testJson = + R"JSON({ + "vault": { + "owner": "abcd", + "seq": "notAnInteger" + }})JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request.", + }, + ParamTestCaseBundle{ + .testName = "InvalidOwnerFormat", + .testJson = + R"JSON({ + "vault": { + "owner": "abcd", + "seq": 10 + }})JSON", + .expectedError = "malformedOwner", + .expectedErrorMessage = "Malformed owner.", + }, + ParamTestCaseBundle{ + .testName = "BothOwnerAndSeqInvalid", + .testJson = + R"JSON({ + "vault": { + "owner": "abcd", + "seq": -200 + }})JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request.", + }, ParamTestCaseBundle{ .testName = "Delegate_InvalidType", .testJson = R"JSON({"delegate": 123})JSON", @@ -2694,7 +2766,7 @@ generateTestValuesForNormalPathTest() R"JSON({{ "binary": true, "ripple_state": {{ - "accounts": ["{}","{}"], + "accounts": ["{}", "{}"], "currency": "USD" }} }})JSON", @@ -2782,11 +2854,11 @@ generateTestValuesForNormalPathTest() "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ - "currency" : "XRP" + "currency": "XRP" }}, "IssuingChainIssue": {{ - "currency" : "JPY", - "issuer" : "{}" + "currency": "JPY", + "issuer": "{}" }} }} }})JSON", @@ -2817,11 +2889,11 @@ generateTestValuesForNormalPathTest() "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ - "currency" : "XRP" + "currency": "XRP" }}, "IssuingChainIssue": {{ - "currency" : "JPY", - "issuer" : "{}" + "currency": "JPY", + "issuer": "{}" }} }} }})JSON", @@ -2851,11 +2923,11 @@ generateTestValuesForNormalPathTest() "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ - "currency" : "XRP" + "currency": "XRP" }}, "IssuingChainIssue": {{ - "currency" : "JPY", - "issuer" : "{}" + "currency": "JPY", + "issuer": "{}" }}, "xchain_owned_claim_id": 10 }} @@ -2885,11 +2957,11 @@ generateTestValuesForNormalPathTest() "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ - "currency" : "XRP" + "currency": "XRP" }}, "IssuingChainIssue": {{ - "currency" : "JPY", - "issuer" : "{}" + "currency": "JPY", + "issuer": "{}" }}, "xchain_owned_create_account_claim_id": 10 }} @@ -3058,6 +3130,55 @@ generateTestValuesForNormalPathTest() .key, .mockedEntity = createPermissionedDomainObject(kACCOUNT, kINDEX1, kRANGE_MAX, 0, ripple::uint256{0}, 0) }, + NormalPathTestBundle{ + .testName = "CreateVaultObjectByHexString", + .testJson = fmt::format( + R"JSON({{ + "binary": true, + "vault": "{}" + }})JSON", + kINDEX1 + ), + .expectedIndex = ripple::uint256(kINDEX1), + .mockedEntity = createVault( + kACCOUNT, + kACCOUNT, + kRANGE_MAX, + "XRP", + ripple::toBase58(ripple::xrpAccount()), + ripple::uint192(0), + 0, + ripple::uint256{0}, + 0 + ) + }, + NormalPathTestBundle{ + .testName = "CreateVaultObjectByAccount", + .testJson = fmt::format( + R"JSON({{ + "binary": true, + "vault": {{ + "owner": "{}", + "seq": {} + }} + }})JSON", + kACCOUNT, + kRANGE_MAX + ), + .expectedIndex = + ripple::keylet::vault(ripple::parseBase58(kACCOUNT).value(), kRANGE_MAX).key, + .mockedEntity = createVault( + kACCOUNT, + kACCOUNT, + kRANGE_MAX, + "XRP", + ripple::toBase58(ripple::xrpAccount()), + ripple::uint192(0), + 0, + ripple::uint256{0}, + 0 + ) + }, NormalPathTestBundle{ .testName = "DelegateViaStringIndex", .testJson = fmt::format( @@ -3131,23 +3252,23 @@ TEST_P(RPCLedgerEntryNormalPathTest, NormalPath) TEST_F(RPCLedgerEntryTest, BinaryFalse) { static constexpr auto kOUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "index":"05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", - "node":{ - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Amount":"100", - "Balance":"200", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Flags":0, - "LedgerEntryType":"PayChannel", - "OwnerNode":"0", - "PreviousTxnID":"05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", - "PreviousTxnLgrSeq":400, - "PublicKey":"020000000000000000000000000000000000000000000000000000000000000000", - "SettleDelay":300, - "index":"05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD" + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", + "node": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Amount": "100", + "Balance": "200", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Flags": 0, + "LedgerEntryType": "PayChannel", + "OwnerNode": "0", + "PreviousTxnID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", + "PreviousTxnLgrSeq": 400, + "PublicKey": "020000000000000000000000000000000000000000000000000000000000000000", + "SettleDelay": 300, + "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD" } })JSON"; @@ -3174,6 +3295,57 @@ TEST_F(RPCLedgerEntryTest, BinaryFalse) }); } +TEST_F(RPCLedgerEntryTest, Vault_BinaryFalse) +{ + // return valid ledgerHeader + auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, kRANGE_MAX); + EXPECT_CALL(*backend_, fetchLedgerBySequence(kRANGE_MAX, _)).WillRepeatedly(Return(ledgerHeader)); + + boost::json::object entry; + + auto const vault = createVault( + kACCOUNT, + kACCOUNT, + kRANGE_MAX, + "XRP", + ripple::toBase58(ripple::xrpAccount()), + ripple::uint192(0), + 0, + ripple::uint256{1}, + 0 + ); + + auto const vaultKey = + ripple::keylet::vault(ripple::parseBase58(kACCOUNT).value(), kRANGE_MAX).key; + + ripple::STLedgerEntry const sle{ + ripple::SerialIter{vault.getSerializer().peekData().data(), vault.getSerializer().peekData().size()}, vaultKey + }; + + EXPECT_CALL(*backend_, doFetchLedgerObject(vaultKey, testing::_, testing::_)) + .WillOnce(Return(vault.getSerializer().peekData())); + + runSpawn([&, this](auto yield) { + auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; + auto const req = json::parse(fmt::format( + R"JSON({{ + "binary": false, + "vault": {{ + "owner": "{}", + "seq": {} + }} + }})JSON", + kACCOUNT, + kRANGE_MAX + )); + auto const output = handler.process(req, Context{yield}); + ASSERT_TRUE(output); + + EXPECT_EQ(output.result->at("node").at("Owner").as_string(), kACCOUNT); + EXPECT_EQ(output.result->at("node").at("Sequence").as_int64(), kRANGE_MAX); + }); +} + TEST_F(RPCLedgerEntryTest, UnexpectedLedgerType) { // return valid ledgerHeader @@ -3622,23 +3794,23 @@ TEST_F(RPCLedgerEntryTest, ObjectSeqNotExist) TEST_F(RPCLedgerEntryTest, SyntheticMPTIssuanceID) { static constexpr auto kOUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "index":"FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336", - "node":{ - "Flags":0, - "Issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "LedgerEntryType":"MPTokenIssuance", - "MPTokenMetadata":"6D65746164617461", - "MaximumAmount":"0", - "OutstandingAmount":"0", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":2, - "index":"FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336", - "mpt_issuance_id":"000000024B4E9C06F24296074F7BC48F92A97916C6DC5EA9" + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "index": "FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336", + "node": { + "Flags": 0, + "Issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "LedgerEntryType": "MPTokenIssuance", + "MPTokenMetadata": "6D65746164617461", + "MaximumAmount": "0", + "OutstandingAmount": "0", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 2, + "index": "FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336", + "mpt_issuance_id": "000000024B4E9C06F24296074F7BC48F92A97916C6DC5EA9" } })JSON"; diff --git a/tests/unit/rpc/handlers/LedgerTests.cpp b/tests/unit/rpc/handlers/LedgerTests.cpp index ff554a6e6..d203fd93e 100644 --- a/tests/unit/rpc/handlers/LedgerTests.cpp +++ b/tests/unit/rpc/handlers/LedgerTests.cpp @@ -264,22 +264,22 @@ TEST_F(RPCLedgerHandlerTest, Default) { static constexpr auto kEXPECTED_OUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "ledger":{ - "account_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "close_flags":0, - "close_time":0, - "close_time_resolution":0, - "closed":true, - "close_time_iso":"2000-01-01T00:00:00Z", - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":"30", - "parent_close_time":0, - "parent_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "total_coins":"0", - "transaction_hash":"0000000000000000000000000000000000000000000000000000000000000000" + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "ledger": { + "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "close_flags": 0, + "close_time": 0, + "close_time_resolution": 0, + "closed": true, + "close_time_iso": "2000-01-01T00:00:00Z", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": "30", + "parent_close_time": 0, + "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "total_coins": "0", + "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000" } })JSON"; @@ -352,12 +352,12 @@ TEST_F(RPCLedgerHandlerTest, BinaryTrue) { static constexpr auto kEXPECTED_OUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "ledger":{ - "ledger_data":"0000001E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "closed":true + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "ledger": { + "ledger_data": "0000001E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "closed": true } })JSON"; @@ -382,20 +382,20 @@ TEST_F(RPCLedgerHandlerTest, TransactionsExpandBinary) { static constexpr auto kEXPECTED_OUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "ledger":{ - "ledger_data":"0000001E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "closed":true, - "transactions":[ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "ledger": { + "ledger_data": "0000001E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "closed": true, + "transactions": [ { - "tx_blob":"120000240000001E61400000000000006468400000000000000373047465737481144B4E9C06F24296074F7BC48F92A97916C6DC5EA98314D31252CF902EF8DD8451243869B38667CBD89DF3", - "meta":"201C00000000F8E5110061E762400000000000006E81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9E1E1E5110061E762400000000000001E8114D31252CF902EF8DD8451243869B38667CBD89DF3E1E1F1031000" + "tx_blob": "120000240000001E61400000000000006468400000000000000373047465737481144B4E9C06F24296074F7BC48F92A97916C6DC5EA98314D31252CF902EF8DD8451243869B38667CBD89DF3", + "meta": "201C00000000F8E5110061E762400000000000006E81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9E1E1E5110061E762400000000000001E8114D31252CF902EF8DD8451243869B38667CBD89DF3E1E1F1031000" }, { - "tx_blob":"120000240000001E61400000000000006468400000000000000373047465737481144B4E9C06F24296074F7BC48F92A97916C6DC5EA98314D31252CF902EF8DD8451243869B38667CBD89DF3", - "meta":"201C00000000F8E5110061E762400000000000006E81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9E1E1E5110061E762400000000000001E8114D31252CF902EF8DD8451243869B38667CBD89DF3E1E1F1031000" + "tx_blob": "120000240000001E61400000000000006468400000000000000373047465737481144B4E9C06F24296074F7BC48F92A97916C6DC5EA98314D31252CF902EF8DD8451243869B38667CBD89DF3", + "meta": "201C00000000F8E5110061E762400000000000006E81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9E1E1E5110061E762400000000000001E8114D31252CF902EF8DD8451243869B38667CBD89DF3E1E1F1031000" } ] } @@ -435,7 +435,7 @@ TEST_F(RPCLedgerHandlerTest, TransactionsExpandBinaryV2) "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", "ledger_index": 30, "validated": true, - "ledger":{ + "ledger": { "ledger_data": "0000001E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "closed": true, "transactions": [ @@ -482,57 +482,57 @@ TEST_F(RPCLedgerHandlerTest, TransactionsExpandNotBinary) { static constexpr auto kEXPECTED_OUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "ledger":{ - "account_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "close_flags":0, - "close_time":0, - "close_time_resolution":0, - "closed":true, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":"30", - "parent_close_time":0, - "close_time_iso":"2000-01-01T00:00:00Z", - "parent_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "total_coins":"0", - "transaction_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "transactions":[ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "ledger": { + "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "close_flags": 0, + "close_time": 0, + "close_time_resolution": 0, + "closed": true, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": "30", + "parent_close_time": 0, + "close_time_iso": "2000-01-01T00:00:00Z", + "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "total_coins": "0", + "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "transactions": [ { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Amount":"100", - "DeliverMax":"100", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"3", - "Sequence":30, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "hash":"70436A9332F7CD928FAEC1A41269A677739D8B11F108CE23AE23CBF0C9113F8C", - "metaData":{ - "AffectedNodes":[ + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Amount": "100", + "DeliverMax": "100", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "3", + "Sequence": 30, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "hash": "70436A9332F7CD928FAEC1A41269A677739D8B11F108CE23AE23CBF0C9113F8C", + "metaData": { + "AffectedNodes": [ { - "ModifiedNode":{ - "FinalFields":{ - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Balance":"110" + "ModifiedNode": { + "FinalFields": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Balance": "110" }, - "LedgerEntryType":"AccountRoot" + "LedgerEntryType": "AccountRoot" } }, { - "ModifiedNode":{ - "FinalFields":{ - "Account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Balance":"30" + "ModifiedNode": { + "FinalFields": { + "Account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Balance": "30" }, - "LedgerEntryType":"AccountRoot" + "LedgerEntryType": "AccountRoot" } } ], - "TransactionIndex":0, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 0, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" } } ] @@ -575,7 +575,7 @@ TEST_F(RPCLedgerHandlerTest, TransactionsExpandNotBinaryV2) "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", "ledger_index": 30, "validated": true, - "ledger":{ + "ledger": { "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", "close_flags": 0, "close_time": 0, @@ -588,7 +588,7 @@ TEST_F(RPCLedgerHandlerTest, TransactionsExpandNotBinaryV2) "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", "total_coins": "0", "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000", - "transactions":[ + "transactions": [ { "validated": true, "close_time_iso": "2000-01-01T00:00:00Z", @@ -605,8 +605,8 @@ TEST_F(RPCLedgerHandlerTest, TransactionsExpandNotBinaryV2) "SigningPubKey": "74657374", "TransactionType": "Payment" }, - "meta":{ - "AffectedNodes":[ + "meta": { + "AffectedNodes": [ { "ModifiedNode": { @@ -733,7 +733,7 @@ TEST_F(RPCLedgerHandlerTest, TransactionsNotExpand) ASSERT_TRUE(output); EXPECT_EQ( output.result->as_object().at("ledger").at("transactions"), - json::parse(fmt::format(R"JSON(["{}","{}"])JSON", kINDEX1, kINDEX2)) + json::parse(fmt::format(R"JSON(["{}", "{}"])JSON", kINDEX1, kINDEX2)) ); }); } @@ -743,22 +743,22 @@ TEST_F(RPCLedgerHandlerTest, DiffNotBinary) static constexpr auto kEXPECTED_OUT = R"JSON([ { - "object_id":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B1", - "object":"" + "object_id": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B1", + "object": "" }, { - "object_id":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "object":{ - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Balance":"10", - "Flags":4194304, - "LedgerEntryType":"AccountRoot", - "OwnerCount":2, - "PreviousTxnID":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "PreviousTxnLgrSeq":3, - "Sequence":1, - "TransferRate":0, - "index":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" + "object_id": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "object": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Balance": "10", + "Flags": 4194304, + "LedgerEntryType": "AccountRoot", + "OwnerCount": 2, + "PreviousTxnID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "PreviousTxnLgrSeq": 3, + "Sequence": 1, + "TransferRate": 0, + "index": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" } } ])JSON"; @@ -798,12 +798,12 @@ TEST_F(RPCLedgerHandlerTest, DiffBinary) static constexpr auto kEXPECTED_OUT = R"JSON([ { - "object_id":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B1", - "object":"" + "object_id": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B1", + "object": "" }, { - "object_id":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "object":"1100612200400000240000000125000000032B000000002D00000002551B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC62400000000000000A81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9" + "object_id": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "object": "1100612200400000240000000125000000032B000000002D00000002551B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC62400000000000000A81144B4E9C06F24296074F7BC48F92A97916C6DC5EA9" } ])JSON"; @@ -842,57 +842,57 @@ TEST_F(RPCLedgerHandlerTest, OwnerFundsEmpty) { static constexpr auto kEXPECTED_OUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "validated":true, - "ledger":{ - "account_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "close_flags":0, - "close_time":0, - "close_time_resolution":0, - "closed":true, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":"30", - "parent_close_time":0, - "close_time_iso":"2000-01-01T00:00:00Z", - "parent_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "total_coins":"0", - "transaction_hash":"0000000000000000000000000000000000000000000000000000000000000000", - "transactions":[ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "ledger": { + "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "close_flags": 0, + "close_time": 0, + "close_time_resolution": 0, + "closed": true, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": "30", + "parent_close_time": 0, + "close_time_iso": "2000-01-01T00:00:00Z", + "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "total_coins": "0", + "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "transactions": [ { - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Amount":"100", - "DeliverMax":"100", - "Destination":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Fee":"3", - "Sequence":30, - "SigningPubKey":"74657374", - "TransactionType":"Payment", - "hash":"70436A9332F7CD928FAEC1A41269A677739D8B11F108CE23AE23CBF0C9113F8C", - "metaData":{ - "AffectedNodes":[ + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Amount": "100", + "DeliverMax": "100", + "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Fee": "3", + "Sequence": 30, + "SigningPubKey": "74657374", + "TransactionType": "Payment", + "hash": "70436A9332F7CD928FAEC1A41269A677739D8B11F108CE23AE23CBF0C9113F8C", + "metaData": { + "AffectedNodes": [ { - "ModifiedNode":{ - "FinalFields":{ - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Balance":"110" + "ModifiedNode": { + "FinalFields": { + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Balance": "110" }, - "LedgerEntryType":"AccountRoot" + "LedgerEntryType": "AccountRoot" } }, { - "ModifiedNode":{ - "FinalFields":{ - "Account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "Balance":"30" + "ModifiedNode": { + "FinalFields": { + "Account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Balance": "30" }, - "LedgerEntryType":"AccountRoot" + "LedgerEntryType": "AccountRoot" } } ], - "TransactionIndex":0, - "TransactionResult":"tesSUCCESS", - "delivered_amount":"unavailable" + "TransactionIndex": 0, + "TransactionResult": "tesSUCCESS", + "delivered_amount": "unavailable" } } ] diff --git a/tests/unit/rpc/handlers/MPTHoldersTests.cpp b/tests/unit/rpc/handlers/MPTHoldersTests.cpp index 2f2421c7f..690465784 100644 --- a/tests/unit/rpc/handlers/MPTHoldersTests.cpp +++ b/tests/unit/rpc/handlers/MPTHoldersTests.cpp @@ -376,7 +376,7 @@ TEST_F(RPCMPTHoldersHandlerTest, DefaultParameters) auto const currentOutput = fmt::format( R"JSON({{ "mpt_issuance_id": "{}", - "limit":50, + "limit": 50, "ledger_index": 30, "mptokens": [{}], "validated": true @@ -420,7 +420,7 @@ TEST_F(RPCMPTHoldersHandlerTest, CustomAmounts) auto const currentOutput = fmt::format( R"JSON({{ "mpt_issuance_id": "{}", - "limit":50, + "limit": 50, "ledger_index": 30, "mptokens": [{{ "account": "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN", @@ -467,7 +467,7 @@ TEST_F(RPCMPTHoldersHandlerTest, SpecificLedgerIndex) auto const currentOutput = fmt::format( R"JSON({{ "mpt_issuance_id": "{}", - "limit":50, + "limit": 50, "ledger_index": {}, "mptokens": [{}], "validated": true @@ -516,7 +516,7 @@ TEST_F(RPCMPTHoldersHandlerTest, MarkerParameter) auto const currentOutput = fmt::format( R"JSON({{ "mpt_issuance_id": "{}", - "limit":50, + "limit": 50, "ledger_index": 30, "mptokens": [{}], "validated": true, @@ -563,7 +563,7 @@ TEST_F(RPCMPTHoldersHandlerTest, MultipleMPTs) auto const currentOutput = fmt::format( R"JSON({{ "mpt_issuance_id": "{}", - "limit":50, + "limit": 50, "ledger_index": 30, "mptokens": [{}, {}], "validated": true @@ -607,7 +607,7 @@ TEST_F(RPCMPTHoldersHandlerTest, LimitMoreThanMAx) auto const currentOutput = fmt::format( R"JSON({{ "mpt_issuance_id": "{}", - "limit":100, + "limit": 100, "ledger_index": 30, "mptokens": [{}], "validated": true diff --git a/tests/unit/rpc/handlers/NFTHistoryTests.cpp b/tests/unit/rpc/handlers/NFTHistoryTests.cpp index ce921a53f..b8d442480 100644 --- a/tests/unit/rpc/handlers/NFTHistoryTests.cpp +++ b/tests/unit/rpc/handlers/NFTHistoryTests.cpp @@ -85,84 +85,84 @@ generateTestValuesForParametersTest() NFTHistoryParamTestCaseBundle{ .testName = "BinaryNotBool", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "binary": 1})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "binary": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, NFTHistoryParamTestCaseBundle{ .testName = "ForwardNotBool", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "forward": 1})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "forward": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, NFTHistoryParamTestCaseBundle{ .testName = "ledger_index_minNotInt", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index_min": "x"})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index_min": "x"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, NFTHistoryParamTestCaseBundle{ .testName = "ledger_index_maxNotInt", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index_max": "x"})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index_max": "x"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, NFTHistoryParamTestCaseBundle{ .testName = "ledger_indexInvalid", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index": "x"})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index": "x"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledgerIndexMalformed" }, NFTHistoryParamTestCaseBundle{ .testName = "ledger_hashInvalid", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_hash": "x"})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_hash": "x"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashMalformed" }, NFTHistoryParamTestCaseBundle{ .testName = "ledger_hashNotString", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_hash": 123})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_hash": 123})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashNotString" }, NFTHistoryParamTestCaseBundle{ .testName = "limitNotInt", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "limit": "123"})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "limit": "123"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, NFTHistoryParamTestCaseBundle{ .testName = "limitNegative", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "limit": -1})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "limit": -1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, NFTHistoryParamTestCaseBundle{ .testName = "limitZero", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "limit": 0})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "limit": 0})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, NFTHistoryParamTestCaseBundle{ .testName = "MarkerNotObject", .testJson = - R"JSON({"nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "marker": 101})JSON", + R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "marker": 101})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "invalidMarker" }, NFTHistoryParamTestCaseBundle{ .testName = "MarkerMissingSeq", .testJson = R"JSON({ - "nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", + "nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "marker": {"ledger": 123} })JSON", .expectedError = "invalidParams", @@ -171,8 +171,8 @@ generateTestValuesForParametersTest() NFTHistoryParamTestCaseBundle{ .testName = "MarkerMissingLedger", .testJson = R"JSON({ - "nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", - "marker":{"seq": 123} + "nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", + "marker": {"seq": 123} })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Required field 'ledger' missing" @@ -180,7 +180,7 @@ generateTestValuesForParametersTest() NFTHistoryParamTestCaseBundle{ .testName = "MarkerLedgerNotInt", .testJson = R"JSON({ - "nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", + "nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "marker": { "seq": "string", @@ -193,7 +193,7 @@ generateTestValuesForParametersTest() NFTHistoryParamTestCaseBundle{ .testName = "MarkerSeqNotInt", .testJson = R"JSON({ - "nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", + "nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "marker": { "ledger": "string", @@ -206,7 +206,7 @@ generateTestValuesForParametersTest() NFTHistoryParamTestCaseBundle{ .testName = "LedgerIndexMinLessThanMinSeq", .testJson = R"JSON({ - "nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", + "nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index_min": 9 })JSON", .expectedError = "lgrIdxMalformed", @@ -215,7 +215,7 @@ generateTestValuesForParametersTest() NFTHistoryParamTestCaseBundle{ .testName = "LedgerIndexMaxLargeThanMaxSeq", .testJson = R"JSON({ - "nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", + "nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index_max": 31 })JSON", .expectedError = "lgrIdxMalformed", @@ -224,7 +224,7 @@ generateTestValuesForParametersTest() NFTHistoryParamTestCaseBundle{ .testName = "LedgerIndexMaxLessThanLedgerIndexMin", .testJson = R"JSON({ - "nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", + "nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index_max": 11, "ledger_index_min": 20 })JSON", @@ -234,7 +234,7 @@ generateTestValuesForParametersTest() NFTHistoryParamTestCaseBundle{ .testName = "LedgerIndexMaxMinAndLedgerIndex", .testJson = R"JSON({ - "nft_id":"00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", + "nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index_max": 20, "ledger_index_min": 11, "ledger_index": 10 @@ -310,9 +310,9 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardTrue) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": true @@ -326,7 +326,7 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardTrue) EXPECT_EQ(output.result->at("nft_id").as_string(), kNFT_ID); EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMIN_SEQ + 1); EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMAX_SEQ - 1); - EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger":12,"seq":34})JSON")); + EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger": 12, "seq": 34})JSON")); EXPECT_EQ(output.result->at("transactions").as_array().size(), 2); EXPECT_FALSE(output.result->as_object().contains("limit")); }); @@ -346,8 +346,8 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardFalseV1) "AffectedNodes": [ { - "ModifiedNode":{ - "FinalFields":{ + "ModifiedNode": { + "FinalFields": { "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Balance": "22" }, @@ -355,8 +355,8 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardFalseV1) } }, { - "ModifiedNode":{ - "FinalFields":{ + "ModifiedNode": { + "FinalFields": { "Account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "Balance": "23" }, @@ -390,8 +390,8 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardFalseV1) "AffectedNodes": [ { - "ModifiedNode":{ - "FinalFields":{ + "ModifiedNode": { + "FinalFields": { "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Balance": "22" }, @@ -399,8 +399,8 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardFalseV1) } }, { - "ModifiedNode":{ - "FinalFields":{ + "ModifiedNode": { + "FinalFields": { "Account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "Balance": "23" }, @@ -454,9 +454,9 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardFalseV1) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": false @@ -608,9 +608,9 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardFalseV2) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": false @@ -640,9 +640,9 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexNotSpecificForwardTrue) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": true @@ -656,7 +656,7 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexNotSpecificForwardTrue) EXPECT_EQ(output.result->at("nft_id").as_string(), kNFT_ID); EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMIN_SEQ); EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMAX_SEQ); - EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger":12,"seq":34})JSON")); + EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger": 12, "seq": 34})JSON")); EXPECT_EQ(output.result->at("transactions").as_array().size(), 2); EXPECT_FALSE(output.result->as_object().contains("limit")); }); @@ -681,9 +681,9 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexNotSpecificForwardFalse) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": false @@ -697,7 +697,7 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexNotSpecificForwardFalse) EXPECT_EQ(output.result->at("nft_id").as_string(), kNFT_ID); EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMIN_SEQ); EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMAX_SEQ); - EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger":12,"seq":34})JSON")); + EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger": 12, "seq": 34})JSON")); EXPECT_EQ(output.result->at("transactions").as_array().size(), 2); EXPECT_FALSE(output.result->as_object().contains("limit")); }); @@ -722,9 +722,9 @@ TEST_F(RPCNFTHistoryHandlerTest, BinaryTrueV1) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "binary": true @@ -738,7 +738,7 @@ TEST_F(RPCNFTHistoryHandlerTest, BinaryTrueV1) EXPECT_EQ(output.result->at("nft_id").as_string(), kNFT_ID); EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMIN_SEQ); EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMAX_SEQ); - EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger":12,"seq":34})JSON")); + EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger": 12, "seq": 34})JSON")); EXPECT_EQ(output.result->at("transactions").as_array().size(), 2); EXPECT_EQ( output.result->at("transactions").as_array()[0].as_object().at("meta").as_string(), @@ -776,9 +776,9 @@ TEST_F(RPCNFTHistoryHandlerTest, BinaryTrueV2) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "binary": true @@ -792,7 +792,7 @@ TEST_F(RPCNFTHistoryHandlerTest, BinaryTrueV2) EXPECT_EQ(output.result->at("nft_id").as_string(), kNFT_ID); EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMIN_SEQ); EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMAX_SEQ); - EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger":12,"seq":34})JSON")); + EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger": 12, "seq": 34})JSON")); EXPECT_EQ(output.result->at("transactions").as_array().size(), 2); EXPECT_EQ( output.result->at("transactions").as_array()[0].as_object().at("meta_blob").as_string(), @@ -827,14 +827,14 @@ TEST_F(RPCNFTHistoryHandlerTest, LimitAndMarker) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "limit": 2, "forward": false, - "marker": {{"ledger":10,"seq":11}} + "marker": {{"ledger": 10, "seq": 11}} }})JSON", kNFT_ID, -1, @@ -846,7 +846,7 @@ TEST_F(RPCNFTHistoryHandlerTest, LimitAndMarker) EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMIN_SEQ); EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMAX_SEQ); EXPECT_EQ(output.result->at("limit").as_uint64(), 2); - EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger":12,"seq":34})JSON")); + EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger": 12, "seq": 34})JSON")); EXPECT_EQ(output.result->at("transactions").as_array().size(), 2); }); } @@ -875,10 +875,10 @@ TEST_F(RPCNFTHistoryHandlerTest, SpecificLedgerIndex) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", - "ledger_index":{} + "nft_id": "{}", + "ledger_index": {} }})JSON", kNFT_ID, kMAX_SEQ - 1 @@ -901,10 +901,10 @@ TEST_F(RPCNFTHistoryHandlerTest, SpecificNonexistLedgerIntIndex) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", - "ledger_index":{} + "nft_id": "{}", + "ledger_index": {} }})JSON", kNFT_ID, kMAX_SEQ - 1 @@ -924,10 +924,10 @@ TEST_F(RPCNFTHistoryHandlerTest, SpecificNonexistLedgerStringIndex) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", - "ledger_index":"{}" + "nft_id": "{}", + "ledger_index": "{}" }})JSON", kNFT_ID, kMAX_SEQ - 1 @@ -964,10 +964,10 @@ TEST_F(RPCNFTHistoryHandlerTest, SpecificLedgerHash) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", - "ledger_hash":"{}" + "nft_id": "{}", + "ledger_hash": "{}" }})JSON", kNFT_ID, kLEDGER_HASH @@ -1002,9 +1002,9 @@ TEST_F(RPCNFTHistoryHandlerTest, TxLessThanMinSeq) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": false @@ -1043,9 +1043,9 @@ TEST_F(RPCNFTHistoryHandlerTest, TxLargerThanMaxSeq) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": false @@ -1061,7 +1061,7 @@ TEST_F(RPCNFTHistoryHandlerTest, TxLargerThanMaxSeq) EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMAX_SEQ - 2); EXPECT_EQ(output.result->at("transactions").as_array().size(), 1); EXPECT_FALSE(output.result->as_object().contains("limit")); - EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger":12,"seq":34})JSON")); + EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger": 12, "seq": 34})JSON")); }); } @@ -1084,9 +1084,9 @@ TEST_F(RPCNFTHistoryHandlerTest, LimitMoreThanMax) runSpawn([&, this](auto yield) { auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": false, @@ -1102,7 +1102,7 @@ TEST_F(RPCNFTHistoryHandlerTest, LimitMoreThanMax) EXPECT_EQ(output.result->at("nft_id").as_string(), kNFT_ID); EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMIN_SEQ + 1); EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMAX_SEQ - 1); - EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger":12,"seq":34})JSON")); + EXPECT_EQ(output.result->at("marker").as_object(), json::parse(R"JSON({"ledger": 12, "seq": 34})JSON")); EXPECT_EQ(output.result->at("transactions").as_array().size(), 2); EXPECT_EQ(output.result->as_object().at("limit").as_uint64(), NFTHistoryHandler::kLIMIT_MAX); }); diff --git a/tests/unit/rpc/handlers/NFTsByIssuerTest.cpp b/tests/unit/rpc/handlers/NFTsByIssuerTest.cpp index 042985988..4b4f6bff3 100644 --- a/tests/unit/rpc/handlers/NFTsByIssuerTest.cpp +++ b/tests/unit/rpc/handlers/NFTsByIssuerTest.cpp @@ -535,7 +535,7 @@ TEST_F(RPCNFTsByIssuerHandlerTest, MultipleNFTs) auto const currentOutput = fmt::format( R"JSON({{ "issuer": "{}", - "limit":50, + "limit": 50, "ledger_index": 30, "nfts": [{}, {}, {}], "validated": true diff --git a/tests/unit/rpc/handlers/NoRippleCheckTests.cpp b/tests/unit/rpc/handlers/NoRippleCheckTests.cpp index d2eb27e6d..cb7122de2 100644 --- a/tests/unit/rpc/handlers/NoRippleCheckTests.cpp +++ b/tests/unit/rpc/handlers/NoRippleCheckTests.cpp @@ -213,7 +213,7 @@ TEST_F(RPCNoRippleCheckTest, LedgerNotExistViaHash) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _)).WillByDefault(Return(std::nullopt)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "role": "gateway", @@ -240,7 +240,7 @@ TEST_F(RPCNoRippleCheckTest, LedgerNotExistViaIntIndex) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(kSEQ, _)).WillByDefault(Return(std::nullopt)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "role": "gateway", @@ -267,7 +267,7 @@ TEST_F(RPCNoRippleCheckTest, LedgerNotExistViaStringIndex) // return empty ledgerHeader ON_CALL(*backend_, fetchLedgerBySequence(kSEQ, _)).WillByDefault(Return(std::nullopt)); - auto static const kINPUT = json::parse(fmt::format( + static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", "role": "gateway", @@ -318,13 +318,13 @@ TEST_F(RPCNoRippleCheckTest, NormalPathRoleUserDefaultRippleSetTrustLineNoRipple static constexpr auto kSEQ = 30; static constexpr auto kEXPECTED_OUTPUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, "problems": [ "You appear to have set your default ripple flag even though you are not a gateway. This is not recommended unless you are experimenting" ], - "validated":true + "validated": true })JSON"; auto ledgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ); @@ -379,13 +379,13 @@ TEST_F(RPCNoRippleCheckTest, NormalPathRoleUserDefaultRippleUnsetTrustLineNoRipp static constexpr auto kSEQ = 30; static constexpr auto kEXPECTED_OUTPUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "problems":[ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "problems": [ "You should probably set the no ripple flag on your USD line to rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "You should probably set the no ripple flag on your USD line to rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun" ], - "validated":true + "validated": true })JSON"; auto ledgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ); @@ -434,14 +434,14 @@ TEST_F(RPCNoRippleCheckTest, NormalPathRoleGatewayDefaultRippleSetTrustLineNoRip static constexpr auto kSEQ = 30; static constexpr auto kEXPECTED_OUTPUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, "problems": [ "You should clear the no ripple flag on your USD line to rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "You should clear the no ripple flag on your USD line to rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun" ], - "validated":true + "validated": true })JSON"; auto ledgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ); @@ -496,13 +496,13 @@ TEST_F(RPCNoRippleCheckTest, NormalPathRoleGatewayDefaultRippleUnsetTrustLineNoR static constexpr auto kSEQ = 30; static constexpr auto kEXPECTED_OUTPUT = R"JSON({ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, "problems": [ "You should immediately set your default ripple flag" ], - "validated":true + "validated": true })JSON"; auto ledgerHeader = createLedgerHeader(kLEDGER_HASH, kSEQ); @@ -653,47 +653,47 @@ TEST_F(RPCNoRippleCheckTest, NormalPathTransactions) constexpr auto kTRANSACTION_SEQ = 123; auto const expectedOutput = fmt::format( R"JSON({{ - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index":30, - "problems":[ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "problems": [ "You should immediately set your default ripple flag", "You should clear the no ripple flag on your USD line to rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "You should clear the no ripple flag on your USD line to rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun" ], - "transactions":[ + "transactions": [ {{ - "Sequence":{}, - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Fee":1, - "TransactionType":"AccountSet", - "SetFlag":8 + "Sequence": {}, + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Fee": 1, + "TransactionType": "AccountSet", + "SetFlag": 8 }}, {{ - "Sequence":{}, - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Fee":1, - "TransactionType":"TrustSet", - "LimitAmount":{{ - "currency":"USD", - "issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "value":"10" + "Sequence": {}, + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Fee": 1, + "TransactionType": "TrustSet", + "LimitAmount": {{ + "currency": "USD", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "value": "10" }}, - "Flags":{} + "Flags": {} }}, {{ - "Sequence":{}, - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Fee":1, - "TransactionType":"TrustSet", - "LimitAmount":{{ - "currency":"USD", - "issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "value":"10" + "Sequence": {}, + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Fee": 1, + "TransactionType": "TrustSet", + "LimitAmount": {{ + "currency": "USD", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "value": "10" }}, - "Flags":{} + "Flags": {} }} ], - "validated":true + "validated": true }})JSON", kTRANSACTION_SEQ, kTRANSACTION_SEQ + 1, diff --git a/tests/unit/rpc/handlers/SubscribeTests.cpp b/tests/unit/rpc/handlers/SubscribeTests.cpp index 99fd36acc..79cfcdd2c 100644 --- a/tests/unit/rpc/handlers/SubscribeTests.cpp +++ b/tests/unit/rpc/handlers/SubscribeTests.cpp @@ -631,7 +631,7 @@ TEST_F(RPCSubscribeHandlerTest, StreamsWithoutLedger) // these streams don't return response auto const input = json::parse( R"JSON({ - "streams": ["transactions_proposed","transactions","validations","manifests","book_changes"] + "streams": ["transactions_proposed", "transactions", "validations", "manifests", "book_changes"] })JSON" ); runSpawn([&, this](auto yield) { @@ -654,13 +654,13 @@ TEST_F(RPCSubscribeHandlerTest, StreamsLedger) { static constexpr auto kEXPECTED_OUTPUT = R"JSON({ - "validated_ledgers":"10-30", - "ledger_index":30, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_time":0, - "fee_base":1, - "reserve_base":3, - "reserve_inc":2 + "validated_ledgers": "10-30", + "ledger_index": 30, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_time": 0, + "fee_base": 1, + "reserve_base": 3, + "reserve_inc": 2 })JSON"; auto const input = json::parse( @@ -686,7 +686,7 @@ TEST_F(RPCSubscribeHandlerTest, Accounts) { auto const input = json::parse(fmt::format( R"JSON({{ - "accounts": ["{}","{}","{}"] + "accounts": ["{}", "{}", "{}"] }})JSON", kACCOUNT, kACCOUNT2, @@ -709,7 +709,7 @@ TEST_F(RPCSubscribeHandlerTest, AccountsProposed) { auto const input = json::parse(fmt::format( R"JSON({{ - "accounts_proposed": ["{}","{}","{}"] + "accounts_proposed": ["{}", "{}", "{}"] }})JSON", kACCOUNT, kACCOUNT2, @@ -901,25 +901,25 @@ TEST_F(RPCSubscribeHandlerTest, BooksBothSnapshotSet) static auto const kEXPECTED_OFFER = fmt::format( R"JSON({{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerGets":"10", + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerGets": "10", "TakerPays": {{ - "currency":"USD", - "issuer":"{}", - "value":"20" + "currency": "USD", + "issuer": "{}", + "value": "20" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"193", - "quality":"2" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "193", + "quality": "2" }})JSON", kACCOUNT2, kPAYS20_USD_GETS10_XRP_BOOK_DIR, @@ -927,25 +927,25 @@ TEST_F(RPCSubscribeHandlerTest, BooksBothSnapshotSet) ); static auto const kEXPECTED_REVERSED_OFFER = fmt::format( R"JSON({{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, "TakerGets": {{ - "currency":"USD", - "issuer":"{}", - "value":"10" + "currency": "USD", + "issuer": "{}", + "value": "10" }}, - "TakerPays":"20", - "index":"1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", - "owner_funds":"10", - "quality":"2" + "TakerPays": "20", + "index": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", + "owner_funds": "10", + "quality": "2" }})JSON", kACCOUNT, kPAYS20_XRP_GETS10_USD_BOOK_DIR, @@ -1072,25 +1072,25 @@ TEST_F(RPCSubscribeHandlerTest, BooksBothUnsetSnapshotSet) static auto const kEXPECTED_OFFER = fmt::format( R"JSON({{ - "Account":"{}", - "BookDirectory":"{}", - "BookNode":"0", - "Flags":0, - "LedgerEntryType":"Offer", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, - "Sequence":0, - "TakerGets":"10", + "Account": "{}", + "BookDirectory": "{}", + "BookNode": "0", + "Flags": 0, + "LedgerEntryType": "Offer", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 0, + "TakerGets": "10", "TakerPays": {{ - "currency":"USD", - "issuer":"{}", - "value":"20" + "currency": "USD", + "issuer": "{}", + "value": "20" }}, - "index":"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", - "owner_funds":"193", - "quality":"2" + "index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321", + "owner_funds": "193", + "quality": "2" }})JSON", kACCOUNT2, kPAYS20_USD_GETS10_XRP_BOOK_DIR, diff --git a/tests/unit/rpc/handlers/TransactionEntryTests.cpp b/tests/unit/rpc/handlers/TransactionEntryTests.cpp index 13403a79f..2c59e02d9 100644 --- a/tests/unit/rpc/handlers/TransactionEntryTests.cpp +++ b/tests/unit/rpc/handlers/TransactionEntryTests.cpp @@ -72,7 +72,7 @@ TEST_F(RPCTransactionEntryHandlerTest, TxHashWrongFormat) { runSpawn([this](auto yield) { auto const handler = AnyHandler{TransactionEntryHandler{backend_}}; - auto const output = handler.process(json::parse(R"JSON({"tx_hash":"123"})JSON"), Context{yield}); + auto const output = handler.process(json::parse(R"JSON({"tx_hash": "123"})JSON"), Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "invalidParams"); diff --git a/tests/unit/rpc/handlers/TxTests.cpp b/tests/unit/rpc/handlers/TxTests.cpp index 60c90fb9c..09b4a7118 100644 --- a/tests/unit/rpc/handlers/TxTests.cpp +++ b/tests/unit/rpc/handlers/TxTests.cpp @@ -573,7 +573,7 @@ TEST_F(RPCTxTest, ReturnBinaryWithCTID) TEST_F(RPCTxTest, MintNFT) { // Note: `inLedger` is API v1 only. See DefaultOutput_* - auto static const kOUT = fmt::format( + static auto const kOUT = fmt::format( R"JSON({{ "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Fee": "50", @@ -846,20 +846,20 @@ TEST_F(RPCTxTest, CTIDNotMatch) TEST_F(RPCTxTest, ReturnCTIDForTxInput) { static constexpr auto kOUT = R"JSON({ - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Fee":"2", - "Sequence":100, - "SigningPubKey":"74657374", + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Fee": "2", + "Sequence": 100, + "SigningPubKey": "74657374", "TakerGets": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "value":"200" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "value": "200" }, - "ctid":"C000006400640002", - "TakerPays":"300", - "TransactionType":"OfferCreate", - "hash":"2E2FBAAFF767227FE4381C4BE9855986A6B9F96C62F6E443731AB36F7BBB8A08", + "ctid": "C000006400640002", + "TakerPays": "300", + "TransactionType": "OfferCreate", + "hash": "2E2FBAAFF767227FE4381C4BE9855986A6B9F96C62F6E443731AB36F7BBB8A08", "meta": { "AffectedNodes": @@ -867,26 +867,26 @@ TEST_F(RPCTxTest, ReturnCTIDForTxInput) { "CreatedNode": { - "LedgerEntryType":"Offer", + "LedgerEntryType": "Offer", "NewFields": { - "TakerGets":"200", + "TakerGets": "200", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"300" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "300" } } } } ], - "TransactionIndex":100, - "TransactionResult":"tesSUCCESS" + "TransactionIndex": 100, + "TransactionResult": "tesSUCCESS" }, - "date":123456, - "ledger_index":100, - "inLedger":100, + "date": 123456, + "ledger_index": 100, + "inLedger": 100, "validated": true })JSON"; @@ -920,19 +920,19 @@ TEST_F(RPCTxTest, ReturnCTIDForTxInput) TEST_F(RPCTxTest, NotReturnCTIDIfETLNotAvailable) { static constexpr auto kOUT = R"JSON({ - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Fee":"2", - "Sequence":100, - "SigningPubKey":"74657374", + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Fee": "2", + "Sequence": 100, + "SigningPubKey": "74657374", "TakerGets": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "value":"200" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "value": "200" }, - "TakerPays":"300", - "TransactionType":"OfferCreate", - "hash":"2E2FBAAFF767227FE4381C4BE9855986A6B9F96C62F6E443731AB36F7BBB8A08", + "TakerPays": "300", + "TransactionType": "OfferCreate", + "hash": "2E2FBAAFF767227FE4381C4BE9855986A6B9F96C62F6E443731AB36F7BBB8A08", "meta": { "AffectedNodes": @@ -940,26 +940,26 @@ TEST_F(RPCTxTest, NotReturnCTIDIfETLNotAvailable) { "CreatedNode": { - "LedgerEntryType":"Offer", + "LedgerEntryType": "Offer", "NewFields": { - "TakerGets":"200", + "TakerGets": "200", "TakerPays": { - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"300" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "300" } } } } ], - "TransactionIndex":100, - "TransactionResult":"tesSUCCESS" + "TransactionIndex": 100, + "TransactionResult": "tesSUCCESS" }, - "date":123456, - "ledger_index":100, - "inLedger":100, + "date": 123456, + "ledger_index": 100, + "inLedger": 100, "validated": true })JSON"; @@ -992,22 +992,22 @@ TEST_F(RPCTxTest, NotReturnCTIDIfETLNotAvailable) TEST_F(RPCTxTest, ViaCTID) { - auto static const kOUT = fmt::format( + static auto const kOUT = fmt::format( R"JSON({{ - "Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "Fee":"2", - "Sequence":100, - "SigningPubKey":"74657374", + "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "Fee": "2", + "Sequence": 100, + "SigningPubKey": "74657374", "TakerGets": {{ - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", - "value":"200" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "value": "200" }}, - "ctid":"{}", - "TakerPays":"300", - "TransactionType":"OfferCreate", - "hash":"2E2FBAAFF767227FE4381C4BE9855986A6B9F96C62F6E443731AB36F7BBB8A08", + "ctid": "{}", + "TakerPays": "300", + "TransactionType": "OfferCreate", + "hash": "2E2FBAAFF767227FE4381C4BE9855986A6B9F96C62F6E443731AB36F7BBB8A08", "meta": {{ "AffectedNodes": @@ -1015,26 +1015,26 @@ TEST_F(RPCTxTest, ViaCTID) {{ "CreatedNode": {{ - "LedgerEntryType":"Offer", + "LedgerEntryType": "Offer", "NewFields": {{ - "TakerGets":"200", + "TakerGets": "200", "TakerPays": {{ - "currency":"0158415500000000C1F76FF6ECB0BAC600000000", - "issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "value":"300" + "currency": "0158415500000000C1F76FF6ECB0BAC600000000", + "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "value": "300" }} }} }} }} ], - "TransactionIndex":1, - "TransactionResult":"tesSUCCESS" + "TransactionIndex": 1, + "TransactionResult": "tesSUCCESS" }}, - "date":123456, - "ledger_index":{}, - "inLedger":{}, + "date": 123456, + "ledger_index": {}, + "inLedger": {}, "validated": true }})JSON", kCTID, diff --git a/tests/unit/rpc/handlers/UnsubscribeTests.cpp b/tests/unit/rpc/handlers/UnsubscribeTests.cpp index 94e0f7440..0c3e59145 100644 --- a/tests/unit/rpc/handlers/UnsubscribeTests.cpp +++ b/tests/unit/rpc/handlers/UnsubscribeTests.cpp @@ -568,7 +568,7 @@ TEST_F(RPCUnsubscribeTest, Streams) { auto const input = json::parse( R"JSON({ - "streams": ["transactions_proposed","transactions","validations","manifests","book_changes","ledger"] + "streams": ["transactions_proposed", "transactions", "validations", "manifests", "book_changes", "ledger"] })JSON" ); @@ -591,7 +591,7 @@ TEST_F(RPCUnsubscribeTest, Accounts) { auto const input = json::parse(fmt::format( R"JSON({{ - "accounts": ["{}","{}"] + "accounts": ["{}", "{}"] }})JSON", kACCOUNT, kACCOUNT2 @@ -613,7 +613,7 @@ TEST_F(RPCUnsubscribeTest, AccountsProposed) { auto const input = json::parse(fmt::format( R"JSON({{ - "accounts_proposed": ["{}","{}"] + "accounts_proposed": ["{}", "{}"] }})JSON", kACCOUNT, kACCOUNT2 diff --git a/tests/unit/rpc/handlers/VaultInfoTests.cpp b/tests/unit/rpc/handlers/VaultInfoTests.cpp new file mode 100644 index 000000000..aff37c0c6 --- /dev/null +++ b/tests/unit/rpc/handlers/VaultInfoTests.cpp @@ -0,0 +1,450 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2025, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "data/Types.hpp" +#include "rpc/Errors.hpp" +#include "rpc/common/AnyHandler.hpp" +#include "rpc/common/Types.hpp" +#include "rpc/handlers/VaultInfo.hpp" +#include "util/HandlerBaseTestFixture.hpp" +#include "util/MockAmendmentCenter.hpp" +#include "util/NameGenerator.hpp" +#include "util/TestObject.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace rpc; +using namespace data; +using namespace testing; +namespace json = boost::json; + +namespace { + +constexpr auto kACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"; +constexpr auto kACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"; +constexpr auto kINDEX1 = "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890"; +constexpr auto kSEQ = 30; +constexpr auto kASSET_CURRENCY = "XRP"; +constexpr auto kASSET_ISSUER = "rrrrrrrrrrrrrrrrrrrrrhoLvTp"; +constexpr auto kVAULT_ID = "61B03A6F8CEBD3AF9D8F696C3D0A9A9F0493B34BF6B5D93CF0BC009E6BA75303"; + +} // namespace + +struct RPCVaultInfoHandlerTest : HandlerBaseTest { + RPCVaultInfoHandlerTest() + { + backend_->setRange(10, kSEQ); + } + +protected: + StrictMockAmendmentCenterSharedPtr mockAmendmentCenterPtr_; +}; + +struct VaultInfoParamTestCaseBundle { + std::string testName; + std::string testJson; + std::string expectedError; + std::string expectedErrorMessage; +}; + +struct VaultInfoParameterTest : RPCVaultInfoHandlerTest, WithParamInterface {}; + +static auto +generateTestValuesForParametersTest() +{ + return std::vector{ + VaultInfoParamTestCaseBundle{ + .testName = "RandomField", + .testJson = R"JSON({ + "idk": "idk" + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + VaultInfoParamTestCaseBundle{ + .testName = "MissingOwnerInVault", + .testJson = R"JSON({ + "seq": 4 + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + VaultInfoParamTestCaseBundle{ + .testName = "MissingSeqInVault", + .testJson = R"JSON({ + "owner": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + VaultInfoParamTestCaseBundle{ + .testName = "SeqNotAnInteger", + .testJson = R"JSON({ + "owner": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "seq": "asdf" + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + VaultInfoParamTestCaseBundle{ + .testName = "OwnerNotAString", + .testJson = R"JSON({ + "owner": true, + "seq": 3 + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "OwnerNotHexString" + }, + VaultInfoParamTestCaseBundle{ + .testName = "OwnerNotAHexString", + .testJson = R"JSON({ + "owner": "asdf", + "seq": 3 + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "OwnerNotHexString" + }, + VaultInfoParamTestCaseBundle{ + .testName = "vaultIDNotString", + .testJson = R"JSON({ + "vault_id": 3 + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + VaultInfoParamTestCaseBundle{ + .testName = "vaultIDNotHex256", + .testJson = R"JSON({ + "vault_id": "idk" + })JSON", + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + }, + VaultInfoParamTestCaseBundle{ + .testName = "vaultIDWithOwner", + .testJson = fmt::format( + R"JSON({{ + "vault_id": "{}", + "owner": "{}" + }})JSON", + kVAULT_ID, + kACCOUNT + ), + .expectedError = "malformedRequest", + .expectedErrorMessage = "Malformed request." + } + }; +} + +INSTANTIATE_TEST_CASE_P( + RPCVaultInfoGroup, + VaultInfoParameterTest, + ValuesIn(generateTestValuesForParametersTest()), + tests::util::kNAME_GENERATOR +); + +TEST_P(VaultInfoParameterTest, InvalidParams) +{ + auto const testBundle = VaultInfoParameterTest::GetParam(); + runSpawn([&, this](auto yield) { + auto const handler = AnyHandler{VaultInfoHandler{backend_}}; + auto const req = json::parse(testBundle.testJson); + auto const output = handler.process(req, Context{.yield = yield, .apiVersion = 2}); + ASSERT_FALSE(output); + + auto const err = rpc::makeError(output.result.error()); + EXPECT_EQ(err.at("error").as_string(), testBundle.expectedError); + EXPECT_EQ(err.at("error_message").as_string(), testBundle.expectedErrorMessage); + }); +} + +TEST_F(RPCVaultInfoHandlerTest, InputHasOwnerButNotFoundResultsInError) +{ + auto const ledgerHeader = createLedgerHeader(kINDEX1, kSEQ); + EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); + + // Input JSON using vault object + auto static const kINPUT = boost::json::parse(fmt::format( + R"JSON({{ + "owner": "{}", + "seq": 3 + }})JSON", + kACCOUNT + )); + + // Run the handler + auto const handler = AnyHandler{VaultInfoHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kINPUT, Context{.yield = yield, .apiVersion = 2}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.result.error()); + EXPECT_EQ(err.at("error").as_string(), "entryNotFound"); + }); +} + +TEST_F(RPCVaultInfoHandlerTest, VaultIDFailsVaultDeserializationReturnsEntryNotFound) +{ + auto const ledgerHeader = createLedgerHeader(kINDEX1, kSEQ); + EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); + + // Mock: vault_id exists, but data is not a valid vault object + ripple::uint256 vaultKey = ripple::uint256{kVAULT_ID}; + EXPECT_CALL(*backend_, doFetchLedgerObject(vaultKey, kSEQ, _)) + .WillOnce(Return(std::nullopt)); // intentionally invalid vault + + auto const kINPUT = boost::json::parse(fmt::format( + R"({{ + "vault_id": "{}" + }})", + kVAULT_ID + )); + + auto const handler = AnyHandler{VaultInfoHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kINPUT, Context{.yield = yield, .apiVersion = 2}); + + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.result.error()); + EXPECT_EQ(err.at("error").as_string(), "entryNotFound"); + }); +} + +TEST_F(RPCVaultInfoHandlerTest, MissingIssuanceObject) +{ + auto const ledgerHeader = createLedgerHeader(kINDEX1, kSEQ); + EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); + + ripple::uint192 mptSharesID{123}; + ripple::uint256 prevTxId{2}; + uint32_t prevTxSeq = 3; + uint64_t ownerNode = 4; + + auto const vault = createVault( + kACCOUNT, kACCOUNT2, kSEQ, kASSET_CURRENCY, kASSET_ISSUER, mptSharesID, ownerNode, prevTxId, prevTxSeq + ); + + auto const vaultKeylet = ripple::keylet::vault(ripple::uint256{kVAULT_ID}).key; + auto const mptIssuance = ripple::keylet::mptIssuance(mptSharesID).key; + + EXPECT_CALL(*backend_, doFetchLedgerObject(vaultKeylet, kSEQ, _)) + .WillOnce(Return(vault.getSerializer().peekData())); + EXPECT_CALL(*backend_, doFetchLedgerObject(mptIssuance, kSEQ, _)) + .WillOnce(Return(std::nullopt)); // Missing issuance + + auto static const kINPUT = boost::json::parse(fmt::format( + R"({{ + "vault_id": "{}" + }})", + kVAULT_ID + )); + + auto const handler = AnyHandler{VaultInfoHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kINPUT, Context{.yield = yield, .apiVersion = 2}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.result.error()); + EXPECT_EQ(err.at("error").as_string(), "entryNotFound"); + }); +} + +TEST_F(RPCVaultInfoHandlerTest, ValidVaultObjectQueryByVaultID) +{ + constexpr auto kEXPECTED_OUTPUT = + R"JSON({ + "ledger_index": 30, + "validated": true, + "vault": { + "Account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Asset": { + "currency": "XRP" + }, + "AssetsAvailable": "300", + "AssetsTotal": "300", + "Flags": 0, + "LedgerEntryType": "Vault", + "LossUnrealized": "0", + "Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "OwnerNode": "4", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000002", + "PreviousTxnLgrSeq": 3, + "Sequence": 30, + "ShareMPTID": "00000000000000000000000000000000000000000000007B", + "WithdrawalPolicy": 200, + "index": "61B03A6F8CEBD3AF9D8F696C3D0A9A9F0493B34BF6B5D93CF0BC009E6BA75303", + "shares": { + "Flags": 0, + "Issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "LedgerEntryType": "MPTokenIssuance", + "MPTokenMetadata": "6D65746164617461", + "MaximumAmount": "0", + "OutstandingAmount": "0", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 30, + "index": "87658CA4D4D7A50EE99E632055FE7A879CD9A331880AC21D538FA6E4032804E3", + "mpt_issuance_id": "0000001E4B4E9C06F24296074F7BC48F92A97916C6DC5EA9" + } + } + })JSON"; + + auto const ledgerHeader = createLedgerHeader(kINDEX1, kSEQ); + EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); + + // Vault params + ripple::uint192 mptSharesID{123}; + ripple::uint256 prevTxId{2}; + uint32_t prevTxSeq = 3; + uint64_t ownerNode = 4; + + // Mock vault object + auto const vault = createVault( + kACCOUNT, kACCOUNT2, kSEQ, kASSET_CURRENCY, kASSET_ISSUER, mptSharesID, ownerNode, prevTxId, prevTxSeq + ); + + // Set up keylet based on vaultID + auto const issuance = createMptIssuanceObject(kACCOUNT, kSEQ, "metadata"); + auto const vaultKeylet = ripple::keylet::vault(ripple::uint256{kVAULT_ID}).key; + auto const mptIssuance = ripple::keylet::mptIssuance(mptSharesID).key; + + EXPECT_CALL(*backend_, doFetchLedgerObject(vaultKeylet, kSEQ, _)) + .WillOnce(Return(vault.getSerializer().peekData())); + EXPECT_CALL(*backend_, doFetchLedgerObject(mptIssuance, kSEQ, _)) + .WillOnce(Return(issuance.getSerializer().peekData())); + + // Input JSON using vault_id + auto static const kINPUT = boost::json::parse(fmt::format( + R"({{ + "vault_id": "{}" + }})", + kVAULT_ID + )); + + // Run the handler + auto const handler = AnyHandler{VaultInfoHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kINPUT, Context{.yield = yield, .apiVersion = 2}); + ASSERT_TRUE(output); + EXPECT_EQ(*output.result, json::parse(kEXPECTED_OUTPUT)); + }); +} + +TEST_F(RPCVaultInfoHandlerTest, ValidVaultObjectQueryByOwnerAndSeq) +{ + constexpr auto kEXPECTED_OUTPUT = + R"JSON({ + "ledger_index": 30, + "validated": true, + "vault": { + "Account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "Asset": { + "currency": "XRP" + }, + "AssetsAvailable": "300", + "AssetsTotal": "300", + "Flags": 0, + "LedgerEntryType": "Vault", + "LossUnrealized": "0", + "Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "OwnerNode": "4", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000002", + "PreviousTxnLgrSeq": 3, + "Sequence": 30, + "ShareMPTID": "00000000000000000000000000000000000000000000007B", + "WithdrawalPolicy": 200, + "index": "1B7BB49E0663E073D1C3EF989271F89E290AAF2D67CEE85F18E2CC76D168F694", + "shares": { + "Flags": 0, + "Issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "LedgerEntryType": "MPTokenIssuance", + "MPTokenMetadata": "6D65746164617461", + "MaximumAmount": "0", + "OutstandingAmount": "0", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, + "Sequence": 30, + "index": "87658CA4D4D7A50EE99E632055FE7A879CD9A331880AC21D538FA6E4032804E3", + "mpt_issuance_id": "0000001E4B4E9C06F24296074F7BC48F92A97916C6DC5EA9" + } + } + })JSON"; + + auto const ledgerHeader = createLedgerHeader(kINDEX1, kSEQ); + EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); + + // Vault params + ripple::uint192 mptSharesID{123}; + ripple::uint256 prevTxId{2}; + uint32_t prevTxSeq = 3; + uint64_t ownerNode = 4; + + // Mock vault object + auto const vault = createVault( + kACCOUNT, kACCOUNT2, kSEQ, kASSET_CURRENCY, kASSET_ISSUER, mptSharesID, ownerNode, prevTxId, prevTxSeq + ); + + auto const issuance = createMptIssuanceObject(kACCOUNT, kSEQ, "metadata"); + + auto const accountRoot = createAccountRootObject(kACCOUNT, 0, kSEQ, 200, 2, kINDEX1, 2); + auto const account = getAccountIdWithString(kACCOUNT); + auto const accountKeylet = ripple::keylet::account(account).key; + auto const vaultKeylet = ripple::keylet::vault(account, kSEQ).key; + auto const mptIssuance = ripple::keylet::mptIssuance(mptSharesID).key; + + EXPECT_CALL(*backend_, doFetchLedgerObject(accountKeylet, kSEQ, _)) + .WillOnce(Return(accountRoot.getSerializer().peekData())); + EXPECT_CALL(*backend_, doFetchLedgerObject(vaultKeylet, kSEQ, _)) + .WillOnce(Return(vault.getSerializer().peekData())); + EXPECT_CALL(*backend_, doFetchLedgerObject(mptIssuance, kSEQ, _)) + .WillOnce(Return(issuance.getSerializer().peekData())); + + // Input JSON using vault object + auto static const kINPUT = boost::json::parse(fmt::format( + R"JSON({{ + "owner": "{}", + "seq": {}, + "ledger_index": 30 + }})JSON", + kACCOUNT, + kSEQ + )); + + // Run the handler + auto const handler = AnyHandler{VaultInfoHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kINPUT, Context{.yield = yield, .apiVersion = 2}); + ASSERT_TRUE(output); + EXPECT_EQ(*output.result, json::parse(kEXPECTED_OUTPUT)); + }); +} diff --git a/tests/unit/util/LedgerUtilsTests.cpp b/tests/unit/util/LedgerUtilsTests.cpp index 47318b148..ba92154f6 100644 --- a/tests/unit/util/LedgerUtilsTests.cpp +++ b/tests/unit/util/LedgerUtilsTests.cpp @@ -57,6 +57,7 @@ TEST(LedgerUtilsTests, LedgerObjectTypeList) JS(permissioned_domain), JS(oracle), JS(credential), + JS(vault), JS(nunl), JS(delegate) }; @@ -92,6 +93,7 @@ TEST(LedgerUtilsTests, AccountOwnedTypeList) JS(mpt_issuance), JS(mptoken), JS(permissioned_domain), + JS(vault), JS(delegate) }; diff --git a/tests/unit/util/config/ConfigFileJsonTests.cpp b/tests/unit/util/config/ConfigFileJsonTests.cpp index cb6d2feb6..d639e938e 100644 --- a/tests/unit/util/config/ConfigFileJsonTests.cpp +++ b/tests/unit/util/config/ConfigFileJsonTests.cpp @@ -117,7 +117,7 @@ INSTANTIATE_TEST_CASE_P( .configStr = R"JSON({ "level_0": { "int": 42, - "level_1":{ + "level_1": { "double": 123.456, "level_2": { "bool": true, diff --git a/tests/unit/web/RPCServerHandlerTests.cpp b/tests/unit/web/RPCServerHandlerTests.cpp index 300d91a0d..f7e76488c 100644 --- a/tests/unit/web/RPCServerHandlerTests.cpp +++ b/tests/unit/web/RPCServerHandlerTests.cpp @@ -186,7 +186,7 @@ TEST_F(WebRPCServerHandlerTest, WsNormalPath) static constexpr auto kRESULT = "{}"; static constexpr auto kRESPONSE = R"JSON({ - "result":{}, + "result": {}, "id": 99, "status": "success", "type": "response", @@ -261,12 +261,12 @@ TEST_F(WebRPCServerHandlerTest, HTTPForwardedPath) "forwarded": true })JSON"; static constexpr auto kRESPONSE = R"JSON({ - "result":{ + "result": { "index": 1, "status": "success" }, "forwarded": true, - "warnings":[ + "warnings": [ { "id": 2001, "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" @@ -307,7 +307,7 @@ TEST_F(WebRPCServerHandlerTest, HTTPForwardedErrorPath) "forwarded": true })JSON"; static constexpr auto kRESPONSE = R"JSON({ - "result":{ + "result": { "error": "error", "error_code": 123, "error_message": "error message", @@ -315,7 +315,7 @@ TEST_F(WebRPCServerHandlerTest, HTTPForwardedErrorPath) "type": "response" }, "forwarded": true, - "warnings":[ + "warnings": [ { "id": 2001, "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" @@ -355,7 +355,7 @@ TEST_F(WebRPCServerHandlerTest, WsForwardedPath) "forwarded": true })JSON"; static constexpr auto kRESPONSE = R"JSON({ - "result":{ + "result": { "index": 1 }, "forwarded": true, @@ -697,7 +697,7 @@ TEST_F(WebRPCServerHandlerTest, WsMissingCommand) "status": "error", "type": "response", "id": 99, - "request":{ + "request": { "command2": "server_info", "id": 99 } @@ -877,11 +877,11 @@ TEST_F(WebRPCServerHandlerTest, WsOutdated) static constexpr auto kRESULT = "{}"; static constexpr auto kRESPONSE = R"JSON({ - "result":{}, + "result": {}, "id": 99, "status": "success", "type": "response", - "warnings":[ + "warnings": [ { "id": 2001, "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" diff --git a/tests/unit/web/ng/RPCServerHandlerTests.cpp b/tests/unit/web/ng/RPCServerHandlerTests.cpp index f9bd82609..2d1213642 100644 --- a/tests/unit/web/ng/RPCServerHandlerTests.cpp +++ b/tests/unit/web/ng/RPCServerHandlerTests.cpp @@ -326,7 +326,7 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_BuildResponseFailed) { backend_->setRange(0, 1); runSpawn([&](boost::asio::yield_context yield) { - std::string const requestStr = R"JSON({"method":"some_method"})JSON"; + std::string const requestStr = R"JSON({"method": "some_method"})JSON"; auto const request = makeHttpRequest(requestStr); EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true)); @@ -357,7 +357,7 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_BuildResponseThrewAnException) { backend_->setRange(0, 1); runSpawn([&](boost::asio::yield_context yield) { - std::string const requestStr = R"JSON({"method":"some_method"})JSON"; + std::string const requestStr = R"JSON({"method": "some_method"})JSON"; auto const request = makeHttpRequest(requestStr); EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true)); @@ -383,7 +383,7 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_Successful_HttpRequest) { backend_->setRange(0, 1); runSpawn([&](boost::asio::yield_context yield) { - std::string const requestStr = R"JSON({"method":"some_method"})JSON"; + std::string const requestStr = R"JSON({"method": "some_method"})JSON"; auto const request = makeHttpRequest(requestStr); EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true)); @@ -416,7 +416,7 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_OutdatedWarning) { backend_->setRange(0, 1); runSpawn([&](boost::asio::yield_context yield) { - std::string const requestStr = R"JSON({"method":"some_method"})JSON"; + std::string const requestStr = R"JSON({"method": "some_method"})JSON"; auto const request = makeHttpRequest(requestStr); EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true)); @@ -455,7 +455,7 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_Successful_HttpRequest_Forwarded) { backend_->setRange(0, 1); runSpawn([&](boost::asio::yield_context yield) { - std::string const requestStr = R"JSON({"method":"some_method"})JSON"; + std::string const requestStr = R"JSON({"method": "some_method"})JSON"; auto const request = makeHttpRequest(requestStr); EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true)); @@ -491,7 +491,7 @@ TEST_F(NgRpcServerHandlerTest, HandleRequest_Successful_HttpRequest_HasError) { backend_->setRange(0, 1); runSpawn([&](boost::asio::yield_context yield) { - std::string const requestStr = R"JSON({"method":"some_method"})JSON"; + std::string const requestStr = R"JSON({"method": "some_method"})JSON"; auto const request = makeHttpRequest(requestStr); EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true)); @@ -543,7 +543,7 @@ TEST_F(NgRpcServerHandlerWsTest, HandleRequest_Successful_WsRequest) backend_->setRange(0, 1); runSpawn([&](boost::asio::yield_context yield) { Request::HttpHeaders const headers; - std::string const requestStr = R"JSON({"method":"some_method", "id": 1234, "api_version": 1})JSON"; + std::string const requestStr = R"JSON({"method": "some_method", "id": 1234, "api_version": 1})JSON"; auto const request = Request(requestStr, headers); EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true)); @@ -578,7 +578,7 @@ TEST_F(NgRpcServerHandlerWsTest, HandleRequest_Successful_WsRequest_HasError) backend_->setRange(0, 1); runSpawn([&](boost::asio::yield_context yield) { Request::HttpHeaders const headers; - std::string const requestStr = R"JSON({"method":"some_method", "id": 1234, "api_version": 1})JSON"; + std::string const requestStr = R"JSON({"method": "some_method", "id": 1234, "api_version": 1})JSON"; auto const request = Request(requestStr, headers); EXPECT_CALL(dosguard_, isOk(ip_)).WillOnce(Return(true));