From 2d172f470d719af2897967d373017010b4dc64d8 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 9 Jun 2025 15:47:43 +0100 Subject: [PATCH 01/49] feat: Always build with native arch in Conan 2 (#2201) Will test it works in https://github.com/XRPLF/clio/pull/2202 Work on: https://github.com/XRPLF/clio/issues/1692 --- docker/ci/conan/clang.profile | 2 +- docker/ci/conan/gcc.profile | 2 +- docs/build-clio.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) 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/docs/build-clio.md b/docs/build-clio.md index a8c07bfdf..fdf788b92 100644 --- a/docs/build-clio.md +++ b/docs/build-clio.md @@ -28,7 +28,7 @@ Clio requires `compiler.cppstd=20` in your Conan profile (`~/.conan/profiles/def ```text [settings] -arch=armv8 +arch={{detect_api.detect_arch()}} build_type=Release compiler=apple-clang compiler.cppstd=20 @@ -44,7 +44,7 @@ tools.build:cxxflags+=["-Wno-missing-template-arg-list-after-template-kw"] ```text [settings] -arch=x86_64 +arch={{detect_api.detect_arch()}} build_type=Release compiler=gcc compiler.cppstd=20 From d3c98ab2a868663c9f5f1c68803067829fb4e6e9 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 10 Jun 2025 15:57:23 +0100 Subject: [PATCH 02/49] fix: Only set compile flag for grpc (#2204) I built all conan packages locally and this flag is only required for grpc, so let's only set it for grpc. This is better - it's explicit, and we'll know that if we update grpc recipe, we can remove this. I also uploaded all rebuilt packages to the artifactory. --- docs/build-clio.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build-clio.md b/docs/build-clio.md index fdf788b92..ca791ea2e 100644 --- a/docs/build-clio.md +++ b/docs/build-clio.md @@ -37,7 +37,7 @@ 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**: From 6e0d7a0fac16187255a9f99401b3b3be41f80161 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 10 Jun 2025 16:04:00 +0100 Subject: [PATCH 03/49] feat: Pass sanitizer as part of conan_profile (#2189) I noticed we don't need `sanitizer` value anymore, so removed it. --- .github/actions/generate/action.yml | 15 +++------------ .github/workflows/build.yml | 1 - .github/workflows/build_and_test.yml | 8 -------- .github/workflows/build_impl.yml | 10 ++-------- .github/workflows/nightly.yml | 6 ------ .github/workflows/sanitizers.yml | 11 ++--------- .github/workflows/test_impl.yml | 9 ++------- 7 files changed, 9 insertions(+), 51 deletions(-) diff --git a/.github/actions/generate/action.yml b/.github/actions/generate/action.yml index 79d3d5e9f..a81daab66 100644 --- a/.github/actions/generate/action.yml +++ b/.github/actions/generate/action.yml @@ -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 @@ -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/workflows/build.yml b/.github/workflows/build.yml index fa94b84e4..3fdbe25ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,7 +76,6 @@ jobs: static: true upload_clio_server: false targets: all - sanitizer: "false" analyze_build_time: false secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 6a73e62f6..c713f5cbd 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -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..1a19ea58d 100644 --- a/.github/workflows/build_impl.yml +++ b/.github/workflows/build_impl.yml @@ -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 @@ -106,7 +101,6 @@ jobs: 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 +134,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 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4db12c363..e472ad0f6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -35,25 +35,21 @@ jobs: conan_profile: default_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 @@ -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/sanitizers.yml b/.github/workflows/sanitizers.yml index 6088be3c7..e831e1920 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -36,24 +36,17 @@ jobs: strategy: fail-fast: false matrix: - include: - - sanitizer: tsan - compiler: gcc - - sanitizer: asan - compiler: gcc - - sanitizer: ubsan - compiler: gcc + conan_profile: ["gcc.asan", "gcc.tsan", "gcc.ubsan"] 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.conan_profile }} 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..079e3c496 100644 --- a/.github/workflows/test_impl.yml +++ b/.github/workflows/test_impl.yml @@ -33,11 +33,6 @@ 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' }} @@ -47,8 +42,8 @@ jobs: 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 From 35c90e64ec23ad59d6d2f2ad044a61618ffd8639 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 11 Jun 2025 16:26:09 +0100 Subject: [PATCH 04/49] feat: Add flags to deps for sanitizer builds (#2205) Fix: https://github.com/XRPLF/clio/issues/2198 Tested in #2208 --- docker/ci/conan/sanitizer_template.profile | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docker/ci/conan/sanitizer_template.profile b/docker/ci/conan/sanitizer_template.profile index ff601481c..9d70d17e1 100644 --- a/docker/ci/conan/sanitizer_template.profile +++ b/docker/ci/conan/sanitizer_template.profile @@ -2,14 +2,17 @@ {% set sanitizer_opt_map = {'asan': 'address', 'tsan': 'thread', 'ubsan': 'undefined'} %} {% set sanitizer = sanitizer_opt_map[sani] %} +{% set sanitizer_build_flags = "-fsanitize=" ~ sanitizer ~ " -g -O1 -fno-omit-frame-pointer" %} +{% set sanitizer_link_flags = "-fsanitize=" ~ sanitizer %} 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 }}\" linkflags=\"{{ sanitizer_link_flags }}\"" +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 }}"] From 743c9b92de9065f92c654e358957b0fd930d9fa9 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 11 Jun 2025 17:53:14 +0100 Subject: [PATCH 05/49] feat: Read-write switching in ETLng (#2199) Fixes #1597 --- cmake/Settings.cmake | 8 +- src/etl/ETLService.cpp | 11 +- src/etl/ETLService.hpp | 2 +- src/etl/SystemState.hpp | 2 +- src/etl/impl/LedgerLoader.hpp | 4 +- src/etlng/ETLService.cpp | 75 +++++-- src/etlng/ETLService.hpp | 16 +- src/etlng/LoadBalancerInterface.hpp | 12 +- src/etlng/LoaderInterface.hpp | 7 +- src/etlng/MonitorInterface.hpp | 23 +- src/etlng/MonitorProviderInterface.hpp | 64 ++++++ src/etlng/TaskManagerProviderInterface.hpp | 2 +- src/etlng/impl/LedgerPublisher.hpp | 5 +- src/etlng/impl/Loading.cpp | 54 ++++- src/etlng/impl/Loading.hpp | 11 +- src/etlng/impl/Monitor.cpp | 84 ++++++- src/etlng/impl/Monitor.hpp | 28 ++- src/etlng/impl/MonitorProvider.hpp | 53 +++++ src/etlng/impl/TaskManager.cpp | 26 ++- src/util/Constants.hpp | 1 + src/util/async/context/impl/Cancellation.hpp | 1 + tests/unit/etl/ExtractorTests.cpp | 2 +- tests/unit/etl/TransformerTests.cpp | 2 +- tests/unit/etlng/ETLServiceTests.cpp | 221 +++++++++++++++++-- tests/unit/etlng/LedgerPublisherTests.cpp | 105 +++++---- tests/unit/etlng/LoadingTests.cpp | 6 +- tests/unit/etlng/MonitorTests.cpp | 59 ++++- tests/unit/etlng/RegistryTests.cpp | 20 +- tests/unit/etlng/TaskManagerTests.cpp | 93 +++++++- 29 files changed, 816 insertions(+), 181 deletions(-) create mode 100644 src/etlng/MonitorProviderInterface.hpp create mode 100644 src/etlng/impl/MonitorProvider.hpp diff --git a/cmake/Settings.cmake b/cmake/Settings.cmake index 85fe7df93..02ef1b1c4 100644 --- a/cmake/Settings.cmake +++ b/cmake/Settings.cmake @@ -1,20 +1,20 @@ 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 -Wunused # FIXME: The following bunch are needed for gcc12 atm. diff --git a/src/etl/ETLService.cpp b/src/etl/ETLService.cpp index 63ea45914..496f019f2 100644 --- a/src/etl/ETLService.cpp +++ b/src/etl/ETLService.cpp @@ -38,6 +38,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 +87,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 +95,7 @@ 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(); auto loader = std::make_shared( backend, @@ -104,7 +107,8 @@ ETLService::makeETLService( etlng::impl::NFTExt{backend}, etlng::impl::MPTExt{backend} ), - amendmentBlockHandler + amendmentBlockHandler, + state ); auto taskManagerProvider = std::make_shared(*ledgers, extractor, loader); @@ -122,6 +126,7 @@ ETLService::makeETLService( loader, // loader itself loader, // initial load observer taskManagerProvider, + monitorProvider, state ); } else { @@ -346,7 +351,7 @@ ETLService::doWork() worker_ = std::thread([this]() { beast::setCurrentThreadName("ETLService worker"); - if (state_.isReadOnly) { + if (state_.isStrictReadonly) { monitorReadOnly(); } else { monitor(); @@ -373,7 +378,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/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..991b867af 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" @@ -52,6 +52,7 @@ #include "util/Assert.hpp" #include "util/Profiler.hpp" #include "util/async/AnyExecutionContext.hpp" +#include "util/async/AnyOperation.hpp" #include "util/log/Logger.hpp" #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,11 @@ ETLService::ETLService( , loader_(std::move(loader)) , initialLoadObserver_(std::move(initialLoadObserver)) , taskManagerProvider_(std::move(taskManagerProvider)) + , monitorProvider_(std::move(monitorProvider)) , state_(std::move(state)) { - LOG(log_.info()) << "Creating ETLng..."; + ASSERT(not state_->isWriting, "ETL should never start in writer mode"); + LOG(log_.info()) << "Starting in " << (state_->isStrictReadonly ? "STRICT READONLY MODE" : "WRITE MODE"); } ETLService::~ETLService() @@ -112,12 +116,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..."; @@ -135,9 +134,8 @@ ETLService::run() 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 +145,8 @@ ETLService::stop() { LOG(log_.info()) << "Stop called"; + if (mainLoop_) + mainLoop_->wait(); if (taskMan_) taskMan_->stop(); if (monitor_) @@ -160,7 +160,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,12 +196,19 @@ ETLService::loadInitialLedgerIfNeeded() { auto rng = backend_->hardFetchLedgerRangeNoThrow(); if (not rng.has_value()) { + ASSERT( + not state_->isStrictReadonly, + "Database is empty but this node is in strict readonly mode. Can't write initial ledger." + ); + LOG(log_.info()) << "Database is empty. Will download a ledger from the network."; + state_->isWriting = true; // immediately become writer as the db is empty 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()) << "Ledger " << seq << " has been validated. " + << "Downloading and extracting (takes a while)..."; auto [ledger, timeDiff] = ::util::timed>([this, seq]() { return extractor_->extractLedgerOnly(seq).and_then([this, seq](auto&& data) { @@ -238,28 +245,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) { + ASSERT(not state_->isStrictReadonly, "This should only happen on writer nodes"); taskMan_ = taskManagerProvider_->make(ctx_, *monitor_, seq); 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..5ec75e0e5 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" @@ -69,6 +69,7 @@ #include #include +#include #include #include #include @@ -106,12 +107,14 @@ 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::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 +134,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 +150,7 @@ public: std::shared_ptr loader, std::shared_ptr initialLoadObserver, std::shared_ptr taskManagerProvider, + std::shared_ptr monitorProvider, std::shared_ptr state ); @@ -173,7 +178,6 @@ public: lastCloseAgeSeconds() const override; private: - // TODO: this better be std::expected std::optional loadInitialLedgerIfNeeded(); @@ -182,6 +186,12 @@ private: void startLoading(uint32_t seq); + + void + attemptTakeoverWriter(); + + void + giveUpWriter(); }; } // namespace etlng diff --git a/src/etlng/LoadBalancerInterface.hpp b/src/etlng/LoadBalancerInterface.hpp index 200bbb3f3..fbba73a48 100644 --- a/src/etlng/LoadBalancerInterface.hpp +++ b/src/etlng/LoadBalancerInterface.hpp @@ -59,7 +59,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, etlng::InitialLoadObserverInterface& loader, @@ -74,7 +74,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 +90,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 +103,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 +115,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 +127,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..1d2f4e533 100644 --- a/src/etlng/LoaderInterface.hpp +++ b/src/etlng/LoaderInterface.hpp @@ -23,10 +23,14 @@ #include +#include #include +#include namespace etlng { +using Error = std::string; + /** * @brief An interface for a ETL Loader */ @@ -36,8 +40,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/TaskManagerProviderInterface.hpp b/src/etlng/TaskManagerProviderInterface.hpp index 2894170bb..532c0ad73 100644 --- a/src/etlng/TaskManagerProviderInterface.hpp +++ b/src/etlng/TaskManagerProviderInterface.hpp @@ -44,7 +44,7 @@ struct TaskManagerProviderInterface { * @param seq The sequence to start at * @return A unique pointer to a TaskManager implementation */ - virtual std::unique_ptr + [[nodiscard]] virtual std::unique_ptr make(util::async::AnyExecutionContext ctx, std::reference_wrapper monitor, uint32_t seq) = 0; }; diff --git a/src/etlng/impl/LedgerPublisher.hpp b/src/etlng/impl/LedgerPublisher.hpp index 2c0d9ed7f..ddac66c80 100644 --- a/src/etlng/impl/LedgerPublisher.hpp +++ b/src/etlng/impl/LedgerPublisher.hpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -164,10 +165,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..0603ef8c8 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("write conflict"); + } + } } catch (std::runtime_error const& e) { LOG(log_.fatal()) << "Failed to load " << data.seq << ": " << e.what(); amendmentBlockHandler_->notifyAmendmentBlocked(); + return std::unexpected("amendment blocked"); } + + 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(); diff --git a/src/etlng/impl/Loading.hpp b/src/etlng/impl/Loading.hpp index 39a1e150a..9da5edf95 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/TaskManager.cpp b/src/etlng/impl/TaskManager.cpp index 8f61dc445..86bb7da8b 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,36 @@ 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; + // perhaps this should return an error if conflict happened, then we can stop loading immediately + auto [expectedSuccess, nanos] = + util::timed([&] { return loader_.get().load(*data); }); + + if (not expectedSuccess.has_value()) { + LOG(log_.warn()) << "Immediately stopping loader with error: " << expectedSuccess.error() + << "; latest ledger cache loaded for " << data->seq; + monitor_.get().notifyWriteConflict(data->seq); + break; + } + + auto const seconds = nanos / util::kNANO_PER_SECOND; auto const txnCount = data->transactions.size(); auto const objCount = data->objects.size(); @@ -133,9 +141,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/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/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/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/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/ETLServiceTests.cpp b/tests/unit/etlng/ETLServiceTests.cpp index dfcffadeb..0215dafae 100644 --- a/tests/unit/etlng/ETLServiceTests.cpp +++ b/tests/unit/etlng/ETLServiceTests.cpp @@ -17,8 +17,10 @@ */ //============================================================================== +#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" @@ -28,6 +30,7 @@ #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" @@ -62,6 +65,7 @@ #include #include #include +#include #include using namespace util::config; @@ -71,8 +75,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 +99,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)); }; @@ -123,6 +140,19 @@ struct MockTaskManagerProvider : etlng::TaskManagerProviderInterface { ); }; +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) + ); +}; + auto createTestData(uint32_t seq) { @@ -134,7 +164,7 @@ createTestData(uint32_t seq) .edgeKeys = {}, .header = header, .rawHeader = {}, - .seq = seq + .seq = seq, }; } } // namespace @@ -150,6 +180,7 @@ struct ETLServiceTests : util::prometheus::WithPrometheus, MockBackendTest { protected: SameThreadTestContext ctx_; util::config::ClioConfigDefinition config_{ + {"read_only", ConfigValue{ConfigType::Boolean}.defaultValue(false)}, {"extractor_threads", ConfigValue{ConfigType::Integer}.defaultValue(4)}, {"io_threads", ConfigValue{ConfigType::Integer}.defaultValue(2)}, {"cache.num_diffs", ConfigValue{ConfigType::Integer}.defaultValue(32)}, @@ -159,7 +190,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 +207,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 +224,7 @@ protected: loader_, initialLoadObserver_, taskManagerProvider_, + monitorProvider_, systemState_ }; }; @@ -258,65 +292,206 @@ 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(mockTaskManagerRef, run); EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, kSEQ + 1)) .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(testing::_, testing::_, testing::_, testing::_, testing::_)) + .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(*loader_, loadInitialLedger).Times(0); EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, testing::_)).Times(0); service_.run(); } +TEST_F(ETLServiceTests, HandlesWriteConflictInMonitorSubscription) +{ + 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 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)) + .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 +} + struct ETLServiceAssertTests : common::util::WithMockAssert, ETLServiceTests {}; TEST_F(ETLServiceAssertTests, FailToLoadInitialLedger) { - EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(std::nullopt)); + 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(*loader_, loadInitialLedger).Times(0); EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, testing::_)).Times(0); EXPECT_CLIO_ASSERT_FAIL({ service_.run(); }); @@ -325,14 +500,14 @@ TEST_F(ETLServiceAssertTests, FailToLoadInitialLedger) TEST_F(ETLServiceAssertTests, 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(*loader_, loadInitialLedger).Times(0); EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, testing::_)).Times(0); EXPECT_CLIO_ASSERT_FAIL({ service_.run(); }); 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/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..54c689ce5 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 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/TaskManagerTests.cpp b/tests/unit/etlng/TaskManagerTests.cpp index d94655a0b..59480207b 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,14 +140,17 @@ 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(); + } + return {}; + }); - EXPECT_CALL(*mockMonitorPtr_, notifyLedgerLoaded(testing::_)).Times(kTOTAL); + EXPECT_CALL(*mockMonitorPtr_, notifySequenceLoaded(testing::_)).Times(kTOTAL); taskManager_.run(kEXTRACTORS); done.acquire(); @@ -145,3 +161,60 @@ TEST_F(TaskManagerTests, LoaderGetsDataIfNextSequenceIsExtracted) 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("write conflict"); + } + + // Only release semaphore if we reach kTOTAL without conflict + 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); + } +} From e44a058b137923077221fb47c296ef83e800900e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 11 Jun 2025 17:57:00 +0100 Subject: [PATCH 06/49] chore: Don't flex and don't install bison in CI image (#2210) Test in: https://github.com/XRPLF/clio/pull/2211 --- docker/ci/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/ci/Dockerfile b/docker/ci/Dockerfile index ecca54396..61f62d759 100644 --- a/docker/ci/Dockerfile +++ b/docker/ci/Dockerfile @@ -33,10 +33,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 \ From d0b2a24a30eee367b4763e08c51566fbd39fb2ed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:47:34 +0100 Subject: [PATCH 07/49] style: clang-tidy auto fixes (#2215) --- tests/unit/etlng/ETLServiceTests.cpp | 2 +- tests/unit/etlng/MonitorTests.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/etlng/ETLServiceTests.cpp b/tests/unit/etlng/ETLServiceTests.cpp index 0215dafae..4e5806790 100644 --- a/tests/unit/etlng/ETLServiceTests.cpp +++ b/tests/unit/etlng/ETLServiceTests.cpp @@ -397,7 +397,7 @@ TEST_F(ETLServiceTests, NormalFlowInMonitorSubscription) service_.run(); systemState_->isWriting = false; - std::vector dummyDiff = {}; + std::vector const dummyDiff = {}; EXPECT_CALL(*backend_, fetchLedgerDiff(kSEQ + 1, testing::_)).WillOnce(testing::Return(dummyDiff)); EXPECT_CALL(*cacheUpdater_, update(kSEQ + 1, testing::A const&>())); diff --git a/tests/unit/etlng/MonitorTests.cpp b/tests/unit/etlng/MonitorTests.cpp index 54c689ce5..68c5574f9 100644 --- a/tests/unit/etlng/MonitorTests.cpp +++ b/tests/unit/etlng/MonitorTests.cpp @@ -143,7 +143,7 @@ TEST_F(MonitorTests, ResumesMonitoringFromNextSequenceAfterWriteConflict) EXPECT_CALL(*ledgers_, subscribe(testing::_)); { - testing::InSequence seq; // second call will produce conflict + 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)); } From 276477c494da1f99bf77b6ffb2079071ee3a394e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 12 Jun 2025 15:48:10 +0100 Subject: [PATCH 08/49] feat: Build GCC natively and then merge the image (#2212) --- .github/workflows/update_docker_ci.yml | 108 +++++++++++++++++++++---- docker/ci/Dockerfile | 3 +- docker/compilers/gcc/Dockerfile | 2 +- 3 files changed, 96 insertions(+), 17 deletions(-) diff --git a/.github/workflows/update_docker_ci.yml b/.github/workflows/update_docker_ci.yml index a0c99cc07..5443f28f2 100644 --- a/.github/workflows/update_docker_ci.yml +++ b/.github/workflows/update_docker_ci.yml @@ -27,9 +27,12 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + GHCR_REPO: ghcr.io/${{ github.repository_owner }} + jobs: - gcc: - name: Build and push GCC docker image + gcc-amd64: + name: Build and push GCC docker image (amd64) runs-on: [self-hosted, heavy] steps: @@ -42,27 +45,104 @@ 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: [self-hosted, 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: [self-hosted, heavy] + needs: [gcc-amd64, gcc-arm64] + if: ${{ github.event_name != 'pull_request' }} + + 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 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_PW }} + + - name: Create and push multi-arch manifest + if: steps.changed-files.outputs.any_changed == 'true' + run: | + for image in ${{ env.GHCR_REPO }}/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] @@ -84,7 +164,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 @@ -115,7 +195,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: | @@ -126,7 +206,7 @@ jobs: ci: name: Build and push CI docker image runs-on: [self-hosted, heavy] - needs: [gcc, clang, tools] + needs: [gcc-merge, clang, tools] steps: - uses: actions/checkout@v4 @@ -137,8 +217,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/docker/ci/Dockerfile b/docker/ci/Dockerfile index 61f62d759..20aed2966 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 diff --git a/docker/compilers/gcc/Dockerfile b/docker/compilers/gcc/Dockerfile index f4e763880..f98da1d03 100644 --- a/docker/compilers/gcc/Dockerfile +++ b/docker/compilers/gcc/Dockerfile @@ -4,7 +4,7 @@ ARG DEBIAN_FRONTEND=noninteractive ARG TARGETARCH ARG UBUNTU_VERSION=20.04 ARG GCC_VERSION=12.3.0 -ARG BUILD_VERSION=2 +ARG BUILD_VERSION=3 RUN apt-get update \ && apt-get install -y --no-install-recommends --no-install-suggests \ From 0273ba0da304b6f7e65b7946bf88236bfa4b0527 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Thu, 12 Jun 2025 16:16:11 +0100 Subject: [PATCH 09/49] chore: Unreachable code is error (#2216) --- cmake/Settings.cmake | 1 + tests/integration/data/cassandra/BackendTests.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/Settings.cmake b/cmake/Settings.cmake index 02ef1b1c4..2754db733 100644 --- a/cmake/Settings.cmake +++ b/cmake/Settings.cmake @@ -16,6 +16,7 @@ set(COMPILER_FLAGS -Wnull-dereference -Wold-style-cast -Wpedantic + -Wunreachable-code -Wunused # FIXME: The following bunch are needed for gcc12 atm. -Wno-missing-requires 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 } From 93add775b268792057d8ab8d8310c59688526ffe Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 12 Jun 2025 20:42:36 +0100 Subject: [PATCH 10/49] fix: Make GHCR lowercase (#2218) --- .github/workflows/update_docker_ci.yml | 13 +++++++++---- docker/compilers/gcc/Dockerfile | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update_docker_ci.yml b/.github/workflows/update_docker_ci.yml index 5443f28f2..449040188 100644 --- a/.github/workflows/update_docker_ci.yml +++ b/.github/workflows/update_docker_ci.yml @@ -103,7 +103,6 @@ jobs: name: Merge and push multi-arch GCC docker image runs-on: [self-hosted, heavy] needs: [gcc-amd64, gcc-arm64] - if: ${{ github.event_name != 'pull_request' }} steps: - uses: actions/checkout@v4 @@ -118,6 +117,7 @@ jobs: 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 @@ -125,15 +125,20 @@ jobs: 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: Create and push multi-arch manifest - if: steps.changed-files.outputs.any_changed == 'true' + - name: Make GHCR_REPO lowercase run: | - for image in ${{ env.GHCR_REPO }}/clio-gcc rippleci/clio_gcc; do + 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 \ diff --git a/docker/compilers/gcc/Dockerfile b/docker/compilers/gcc/Dockerfile index f98da1d03..f18901cd8 100644 --- a/docker/compilers/gcc/Dockerfile +++ b/docker/compilers/gcc/Dockerfile @@ -4,7 +4,7 @@ ARG DEBIAN_FRONTEND=noninteractive ARG TARGETARCH ARG UBUNTU_VERSION=20.04 ARG GCC_VERSION=12.3.0 -ARG BUILD_VERSION=3 +ARG BUILD_VERSION=4 RUN apt-get update \ && apt-get install -y --no-install-recommends --no-install-suggests \ From 3d0e722176da707dc3af2ef42bc37fa074b9e979 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 12 Jun 2025 21:50:14 +0100 Subject: [PATCH 11/49] fix: Use conan v2 dirs and commands in docs (#2219) --- .github/actions/save_cache/action.yml | 2 +- docker/develop/compose.yaml | 2 +- docs/build-clio.md | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/actions/save_cache/action.yml b/.github/actions/save_cache/action.yml index 9898def51..f32f061ea 100644 --- a/.github/actions/save_cache/action.yml +++ b/.github/actions/save_cache/action.yml @@ -3,7 +3,7 @@ description: Save conan and ccache cache for develop branch inputs: conan_dir: - description: Path to .conan directory + description: Path to Conan directory required: true conan_profile: description: Conan profile name 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 ca791ea2e..84deeee41 100644 --- a/docs/build-clio.md +++ b/docs/build-clio.md @@ -19,7 +19,7 @@ ### Conan Configuration -Clio requires `compiler.cppstd=20` in your Conan profile (`~/.conan/profiles/default`). +Clio requires `compiler.cppstd=20` in your Conan profile (`~/.conan2/profiles/default`). > [!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. @@ -67,12 +67,12 @@ conan remote add --index 0 ripple http://18.143.149.228:8081/artifactory/api/con Now you should be able to download the prebuilt `xrpl` package on some 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). +> You may need to edit the `~/.conan2/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). -Remove old packages you may have cached. +Remove old packages you may have cached interactively. ```sh -conan remove -f xrpl +conan remove xrpl ``` ## Building Clio @@ -152,24 +152,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 +180,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' ] From ac5fcc7f4b969e3aeabbeee01c31fa14714e125b Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 13 Jun 2025 13:51:59 +0100 Subject: [PATCH 12/49] feat: Add conan lockfile (#2220) --- .github/workflows/build.yml | 1 + .github/workflows/sanitizers.yml | 1 + .pre-commit-config.yaml | 2 +- conan.lock | 57 ++++++++++++++++++++++++++++++++ docs/build-clio.md | 15 +++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 conan.lock diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3fdbe25ec..fc8076c39 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/**" diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index e831e1920..1e54b9547 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/**" 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/conan.lock b/conan.lock new file mode 100644 index 000000000..b52238f17 --- /dev/null +++ b/conan.lock @@ -0,0 +1,57 @@ +{ + "version": "0.5", + "requires": [ + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1745733265.169", + "xxhash/0.8.2#7856c968c985b2981b707ee8f2413b2b%1745733265.369", + "xrpl/2.5.0-rc1#e5897e048ea5712d2c71561c507d949d%1749232354.05", + "sqlite3/3.47.0#7a0904fd061f5f8a2366c294f9387830%1745733265.179", + "soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1745996961.386", + "re2/20230301#dfd6e2bf050eb90ddd8729cfb4c844a4%1748907478.301", + "rapidjson/cci.20220822#1b9d8c2256876a154172dc5cfbe447c6%1745998516.625", + "protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1748907473.682", + "openssl/1.1.1v#216374e4fb5b2e0f5ab1fb6f27b5b434%1745733265.17", + "nudb/2.0.8#63990d3e517038e04bf529eb8167f69f%1745995837.29", + "minizip/1.2.13#9e87d57804bd372d6d1e32b1871517a3%1745998515.33", + "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1745733265.361", + "libuv/1.46.0#78565d142ac7102776256328a26cdf60%1745998515.314", + "libiconv/1.17#1ae2f60ab5d08de1643a22a81b360c59%1745984706.599", + "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1745733265.166", + "libarchive/3.7.6#e0453864b2a4d225f06b3304903cb2b7%1745733265.013", + "http_parser/2.9.4#98d91690d6fd021e9e624218a85d9d97%1745998514.902", + "gtest/1.14.0#f8f0757a574a8dd747d16af62d6eb1b7%1749481752.277", + "grpc/1.50.1#02291451d1e17200293a409410d1c4e1%1748907469.938", + "fmt/10.1.1#021e170cf81db57da82b5f737b6906c1%1745998514.906", + "date/3.0.3#cf28fe9c0aab99fe12da08aa42df65e1%1745733264.487", + "cassandra-cpp-driver/2.17.0#e50919efac8418c26be6671fd702540a%1745998514.898", + "c-ares/1.34.5#b78b91e7cfb1f11ce777a285bbf169c6%1748907468.021", + "bzip2/1.0.8#00b4a4658791c1f06914e087f0e792f5%1745733264.985", + "boost/1.83.0#8eb22f36ddfb61f54bbc412c4555bd66%1748907463.167", + "benchmark/1.8.3#1a2ce62c99e2b3feaa57b1f0c15a8c46%1724323740.181", + "abseil/20230802.1#f0f91485b111dc9837a68972cb19ca7b%1748907459.618" + ], + "build_requires": [ + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1745733265.169", + "protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1748907473.682", + "protobuf/3.21.9#64ce20e1d9ea24f3d6c504015d5f6fa8%1748596927.759", + "cmake/3.31.6#ed0e6c1d49bd564ce6fed1a19653b86d%1745733264.493", + "b2/5.3.2#7b5fabfe7088ae933fb3e78302343ea0%1745733264.471" + ], + "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/docs/build-clio.md b/docs/build-clio.md index 84deeee41..58eb35e67 100644 --- a/docs/build-clio.md +++ b/docs/build-clio.md @@ -75,6 +75,21 @@ Remove old packages you may have cached interactively. conan remove xrpl ``` +#### Conan lockfile + +To achieve reproducible dependencies, we use [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html). + +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 Navigate to Clio's root directory and run: From 59bb9a11abdc37ab84063156d9ac996c20becb4d Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 13 Jun 2025 13:55:35 +0100 Subject: [PATCH 13/49] ci: Upload conan deps for all profiles (#2217) --- .github/workflows/upload_conan_deps.yml | 81 +++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 .github/workflows/upload_conan_deps.yml diff --git a/.github/workflows/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml new file mode 100644 index 000000000..96280bbb0 --- /dev/null +++ b/.github/workflows/upload_conan_deps.yml @@ -0,0 +1,81 @@ +name: Upload Conan Dependencies + +on: + workflow_dispatch: + pull_request: + branches: + - develop + paths: + - .github/workflows/upload_conan_deps.yml + - conanfile.py + push: + branches: + - develop + paths: + - .github/workflows/upload_conan_deps.yml + - conanfile.py + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + upload-conan-deps: + name: Build and Upload Conan Deps + + strategy: + fail-fast: false + matrix: + os: [heavy] + container: ['{ "image": "ghcr.io/xrplf/clio-ci:latest" }'] + compiler: ["gcc", "clang"] + sanitizer_ext: [".asan", ".tsan", ".ubsan"] + build_type: ["Release"] + include: + - os: heavy + container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' + compiler: gcc + sanitizer_ext: "" + build_type: Debug + - os: macos15 + container: "" + compiler: default_apple_clang + sanitizer_ext: "" + build_type: Release + + 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 + uses: ./.github/actions/setup_conan + with: + conan_profile: ${{ env.CONAN_PROFILE }} + + - 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 }} + conan_cache_hit: "false" + build_type: Release + + - 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' + run: conan upload "*" -r=ripple --confirm From 7fcabd1ce72a7707f820bb62a935a3483a54bc77 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 13 Jun 2025 16:53:04 +0100 Subject: [PATCH 14/49] feat: Build all possible conan configurations in CI (#2225) --- .github/workflows/upload_conan_deps.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml index 96280bbb0..f0c0ff433 100644 --- a/.github/workflows/upload_conan_deps.yml +++ b/.github/workflows/upload_conan_deps.yml @@ -26,17 +26,24 @@ jobs: strategy: fail-fast: false matrix: - os: [heavy] + os: [heavy, heavy-arm64] container: ['{ "image": "ghcr.io/xrplf/clio-ci:latest" }'] compiler: ["gcc", "clang"] - sanitizer_ext: [".asan", ".tsan", ".ubsan"] - build_type: ["Release"] - include: - - os: heavy + sanitizer_ext: [".asan", ".tsan", ".ubsan", ""] + build_type: ["Release", "Debug"] + # libbacktrace doesn't build on arm64 with gcc.tsan + exclude: + - os: heavy-arm64 container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' compiler: gcc - sanitizer_ext: "" + sanitizer_ext: .tsan + build_type: Release + - os: heavy-arm64 + container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' + compiler: gcc + sanitizer_ext: .tsan build_type: Debug + include: - os: macos15 container: "" compiler: default_apple_clang @@ -70,7 +77,7 @@ jobs: with: conan_profile: ${{ env.CONAN_PROFILE }} conan_cache_hit: "false" - build_type: Release + build_type: ${{ matrix.build_type }} - name: Login to Conan if: github.event_name != 'pull_request' From 3d3db68508d67b324b4b9095d67dc525b50f2adf Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Fri, 13 Jun 2025 17:39:21 +0100 Subject: [PATCH 15/49] feat: Support start and finish sequence in ETLng (#2226) This PR adds support for start and finish sequence specified through the config (for parity). --- src/etl/ETLService.cpp | 2 ++ src/etlng/ETLService.cpp | 26 ++++++++++++++++------ src/etlng/ETLService.hpp | 4 +++- src/etlng/TaskManagerProviderInterface.hpp | 11 +++++++-- src/etlng/impl/TaskManagerProvider.hpp | 16 +++++++++---- tests/unit/etlng/ETLServiceTests.cpp | 23 ++++++++++++------- 6 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/etl/ETLService.cpp b/src/etl/ETLService.cpp index 496f019f2..f5cf1fbb9 100644 --- a/src/etl/ETLService.cpp +++ b/src/etl/ETLService.cpp @@ -97,6 +97,8 @@ ETLService::makeETLService( auto amendmentBlockHandler = std::make_shared(ctx, *state); auto monitorProvider = std::make_shared(); + backend->setCorruptionDetector(CorruptionDetector{*state, backend->cache()}); + auto loader = std::make_shared( backend, etlng::impl::makeRegistry( diff --git a/src/etlng/ETLService.cpp b/src/etlng/ETLService.cpp index 991b867af..248a3662a 100644 --- a/src/etlng/ETLService.cpp +++ b/src/etlng/ETLService.cpp @@ -52,7 +52,6 @@ #include "util/Assert.hpp" #include "util/Profiler.hpp" #include "util/async/AnyExecutionContext.hpp" -#include "util/async/AnyOperation.hpp" #include "util/log/Logger.hpp" #include @@ -100,8 +99,17 @@ ETLService::ETLService( , taskManagerProvider_(std::move(taskManagerProvider)) , monitorProvider_(std::move(monitorProvider)) , state_(std::move(state)) + , startSequence_(config.get().maybeValue("start_sequence")) + , finishSequence_(config.get().maybeValue("finish_sequence")) { 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"); } @@ -204,11 +212,15 @@ ETLService::loadInitialLedgerIfNeeded() LOG(log_.info()) << "Database is empty. Will download a ledger from the network."; state_->isWriting = true; // immediately become writer as the db is empty - 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 and extracting (takes a while)..."; + 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) { @@ -280,7 +292,7 @@ void ETLService::startLoading(uint32_t seq) { ASSERT(not state_->isStrictReadonly, "This should only happen on writer nodes"); - taskMan_ = taskManagerProvider_->make(ctx_, *monitor_, seq); + taskMan_ = taskManagerProvider_->make(ctx_, *monitor_, seq, finishSequence_); taskMan_->run(config_.get().get("extractor_threads")); } diff --git a/src/etlng/ETLService.hpp b/src/etlng/ETLService.hpp index 5ec75e0e5..4dc061e1b 100644 --- a/src/etlng/ETLService.hpp +++ b/src/etlng/ETLService.hpp @@ -69,7 +69,6 @@ #include #include -#include #include #include #include @@ -110,6 +109,9 @@ class ETLService : public ETLServiceInterface { std::shared_ptr monitorProvider_; std::shared_ptr state_; + std::optional startSequence_; + std::optional finishSequence_; + std::unique_ptr monitor_; std::unique_ptr taskMan_; diff --git a/src/etlng/TaskManagerProviderInterface.hpp b/src/etlng/TaskManagerProviderInterface.hpp index 532c0ad73..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 */ [[nodiscard]] virtual std::unique_ptr - make(util::async::AnyExecutionContext ctx, std::reference_wrapper monitor, uint32_t seq) = 0; + 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/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/tests/unit/etlng/ETLServiceTests.cpp b/tests/unit/etlng/ETLServiceTests.cpp index 4e5806790..676c2adfe 100644 --- a/tests/unit/etlng/ETLServiceTests.cpp +++ b/tests/unit/etlng/ETLServiceTests.cpp @@ -46,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" @@ -135,7 +136,10 @@ 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) ); }; @@ -181,6 +185,8 @@ 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)}, @@ -306,7 +312,7 @@ TEST_F(ETLServiceTests, RunWithEmptyDatabase) .InSequence(s) .WillOnce(testing::Return(data::LedgerRange{.minSequence = 1, .maxSequence = kSEQ})); EXPECT_CALL(mockTaskManagerRef, run); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, kSEQ + 1)) + 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>(); }); @@ -318,8 +324,9 @@ TEST_F(ETLServiceTests, RunWithPopulatedDatabase) { EXPECT_CALL(*backend_, hardFetchLedgerRange) .WillRepeatedly(testing::Return(data::LedgerRange{.minSequence = 1, .maxSequence = kSEQ})); - EXPECT_CALL(*monitorProvider_, make(testing::_, testing::_, testing::_, testing::_, testing::_)) - .WillOnce([](auto, auto, auto, auto, auto) { return std::make_unique>(); }); + 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)); @@ -335,7 +342,7 @@ TEST_F(ETLServiceTests, WaitForValidatedLedgerIsAborted) EXPECT_CALL(*extractor_, extractLedgerOnly).Times(0); EXPECT_CALL(*balancer_, loadInitialLedger(testing::_, testing::_, testing::_)).Times(0); EXPECT_CALL(*loader_, loadInitialLedger).Times(0); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, testing::_)).Times(0); + EXPECT_CALL(*taskManagerProvider_, make).Times(0); service_.run(); } @@ -437,7 +444,7 @@ TEST_F(ETLServiceTests, AttemptTakeoverWriter) auto& mockTaskManagerRef = *mockTaskManager; EXPECT_CALL(mockTaskManagerRef, run); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, kSEQ + 1)) + EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, kSEQ + 1, testing::_)) .WillOnce(testing::Return(std::move(mockTaskManager))); ASSERT_TRUE(capturedDbStalledCallback); @@ -492,7 +499,7 @@ TEST_F(ETLServiceAssertTests, FailToLoadInitialLedger) // These calls should not happen because loading the initial ledger fails EXPECT_CALL(*balancer_, loadInitialLedger(testing::_, testing::_, testing::_)).Times(0); EXPECT_CALL(*loader_, loadInitialLedger).Times(0); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, testing::_)).Times(0); + EXPECT_CALL(*taskManagerProvider_, make).Times(0); EXPECT_CLIO_ASSERT_FAIL({ service_.run(); }); } @@ -508,7 +515,7 @@ TEST_F(ETLServiceAssertTests, WaitForValidatedLedgerIsAbortedLeadToFailToLoadIni EXPECT_CALL(*extractor_, extractLedgerOnly).Times(0); EXPECT_CALL(*balancer_, loadInitialLedger(testing::_, testing::_, testing::_)).Times(0); EXPECT_CALL(*loader_, loadInitialLedger).Times(0); - EXPECT_CALL(*taskManagerProvider_, make(testing::_, testing::_, testing::_)).Times(0); + EXPECT_CALL(*taskManagerProvider_, make).Times(0); EXPECT_CLIO_ASSERT_FAIL({ service_.run(); }); } From 95698ee2ded74468f27e3388a05dd9e5f83c25aa Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 13 Jun 2025 17:40:55 +0100 Subject: [PATCH 16/49] fix: Run upload_conan_deps.yml on conan.lock changes (#2227) --- .github/workflows/upload_conan_deps.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml index f0c0ff433..6c0c8f024 100644 --- a/.github/workflows/upload_conan_deps.yml +++ b/.github/workflows/upload_conan_deps.yml @@ -8,12 +8,14 @@ on: paths: - .github/workflows/upload_conan_deps.yml - conanfile.py + - conan.lock push: branches: - develop paths: - .github/workflows/upload_conan_deps.yml - conanfile.py + - conan.lock concurrency: group: ${{ github.workflow }}-${{ github.ref }} From f58c85d203ed790d8d2c83f801377e0472c4965c Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 16 Jun 2025 15:42:19 +0100 Subject: [PATCH 17/49] fix: Return domainMalformed when domain is malformed (#2228) Fix: https://github.com/XRPLF/clio/issues/2222 Code in rippled to handle errors: https://github.com/XRPLF/rippled/blob/2.5.0-rc1/src/xrpld/rpc/handlers/BookOffers.cpp#L183 --- src/rpc/handlers/BookOffers.hpp | 13 ++++++++----- tests/unit/rpc/handlers/BookOffersTests.cpp | 12 ++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) 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/tests/unit/rpc/handlers/BookOffersTests.cpp b/tests/unit/rpc/handlers/BookOffersTests.cpp index 765ec30b3..1db9e9697 100644 --- a/tests/unit/rpc/handlers/BookOffersTests.cpp +++ b/tests/unit/rpc/handlers/BookOffersTests.cpp @@ -327,8 +327,8 @@ generateParameterBookOffersTestBundles() }, "domain": 0 })JSON", - .expectedError = "invalidParams", - .expectedErrorMessage = "Invalid parameters." + .expectedError = "domainMalformed", + .expectedErrorMessage = "Unable to parse domain." }, ParameterTestBundle{ .testName = "Domain_InvalidInt", @@ -344,8 +344,8 @@ generateParameterBookOffersTestBundles() }, "domain": "123" })JSON", - .expectedError = "invalidParams", - .expectedErrorMessage = "domainMalformed" + .expectedError = "domainMalformed", + .expectedErrorMessage = "Unable to parse domain." }, ParameterTestBundle{ .testName = "Domain_InvalidObject", @@ -361,8 +361,8 @@ generateParameterBookOffersTestBundles() }, "domain": {} })JSON", - .expectedError = "invalidParams", - .expectedErrorMessage = "Invalid parameters." + .expectedError = "domainMalformed", + .expectedErrorMessage = "Unable to parse domain." }, ParameterTestBundle{ .testName = "LimitNotInt", From 7584a683ddd75444807e919c45334cba46e2d9ff Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 17 Jun 2025 14:22:06 +0100 Subject: [PATCH 18/49] fix: Add domain to book_changes (#2229) Fix: https://github.com/XRPLF/clio/issues/2221 --- src/rpc/BookChangesHelper.hpp | 3 + tests/common/util/TestObject.cpp | 13 ++- tests/common/util/TestObject.hpp | 7 +- tests/unit/rpc/handlers/BookChangesTests.cpp | 106 +++++++++++++------ 4 files changed, 90 insertions(+), 39 deletions(-) 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/tests/common/util/TestObject.cpp b/tests/common/util/TestObject.cpp index 7d2152025..4f10b5937 100644 --- a/tests/common/util/TestObject.cpp +++ b/tests/common/util/TestObject.cpp @@ -328,18 +328,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 +487,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); diff --git a/tests/common/util/TestObject.hpp b/tests/common/util/TestObject.hpp index 84c1cc979..3e4ba2f38 100644 --- a/tests/common/util/TestObject.hpp +++ b/tests/common/util/TestObject.hpp @@ -182,9 +182,10 @@ createMetaDataForBookChange( std::string_view issueId, uint32_t transactionIndex, int finalTakerGets, - int perviousTakerGets, + int previousTakerGets, int finalTakerPays, - int perviousTakerPays + int previousTakerPays, + std::optional 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 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) { From 082f2fe21edf5fc0989c06bb4ec28e0a413539da Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 17 Jun 2025 16:07:19 +0100 Subject: [PATCH 19/49] style: Put static before type (#2231) --- src/rpc/Errors.cpp | 2 +- src/rpc/common/ValidationHelpers.hpp | 3 +- src/rpc/handlers/GatewayBalances.hpp | 4 +- src/util/prometheus/MetricBuilder.hpp | 3 +- src/util/prometheus/Prometheus.hpp | 3 +- .../integration/data/BackendFactoryTests.cpp | 2 +- .../CassandraMigrationTestBackend.hpp | 14 ++--- tests/unit/etl/LoadBalancerTests.cpp | 4 +- .../unit/etlng/AmendmentBlockHandlerTests.cpp | 2 +- tests/unit/rpc/handlers/AMMInfoTests.cpp | 34 +++++------ .../rpc/handlers/AccountCurrenciesTests.cpp | 14 ++--- tests/unit/rpc/handlers/AccountInfoTests.cpp | 24 ++++---- tests/unit/rpc/handlers/AccountNFTsTests.cpp | 24 ++++---- .../unit/rpc/handlers/AccountObjectsTests.cpp | 58 +++++++++---------- .../unit/rpc/handlers/AccountOffersTests.cpp | 20 +++---- tests/unit/rpc/handlers/AccountTxTests.cpp | 36 ++++++------ tests/unit/rpc/handlers/AllHandlerTests.cpp | 10 ++-- tests/unit/rpc/handlers/BookOffersTests.cpp | 10 ++-- tests/unit/rpc/handlers/NFTHistoryTests.cpp | 30 +++++----- .../unit/rpc/handlers/NoRippleCheckTests.cpp | 6 +- tests/unit/rpc/handlers/TxTests.cpp | 4 +- 21 files changed, 155 insertions(+), 152 deletions(-) diff --git a/src/rpc/Errors.cpp b/src/rpc/Errors.cpp index b57f85bf7..b71679f35 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."}, 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/handlers/GatewayBalances.hpp b/src/rpc/handlers/GatewayBalances.hpp index 08a9e97d2..f26abfe98 100644 --- a/src/rpc/handlers/GatewayBalances.hpp +++ b/src/rpc/handlers/GatewayBalances.hpp @@ -147,9 +147,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/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/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/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/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/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/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/AccountCurrenciesTests.cpp b/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp index dedb02945..ac7990af5 100644 --- a/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp +++ b/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp @@ -73,7 +73,7 @@ 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":"{}" }})JSON", @@ -95,7 +95,7 @@ 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":"{}" }})JSON", @@ -119,7 +119,7 @@ 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":"{}" @@ -144,7 +144,7 @@ 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":"{}" @@ -210,7 +210,7 @@ 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":"{}" }})JSON", @@ -245,7 +245,7 @@ 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":"{}" @@ -282,7 +282,7 @@ 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":{} diff --git a/tests/unit/rpc/handlers/AccountInfoTests.cpp b/tests/unit/rpc/handlers/AccountInfoTests.cpp index d67b1169c..2f1a4fd0b 100644 --- a/tests/unit/rpc/handlers/AccountInfoTests.cpp +++ b/tests/unit/rpc/handlers/AccountInfoTests.cpp @@ -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..49ad67d57 100644 --- a/tests/unit/rpc/handlers/AccountNFTsTests.cpp +++ b/tests/unit/rpc/handlers/AccountNFTsTests.cpp @@ -181,7 +181,7 @@ 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":"{}" @@ -207,7 +207,7 @@ 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":"{}" @@ -233,7 +233,7 @@ 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":{} @@ -260,7 +260,7 @@ 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":"{}" }})JSON", @@ -323,7 +323,7 @@ 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":"{}" }})JSON", @@ -357,7 +357,7 @@ 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":{} @@ -392,7 +392,7 @@ 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":"{}" @@ -419,7 +419,7 @@ 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":"{}" @@ -448,7 +448,7 @@ 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":"{}" }})JSON", @@ -480,7 +480,7 @@ 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":"{}" @@ -546,7 +546,7 @@ 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":{} @@ -610,7 +610,7 @@ 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":{} diff --git a/tests/unit/rpc/handlers/AccountObjectsTests.cpp b/tests/unit/rpc/handlers/AccountObjectsTests.cpp index a9b0cd398..3a8b90762 100644 --- a/tests/unit/rpc/handlers/AccountObjectsTests.cpp +++ b/tests/unit/rpc/handlers/AccountObjectsTests.cpp @@ -223,7 +223,7 @@ 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 @@ -245,7 +245,7 @@ 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" @@ -268,7 +268,7 @@ 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":"{}" @@ -293,7 +293,7 @@ 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":"{}" }})JSON", @@ -365,7 +365,7 @@ 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":"{}" }})JSON", @@ -409,7 +409,7 @@ 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":{} @@ -453,7 +453,7 @@ 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":"{},{}" @@ -507,7 +507,7 @@ 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":{} @@ -561,7 +561,7 @@ 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" @@ -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,7 +658,7 @@ 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":"{}", "type": "check" @@ -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, @@ -992,7 +992,7 @@ 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":"{}" }})JSON", @@ -1031,7 +1031,7 @@ 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":{} @@ -1080,7 +1080,7 @@ 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":{} @@ -1158,7 +1158,7 @@ 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":"{},{}" @@ -1214,7 +1214,7 @@ 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":"{},{}" @@ -1242,7 +1242,7 @@ 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" : "{},{}" @@ -1275,7 +1275,7 @@ 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" : "{},{}" @@ -1349,7 +1349,7 @@ 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":"{},{}", @@ -1445,7 +1445,7 @@ 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":"{}", "type": "nft_page" @@ -1486,7 +1486,7 @@ 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":{}, @@ -1566,7 +1566,7 @@ 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":"{}", "limit": {} @@ -1642,7 +1642,7 @@ 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":"{}", "limit": {} @@ -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..373d38a89 100644 --- a/tests/unit/rpc/handlers/AccountOffersTests.cpp +++ b/tests/unit/rpc/handlers/AccountOffersTests.cpp @@ -176,7 +176,7 @@ 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":"{}" @@ -202,7 +202,7 @@ 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":"{}" @@ -228,7 +228,7 @@ 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":{} @@ -255,7 +255,7 @@ 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":"{}" }})JSON", @@ -332,7 +332,7 @@ 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":"{}" }})JSON", @@ -380,7 +380,7 @@ 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 @@ -433,7 +433,7 @@ 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":"{},{}" @@ -469,7 +469,7 @@ 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":"{},{}" @@ -527,7 +527,7 @@ 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":{} @@ -582,7 +582,7 @@ 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":{} diff --git a/tests/unit/rpc/handlers/AccountTxTests.cpp b/tests/unit/rpc/handlers/AccountTxTests.cpp index a4314d1ee..e93373988 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,7 +784,7 @@ 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": {}, @@ -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..72040ea41 100644 --- a/tests/unit/rpc/handlers/AllHandlerTests.cpp +++ b/tests/unit/rpc/handlers/AllHandlerTests.cpp @@ -74,11 +74,11 @@ 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"; using AnyHandlerType = Types< AccountChannelsHandler, diff --git a/tests/unit/rpc/handlers/BookOffersTests.cpp b/tests/unit/rpc/handlers/BookOffersTests.cpp index 1db9e9697..9ff8b305e 100644 --- a/tests/unit/rpc/handlers/BookOffersTests.cpp +++ b/tests/unit/rpc/handlers/BookOffersTests.cpp @@ -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/NFTHistoryTests.cpp b/tests/unit/rpc/handlers/NFTHistoryTests.cpp index ce921a53f..6551b49bb 100644 --- a/tests/unit/rpc/handlers/NFTHistoryTests.cpp +++ b/tests/unit/rpc/handlers/NFTHistoryTests.cpp @@ -310,7 +310,7 @@ 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":"{}", "ledger_index_min": {}, @@ -454,7 +454,7 @@ 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":"{}", "ledger_index_min": {}, @@ -608,7 +608,7 @@ 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":"{}", "ledger_index_min": {}, @@ -640,7 +640,7 @@ 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":"{}", "ledger_index_min": {}, @@ -681,7 +681,7 @@ 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":"{}", "ledger_index_min": {}, @@ -722,7 +722,7 @@ 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":"{}", "ledger_index_min": {}, @@ -776,7 +776,7 @@ 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":"{}", "ledger_index_min": {}, @@ -827,7 +827,7 @@ 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":"{}", "ledger_index_min": {}, @@ -875,7 +875,7 @@ 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":{} @@ -901,7 +901,7 @@ 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":{} @@ -924,7 +924,7 @@ 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":"{}" @@ -964,7 +964,7 @@ 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":"{}" @@ -1002,7 +1002,7 @@ 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":"{}", "ledger_index_min": {}, @@ -1043,7 +1043,7 @@ 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":"{}", "ledger_index_min": {}, @@ -1084,7 +1084,7 @@ 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":"{}", "ledger_index_min": {}, diff --git a/tests/unit/rpc/handlers/NoRippleCheckTests.cpp b/tests/unit/rpc/handlers/NoRippleCheckTests.cpp index d2eb27e6d..0793b8afa 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", diff --git a/tests/unit/rpc/handlers/TxTests.cpp b/tests/unit/rpc/handlers/TxTests.cpp index 60c90fb9c..3fda9bee4 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", @@ -992,7 +992,7 @@ 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", From 67b27ee344c29e94841c0ff02d0413e0c192ff44 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 17 Jun 2025 18:45:41 +0100 Subject: [PATCH 20/49] =?UTF-8?q?fix:=20Update=20CI=20image=20to=20provide?= =?UTF-8?q?=20global.conf=20for=20sanitizers=20to=20affect=20=E2=80=A6=20(?= =?UTF-8?q?#2233)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …package_id --- docker/ci/Dockerfile | 3 +++ docker/ci/conan/global.conf | 1 + docker/ci/conan/sanitizer_template.profile | 16 +++++++++------- 3 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 docker/ci/conan/global.conf diff --git a/docker/ci/Dockerfile b/docker/ci/Dockerfile index 20aed2966..db60e0e7d 100644 --- a/docker/ci/Dockerfile +++ b/docker/ci/Dockerfile @@ -87,6 +87,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/global.conf b/docker/ci/conan/global.conf new file mode 100644 index 000000000..c61cf077f --- /dev/null +++ b/docker/ci/conan/global.conf @@ -0,0 +1 @@ +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 9d70d17e1..5f015335f 100644 --- a/docker/ci/conan/sanitizer_template.profile +++ b/docker/ci/conan/sanitizer_template.profile @@ -2,17 +2,19 @@ {% set sanitizer_opt_map = {'asan': 'address', 'tsan': 'thread', 'ubsan': 'undefined'} %} {% set sanitizer = sanitizer_opt_map[sani] %} -{% set sanitizer_build_flags = "-fsanitize=" ~ sanitizer ~ " -g -O1 -fno-omit-frame-pointer" %} -{% set sanitizer_link_flags = "-fsanitize=" ~ sanitizer %} +{% 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=\"{{ sanitizer_build_flags }}\" linkflags=\"{{ sanitizer_link_flags }}\"" +boost/*:extra_b2_flags = "cxxflags=\"{{ sanitizer_build_flags_str }}\" linkflags=\"{{ sanitizer_link_flags_str }}\"" boost/*:without_stacktrace = True [conf] -tools.build:cflags += ["{{ sanitizer_build_flags }}"] -tools.build:cxxflags += ["{{ sanitizer_build_flags }}"] -tools.build:exelinkflags += ["{{ sanitizer_link_flags }}"] -tools.build:sharedlinkflags += ["{{ sanitizer_link_flags }}"] +tools.build:cflags += {{ sanitizer_build_flags }} +tools.build:cxxflags += {{ sanitizer_build_flags }} +tools.build:exelinkflags += {{ sanitizer_link_flags }} +tools.build:sharedlinkflags += {{ sanitizer_link_flags }} From f20efae75aa0cd8b4f8682222db9ea242736d3eb Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 17 Jun 2025 19:26:25 +0100 Subject: [PATCH 21/49] =?UTF-8?q?fix:=20Use=20new=20CI=20image=20with=20gl?= =?UTF-8?q?obal.conf=20for=20sanitizers=20to=20affect=20packa=E2=80=A6=20(?= =?UTF-8?q?#2234)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/setup_conan/action.yml | 22 ----------- .github/actions/setup_conan_macos/action.yml | 39 ++++++++++++++++++++ .github/dependabot.yml | 2 +- .github/workflows/build_impl.yml | 4 +- .github/workflows/check_libxrpl.yml | 5 --- .github/workflows/clang-tidy.yml | 5 --- .github/workflows/upload_conan_deps.yml | 4 +- 7 files changed, 46 insertions(+), 35 deletions(-) delete mode 100644 .github/actions/setup_conan/action.yml create mode 100644 .github/actions/setup_conan_macos/action.yml 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/actions/setup_conan_macos/action.yml b/.github/actions/setup_conan_macos/action.yml new file mode 100644 index 000000000..f87359fbe --- /dev/null +++ b/.github/actions/setup_conan_macos/action.yml @@ -0,0 +1,39 @@ +name: Setup conan +description: Setup conan profile and artifactory on macOS runner + +inputs: + conan_profile: + description: Conan profile name + required: true + global_conf_file: + description: Path to global.conf file + required: true + +runs: + using: composite + steps: + - name: Fail on non-macOS + if: runner.os != 'macOS' + shell: bash + run: exit 1 + + - name: Check profile name is default_apple_clang + if: inputs.conan_profile != 'default_apple_clang' + shell: bash + run: exit 1 + + - name: Create conan profile + shell: bash + run: | + conan profile detect --name "${{ inputs.conan_profile }}" + sed -i '' 's/compiler.cppstd=[^ ]*/compiler.cppstd=20/' "${{ env.CONAN_HOME }}/profiles/${{ inputs.conan_profile }}" + + - name: Copy global.conf + shell: bash + run: | + cp "${{ inputs.global_conf_file }}" "${{ env.CONAN_HOME }}/global.conf" + + - name: Add artifactory remote + shell: bash + run: | + conan remote add --index 0 ripple http://18.143.149.228:8081/artifactory/api/conan/dev diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e6d1d3873..ff5282bdb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -144,7 +144,7 @@ updates: target-branch: develop - package-ecosystem: github-actions - directory: .github/actions/setup_conan/ + directory: .github/actions/setup_conan_macos/ schedule: interval: weekly day: monday diff --git a/.github/workflows/build_impl.yml b/.github/workflows/build_impl.yml index 1a19ea58d..f04eeb678 100644 --- a/.github/workflows/build_impl.yml +++ b/.github/workflows/build_impl.yml @@ -78,9 +78,11 @@ jobs: disable_ccache: ${{ inputs.disable_cache }} - name: Setup conan - uses: ./.github/actions/setup_conan + if: runner.os == 'macOS' + uses: ./.github/actions/setup_conan_macos with: conan_profile: ${{ inputs.conan_profile }} + global_conf_file: docker/ci/conan/global.conf - name: Restore cache if: ${{ !inputs.disable_cache }} diff --git a/.github/workflows/check_libxrpl.yml b/.github/workflows/check_libxrpl.yml index a3e5e8fb7..df9041d53 100644 --- a/.github/workflows/check_libxrpl.yml +++ b/.github/workflows/check_libxrpl.yml @@ -34,11 +34,6 @@ jobs: 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: diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 60239f1b0..2b26ae893 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -40,11 +40,6 @@ 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 diff --git a/.github/workflows/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml index 6c0c8f024..090c50316 100644 --- a/.github/workflows/upload_conan_deps.yml +++ b/.github/workflows/upload_conan_deps.yml @@ -67,9 +67,11 @@ jobs: disable_ccache: true - name: Setup conan - uses: ./.github/actions/setup_conan + if: runner.os == 'macOS' + uses: ./.github/actions/setup_conan_macos with: conan_profile: ${{ env.CONAN_PROFILE }} + global_conf_file: docker/ci/conan/global.conf - name: Show conan profile run: conan profile show --profile:all ${{ env.CONAN_PROFILE }} From 4364c07f1eb038022eeed2c8b80cfe654219b007 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 21:30:16 +0100 Subject: [PATCH 22/49] ci: [DEPENDABOT] bump docker/setup-buildx-action from 3.10.0 to 3.11.0 in /.github/actions/build_docker_image (#2235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.10.0 to 3.11.0.
Release notes

Sourced from docker/setup-buildx-action's releases.

v3.11.0

Full Changelog: https://github.com/docker/setup-buildx-action/compare/v3.10.0...v3.11.0

Commits
  • 18ce135 Merge pull request #425 from docker/dependabot/npm_and_yarn/docker/actions-to...
  • 0e198e9 chore: update generated content
  • 05f3f3a build(deps): bump @​docker/actions-toolkit from 0.61.0 to 0.62.1
  • 6229134 Merge pull request #427 from crazy-max/keep-state
  • c6f6a07 chore: update generated content
  • 6c5e29d skip builder creation if one already exists with the same name
  • 548b297 ci: keep-state check
  • 36590ad check if driver compatible with keep-state
  • 4143b58 Support to retain cache
  • 3f1544e Merge pull request #139 from hashhar/hashhar/cleanup-aliases
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/setup-buildx-action&package-manager=github_actions&previous-version=3.10.0&new-version=3.11.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/build_docker_image/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build_docker_image/action.yml b/.github/actions/build_docker_image/action.yml index 831ee5402..95c6f953b 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@18ce135bb5112fa8ce4ed6c17ab05699d7f3a5e0 # v3.11.0 - uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 id: meta From 4ed51c22d01269396c5c4d98d679933843f9003d Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 18 Jun 2025 11:14:03 +0100 Subject: [PATCH 23/49] fix: Force reupload new artifacts (#2236) The issue is that we previously didn't care about `[conf]` section. And for example, we uploaded `clang.ubsan` build with the same package_id as a regular clang build. This was fixed in https://github.com/XRPLF/clio/pull/2233 and https://github.com/XRPLF/clio/pull/2234 Adding `global.conf` almost fixed the problem, but since our non-sanitized builds don't set anything in `[conf]`, we use the same package_id as before. So, for the `clang` build we end up with previously uploaded `clang.ubsan` build artifacts. To fix this, we should force the upload. --- .github/workflows/upload_conan_deps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml index 090c50316..666aa62d4 100644 --- a/.github/workflows/upload_conan_deps.yml +++ b/.github/workflows/upload_conan_deps.yml @@ -89,4 +89,4 @@ jobs: - name: Upload Conan packages if: github.event_name != 'pull_request' - run: conan upload "*" -r=ripple --confirm + run: conan upload "*" -r=ripple --confirm --force From 534518f13eb618f12c6c9b624db8c616d28ad1bb Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 18 Jun 2025 11:26:46 +0100 Subject: [PATCH 24/49] docs: Add information about global.conf (#2238) --- docs/build-clio.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/build-clio.md b/docs/build-clio.md index 58eb35e67..9c8909420 100644 --- a/docs/build-clio.md +++ b/docs/build-clio.md @@ -56,6 +56,12 @@ os=Linux tools.build:compiler_executables={'c': '/usr/bin/gcc-12', 'cpp': '/usr/bin/g++-12'} ``` +Add the following to the `~/.conan2/global.conf` file: + +```text +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. From ebfe4e6468160e02be022ebcafc59607a0e303d0 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 18 Jun 2025 15:25:40 +0100 Subject: [PATCH 25/49] ci: Don't use save/restore cache for conan; use artifactory (#2230) --- .github/actions/generate/action.yml | 8 +++--- .github/actions/restore_cache/action.yml | 30 ++------------------ .github/actions/save_cache/action.yml | 35 +++++------------------- .github/workflows/build_and_test.yml | 2 +- .github/workflows/build_impl.yml | 14 ++++------ .github/workflows/check_libxrpl.yml | 2 -- .github/workflows/clang-tidy.yml | 5 +--- .github/workflows/upload_conan_deps.yml | 2 +- 8 files changed, 21 insertions(+), 77 deletions(-) diff --git a/.github/actions/generate/action.yml b/.github/actions/generate/action.yml index a81daab66..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: @@ -40,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' }}" @@ -50,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" \ 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 f32f061ea..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/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index c713f5cbd..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 diff --git a/.github/workflows/build_impl.yml b/.github/workflows/build_impl.yml index f04eeb678..cc52591fd 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 @@ -89,7 +89,6 @@ jobs: 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 }} @@ -99,7 +98,6 @@ 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 }} @@ -168,15 +166,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 df9041d53..d0c524f11 100644 --- a/.github/workflows/check_libxrpl.yml +++ b/.github/workflows/check_libxrpl.yml @@ -38,8 +38,6 @@ jobs: 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 diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 2b26ae893..e8bfe4249 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -44,16 +44,13 @@ jobs: 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/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml index 666aa62d4..72fe1523c 100644 --- a/.github/workflows/upload_conan_deps.yml +++ b/.github/workflows/upload_conan_deps.yml @@ -80,7 +80,7 @@ jobs: uses: ./.github/actions/generate with: conan_profile: ${{ env.CONAN_PROFILE }} - conan_cache_hit: "false" + force_conan_source_build: "true" build_type: ${{ matrix.build_type }} - name: Login to Conan From 97956b1718f39113811735031263fea6b123936b Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 18 Jun 2025 18:20:26 +0100 Subject: [PATCH 26/49] feat: Build macos dependencies with sanitizers (#2240) --- .github/actions/setup_conan_macos/action.yml | 36 +++++++-------- .../setup_conan_macos/apple-clang.profile | 8 ++++ .github/scripts/generate_conan_matrix.py | 42 ++++++++++++++++++ .github/workflows/build.yml | 2 +- .github/workflows/build_impl.yml | 3 +- .github/workflows/nightly.yml | 4 +- .github/workflows/release.yml | 2 +- .github/workflows/upload_conan_deps.yml | 44 +++++++------------ 8 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 .github/actions/setup_conan_macos/apple-clang.profile create mode 100755 .github/scripts/generate_conan_matrix.py diff --git a/.github/actions/setup_conan_macos/action.yml b/.github/actions/setup_conan_macos/action.yml index f87359fbe..a30f88057 100644 --- a/.github/actions/setup_conan_macos/action.yml +++ b/.github/actions/setup_conan_macos/action.yml @@ -1,12 +1,9 @@ name: Setup conan -description: Setup conan profile and artifactory on macOS runner +description: Setup conan profiles and artifactory on macOS runner inputs: - conan_profile: - description: Conan profile name - required: true - global_conf_file: - description: Path to global.conf file + conan_files_dir: + description: Directory with conan files required: true runs: @@ -17,21 +14,24 @@ runs: shell: bash run: exit 1 - - name: Check profile name is default_apple_clang - if: inputs.conan_profile != 'default_apple_clang' - shell: bash - run: exit 1 - - - name: Create conan profile - shell: bash - run: | - conan profile detect --name "${{ inputs.conan_profile }}" - sed -i '' 's/compiler.cppstd=[^ ]*/compiler.cppstd=20/' "${{ env.CONAN_HOME }}/profiles/${{ inputs.conan_profile }}" - - name: Copy global.conf shell: bash run: | - cp "${{ inputs.global_conf_file }}" "${{ env.CONAN_HOME }}/global.conf" + cp "${{ inputs.conan_files_dir }}/global.conf" "${{ env.CONAN_HOME }}/global.conf" + + - name: Create apple-clang conan profile + shell: bash + run: | + mkdir -p "${{ env.CONAN_HOME }}/profiles" + cp .github/actions/setup_conan_macos/apple-clang.profile "${{ env.CONAN_HOME }}/profiles/apple-clang" + + - name: Create conan profiles for sanitizers + shell: bash + working-directory: ${{ inputs.conan_files_dir }} + run: | + cp ./sanitizer_template.profile "${{ env.CONAN_HOME }}/profiles/apple-clang.asan" + cp ./sanitizer_template.profile "${{ env.CONAN_HOME }}/profiles/apple-clang.tsan" + cp ./sanitizer_template.profile "${{ env.CONAN_HOME }}/profiles/apple-clang.ubsan" - name: Add artifactory remote shell: bash diff --git a/.github/actions/setup_conan_macos/apple-clang.profile b/.github/actions/setup_conan_macos/apple-clang.profile new file mode 100644 index 000000000..b61f5eb4d --- /dev/null +++ b/.github/actions/setup_conan_macos/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/generate_conan_matrix.py b/.github/scripts/generate_conan_matrix.py new file mode 100755 index 000000000..c645d487d --- /dev/null +++ b/.github/scripts/generate_conan_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/workflows/build.yml b/.github/workflows/build.yml index fc8076c39..2e38116de 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,7 @@ jobs: include: - os: macos15 - conan_profile: default_apple_clang + conan_profile: apple-clang build_type: Release container: "" static: false diff --git a/.github/workflows/build_impl.yml b/.github/workflows/build_impl.yml index cc52591fd..c3ca9eb3a 100644 --- a/.github/workflows/build_impl.yml +++ b/.github/workflows/build_impl.yml @@ -81,8 +81,7 @@ jobs: if: runner.os == 'macOS' uses: ./.github/actions/setup_conan_macos with: - conan_profile: ${{ inputs.conan_profile }} - global_conf_file: docker/ci/conan/global.conf + conan_files_dir: docker/ci/conan/ - name: Restore cache if: ${{ !inputs.disable_cache }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e472ad0f6..f4dec61a3 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -32,7 +32,7 @@ jobs: matrix: include: - os: macos15 - conan_profile: default_apple_clang + conan_profile: apple-clang build_type: Release static: false - os: heavy @@ -75,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 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/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml index 72fe1523c..1e43a1725 100644 --- a/.github/workflows/upload_conan_deps.yml +++ b/.github/workflows/upload_conan_deps.yml @@ -22,35 +22,26 @@ concurrency: cancel-in-progress: true jobs: + generate-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout Repo ⚡️ + uses: actions/checkout@v4 + + - name: Calculate recipes matrix 🛠 + id: set-matrix + run: .github/scripts/generate_conan_matrix.py >> "${GITHUB_OUTPUT}" + upload-conan-deps: name: Build and Upload Conan Deps + needs: generate-matrix + strategy: fail-fast: false - matrix: - os: [heavy, heavy-arm64] - container: ['{ "image": "ghcr.io/xrplf/clio-ci:latest" }'] - compiler: ["gcc", "clang"] - sanitizer_ext: [".asan", ".tsan", ".ubsan", ""] - build_type: ["Release", "Debug"] - # libbacktrace doesn't build on arm64 with gcc.tsan - exclude: - - os: heavy-arm64 - container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' - compiler: gcc - sanitizer_ext: .tsan - build_type: Release - - os: heavy-arm64 - container: '{ "image": "ghcr.io/xrplf/clio-ci:latest" }' - compiler: gcc - sanitizer_ext: .tsan - build_type: Debug - include: - - os: macos15 - container: "" - compiler: default_apple_clang - sanitizer_ext: "" - build_type: Release + matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} runs-on: ${{ matrix.os }} container: ${{ matrix.container != '' && fromJson(matrix.container) || null }} @@ -70,8 +61,7 @@ jobs: if: runner.os == 'macOS' uses: ./.github/actions/setup_conan_macos with: - conan_profile: ${{ env.CONAN_PROFILE }} - global_conf_file: docker/ci/conan/global.conf + conan_files_dir: docker/ci/conan/ - name: Show conan profile run: conan profile show --profile:all ${{ env.CONAN_PROFILE }} @@ -89,4 +79,4 @@ jobs: - name: Upload Conan packages if: github.event_name != 'pull_request' - run: conan upload "*" -r=ripple --confirm --force + run: conan upload "*" -r=ripple --confirm From 2c6f52a0ed94a44857e19a9cc1f139addbeabcd7 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 18 Jun 2025 20:03:58 +0100 Subject: [PATCH 27/49] ci: Full build conan dependencies only on schedule (#2241) --- .github/workflows/upload_conan_deps.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml index 1e43a1725..ec1ec450d 100644 --- a/.github/workflows/upload_conan_deps.yml +++ b/.github/workflows/upload_conan_deps.yml @@ -1,12 +1,15 @@ name: Upload Conan Dependencies on: + schedule: + - cron: "0 9 * * 1-5" workflow_dispatch: pull_request: branches: - develop paths: - .github/workflows/upload_conan_deps.yml + - .github/scripts/generate_conan_matrix.py - conanfile.py - conan.lock push: @@ -14,6 +17,7 @@ on: - develop paths: - .github/workflows/upload_conan_deps.yml + - .github/scripts/generate_conan_matrix.py - conanfile.py - conan.lock @@ -27,15 +31,14 @@ jobs: outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - name: Checkout Repo ⚡️ - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Calculate recipes matrix 🛠 + - name: Calculate conan matrix id: set-matrix run: .github/scripts/generate_conan_matrix.py >> "${GITHUB_OUTPUT}" upload-conan-deps: - name: Build and Upload Conan Deps + name: Build ${{ matrix.compiler }}${{ matrix.sanitizer_ext }} ${{ matrix.build_type }} needs: generate-matrix @@ -70,7 +73,7 @@ jobs: uses: ./.github/actions/generate with: conan_profile: ${{ env.CONAN_PROFILE }} - force_conan_source_build: "true" + force_conan_source_build: ${{ github.event_name == 'schedule' }} build_type: ${{ matrix.build_type }} - name: Login to Conan From 63ec5631356fde94969ea7960ced3a3b1d23adaf Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 18 Jun 2025 21:40:11 +0100 Subject: [PATCH 28/49] feat: ETLng cleanup and graceful shutdown (#2232) --- src/etl/ETLService.cpp | 1 - src/etl/LoadBalancer.hpp | 6 +- src/etlng/ETLService.cpp | 29 ++++++--- src/etlng/LoadBalancer.cpp | 16 ++--- src/etlng/LoadBalancer.hpp | 7 ++- src/etlng/LoadBalancerInterface.hpp | 20 +++++- src/etlng/LoaderInterface.hpp | 11 +++- src/etlng/Source.cpp | 4 +- src/etlng/Source.hpp | 4 +- src/etlng/impl/GrpcSource.cpp | 30 +++++++-- src/etlng/impl/GrpcSource.hpp | 17 ++++- src/etlng/impl/LedgerPublisher.hpp | 1 - src/etlng/impl/Loading.cpp | 10 ++- src/etlng/impl/Loading.hpp | 2 +- src/etlng/impl/SourceImpl.hpp | 8 ++- src/etlng/impl/TaskManager.cpp | 25 ++++++-- tests/common/util/MockLoadBalancer.hpp | 2 +- tests/common/util/MockSourceNg.hpp | 5 +- tests/unit/etlng/ETLServiceTests.cpp | 35 ++++++++--- tests/unit/etlng/GrpcSourceTests.cpp | 87 ++++++++++++++++++++++---- tests/unit/etlng/LoadBalancerTests.cpp | 17 ++--- tests/unit/etlng/SourceImplTests.cpp | 31 +++++++-- tests/unit/etlng/TaskManagerTests.cpp | 75 ++++++++++++++++++---- 23 files changed, 338 insertions(+), 105 deletions(-) diff --git a/src/etl/ETLService.cpp b/src/etl/ETLService.cpp index f5cf1fbb9..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" 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/etlng/ETLService.cpp b/src/etlng/ETLService.cpp index 248a3662a..b54b459e8 100644 --- a/src/etlng/ETLService.cpp +++ b/src/etlng/ETLService.cpp @@ -34,6 +34,7 @@ #include "etlng/LedgerPublisherInterface.hpp" #include "etlng/LoadBalancerInterface.hpp" #include "etlng/LoaderInterface.hpp" +#include "etlng/Models.hpp" #include "etlng/MonitorInterface.hpp" #include "etlng/MonitorProviderInterface.hpp" #include "etlng/TaskManagerProviderInterface.hpp" @@ -57,6 +58,7 @@ #include #include #include +#include #include #include @@ -66,6 +68,7 @@ #include #include #include +#include namespace etlng { @@ -136,7 +139,11 @@ 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; @@ -223,13 +230,21 @@ ETLService::loadInitialLedgerIfNeeded() << ". 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()) { 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 fbba73a48..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 */ - [[nodiscard]] virtual std::vector + [[nodiscard]] virtual InitialLedgerLoadResult loadInitialLedger( uint32_t sequence, etlng::InitialLoadObserverInterface& loader, diff --git a/src/etlng/LoaderInterface.hpp b/src/etlng/LoaderInterface.hpp index 1d2f4e533..599982eab 100644 --- a/src/etlng/LoaderInterface.hpp +++ b/src/etlng/LoaderInterface.hpp @@ -25,11 +25,16 @@ #include #include -#include namespace etlng { -using Error = std::string; +/** + * @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 @@ -42,7 +47,7 @@ struct LoaderInterface { * @param data The data to load * @return Nothing or error as std::expected */ - [[nodiscard]] virtual std::expected + [[nodiscard]] virtual std::expected load(model::LedgerData const& data) = 0; /** 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/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 ddac66c80..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" diff --git a/src/etlng/impl/Loading.cpp b/src/etlng/impl/Loading.cpp index 0603ef8c8..4914d7a41 100644 --- a/src/etlng/impl/Loading.cpp +++ b/src/etlng/impl/Loading.cpp @@ -59,7 +59,7 @@ Loader::Loader( { } -std::expected +std::expected Loader::load(model::LedgerData const& data) { try { @@ -78,13 +78,13 @@ Loader::load(model::LedgerData const& data) 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("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("amendment blocked"); + return std::unexpected(LoaderError::AmendmentBlocked); } return {}; @@ -133,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 9da5edf95..19b1c381b 100644 --- a/src/etlng/impl/Loading.hpp +++ b/src/etlng/impl/Loading.hpp @@ -77,7 +77,7 @@ public: Loader& operator=(Loader&&) = delete; - std::expected + std::expected load(model::LedgerData const& data) override; void 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 86bb7da8b..33f75a132 100644 --- a/src/etlng/impl/TaskManager.cpp +++ b/src/etlng/impl/TaskManager.cpp @@ -121,16 +121,29 @@ TaskManager::spawnLoader(TaskQueue& queue) 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()) { - // perhaps this should return an error if conflict happened, then we can stop loading immediately auto [expectedSuccess, nanos] = util::timed([&] { return loader_.get().load(*data); }); - if (not expectedSuccess.has_value()) { - LOG(log_.warn()) << "Immediately stopping loader with error: " << expectedSuccess.error() - << "; latest ledger cache loaded for " << data->seq; - monitor_.get().notifyWriteConflict(data->seq); + 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(); 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/unit/etlng/ETLServiceTests.cpp b/tests/unit/etlng/ETLServiceTests.cpp index 676c2adfe..9ec2da2a0 100644 --- a/tests/unit/etlng/ETLServiceTests.cpp +++ b/tests/unit/etlng/ETLServiceTests.cpp @@ -27,6 +27,7 @@ #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" @@ -100,7 +101,7 @@ struct MockExtractor : etlng::ExtractorInterface { }; struct MockLoader : etlng::LoaderInterface { - using ExpectedType = std::expected; + using ExpectedType = std::expected; MOCK_METHOD(ExpectedType, load, (etlng::model::LedgerData const&), (override)); MOCK_METHOD(std::optional, loadInitialLedger, (etlng::model::LedgerData const&), (override)); }; @@ -488,9 +489,7 @@ TEST_F(ETLServiceTests, GiveUpWriterAfterWriteConflict) EXPECT_FALSE(systemState_->writeConflict); // and removes write conflict flag } -struct ETLServiceAssertTests : common::util::WithMockAssert, ETLServiceTests {}; - -TEST_F(ETLServiceAssertTests, FailToLoadInitialLedger) +TEST_F(ETLServiceTests, CancelledLoadInitialLedger) { EXPECT_CALL(*backend_, hardFetchLedgerRange).WillOnce(testing::Return(std::nullopt)); EXPECT_CALL(*ledgers_, getMostRecent()).WillRepeatedly(testing::Return(kSEQ)); @@ -501,10 +500,10 @@ TEST_F(ETLServiceAssertTests, FailToLoadInitialLedger) 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).WillOnce(testing::Return(std::nullopt)); @@ -517,5 +516,27 @@ TEST_F(ETLServiceAssertTests, WaitForValidatedLedgerIsAbortedLeadToFailToLoadIni 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..85411b8cf 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 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 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/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/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 59480207b..5931dcfbd 100644 --- a/tests/unit/etlng/TaskManagerTests.cpp +++ b/tests/unit/etlng/TaskManagerTests.cpp @@ -62,7 +62,7 @@ struct MockExtractor : etlng::ExtractorInterface { }; struct MockLoader : etlng::LoaderInterface { - using ExpectedType = std::expected; + using ExpectedType = std::expected; MOCK_METHOD(ExpectedType, load, (LedgerData const&), (override)); MOCK_METHOD(std::optional, loadInitialLedger, (LedgerData const&), (override)); }; @@ -142,11 +142,11 @@ TEST_F(TaskManagerTests, LoaderGetsDataIfNextSequenceIsExtracted) EXPECT_CALL(*mockLoaderPtr_, load(testing::_)) .Times(kTOTAL) - .WillRepeatedly([&](LedgerData data) -> std::expected { + .WillRepeatedly([&](LedgerData data) -> std::expected { loaded.push_back(data.seq); - if (loaded.size() == kTOTAL) { + if (loaded.size() == kTOTAL) done.release(); - } + return {}; }); @@ -157,9 +157,8 @@ TEST_F(TaskManagerTests, LoaderGetsDataIfNextSequenceIsExtracted) 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) @@ -187,19 +186,17 @@ TEST_F(TaskManagerTests, WriteConflictHandling) // First kCONFLICT_AFTER calls succeed, then we get a write conflict EXPECT_CALL(*mockLoaderPtr_, load(testing::_)) - .WillRepeatedly([&](LedgerData data) -> std::expected { + .WillRepeatedly([&](LedgerData data) -> std::expected { loaded.push_back(data.seq); if (loaded.size() == kCONFLICT_AFTER) { conflictOccurred = true; done.release(); - return std::unexpected("write conflict"); + return std::unexpected(etlng::LoaderError::WriteConflict); } - // Only release semaphore if we reach kTOTAL without conflict - if (loaded.size() == kTOTAL) { + if (loaded.size() == kTOTAL) done.release(); - } return {}; }); @@ -214,7 +211,59 @@ TEST_F(TaskManagerTests, WriteConflictHandling) EXPECT_EQ(loaded.size(), kCONFLICT_AFTER); EXPECT_TRUE(conflictOccurred); - 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, 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); - } } From 27e29d042174e92ac4a93a519093c58264b076da Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:27:06 +0100 Subject: [PATCH 29/49] style: clang-tidy auto fixes (#2245) Fixes #2244. Please review and commit clang-tidy fixes. Co-authored-by: godexsoft <385326+godexsoft@users.noreply.github.com> --- src/etlng/ETLService.cpp | 2 -- tests/unit/etlng/ETLServiceTests.cpp | 1 - tests/unit/etlng/GrpcSourceTests.cpp | 4 ++-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/etlng/ETLService.cpp b/src/etlng/ETLService.cpp index b54b459e8..64da45713 100644 --- a/src/etlng/ETLService.cpp +++ b/src/etlng/ETLService.cpp @@ -34,7 +34,6 @@ #include "etlng/LedgerPublisherInterface.hpp" #include "etlng/LoadBalancerInterface.hpp" #include "etlng/LoaderInterface.hpp" -#include "etlng/Models.hpp" #include "etlng/MonitorInterface.hpp" #include "etlng/MonitorProviderInterface.hpp" #include "etlng/TaskManagerProviderInterface.hpp" @@ -68,7 +67,6 @@ #include #include #include -#include namespace etlng { diff --git a/tests/unit/etlng/ETLServiceTests.cpp b/tests/unit/etlng/ETLServiceTests.cpp index 9ec2da2a0..ec1b04502 100644 --- a/tests/unit/etlng/ETLServiceTests.cpp +++ b/tests/unit/etlng/ETLServiceTests.cpp @@ -35,7 +35,6 @@ #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" diff --git a/tests/unit/etlng/GrpcSourceTests.cpp b/tests/unit/etlng/GrpcSourceTests.cpp index 85411b8cf..cdc7390b9 100644 --- a/tests/unit/etlng/GrpcSourceTests.cpp +++ b/tests/unit/etlng/GrpcSourceTests.cpp @@ -319,7 +319,7 @@ TEST_F(GrpcSourceStopTests, LoadInitialLedgerStopsWhenRequested) EXPECT_EQ(request->user(), "ETL"); { - std::unique_lock lk(mtx); + std::unique_lock const lk(mtx); grpcCallIsActive = true; } cvGrpcCallActive.notify_one(); @@ -347,7 +347,7 @@ TEST_F(GrpcSourceStopTests, LoadInitialLedgerStopsWhenRequested) runSyncOperation([&](boost::asio::yield_context yield) { grpcSource_.stop(yield); { - std::unique_lock lk(mtx); + std::unique_lock const lk(mtx); stopHasBeenCalled = true; } cvStopCalled.notify_one(); From 87ee358297e36e3cc98f225b96d244fe5eccfb5d Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 11:22:19 +0100 Subject: [PATCH 30/49] ci: Silence brew warnings (#2255) --- .github/actions/prepare_runner/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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' }} From e4fbf5131f3622fd18c2747307289e6f3e686df8 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 11:26:05 +0100 Subject: [PATCH 31/49] feat: Build sanitizers with clang (#2239) --- .github/workflows/sanitizers.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 1e54b9547..f6afd5b30 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -37,14 +37,19 @@ jobs: strategy: fail-fast: false matrix: - conan_profile: ["gcc.asan", "gcc.tsan", "gcc.ubsan"] + 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.conan_profile }} + conan_profile: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }} build_type: Release static: false run_unit_tests: true From c8574ea42af18d2713abf5a8266730c8b3772306 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 11:28:22 +0100 Subject: [PATCH 32/49] chore: Cleanup conanfile (#2243) --- conanfile.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/conanfile.py b/conanfile.py index 5e346baf8..adb96b177 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 @@ -38,7 +37,6 @@ class ClioConan(ConanFile): 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): From 70f7635dda0d059bef3b86d1d413438be3b7902a Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 11:34:43 +0100 Subject: [PATCH 33/49] refactor: Simplify check_config implementation (#2247) --- .github/workflows/build.yml | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2e38116de..b3d850c6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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}" From 6cabe89601a9f08f53b882ab023abe14786b9bb5 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 11:35:34 +0100 Subject: [PATCH 34/49] chore: Don't use self-hosted runner tag (#2248) --- .github/workflows/check_libxrpl.yml | 4 ++-- .github/workflows/update_docker_ci.yml | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check_libxrpl.yml b/.github/workflows/check_libxrpl.yml index d0c524f11..5783c830e 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 @@ -54,7 +54,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/update_docker_ci.yml b/.github/workflows/update_docker_ci.yml index 449040188..44d12f3e1 100644 --- a/.github/workflows/update_docker_ci.yml +++ b/.github/workflows/update_docker_ci.yml @@ -33,7 +33,7 @@ env: jobs: gcc-amd64: name: Build and push GCC docker image (amd64) - runs-on: [self-hosted, heavy] + runs-on: heavy steps: - uses: actions/checkout@v4 @@ -67,7 +67,7 @@ jobs: gcc-arm64: name: Build and push GCC docker image (arm64) - runs-on: [self-hosted, heavy-arm64] + runs-on: heavy-arm64 steps: - uses: actions/checkout@v4 @@ -101,7 +101,7 @@ jobs: gcc-merge: name: Merge and push multi-arch GCC docker image - runs-on: [self-hosted, heavy] + runs-on: heavy needs: [gcc-amd64, gcc-arm64] steps: @@ -150,7 +150,7 @@ jobs: clang: name: Build and push Clang docker image - runs-on: [self-hosted, heavy] + runs-on: heavy steps: - uses: actions/checkout@v4 @@ -183,7 +183,7 @@ jobs: tools: name: Build and push tools docker image - runs-on: [self-hosted, heavy] + runs-on: heavy steps: - uses: actions/checkout@v4 @@ -210,7 +210,7 @@ jobs: ci: name: Build and push CI docker image - runs-on: [self-hosted, heavy] + runs-on: heavy needs: [gcc-merge, clang, tools] steps: From 7661ee6a3bccee941c9dd29d2ab13f7092a647e6 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 11:38:28 +0100 Subject: [PATCH 35/49] fix: Make update-libxrpl-version work with lockfile (#2249) --- .github/scripts/update-libxrpl-version | 28 -------------------------- .github/workflows/check_libxrpl.yml | 8 +++++++- 2 files changed, 7 insertions(+), 29 deletions(-) delete mode 100755 .github/scripts/update-libxrpl-version 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/check_libxrpl.yml b/.github/workflows/check_libxrpl.yml index 5783c830e..1c8cca621 100644 --- a/.github/workflows/check_libxrpl.yml +++ b/.github/workflows/check_libxrpl.yml @@ -27,7 +27,13 @@ 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 From 6c69453bda264b3d9a9303fcb65a5a224123e31b Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 11:41:31 +0100 Subject: [PATCH 36/49] fix: Disable conan uploads on schedule (#2253) --- .github/workflows/upload_conan_deps.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml index ec1ec450d..307ae37d4 100644 --- a/.github/workflows/upload_conan_deps.yml +++ b/.github/workflows/upload_conan_deps.yml @@ -4,6 +4,12 @@ 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 @@ -73,7 +79,9 @@ jobs: uses: ./.github/actions/generate with: conan_profile: ${{ env.CONAN_PROFILE }} - force_conan_source_build: ${{ github.event_name == 'schedule' }} + # 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 @@ -81,5 +89,5 @@ jobs: run: conan remote login -p ${{ secrets.CONAN_PASSWORD }} ripple ${{ secrets.CONAN_USERNAME }} - name: Upload Conan packages - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' && github.event_name != 'schedule' run: conan upload "*" -r=ripple --confirm From bdaa04d1ec42b9505bccf92f435077a740bc979c Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 11:44:17 +0100 Subject: [PATCH 37/49] ci: Don't use dynamic names when they are not needed (#2251) --- .github/workflows/build_impl.yml | 2 +- .github/workflows/test_impl.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_impl.yml b/.github/workflows/build_impl.yml index c3ca9eb3a..f8020c3a4 100644 --- a/.github/workflows/build_impl.yml +++ b/.github/workflows/build_impl.yml @@ -59,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 }} diff --git a/.github/workflows/test_impl.yml b/.github/workflows/test_impl.yml index 079e3c496..72c962c92 100644 --- a/.github/workflows/test_impl.yml +++ b/.github/workflows/test_impl.yml @@ -35,7 +35,7 @@ on: 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 }} @@ -104,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 }} From 162b1305e01d7776b24f2fe75d1cfaf7b18289ce Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 12:06:38 +0100 Subject: [PATCH 38/49] feat: Download and upload conan packages in parallel (#2254) --- docker/ci/conan/global.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/ci/conan/global.conf b/docker/ci/conan/global.conf index c61cf077f..d2ae941c8 100644 --- a/docker/ci/conan/global.conf +++ b/docker/ci/conan/global.conf @@ -1 +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"] From b054a8424dbc78628a1b44e9092cfe4690973a7e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 12:07:44 +0100 Subject: [PATCH 39/49] refactor: Refactor GCC image to make upgrades easier (#2246) Work on: https://github.com/XRPLF/clio/issues/2047 --- docker/compilers/gcc/Dockerfile | 82 ++++++++++++++++++++++----------- docker/compilers/gcc/README.md | 4 +- docker/compilers/gcc/control.m4 | 3 +- 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/docker/compilers/gcc/Dockerfile b/docker/compilers/gcc/Dockerfile index f18901cd8..548dd6062 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=5 ARG DEBIAN_FRONTEND=noninteractive ARG TARGETARCH -ARG UBUNTU_VERSION=20.04 -ARG GCC_VERSION=12.3.0 -ARG BUILD_VERSION=4 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 From 92fdebf590057b7f6d2bcea797151a90372c44f2 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 23 Jun 2025 12:11:03 +0100 Subject: [PATCH 40/49] chore: Update conan.lock (#2250) --- conan.lock | 62 +++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/conan.lock b/conan.lock index b52238f17..29a83f69d 100644 --- a/conan.lock +++ b/conan.lock @@ -1,40 +1,40 @@ { "version": "0.5", "requires": [ - "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1745733265.169", - "xxhash/0.8.2#7856c968c985b2981b707ee8f2413b2b%1745733265.369", - "xrpl/2.5.0-rc1#e5897e048ea5712d2c71561c507d949d%1749232354.05", - "sqlite3/3.47.0#7a0904fd061f5f8a2366c294f9387830%1745733265.179", - "soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1745996961.386", - "re2/20230301#dfd6e2bf050eb90ddd8729cfb4c844a4%1748907478.301", - "rapidjson/cci.20220822#1b9d8c2256876a154172dc5cfbe447c6%1745998516.625", - "protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1748907473.682", - "openssl/1.1.1v#216374e4fb5b2e0f5ab1fb6f27b5b434%1745733265.17", - "nudb/2.0.8#63990d3e517038e04bf529eb8167f69f%1745995837.29", - "minizip/1.2.13#9e87d57804bd372d6d1e32b1871517a3%1745998515.33", - "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1745733265.361", - "libuv/1.46.0#78565d142ac7102776256328a26cdf60%1745998515.314", - "libiconv/1.17#1ae2f60ab5d08de1643a22a81b360c59%1745984706.599", - "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1745733265.166", - "libarchive/3.7.6#e0453864b2a4d225f06b3304903cb2b7%1745733265.013", - "http_parser/2.9.4#98d91690d6fd021e9e624218a85d9d97%1745998514.902", - "gtest/1.14.0#f8f0757a574a8dd747d16af62d6eb1b7%1749481752.277", - "grpc/1.50.1#02291451d1e17200293a409410d1c4e1%1748907469.938", - "fmt/10.1.1#021e170cf81db57da82b5f737b6906c1%1745998514.906", - "date/3.0.3#cf28fe9c0aab99fe12da08aa42df65e1%1745733264.487", - "cassandra-cpp-driver/2.17.0#e50919efac8418c26be6671fd702540a%1745998514.898", - "c-ares/1.34.5#b78b91e7cfb1f11ce777a285bbf169c6%1748907468.021", - "bzip2/1.0.8#00b4a4658791c1f06914e087f0e792f5%1745733264.985", - "boost/1.83.0#8eb22f36ddfb61f54bbc412c4555bd66%1748907463.167", + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1750263732.782", + "xxhash/0.8.2#7856c968c985b2981b707ee8f2413b2b%1750263730.908", + "xrpl/2.5.0-rc1#e5897e048ea5712d2c71561c507d949d%1750263725.455", + "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%1748907459.618" + "abseil/20230802.1#f0f91485b111dc9837a68972cb19ca7b%1750263609.776" ], "build_requires": [ - "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1745733265.169", - "protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1748907473.682", - "protobuf/3.21.9#64ce20e1d9ea24f3d6c504015d5f6fa8%1748596927.759", - "cmake/3.31.6#ed0e6c1d49bd564ce6fed1a19653b86d%1745733264.493", - "b2/5.3.2#7b5fabfe7088ae933fb3e78302343ea0%1745733264.471" + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1750263732.782", + "protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1750263698.841", + "protobuf/3.21.9#64ce20e1d9ea24f3d6c504015d5f6fa8%1750263690.822", + "cmake/3.31.6#ed0e6c1d49bd564ce6fed1a19653b86d%1750263636.055", + "b2/5.3.2#7b5fabfe7088ae933fb3e78302343ea0%1750263614.565" ], "python_requires": [], "overrides": { From 04c80c62f58ce39409bf158f1ed5de213acf350b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:26:26 +0100 Subject: [PATCH 41/49] ci: [DEPENDABOT] bump docker/setup-buildx-action from 3.11.0 to 3.11.1 in /.github/actions/build_docker_image (#2256) --- .github/actions/build_docker_image/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build_docker_image/action.yml b/.github/actions/build_docker_image/action.yml index 95c6f953b..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@18ce135bb5112fa8ce4ed6c17ab05699d7f3a5e0 # v3.11.0 + - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 id: meta From 4f7e8194f02f6d5b375170a7c60161f1a36ded45 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 26 Jun 2025 14:51:34 +0100 Subject: [PATCH 42/49] fix: Don't cancel ci image builds (#2259) --- .github/workflows/update_docker_ci.yml | 5 +++-- docker/compilers/gcc/Dockerfile | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update_docker_ci.yml b/.github/workflows/update_docker_ci.yml index 44d12f3e1..d566b5d3d 100644 --- a/.github/workflows/update_docker_ci.yml +++ b/.github/workflows/update_docker_ci.yml @@ -23,9 +23,10 @@ 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 }} diff --git a/docker/compilers/gcc/Dockerfile b/docker/compilers/gcc/Dockerfile index 548dd6062..94091b7d2 100644 --- a/docker/compilers/gcc/Dockerfile +++ b/docker/compilers/gcc/Dockerfile @@ -10,7 +10,7 @@ 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=5 +ARG BUILD_VERSION=6 ARG DEBIAN_FRONTEND=noninteractive ARG TARGETARCH From 363344d36e5e87306506f52e4d22a6fb18d8b5ca Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 26 Jun 2025 17:12:32 +0100 Subject: [PATCH 43/49] feat: Add init_conan script (#2242) This should make life of a developer so much easier --- .github/actions/setup_conan_macos/action.yml | 39 ----------------- .github/dependabot.yml | 13 ------ .../conan}/apple-clang.profile | 0 .../generate_matrix.py} | 0 .github/scripts/conan/init.sh | 43 +++++++++++++++++++ .github/workflows/build_impl.yml | 7 ++- .github/workflows/upload_conan_deps.yml | 23 +++++++--- docs/build-clio.md | 38 ++++++++++------ 8 files changed, 86 insertions(+), 77 deletions(-) delete mode 100644 .github/actions/setup_conan_macos/action.yml rename .github/{actions/setup_conan_macos => scripts/conan}/apple-clang.profile (100%) rename .github/scripts/{generate_conan_matrix.py => conan/generate_matrix.py} (100%) create mode 100755 .github/scripts/conan/init.sh diff --git a/.github/actions/setup_conan_macos/action.yml b/.github/actions/setup_conan_macos/action.yml deleted file mode 100644 index a30f88057..000000000 --- a/.github/actions/setup_conan_macos/action.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Setup conan -description: Setup conan profiles and artifactory on macOS runner - -inputs: - conan_files_dir: - description: Directory with conan files - required: true - -runs: - using: composite - steps: - - name: Fail on non-macOS - if: runner.os != 'macOS' - shell: bash - run: exit 1 - - - name: Copy global.conf - shell: bash - run: | - cp "${{ inputs.conan_files_dir }}/global.conf" "${{ env.CONAN_HOME }}/global.conf" - - - name: Create apple-clang conan profile - shell: bash - run: | - mkdir -p "${{ env.CONAN_HOME }}/profiles" - cp .github/actions/setup_conan_macos/apple-clang.profile "${{ env.CONAN_HOME }}/profiles/apple-clang" - - - name: Create conan profiles for sanitizers - shell: bash - working-directory: ${{ inputs.conan_files_dir }} - run: | - cp ./sanitizer_template.profile "${{ env.CONAN_HOME }}/profiles/apple-clang.asan" - cp ./sanitizer_template.profile "${{ env.CONAN_HOME }}/profiles/apple-clang.tsan" - cp ./sanitizer_template.profile "${{ env.CONAN_HOME }}/profiles/apple-clang.ubsan" - - - name: Add artifactory remote - shell: bash - run: | - conan remote add --index 0 ripple http://18.143.149.228:8081/artifactory/api/conan/dev diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ff5282bdb..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_macos/ - 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/actions/setup_conan_macos/apple-clang.profile b/.github/scripts/conan/apple-clang.profile similarity index 100% rename from .github/actions/setup_conan_macos/apple-clang.profile rename to .github/scripts/conan/apple-clang.profile diff --git a/.github/scripts/generate_conan_matrix.py b/.github/scripts/conan/generate_matrix.py similarity index 100% rename from .github/scripts/generate_conan_matrix.py rename to .github/scripts/conan/generate_matrix.py 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/workflows/build_impl.yml b/.github/workflows/build_impl.yml index f8020c3a4..0d8dc0ce0 100644 --- a/.github/workflows/build_impl.yml +++ b/.github/workflows/build_impl.yml @@ -77,11 +77,10 @@ jobs: with: disable_ccache: ${{ inputs.disable_cache }} - - name: Setup conan + - name: Setup conan on macOS if: runner.os == 'macOS' - uses: ./.github/actions/setup_conan_macos - with: - conan_files_dir: docker/ci/conan/ + shell: bash + run: ./.github/scripts/conan/init.sh - name: Restore cache if: ${{ !inputs.disable_cache }} diff --git a/.github/workflows/upload_conan_deps.yml b/.github/workflows/upload_conan_deps.yml index 307ae37d4..9c0fdd8b6 100644 --- a/.github/workflows/upload_conan_deps.yml +++ b/.github/workflows/upload_conan_deps.yml @@ -15,7 +15,12 @@ on: - develop paths: - .github/workflows/upload_conan_deps.yml - - .github/scripts/generate_conan_matrix.py + + - .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: @@ -23,7 +28,12 @@ on: - develop paths: - .github/workflows/upload_conan_deps.yml - - .github/scripts/generate_conan_matrix.py + + - .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 @@ -41,7 +51,7 @@ jobs: - name: Calculate conan matrix id: set-matrix - run: .github/scripts/generate_conan_matrix.py >> "${GITHUB_OUTPUT}" + run: .github/scripts/conan/generate_matrix.py >> "${GITHUB_OUTPUT}" upload-conan-deps: name: Build ${{ matrix.compiler }}${{ matrix.sanitizer_ext }} ${{ matrix.build_type }} @@ -66,11 +76,10 @@ jobs: with: disable_ccache: true - - name: Setup conan + - name: Setup conan on macOS if: runner.os == 'macOS' - uses: ./.github/actions/setup_conan_macos - with: - conan_files_dir: docker/ci/conan/ + shell: bash + run: ./.github/scripts/conan/init.sh - name: Show conan profile run: conan profile show --profile:all ${{ env.CONAN_PROFILE }} diff --git a/docs/build-clio.md b/docs/build-clio.md index 9c8909420..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,10 +19,21 @@ ### Conan Configuration -Clio requires `compiler.cppstd=20` in your Conan profile (`~/.conan2/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**: @@ -56,9 +67,16 @@ 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"] ``` @@ -70,16 +88,7 @@ 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. - -> [!NOTE] -> You may need to edit the `~/.conan2/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). - -Remove old packages you may have cached interactively. - -```sh -conan remove xrpl -``` +Now you should be able to download the prebuilt dependencies (including `xrpl` package) on supported platforms. #### Conan lockfile @@ -102,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 .. From 769fdab6b7b20a44a39c002f4090631501fd7e53 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 26 Jun 2025 18:03:26 +0100 Subject: [PATCH 44/49] feat: Add Support For Token Escrow (#2252) Fix: https://github.com/XRPLF/clio/issues/2174 --- src/rpc/handlers/GatewayBalances.cpp | 33 +++ src/rpc/handlers/GatewayBalances.hpp | 1 + .../rpc/handlers/GatewayBalancesTests.cpp | 274 +++++++++++++----- 3 files changed, 230 insertions(+), 78 deletions(-) diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index a312aedc1..f6b02280c 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& locked_balance = output.locked[escrow.getCurrency()]; + if (locked_balance == beast::zero) { + // This is needed to set the currency code correctly + locked_balance = escrow; + } else { + try { + locked_balance += 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. + locked_balance = ripple::STAmount( + locked_balance.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 f26abfe98..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; }; diff --git a/tests/unit/rpc/handlers/GatewayBalancesTests.cpp b/tests/unit/rpc/handlers/GatewayBalancesTests.cpp index c5eaba04a..dd4c0aa7f 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) { @@ -479,43 +463,43 @@ generateNormalPathTestBundles() .expectedJson = fmt::format( R"JSON({{ "obligations":{{ - "JPY":"50" + "JPY": "50" }}, "balances":{{ - "{}":[ + "{}": [ {{ - "currency":"USD", - "value":"10" + "currency": "USD", + "value": "10" }}, {{ - "currency":"CNY", - "value":"20" + "currency": "CNY", + "value": "20" }} ] }}, "frozen_balances":{{ - "{}":[ + "{}": [ {{ - "currency":"JPY", - "value":"50" + "currency": "JPY", + "value": "50" }} ] }}, "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, @@ -533,11 +517,11 @@ generateNormalPathTestBundles() .expectedJson = fmt::format( R"JSON({{ "obligations":{{ - "JPY":"50" + "JPY": "50" }}, - "account":"{}", - "ledger_index":300, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + "account": "{}", + "ledger_index": 300, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" }})JSON", kACCOUNT ), @@ -550,11 +534,11 @@ generateNormalPathTestBundles() .expectedJson = fmt::format( R"JSON({{ "obligations":{{ - "JPY":"9999999999999999e80" + "JPY": "9999999999999999e80" }}, - "account":"{}", - "ledger_index":300, - "ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + "account": "{}", + "ledger_index": 300, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" }})JSON", kACCOUNT ), @@ -579,31 +563,31 @@ generateNormalPathTestBundles() .expectedJson = fmt::format( R"JSON({{ "obligations":{{ - "EUR":"30" + "EUR": "30" }}, "balances":{{ - "{}":[ + "{}": [ {{ - "currency":"USD", - "value":"10" + "currency": "USD", + "value": "10" }}, {{ - "currency":"CNY", - "value":"20" + "currency": "CNY", + "value": "20" }} ] }}, "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, @@ -626,26 +610,26 @@ generateNormalPathTestBundles() .expectedJson = fmt::format( R"JSON({{ "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 +); From e92dbc8fce39e3bd767a1c51ebe4bc806056a646 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 10:30:17 +0100 Subject: [PATCH 45/49] style: clang-tidy auto fixes (#2264) Fixes #2263. Please review and commit clang-tidy fixes. Co-authored-by: godexsoft <385326+godexsoft@users.noreply.github.com> --- src/rpc/handlers/GatewayBalances.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index f6b02280c..41996fabf 100644 --- a/src/rpc/handlers/GatewayBalances.cpp +++ b/src/rpc/handlers/GatewayBalances.cpp @@ -82,20 +82,20 @@ GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context con auto addEscrow = [&](ripple::SLE const& sle) { if (sle.getType() == ripple::ltESCROW) { auto const& escrow = sle.getFieldAmount(ripple::sfAmount); - auto& locked_balance = output.locked[escrow.getCurrency()]; - if (locked_balance == beast::zero) { + auto& lockedBalance = output.locked[escrow.getCurrency()]; + if (lockedBalance == beast::zero) { // This is needed to set the currency code correctly - locked_balance = escrow; + lockedBalance = escrow; } else { try { - locked_balance += escrow; + 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. - locked_balance = ripple::STAmount( - locked_balance.issue(), ripple::STAmount::cMaxValue, ripple::STAmount::cMaxOffset + lockedBalance = ripple::STAmount( + lockedBalance.issue(), ripple::STAmount::cMaxValue, ripple::STAmount::cMaxOffset ); } } From d97f19ba1db223e9fa2eab8d847f3f50f8d7417a Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 27 Jun 2025 11:45:11 +0100 Subject: [PATCH 46/49] style: Fix JSON style in C++ code (#2262) --- tests/common/util/config/FakeConfigData.hpp | 20 +- tests/unit/feed/BookChangesFeedTests.cpp | 24 +- tests/unit/feed/ForwardFeedTests.cpp | 2 +- tests/unit/feed/LedgerFeedTests.cpp | 46 +- .../feed/ProposedTransactionFeedTests.cpp | 20 +- tests/unit/feed/SingleFeedBaseTests.cpp | 2 +- tests/unit/feed/SubscriptionManagerTests.cpp | 214 ++--- tests/unit/feed/TransactionFeedTests.cpp | 504 +++++------ tests/unit/rpc/BaseTests.cpp | 8 +- tests/unit/rpc/RPCEngineTests.cpp | 2 +- .../rpc/handlers/AccountChannelsTests.cpp | 90 +- .../rpc/handlers/AccountCurrenciesTests.cpp | 32 +- tests/unit/rpc/handlers/AccountInfoTests.cpp | 18 +- tests/unit/rpc/handlers/AccountNFTsTests.cpp | 120 +-- .../unit/rpc/handlers/AccountObjectsTests.cpp | 350 ++++---- .../unit/rpc/handlers/AccountOffersTests.cpp | 60 +- tests/unit/rpc/handlers/AccountTxTests.cpp | 2 +- tests/unit/rpc/handlers/BookOffersTests.cpp | 802 +++++++++--------- .../rpc/handlers/GatewayBalancesTests.cpp | 20 +- .../rpc/handlers/GetAggregatePriceTests.cpp | 12 +- tests/unit/rpc/handlers/LedgerDataTests.cpp | 128 +-- tests/unit/rpc/handlers/LedgerEntryTests.cpp | 164 ++-- tests/unit/rpc/handlers/LedgerTests.cpp | 278 +++--- tests/unit/rpc/handlers/MPTHoldersTests.cpp | 12 +- tests/unit/rpc/handlers/NFTHistoryTests.cpp | 112 +-- tests/unit/rpc/handlers/NFTsByIssuerTest.cpp | 2 +- .../unit/rpc/handlers/NoRippleCheckTests.cpp | 82 +- tests/unit/rpc/handlers/SubscribeTests.cpp | 116 +-- .../rpc/handlers/TransactionEntryTests.cpp | 2 +- tests/unit/rpc/handlers/TxTests.cpp | 124 +-- tests/unit/rpc/handlers/UnsubscribeTests.cpp | 6 +- .../unit/util/config/ConfigFileJsonTests.cpp | 2 +- tests/unit/web/RPCServerHandlerTests.cpp | 18 +- tests/unit/web/ng/RPCServerHandlerTests.cpp | 16 +- 34 files changed, 1705 insertions(+), 1705 deletions(-) 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/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/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 ac7990af5..f27c80ef8 100644 --- a/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp +++ b/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp @@ -75,7 +75,7 @@ TEST_F(RPCAccountCurrenciesHandlerTest, AccountNotExist) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -97,7 +97,7 @@ TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaIntSequence) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -121,8 +121,8 @@ TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaStringSequence) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":"{}" + "account": "{}", + "ledger_index": "{}" }})JSON", kACCOUNT, kSEQ @@ -146,8 +146,8 @@ TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaHash) 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" ] @@ -212,7 +212,7 @@ TEST_F(RPCAccountCurrenciesHandlerTest, DefaultParameter) EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -247,8 +247,8 @@ TEST_F(RPCAccountCurrenciesHandlerTest, RequestViaLegderHash) EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})JSON", kACCOUNT, kLEDGER_HASH @@ -284,8 +284,8 @@ TEST_F(RPCAccountCurrenciesHandlerTest, RequestViaLegderSeq) EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); 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 2f1a4fd0b..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); diff --git a/tests/unit/rpc/handlers/AccountNFTsTests.cpp b/tests/unit/rpc/handlers/AccountNFTsTests.cpp index 49ad67d57..757593ebb 100644 --- a/tests/unit/rpc/handlers/AccountNFTsTests.cpp +++ b/tests/unit/rpc/handlers/AccountNFTsTests.cpp @@ -183,8 +183,8 @@ TEST_F(RPCAccountNFTsHandlerTest, LedgerNotFoundViaHash) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})JSON", kACCOUNT, kLEDGER_HASH @@ -209,8 +209,8 @@ TEST_F(RPCAccountNFTsHandlerTest, LedgerNotFoundViaStringIndex) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":"{}" + "account": "{}", + "ledger_index": "{}" }})JSON", kACCOUNT, kSEQ @@ -235,8 +235,8 @@ TEST_F(RPCAccountNFTsHandlerTest, LedgerNotFoundViaIntIndex) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":{} + "account": "{}", + "ledger_index": {} }})JSON", kACCOUNT, kSEQ @@ -262,7 +262,7 @@ TEST_F(RPCAccountNFTsHandlerTest, AccountNotFound) 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, @@ -325,7 +325,7 @@ TEST_F(RPCAccountNFTsHandlerTest, NormalPath) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -359,8 +359,8 @@ TEST_F(RPCAccountNFTsHandlerTest, Limit) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, kLIMIT @@ -394,8 +394,8 @@ TEST_F(RPCAccountNFTsHandlerTest, Marker) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{}" + "account": "{}", + "marker": "{}" }})JSON", kACCOUNT, kPAGE @@ -421,8 +421,8 @@ TEST_F(RPCAccountNFTsHandlerTest, InvalidMarker) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{}" + "account": "{}", + "marker": "{}" }})JSON", kACCOUNT, kINVALID_PAGE @@ -450,7 +450,7 @@ TEST_F(RPCAccountNFTsHandlerTest, AccountWithNoNFT) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -482,8 +482,8 @@ TEST_F(RPCAccountNFTsHandlerTest, invalidPage) 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, @@ -548,8 +548,8 @@ TEST_F(RPCAccountNFTsHandlerTest, LimitLessThanMin) 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, @@ -612,8 +612,8 @@ TEST_F(RPCAccountNFTsHandlerTest, LimitMoreThanMax) 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 3a8b90762..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", @@ -225,8 +225,8 @@ TEST_F(RPCAccountObjectsHandlerTest, LedgerNonExistViaIntSequence) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":30 + "account": "{}", + "ledger_index": 30 }})JSON", kACCOUNT )); @@ -247,8 +247,8 @@ TEST_F(RPCAccountObjectsHandlerTest, LedgerNonExistViaStringSequence) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":"30" + "account": "{}", + "ledger_index": "30" }})JSON", kACCOUNT )); @@ -270,8 +270,8 @@ TEST_F(RPCAccountObjectsHandlerTest, LedgerNonExistViaHash) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})JSON", kACCOUNT, kLEDGER_HASH @@ -295,7 +295,7 @@ TEST_F(RPCAccountObjectsHandlerTest, AccountNotExist) 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"; @@ -367,7 +367,7 @@ TEST_F(RPCAccountObjectsHandlerTest, DefaultParameterNoNFTFound) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -411,8 +411,8 @@ TEST_F(RPCAccountObjectsHandlerTest, Limit) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, kLIMIT @@ -455,8 +455,8 @@ TEST_F(RPCAccountObjectsHandlerTest, Marker) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, kINDEX1, @@ -509,8 +509,8 @@ TEST_F(RPCAccountObjectsHandlerTest, MultipleDirNoNFT) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, 2 * kCOUNT @@ -563,8 +563,8 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilter) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "type":"offer" + "account": "{}", + "type": "offer" }})JSON", kACCOUNT )); @@ -660,7 +660,7 @@ TEST_F(RPCAccountObjectsHandlerTest, TypeFilterReturnEmpty) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", + "account": "{}", "type": "check" }})JSON", kACCOUNT @@ -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"; @@ -994,7 +994,7 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMixOtherObjects) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -1033,8 +1033,8 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTReachLimitReturnMarker) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, 10 @@ -1082,8 +1082,8 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTReachLimitNoMarker) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, 11 @@ -1160,8 +1160,8 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMarker) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, ripple::strHex(marker), @@ -1216,8 +1216,8 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMarkerNoMoreNFT) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, ripple::strHex(ripple::uint256{beast::zero}), @@ -1245,7 +1245,7 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMarkerNotInRange) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", - "marker" : "{},{}" + "marker": "{},{}" }})JSON", kACCOUNT, kINDEX1, @@ -1278,7 +1278,7 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTMarkerNotExist) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ "account": "{}", - "marker" : "{},{}" + "marker": "{},{}" }})JSON", kACCOUNT, ripple::strHex(accountNftMax), @@ -1351,8 +1351,8 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTLimitAdjust) 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"; @@ -1447,7 +1447,7 @@ TEST_F(RPCAccountObjectsHandlerTest, FilterNFT) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", + "account": "{}", "type": "nft_page" }})JSON", kACCOUNT @@ -1488,8 +1488,8 @@ TEST_F(RPCAccountObjectsHandlerTest, NFTZeroMarkerNotAffectOtherMarker) 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", @@ -1568,7 +1568,7 @@ TEST_F(RPCAccountObjectsHandlerTest, LimitLessThanMin) 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", @@ -1644,7 +1644,7 @@ TEST_F(RPCAccountObjectsHandlerTest, LimitMoreThanMax) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", + "account": "{}", "limit": {} }})JSON", kACCOUNT, diff --git a/tests/unit/rpc/handlers/AccountOffersTests.cpp b/tests/unit/rpc/handlers/AccountOffersTests.cpp index 373d38a89..e3ac62a08 100644 --- a/tests/unit/rpc/handlers/AccountOffersTests.cpp +++ b/tests/unit/rpc/handlers/AccountOffersTests.cpp @@ -178,8 +178,8 @@ TEST_F(RPCAccountOffersHandlerTest, LedgerNotFoundViaHash) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})JSON", kACCOUNT, kLEDGER_HASH @@ -204,8 +204,8 @@ TEST_F(RPCAccountOffersHandlerTest, LedgerNotFoundViaStringIndex) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":"{}" + "account": "{}", + "ledger_index": "{}" }})JSON", kACCOUNT, kSEQ @@ -230,8 +230,8 @@ TEST_F(RPCAccountOffersHandlerTest, LedgerNotFoundViaIntIndex) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "ledger_index":{} + "account": "{}", + "ledger_index": {} }})JSON", kACCOUNT, kSEQ @@ -257,7 +257,7 @@ TEST_F(RPCAccountOffersHandlerTest, AccountNotFound) 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", @@ -334,7 +334,7 @@ TEST_F(RPCAccountOffersHandlerTest, DefaultParams) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}" + "account": "{}" }})JSON", kACCOUNT )); @@ -382,8 +382,8 @@ TEST_F(RPCAccountOffersHandlerTest, Limit) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":10 + "account": "{}", + "limit": 10 }})JSON", kACCOUNT )); @@ -435,8 +435,8 @@ TEST_F(RPCAccountOffersHandlerTest, Marker) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, kINDEX1, @@ -471,8 +471,8 @@ TEST_F(RPCAccountOffersHandlerTest, MarkerNotExists) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "marker":"{},{}" + "account": "{}", + "marker": "{},{}" }})JSON", kACCOUNT, kINDEX1, @@ -529,8 +529,8 @@ TEST_F(RPCAccountOffersHandlerTest, LimitLessThanMin) static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "account":"{}", - "limit":{} + "account": "{}", + "limit": {} }})JSON", kACCOUNT, AccountOffersHandler::kLIMIT_MIN - 1 @@ -584,8 +584,8 @@ TEST_F(RPCAccountOffersHandlerTest, LimitMoreThanMax) 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 e93373988..e51df9bc1 100644 --- a/tests/unit/rpc/handlers/AccountTxTests.cpp +++ b/tests/unit/rpc/handlers/AccountTxTests.cpp @@ -791,7 +791,7 @@ TEST_F(RPCAccountTxHandlerTest, LimitAndMarker) "ledger_index_max": {}, "limit": 2, "forward": false, - "marker": {{"ledger":10,"seq":11}} + "marker": {{"ledger": 10, "seq": 11}} }})JSON", kACCOUNT, -1, diff --git a/tests/unit/rpc/handlers/BookOffersTests.cpp b/tests/unit/rpc/handlers/BookOffersTests.cpp index 9ff8b305e..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,14 +316,14 @@ 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", @@ -333,14 +333,14 @@ generateParameterBookOffersTestBundles() 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", @@ -350,14 +350,14 @@ generateParameterBookOffersTestBundles() 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", @@ -367,14 +367,14 @@ generateParameterBookOffersTestBundles() 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", diff --git a/tests/unit/rpc/handlers/GatewayBalancesTests.cpp b/tests/unit/rpc/handlers/GatewayBalancesTests.cpp index dd4c0aa7f..7f37b1d78 100644 --- a/tests/unit/rpc/handlers/GatewayBalancesTests.cpp +++ b/tests/unit/rpc/handlers/GatewayBalancesTests.cpp @@ -462,10 +462,10 @@ generateNormalPathTestBundles() }, .expectedJson = fmt::format( R"JSON({{ - "obligations":{{ + "obligations": {{ "JPY": "50" }}, - "balances":{{ + "balances": {{ "{}": [ {{ "currency": "USD", @@ -477,7 +477,7 @@ generateNormalPathTestBundles() }} ] }}, - "frozen_balances":{{ + "frozen_balances": {{ "{}": [ {{ "currency": "JPY", @@ -485,7 +485,7 @@ generateNormalPathTestBundles() }} ] }}, - "assets":{{ + "assets": {{ "{}": [ {{ "currency": "EUR", @@ -516,7 +516,7 @@ generateNormalPathTestBundles() )}, .expectedJson = fmt::format( R"JSON({{ - "obligations":{{ + "obligations": {{ "JPY": "50" }}, "account": "{}", @@ -533,7 +533,7 @@ generateNormalPathTestBundles() .mockedObjects = std::vector{overflowState, overflowState}, .expectedJson = fmt::format( R"JSON({{ - "obligations":{{ + "obligations": {{ "JPY": "9999999999999999e80" }}, "account": "{}", @@ -562,10 +562,10 @@ generateNormalPathTestBundles() }, .expectedJson = fmt::format( R"JSON({{ - "obligations":{{ + "obligations": {{ "EUR": "30" }}, - "balances":{{ + "balances": {{ "{}": [ {{ "currency": "USD", @@ -577,7 +577,7 @@ generateNormalPathTestBundles() }} ] }}, - "assets":{{ + "assets": {{ "{}": [ {{ "currency": "JPY", @@ -609,7 +609,7 @@ generateNormalPathTestBundles() }, .expectedJson = fmt::format( R"JSON({{ - "balances":{{ + "balances": {{ "{}": [ {{ "currency": "EUR", 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..7fc33d2a1 100644 --- a/tests/unit/rpc/handlers/LedgerEntryTests.cpp +++ b/tests/unit/rpc/handlers/LedgerEntryTests.cpp @@ -687,7 +687,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}"] + "accounts": ["{}"] }} }})JSON", kACCOUNT @@ -701,7 +701,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}","{}"], + "accounts": ["{}", "{}"], "currency": "USD" }} }})JSON", @@ -717,7 +717,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}",123], + "accounts": ["{}",123], "currency": "USD" }} }})JSON", @@ -732,7 +732,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}","123"], + "accounts": ["{}", "123"], "currency": "USD" }} }})JSON", @@ -747,7 +747,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}","{}"], + "accounts": ["{}", "{}"], "currency": "XXXX" }} }})JSON", @@ -763,7 +763,7 @@ generateTestValuesForParametersTest() .testJson = fmt::format( R"JSON({{ "ripple_state": {{ - "accounts" : ["{}","{}"], + "accounts": ["{}", "{}"], "currency": 123 }} }})JSON", @@ -912,11 +912,11 @@ generateTestValuesForParametersTest() R"JSON({{ "amm": {{ - "asset":{{}}, + "asset": {{}}, "asset2": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -932,11 +932,11 @@ generateTestValuesForParametersTest() R"JSON({{ "amm": {{ - "asset2":{{}}, + "asset2": {{}}, "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -954,8 +954,8 @@ generateTestValuesForParametersTest() {{ "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -973,8 +973,8 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -993,8 +993,8 @@ generateTestValuesForParametersTest() "asset": "invalid", "asset2": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -1013,8 +1013,8 @@ generateTestValuesForParametersTest() "asset2": "invalid", "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -1032,12 +1032,12 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"XRP" + "currency": "XRP" }}, "asset": {{ - "currency" : "USD2", - "issuer" : "{}" + "currency": "USD2", + "issuer": "{}" }} }} }})JSON", @@ -1055,12 +1055,12 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"XRP" + "currency": "XRP" }}, "asset": {{ - "currency" : "USD", - "issuer" : "aa{}" + "currency": "USD", + "issuer": "aa{}" }} }} }})JSON", @@ -1078,12 +1078,12 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"JPY" + "currency": "JPY" }}, "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -1101,13 +1101,13 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"XRP", - "issuer":"{}" + "currency": "XRP", + "issuer": "{}" }}, "asset": {{ - "currency" : "USD", - "issuer" : "{}" + "currency": "USD", + "issuer": "{}" }} }} }})JSON", @@ -1126,11 +1126,11 @@ generateTestValuesForParametersTest() {{ "asset2": {{ - "currency":"XRP" + "currency": "XRP" }}, "asset": {{ - "issuer" : "{}" + "issuer": "{}" }} }} }})JSON", @@ -2694,7 +2694,7 @@ generateTestValuesForNormalPathTest() R"JSON({{ "binary": true, "ripple_state": {{ - "accounts": ["{}","{}"], + "accounts": ["{}", "{}"], "currency": "USD" }} }})JSON", @@ -2782,11 +2782,11 @@ generateTestValuesForNormalPathTest() "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ - "currency" : "XRP" + "currency": "XRP" }}, "IssuingChainIssue": {{ - "currency" : "JPY", - "issuer" : "{}" + "currency": "JPY", + "issuer": "{}" }} }} }})JSON", @@ -2817,11 +2817,11 @@ generateTestValuesForNormalPathTest() "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ - "currency" : "XRP" + "currency": "XRP" }}, "IssuingChainIssue": {{ - "currency" : "JPY", - "issuer" : "{}" + "currency": "JPY", + "issuer": "{}" }} }} }})JSON", @@ -2851,11 +2851,11 @@ generateTestValuesForNormalPathTest() "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ - "currency" : "XRP" + "currency": "XRP" }}, "IssuingChainIssue": {{ - "currency" : "JPY", - "issuer" : "{}" + "currency": "JPY", + "issuer": "{}" }}, "xchain_owned_claim_id": 10 }} @@ -2885,11 +2885,11 @@ generateTestValuesForNormalPathTest() "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ - "currency" : "XRP" + "currency": "XRP" }}, "IssuingChainIssue": {{ - "currency" : "JPY", - "issuer" : "{}" + "currency": "JPY", + "issuer": "{}" }}, "xchain_owned_create_account_claim_id": 10 }} @@ -3131,23 +3131,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"; @@ -3622,23 +3622,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 6551b49bb..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 @@ -312,7 +312,7 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardTrue) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; 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" }, @@ -456,7 +456,7 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardFalseV1) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": false @@ -610,7 +610,7 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexSpecificForwardFalseV2) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": false @@ -642,7 +642,7 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexNotSpecificForwardTrue) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; 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")); }); @@ -683,7 +683,7 @@ TEST_F(RPCNFTHistoryHandlerTest, IndexNotSpecificForwardFalse) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; 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")); }); @@ -724,7 +724,7 @@ TEST_F(RPCNFTHistoryHandlerTest, BinaryTrueV1) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; 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(), @@ -778,7 +778,7 @@ TEST_F(RPCNFTHistoryHandlerTest, BinaryTrueV2) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; 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(), @@ -829,12 +829,12 @@ TEST_F(RPCNFTHistoryHandlerTest, LimitAndMarker) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; 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); }); } @@ -877,8 +877,8 @@ TEST_F(RPCNFTHistoryHandlerTest, SpecificLedgerIndex) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", - "ledger_index":{} + "nft_id": "{}", + "ledger_index": {} }})JSON", kNFT_ID, kMAX_SEQ - 1 @@ -903,8 +903,8 @@ TEST_F(RPCNFTHistoryHandlerTest, SpecificNonexistLedgerIntIndex) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", - "ledger_index":{} + "nft_id": "{}", + "ledger_index": {} }})JSON", kNFT_ID, kMAX_SEQ - 1 @@ -926,8 +926,8 @@ TEST_F(RPCNFTHistoryHandlerTest, SpecificNonexistLedgerStringIndex) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", - "ledger_index":"{}" + "nft_id": "{}", + "ledger_index": "{}" }})JSON", kNFT_ID, kMAX_SEQ - 1 @@ -966,8 +966,8 @@ TEST_F(RPCNFTHistoryHandlerTest, SpecificLedgerHash) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", - "ledger_hash":"{}" + "nft_id": "{}", + "ledger_hash": "{}" }})JSON", kNFT_ID, kLEDGER_HASH @@ -1004,7 +1004,7 @@ TEST_F(RPCNFTHistoryHandlerTest, TxLessThanMinSeq) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; static auto const kINPUT = json::parse(fmt::format( R"JSON({{ - "nft_id":"{}", + "nft_id": "{}", "ledger_index_min": {}, "ledger_index_max": {}, "forward": false @@ -1045,7 +1045,7 @@ TEST_F(RPCNFTHistoryHandlerTest, TxLargerThanMaxSeq) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; 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")); }); } @@ -1086,7 +1086,7 @@ TEST_F(RPCNFTHistoryHandlerTest, LimitMoreThanMax) auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; 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 0793b8afa..cb7122de2 100644 --- a/tests/unit/rpc/handlers/NoRippleCheckTests.cpp +++ b/tests/unit/rpc/handlers/NoRippleCheckTests.cpp @@ -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 3fda9bee4..09b4a7118 100644 --- a/tests/unit/rpc/handlers/TxTests.cpp +++ b/tests/unit/rpc/handlers/TxTests.cpp @@ -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"; @@ -994,20 +994,20 @@ TEST_F(RPCTxTest, ViaCTID) { 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/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)); From 371237487b43f12a99863ec4c21d2e9abfc6bbc0 Mon Sep 17 00:00:00 2001 From: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Date: Fri, 27 Jun 2025 07:27:34 -0700 Subject: [PATCH 47/49] feat: Support single asset vault (#1979) fixes #1921 --------- Co-authored-by: Sergey Kuznetsov Co-authored-by: Ayaz Salikhov --- src/rpc/CMakeLists.txt | 1 + src/rpc/Errors.cpp | 2 +- src/rpc/Errors.hpp | 1 + src/rpc/RPCCenter.cpp | 3 + src/rpc/RPCHelpers.hpp | 7 + src/rpc/common/impl/HandlerProvider.cpp | 2 + src/rpc/handlers/LedgerEntry.cpp | 14 +- src/rpc/handlers/LedgerEntry.hpp | 18 + src/rpc/handlers/VaultInfo.cpp | 191 ++++++++ src/rpc/handlers/VaultInfo.hpp | 134 ++++++ src/util/LedgerUtils.hpp | 1 + src/web/impl/ErrorHandling.hpp | 1 + src/web/ng/impl/ErrorHandling.cpp | 1 + tests/common/util/TestObject.cpp | 35 ++ tests/common/util/TestObject.hpp | 13 + tests/unit/CMakeLists.txt | 1 + tests/unit/rpc/RPCHelpersTests.cpp | 2 + tests/unit/rpc/handlers/AllHandlerTests.cpp | 15 +- tests/unit/rpc/handlers/LedgerEntryTests.cpp | 172 +++++++ tests/unit/rpc/handlers/VaultInfoTests.cpp | 450 +++++++++++++++++++ tests/unit/util/LedgerUtilsTests.cpp | 2 + 21 files changed, 1061 insertions(+), 5 deletions(-) create mode 100644 src/rpc/handlers/VaultInfo.cpp create mode 100644 src/rpc/handlers/VaultInfo.hpp create mode 100644 tests/unit/rpc/handlers/VaultInfoTests.cpp 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 b71679f35..a7ad6a136 100644 --- a/src/rpc/Errors.cpp +++ b/src/rpc/Errors.cpp @@ -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/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/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/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/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/TestObject.cpp b/tests/common/util/TestObject.cpp index 4f10b5937..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 @@ -1642,3 +1643,37 @@ createAuthCredentialArray(std::vector issuer, 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/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/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/AllHandlerTests.cpp b/tests/unit/rpc/handlers/AllHandlerTests.cpp index 72040ea41..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" @@ -79,6 +80,7 @@ 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/LedgerEntryTests.cpp b/tests/unit/rpc/handlers/LedgerEntryTests.cpp index 7fc33d2a1..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 @@ -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", @@ -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( @@ -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 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) }; From 123e09695eeade593ec44594c8bf515382c23cc8 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 27 Jun 2025 17:13:05 +0100 Subject: [PATCH 48/49] feat: Switch to xrpl/2.5.0 release (#2267) --- conan.lock | 4 ++-- conanfile.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/conan.lock b/conan.lock index 29a83f69d..35b23e246 100644 --- a/conan.lock +++ b/conan.lock @@ -3,7 +3,7 @@ "requires": [ "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1750263732.782", "xxhash/0.8.2#7856c968c985b2981b707ee8f2413b2b%1750263730.908", - "xrpl/2.5.0-rc1#e5897e048ea5712d2c71561c507d949d%1750263725.455", + "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", @@ -33,7 +33,7 @@ "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1750263732.782", "protobuf/3.21.12#d927114e28de9f4691a6bbcdd9a529d1%1750263698.841", "protobuf/3.21.9#64ce20e1d9ea24f3d6c504015d5f6fa8%1750263690.822", - "cmake/3.31.6#ed0e6c1d49bd564ce6fed1a19653b86d%1750263636.055", + "cmake/3.31.7#57c3e118bcf267552c0ea3f8bee1e7d5%1749863707.208", "b2/5.3.2#7b5fabfe7088ae933fb3e78302343ea0%1750263614.565" ], "python_requires": [], diff --git a/conanfile.py b/conanfile.py index adb96b177..039a6d2bd 100644 --- a/conanfile.py +++ b/conanfile.py @@ -30,7 +30,7 @@ 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' ] From 8fcc2dfa196761dabe8a4b9718725ec0ceb1d5bc Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 27 Jun 2025 18:56:14 +0100 Subject: [PATCH 49/49] fix: Pin lxml<6.0.0 (#2269) --- docker/ci/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/ci/Dockerfile b/docker/ci/Dockerfile index db60e0e7d..503e07501 100644 --- a/docker/ci/Dockerfile +++ b/docker/ci/Dockerfile @@ -45,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 \