mirror of
				https://github.com/XRPLF/clio.git
				synced 2025-11-04 11:55:51 +00:00 
			
		
		
		
	Compare commits
	
		
			56 Commits
		
	
	
		
			890279b15e
			...
			2.3.0-b6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d001e35427 | ||
| 
						 | 
					592af70f03 | ||
| 
						 | 
					33cf336964 | ||
| 
						 | 
					16e07b90db | ||
| 
						 | 
					39419c8b58 | ||
| 
						 | 
					e38658a0d6 | ||
| 
						 | 
					fb98a6a394 | ||
| 
						 | 
					b8a8248c42 | ||
| 
						 | 
					a092b7ae08 | ||
| 
						 | 
					07438a2e02 | ||
| 
						 | 
					09aa688de4 | ||
| 
						 | 
					a7bff26fd6 | ||
| 
						 | 
					081adf1cae | ||
| 
						 | 
					ffc9deb0f8 | ||
| 
						 | 
					717a29ecdf | ||
| 
						 | 
					e8db74456a | ||
| 
						 | 
					4947a83696 | ||
| 
						 | 
					164387cab0 | ||
| 
						 | 
					b8f1deb90f | ||
| 
						 | 
					5c77e59374 | ||
| 
						 | 
					6d070132c7 | ||
| 
						 | 
					d2dda69448 | ||
| 
						 | 
					e2aeaa0956 | ||
| 
						 | 
					2951b4aaa0 | ||
| 
						 | 
					6c3c761dd1 | ||
| 
						 | 
					527020680a | ||
| 
						 | 
					401448f771 | ||
| 
						 | 
					0f12a6d7f2 | ||
| 
						 | 
					5c8fc939f2 | ||
| 
						 | 
					b1be848098 | ||
| 
						 | 
					41aabbfcce | ||
| 
						 | 
					c00d25aa6b | ||
| 
						 | 
					8d5c588e35 | ||
| 
						 | 
					9df3e936cc | ||
| 
						 | 
					4166c46820 | ||
| 
						 | 
					f75cbd456b | ||
| 
						 | 
					d189651821 | ||
| 
						 | 
					3f791c1315 | ||
| 
						 | 
					418511332e | ||
| 
						 | 
					e5a0477352 | ||
| 
						 | 
					3118110eb8 | ||
| 
						 | 
					6d20f39f67 | ||
| 
						 | 
					9cb1e06c8e | ||
| 
						 | 
					423244eb4b | ||
| 
						 | 
					7aaba1cbad | ||
| 
						 | 
					b7c50fd73d | ||
| 
						 | 
					442ee874d5 | ||
| 
						 | 
					0679034978 | ||
| 
						 | 
					b41ea34212 | ||
| 
						 | 
					4e147deafa | ||
| 
						 | 
					b08447e8e0 | ||
| 
						 | 
					9432165ace | ||
| 
						 | 
					3b6a87249c | ||
| 
						 | 
					b7449f72b7 | ||
| 
						 | 
					443c74436e | ||
| 
						 | 
					7b5e02731d | 
@@ -26,12 +26,12 @@ sources="src tests"
 | 
			
		||||
formatter="clang-format -i"
 | 
			
		||||
version=$($formatter --version | grep -o '[0-9\.]*')
 | 
			
		||||
 | 
			
		||||
if [[ "18.0.0" > "$version" ]]; then
 | 
			
		||||
if [[ "19.0.0" > "$version" ]]; then
 | 
			
		||||
    cat <<EOF
 | 
			
		||||
 | 
			
		||||
                                    ERROR
 | 
			
		||||
-----------------------------------------------------------------------------
 | 
			
		||||
            A minimum of version 18 of `which clang-format` is required.
 | 
			
		||||
            A minimum of version 19 of `which clang-format` is required.
 | 
			
		||||
            Your version is $version.
 | 
			
		||||
            Please fix paths and run again.
 | 
			
		||||
-----------------------------------------------------------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ verify_tag_signed() {
 | 
			
		||||
while read local_ref local_oid remote_ref remote_oid; do
 | 
			
		||||
    # Check some things if we're pushing a branch called "release/"
 | 
			
		||||
    if echo "$remote_ref" | grep ^refs\/heads\/release\/ &> /dev/null ; then
 | 
			
		||||
        version=$(echo $remote_ref | awk -F/ '{print $NF}')
 | 
			
		||||
        version=$(git tag --points-at HEAD)
 | 
			
		||||
        echo "Looks like you're trying to push a $version release..."
 | 
			
		||||
        echo "Making sure you've signed and tagged it."
 | 
			
		||||
        if verify_commit_signed && verify_tag && verify_tag_signed ; then
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -149,13 +149,6 @@ jobs:
 | 
			
		||||
          name: clio_tests_${{ runner.os }}_${{ matrix.build_type }}_${{ steps.conan.outputs.conan_profile }}
 | 
			
		||||
          path: build/clio_*tests
 | 
			
		||||
 | 
			
		||||
      - name: Upload test data
 | 
			
		||||
        if: ${{ !matrix.code_coverage }}
 | 
			
		||||
        uses: actions/upload-artifact@v4
 | 
			
		||||
        with:
 | 
			
		||||
          name: clio_test_data_${{ runner.os }}_${{ matrix.build_type }}_${{ steps.conan.outputs.conan_profile }}
 | 
			
		||||
          path: build/tests/unit/test_data
 | 
			
		||||
 | 
			
		||||
      - name: Save cache
 | 
			
		||||
        uses: ./.github/actions/save_cache
 | 
			
		||||
        with:
 | 
			
		||||
@@ -219,11 +212,6 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          name: clio_tests_${{ runner.os }}_${{ matrix.build_type }}_${{ matrix.conan_profile }}
 | 
			
		||||
 | 
			
		||||
      - uses: actions/download-artifact@v4
 | 
			
		||||
        with:
 | 
			
		||||
          name: clio_test_data_${{ runner.os }}_${{ matrix.build_type }}_${{ matrix.conan_profile }}
 | 
			
		||||
          path: tests/unit/test_data
 | 
			
		||||
 | 
			
		||||
      - name: Run clio_tests
 | 
			
		||||
        run: |
 | 
			
		||||
          chmod +x ./clio_tests
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/check_pr_title.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/check_pr_title.yml
									
									
									
									
										vendored
									
									
								
							@@ -10,7 +10,7 @@ jobs:
 | 
			
		||||
    # permissions:
 | 
			
		||||
    #   pull-requests: write
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: ytanikin/PRConventionalCommits@1.2.0
 | 
			
		||||
      - uses: ytanikin/PRConventionalCommits@1.3.0
 | 
			
		||||
        with:
 | 
			
		||||
          task_types: '["build","feat","fix","docs","test","ci","style","refactor","perf","chore"]'
 | 
			
		||||
          add_label: false
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/clang-tidy.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/clang-tidy.yml
									
									
									
									
										vendored
									
									
								
							@@ -60,7 +60,7 @@ jobs:
 | 
			
		||||
        shell: bash
 | 
			
		||||
        id: run_clang_tidy
 | 
			
		||||
        run: |
 | 
			
		||||
          run-clang-tidy-18 -p build -j ${{ steps.number_of_threads.outputs.threads_number }} -fix -quiet 1>output.txt
 | 
			
		||||
          run-clang-tidy-19 -p build -j ${{ steps.number_of_threads.outputs.threads_number }} -fix -quiet 1>output.txt
 | 
			
		||||
 | 
			
		||||
      - name: Check format
 | 
			
		||||
        if: ${{ steps.run_clang_tidy.outcome != 'success' }}
 | 
			
		||||
@@ -99,7 +99,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Create PR with fixes
 | 
			
		||||
        if: ${{ steps.run_clang_tidy.outcome != 'success' }}
 | 
			
		||||
        uses: peter-evans/create-pull-request@v6
 | 
			
		||||
        uses: peter-evans/create-pull-request@v7
 | 
			
		||||
        env:
 | 
			
		||||
          GH_REPO: ${{ github.repository }}
 | 
			
		||||
          GH_TOKEN: ${{ github.token }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/nightly.yml
									
									
									
									
										vendored
									
									
								
							@@ -71,12 +71,6 @@ jobs:
 | 
			
		||||
          name: clio_tests_${{ runner.os }}_${{ matrix.build_type }}
 | 
			
		||||
          path: build/clio_*tests
 | 
			
		||||
 | 
			
		||||
      - name: Upload test data
 | 
			
		||||
        uses: actions/upload-artifact@v4
 | 
			
		||||
        with:
 | 
			
		||||
          name: clio_test_data_${{ runner.os }}_${{ matrix.build_type }}
 | 
			
		||||
          path: build/tests/unit/test_data
 | 
			
		||||
 | 
			
		||||
      - name: Compress clio_server
 | 
			
		||||
        shell: bash
 | 
			
		||||
        run: |
 | 
			
		||||
@@ -130,11 +124,6 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          name: clio_tests_${{ runner.os }}_${{ matrix.build_type }}
 | 
			
		||||
 | 
			
		||||
      - uses: actions/download-artifact@v4
 | 
			
		||||
        with:
 | 
			
		||||
          name: clio_test_data_${{ runner.os }}_${{ matrix.build_type }}
 | 
			
		||||
          path: tests/unit/test_data
 | 
			
		||||
 | 
			
		||||
      - name: Run clio_tests
 | 
			
		||||
        run: |
 | 
			
		||||
          chmod +x ./clio_tests
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/upload_coverage_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/upload_coverage_report.yml
									
									
									
									
										vendored
									
									
								
							@@ -23,7 +23,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Upload coverage report
 | 
			
		||||
        if: ${{ hashFiles('build/coverage_report.xml') != '' }}
 | 
			
		||||
        uses: wandalen/wretry.action@v3.5.0
 | 
			
		||||
        uses: wandalen/wretry.action@v3.7.2
 | 
			
		||||
        with:
 | 
			
		||||
          action: codecov/codecov-action@v4
 | 
			
		||||
          with: |
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ git config --local core.hooksPath .githooks
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Git hooks dependencies
 | 
			
		||||
The pre-commit hook requires `clang-format >= 18.0.0` and `cmake-format` to be installed on your machine.
 | 
			
		||||
The pre-commit hook requires `clang-format >= 19.0.0` and `cmake-format` to be installed on your machine.
 | 
			
		||||
`clang-format` can be installed using `brew` on macOS and default package manager on Linux.
 | 
			
		||||
`cmake-format` can be installed using `pip`.
 | 
			
		||||
The hook will also attempt to automatically use `doxygen` to verify that everything public in the codebase is covered by doc comments. If `doxygen` is not installed, the hook will raise a warning suggesting to install `doxygen` for future commits.
 | 
			
		||||
@@ -105,7 +105,7 @@ The button for that is near the bottom of the PR's page on GitHub.
 | 
			
		||||
This is a non-exhaustive list of recommended style guidelines. These are not always strictly enforced and serve as a way to keep the codebase coherent.
 | 
			
		||||
 | 
			
		||||
## Formatting
 | 
			
		||||
Code must conform to `clang-format` version 18, unless the result would be unreasonably difficult to read or maintain.
 | 
			
		||||
Code must conform to `clang-format` version 19, unless the result would be unreasonably difficult to read or maintain.
 | 
			
		||||
In most cases the pre-commit hook will take care of formatting and will fix any issues automatically.
 | 
			
		||||
To manually format your code, use `clang-format -i <your changed files>` for C++ files and `cmake-format -i <your changed files>` for CMake files.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ if (lint)
 | 
			
		||||
    endif ()
 | 
			
		||||
    message(STATUS "Using clang-tidy from CLIO_CLANG_TIDY_BIN")
 | 
			
		||||
  else ()
 | 
			
		||||
    find_program(_CLANG_TIDY_BIN NAMES "clang-tidy-18" "clang-tidy" REQUIRED)
 | 
			
		||||
    find_program(_CLANG_TIDY_BIN NAMES "clang-tidy-19" "clang-tidy" REQUIRED)
 | 
			
		||||
  endif ()
 | 
			
		||||
 | 
			
		||||
  if (NOT _CLANG_TIDY_BIN)
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,8 @@ class Clio(ConanFile):
 | 
			
		||||
        'protobuf/3.21.9',
 | 
			
		||||
        'grpc/1.50.1',
 | 
			
		||||
        'openssl/1.1.1u',
 | 
			
		||||
        'xrpl/2.3.0-b1',
 | 
			
		||||
        'xrpl/2.3.0-rc2',
 | 
			
		||||
        'zlib/1.3.1',
 | 
			
		||||
        'libbacktrace/cci.20210118'
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ USER root
 | 
			
		||||
WORKDIR /root
 | 
			
		||||
 | 
			
		||||
ENV CCACHE_VERSION=4.10.2 \
 | 
			
		||||
    LLVM_TOOLS_VERSION=18 \
 | 
			
		||||
    LLVM_TOOLS_VERSION=19 \
 | 
			
		||||
    GH_VERSION=2.40.0 \
 | 
			
		||||
    DOXYGEN_VERSION=1.12.0
 | 
			
		||||
 
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,9 @@
 | 
			
		||||
        "cache_timeout": 0.250, // in seconds, could be 0, which means no cache
 | 
			
		||||
        "request_timeout": 10.0 // time for Clio to wait for rippled to reply on a forwarded request (default is 10 seconds)
 | 
			
		||||
    },
 | 
			
		||||
    "rpc": {
 | 
			
		||||
        "cache_timeout": 0.5 // in seconds, could be 0, which means no cache for rpc
 | 
			
		||||
    },
 | 
			
		||||
    "dos_guard": {
 | 
			
		||||
        // Comma-separated list of IPs to exclude from rate limiting
 | 
			
		||||
        "whitelist": [
 | 
			
		||||
@@ -67,7 +70,14 @@
 | 
			
		||||
        "admin_password": "xrp",
 | 
			
		||||
        // If local_admin is true, Clio will consider requests come from 127.0.0.1 as admin requests
 | 
			
		||||
        // It's true by default unless admin_password is set,'local_admin' : true and 'admin_password' can not be set at the same time
 | 
			
		||||
        "local_admin": false
 | 
			
		||||
        "local_admin": false,
 | 
			
		||||
        "processing_policy": "parallel", // Could be "sequent" or "parallel".
 | 
			
		||||
        // For sequent policy request from one client connection will be processed one by one and the next one will not be read before
 | 
			
		||||
        // the previous one is processed. For parallel policy Clio will take all requests and process them in parallel and
 | 
			
		||||
        // send a reply for each request whenever it is ready.
 | 
			
		||||
        "parallel_requests_limit": 10, // Optional parameter, used only if "processing_strategy" is "parallel". It limits the number of requests for one client connection processed in parallel. Infinite if not specified.
 | 
			
		||||
        // Max number of responses to queue up before sent successfully. If a client's waiting queue is too long, the server will close the connection.
 | 
			
		||||
        "ws_max_sending_queue_size": 1500
 | 
			
		||||
    },
 | 
			
		||||
    // Time in seconds for graceful shutdown. Defaults to 10 seconds. Not fully implemented yet.
 | 
			
		||||
    "graceful_period": 10.0,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ You can find an example docker-compose file, with Prometheus and Grafana configs
 | 
			
		||||
 | 
			
		||||
## Using `clang-tidy` for static analysis
 | 
			
		||||
 | 
			
		||||
The minimum [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) version required is 17.0.
 | 
			
		||||
The minimum [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) version required is 19.0.
 | 
			
		||||
 | 
			
		||||
Clang-tidy can be run by Cmake when building the project. To achieve this, you just need to provide the option `-o lint=True` for the `conan install` command:
 | 
			
		||||
 | 
			
		||||
@@ -26,5 +26,5 @@ By default Cmake will try to find `clang-tidy` automatically in your system.
 | 
			
		||||
To force Cmake to use your desired binary, set the `CLIO_CLANG_TIDY_BIN` environment variable to the path of the `clang-tidy` binary. For example:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
export CLIO_CLANG_TIDY_BIN=/opt/homebrew/opt/llvm@17/bin/clang-tidy
 | 
			
		||||
export CLIO_CLANG_TIDY_BIN=/opt/homebrew/opt/llvm@19/bin/clang-tidy
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
add_subdirectory(util)
 | 
			
		||||
add_subdirectory(data)
 | 
			
		||||
add_subdirectory(etl)
 | 
			
		||||
add_subdirectory(etlng)
 | 
			
		||||
add_subdirectory(feed)
 | 
			
		||||
add_subdirectory(rpc)
 | 
			
		||||
add_subdirectory(web)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
add_library(clio_app)
 | 
			
		||||
target_sources(clio_app PRIVATE CliArgs.cpp ClioApplication.cpp)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(clio_app PUBLIC clio_etl clio_feed clio_web clio_rpc)
 | 
			
		||||
target_link_libraries(clio_app PUBLIC clio_etl clio_etlng clio_feed clio_web clio_rpc)
 | 
			
		||||
 
 | 
			
		||||
@@ -101,9 +101,7 @@ ClioApplication::run()
 | 
			
		||||
    auto backend = data::make_Backend(config_);
 | 
			
		||||
 | 
			
		||||
    // Manages clients subscribed to streams
 | 
			
		||||
    auto subscriptionsRunner = feed::SubscriptionManagerRunner(config_, backend);
 | 
			
		||||
 | 
			
		||||
    auto const subscriptions = subscriptionsRunner.getManager();
 | 
			
		||||
    auto subscriptions = feed::SubscriptionManager::make_SubscriptionManager(config_, backend);
 | 
			
		||||
 | 
			
		||||
    // Tracks which ledgers have been validated by the network
 | 
			
		||||
    auto ledgers = etl::NetworkValidatedLedgers::make_ValidatedLedgers();
 | 
			
		||||
@@ -123,12 +121,14 @@ ClioApplication::run()
 | 
			
		||||
    auto const handlerProvider = std::make_shared<rpc::impl::ProductionHandlerProvider const>(
 | 
			
		||||
        config_, backend, subscriptions, balancer, etl, amendmentCenter, counters
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    using RPCEngineType = rpc::RPCEngine<etl::LoadBalancer, rpc::Counters>;
 | 
			
		||||
    auto const rpcEngine =
 | 
			
		||||
        rpc::RPCEngine::make_RPCEngine(backend, balancer, dosGuard, workQueue, counters, handlerProvider);
 | 
			
		||||
        RPCEngineType::make_RPCEngine(config_, backend, balancer, dosGuard, workQueue, counters, handlerProvider);
 | 
			
		||||
 | 
			
		||||
    // Init the web server
 | 
			
		||||
    auto handler =
 | 
			
		||||
        std::make_shared<web::RPCServerHandler<rpc::RPCEngine, etl::ETLService>>(config_, backend, rpcEngine, etl);
 | 
			
		||||
        std::make_shared<web::RPCServerHandler<RPCEngineType, etl::ETLService>>(config_, backend, rpcEngine, etl);
 | 
			
		||||
    auto const httpServer = web::make_HttpServer(config_, ioc, dosGuard, handler);
 | 
			
		||||
 | 
			
		||||
    // Blocks until stopped.
 | 
			
		||||
 
 | 
			
		||||
@@ -124,6 +124,13 @@ struct Amendments {
 | 
			
		||||
    REGISTER(NFTokenMintOffer);
 | 
			
		||||
    REGISTER(fixReducedOffersV2);
 | 
			
		||||
    REGISTER(fixEnforceNFTokenTrustline);
 | 
			
		||||
    REGISTER(fixInnerObjTemplate2);
 | 
			
		||||
    REGISTER(fixNFTokenPageLinks);
 | 
			
		||||
    REGISTER(InvariantsV1_1);
 | 
			
		||||
    REGISTER(MPTokensV1);
 | 
			
		||||
    REGISTER(fixAMMv1_2);
 | 
			
		||||
    REGISTER(AMMClawback);
 | 
			
		||||
    REGISTER(Credentials);
 | 
			
		||||
 | 
			
		||||
    // Obsolete but supported by libxrpl
 | 
			
		||||
    REGISTER(CryptoConditionsSuite);
 | 
			
		||||
 
 | 
			
		||||
@@ -364,6 +364,25 @@ public:
 | 
			
		||||
        boost::asio::yield_context yield
 | 
			
		||||
    ) const = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Fetches all holders' balances for a MPTIssuanceID
 | 
			
		||||
     *
 | 
			
		||||
     * @param mptID MPTIssuanceID you wish you query.
 | 
			
		||||
     * @param limit Paging limit.
 | 
			
		||||
     * @param cursorIn Optional cursor to allow us to pick up from where we last left off.
 | 
			
		||||
     * @param ledgerSequence The ledger sequence to fetch for
 | 
			
		||||
     * @param yield Currently executing coroutine.
 | 
			
		||||
     * @return std::vector<Blob> of MPToken balances and an optional marker
 | 
			
		||||
     */
 | 
			
		||||
    virtual MPTHoldersAndCursor
 | 
			
		||||
    fetchMPTHolders(
 | 
			
		||||
        ripple::uint192 const& mptID,
 | 
			
		||||
        std::uint32_t const limit,
 | 
			
		||||
        std::optional<ripple::AccountID> const& cursorIn,
 | 
			
		||||
        std::uint32_t const ledgerSequence,
 | 
			
		||||
        boost::asio::yield_context yield
 | 
			
		||||
    ) const = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Fetches a specific ledger object.
 | 
			
		||||
     *
 | 
			
		||||
@@ -617,6 +636,14 @@ public:
 | 
			
		||||
    virtual void
 | 
			
		||||
    writeNFTTransactions(std::vector<NFTTransactionsData> const& data) = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Write accounts that started holding onto a MPT.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data A vector of MPT ID and account pairs
 | 
			
		||||
     */
 | 
			
		||||
    virtual void
 | 
			
		||||
    writeMPTHolders(std::vector<MPTHolderData> const& data) = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Write a new successor.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -547,6 +547,45 @@ public:
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MPTHoldersAndCursor
 | 
			
		||||
    fetchMPTHolders(
 | 
			
		||||
        ripple::uint192 const& mptID,
 | 
			
		||||
        std::uint32_t const limit,
 | 
			
		||||
        std::optional<ripple::AccountID> const& cursorIn,
 | 
			
		||||
        std::uint32_t const ledgerSequence,
 | 
			
		||||
        boost::asio::yield_context yield
 | 
			
		||||
    ) const override
 | 
			
		||||
    {
 | 
			
		||||
        auto const holderEntries = executor_.read(
 | 
			
		||||
            yield, schema_->selectMPTHolders, mptID, cursorIn.value_or(ripple::AccountID(0)), Limit{limit}
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        auto const& holderResults = holderEntries.value();
 | 
			
		||||
        if (not holderResults.hasRows()) {
 | 
			
		||||
            LOG(log_.debug()) << "No rows returned";
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::vector<ripple::uint256> mptKeys;
 | 
			
		||||
        std::optional<ripple::AccountID> cursor;
 | 
			
		||||
        for (auto const [holder] : extract<ripple::AccountID>(holderResults)) {
 | 
			
		||||
            mptKeys.push_back(ripple::keylet::mptoken(mptID, holder).key);
 | 
			
		||||
            cursor = holder;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auto mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield);
 | 
			
		||||
 | 
			
		||||
        auto it = std::remove_if(mptObjects.begin(), mptObjects.end(), [](Blob const& mpt) { return mpt.empty(); });
 | 
			
		||||
 | 
			
		||||
        mptObjects.erase(it, mptObjects.end());
 | 
			
		||||
 | 
			
		||||
        ASSERT(mptKeys.size() <= limit, "Number of keys can't exceed the limit");
 | 
			
		||||
        if (mptKeys.size() == limit)
 | 
			
		||||
            return {mptObjects, cursor};
 | 
			
		||||
 | 
			
		||||
        return {mptObjects, {}};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::optional<Blob>
 | 
			
		||||
    doFetchLedgerObject(ripple::uint256 const& key, std::uint32_t const sequence, boost::asio::yield_context yield)
 | 
			
		||||
        const override
 | 
			
		||||
@@ -905,6 +944,17 @@ public:
 | 
			
		||||
        executor_.write(std::move(statements));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    writeMPTHolders(std::vector<MPTHolderData> const& data) override
 | 
			
		||||
    {
 | 
			
		||||
        std::vector<Statement> statements;
 | 
			
		||||
        statements.reserve(data.size());
 | 
			
		||||
        for (auto [mptId, holder] : data)
 | 
			
		||||
            statements.push_back(schema_->insertMPTHolder.bind(std::move(mptId), std::move(holder)));
 | 
			
		||||
 | 
			
		||||
        executor_.write(std::move(statements));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    startWrites() const override
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -172,6 +172,14 @@ struct NFTsData {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents an MPT and holder pair
 | 
			
		||||
 */
 | 
			
		||||
struct MPTHolderData {
 | 
			
		||||
    ripple::uint192 mptID;
 | 
			
		||||
    ripple::AccountID holder;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Check whether the supplied object is an offer.
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -233,6 +233,14 @@ struct NFTsAndCursor {
 | 
			
		||||
    std::optional<ripple::uint256> cursor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents an array of MPTokens
 | 
			
		||||
 */
 | 
			
		||||
struct MPTHoldersAndCursor {
 | 
			
		||||
    std::vector<Blob> mptokens;
 | 
			
		||||
    std::optional<ripple::AccountID> cursor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Stores a range of sequences as a min and max pair.
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -257,6 +257,19 @@ public:
 | 
			
		||||
            qualifiedTableName(settingsProvider_.get(), "nf_token_transactions")
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        statements.emplace_back(fmt::format(
 | 
			
		||||
            R"(
 | 
			
		||||
           CREATE TABLE IF NOT EXISTS {}
 | 
			
		||||
                  ( 
 | 
			
		||||
                    mpt_id blob,
 | 
			
		||||
                    holder blob,
 | 
			
		||||
                   PRIMARY KEY (mpt_id, holder)
 | 
			
		||||
                  ) 
 | 
			
		||||
             WITH CLUSTERING ORDER BY (holder ASC)
 | 
			
		||||
            )",
 | 
			
		||||
            qualifiedTableName(settingsProvider_.get(), "mp_token_holders")
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        return statements;
 | 
			
		||||
    }();
 | 
			
		||||
 | 
			
		||||
@@ -393,6 +406,17 @@ public:
 | 
			
		||||
            ));
 | 
			
		||||
        }();
 | 
			
		||||
 | 
			
		||||
        PreparedStatement insertMPTHolder = [this]() {
 | 
			
		||||
            return handle_.get().prepare(fmt::format(
 | 
			
		||||
                R"(
 | 
			
		||||
                INSERT INTO {} 
 | 
			
		||||
                       (mpt_id, holder)
 | 
			
		||||
                VALUES (?, ?)
 | 
			
		||||
                )",
 | 
			
		||||
                qualifiedTableName(settingsProvider_.get(), "mp_token_holders")
 | 
			
		||||
            ));
 | 
			
		||||
        }();
 | 
			
		||||
 | 
			
		||||
        PreparedStatement insertLedgerHeader = [this]() {
 | 
			
		||||
            return handle_.get().prepare(fmt::format(
 | 
			
		||||
                R"(
 | 
			
		||||
@@ -687,6 +711,20 @@ public:
 | 
			
		||||
            ));
 | 
			
		||||
        }();
 | 
			
		||||
 | 
			
		||||
        PreparedStatement selectMPTHolders = [this]() {
 | 
			
		||||
            return handle_.get().prepare(fmt::format(
 | 
			
		||||
                R"(
 | 
			
		||||
                SELECT holder
 | 
			
		||||
                  FROM {}    
 | 
			
		||||
                 WHERE mpt_id = ?
 | 
			
		||||
                   AND holder > ?
 | 
			
		||||
              ORDER BY holder ASC
 | 
			
		||||
                 LIMIT ?
 | 
			
		||||
                )",
 | 
			
		||||
                qualifiedTableName(settingsProvider_.get(), "mp_token_holders")
 | 
			
		||||
            ));
 | 
			
		||||
        }();
 | 
			
		||||
 | 
			
		||||
        PreparedStatement selectLedgerByHash = [this]() {
 | 
			
		||||
            return handle_.get().prepare(fmt::format(
 | 
			
		||||
                R"(
 | 
			
		||||
 
 | 
			
		||||
@@ -106,9 +106,9 @@ public:
 | 
			
		||||
        using UintByteTupleType = std::tuple<uint32_t, ripple::uint256>;
 | 
			
		||||
        using ByteVectorType = std::vector<ripple::uint256>;
 | 
			
		||||
 | 
			
		||||
        if constexpr (std::is_same_v<DecayedType, ripple::uint256>) {
 | 
			
		||||
        if constexpr (std::is_same_v<DecayedType, ripple::uint256> || std::is_same_v<DecayedType, ripple::uint192>) {
 | 
			
		||||
            auto const rc = bindBytes(value.data(), value.size());
 | 
			
		||||
            throwErrorIfNeeded(rc, "Bind ripple::uint256");
 | 
			
		||||
            throwErrorIfNeeded(rc, "Bind ripple::base_uint");
 | 
			
		||||
        } else if constexpr (std::is_same_v<DecayedType, ripple::AccountID>) {
 | 
			
		||||
            auto const rc = bindBytes(value.data(), value.size());
 | 
			
		||||
            throwErrorIfNeeded(rc, "Bind ripple::AccountID");
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,8 @@ target_sources(
 | 
			
		||||
          NetworkValidatedLedgers.cpp
 | 
			
		||||
          NFTHelpers.cpp
 | 
			
		||||
          Source.cpp
 | 
			
		||||
          MPTHelpers.cpp
 | 
			
		||||
          impl/AmendmentBlockHandler.cpp
 | 
			
		||||
          impl/ForwardingCache.cpp
 | 
			
		||||
          impl/ForwardingSource.cpp
 | 
			
		||||
          impl/GrpcSource.cpp
 | 
			
		||||
          impl/SubscriptionSource.cpp
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@
 | 
			
		||||
#include "rpc/Errors.hpp"
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/Random.hpp"
 | 
			
		||||
#include "util/ResponseExpirationCache.hpp"
 | 
			
		||||
#include "util/log/Logger.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/io_context.hpp>
 | 
			
		||||
@@ -34,6 +35,7 @@
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <boost/json/value_to.hpp>
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
@@ -79,7 +81,10 @@ LoadBalancer::LoadBalancer(
 | 
			
		||||
{
 | 
			
		||||
    auto const forwardingCacheTimeout = config.valueOr<float>("forwarding.cache_timeout", 0.f);
 | 
			
		||||
    if (forwardingCacheTimeout > 0.f) {
 | 
			
		||||
        forwardingCache_ = impl::ForwardingCache{Config::toMilliseconds(forwardingCacheTimeout)};
 | 
			
		||||
        forwardingCache_ = util::ResponseExpirationCache{
 | 
			
		||||
            Config::toMilliseconds(forwardingCacheTimeout),
 | 
			
		||||
            {"server_info", "server_state", "server_definitions", "fee", "ledger_closed"}
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static constexpr std::uint32_t MAX_DOWNLOAD = 256;
 | 
			
		||||
@@ -111,10 +116,13 @@ LoadBalancer::LoadBalancer(
 | 
			
		||||
            validatedLedgers,
 | 
			
		||||
            forwardingTimeout,
 | 
			
		||||
            [this]() {
 | 
			
		||||
                if (not hasForwardingSource_)
 | 
			
		||||
                if (not hasForwardingSource_.lock().get())
 | 
			
		||||
                    chooseForwardingSource();
 | 
			
		||||
            },
 | 
			
		||||
            [this](bool wasForwarding) {
 | 
			
		||||
                if (wasForwarding)
 | 
			
		||||
                    chooseForwardingSource();
 | 
			
		||||
            },
 | 
			
		||||
            [this]() { chooseForwardingSource(); },
 | 
			
		||||
            [this]() {
 | 
			
		||||
                if (forwardingCache_.has_value())
 | 
			
		||||
                    forwardingCache_->invalidate();
 | 
			
		||||
@@ -221,8 +229,12 @@ LoadBalancer::forwardToRippled(
 | 
			
		||||
    boost::asio::yield_context yield
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
    if (not request.contains("command"))
 | 
			
		||||
        return std::unexpected{rpc::ClioError::rpcCOMMAND_IS_MISSING};
 | 
			
		||||
 | 
			
		||||
    auto const cmd = boost::json::value_to<std::string>(request.at("command"));
 | 
			
		||||
    if (forwardingCache_) {
 | 
			
		||||
        if (auto cachedResponse = forwardingCache_->get(request); cachedResponse) {
 | 
			
		||||
        if (auto cachedResponse = forwardingCache_->get(cmd); cachedResponse) {
 | 
			
		||||
            return std::move(cachedResponse).value();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -250,7 +262,7 @@ LoadBalancer::forwardToRippled(
 | 
			
		||||
 | 
			
		||||
    if (response) {
 | 
			
		||||
        if (forwardingCache_ and not response->contains("error"))
 | 
			
		||||
            forwardingCache_->put(request, *response);
 | 
			
		||||
            forwardingCache_->put(cmd, *response);
 | 
			
		||||
        return std::move(response).value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -322,11 +334,13 @@ LoadBalancer::getETLState() noexcept
 | 
			
		||||
void
 | 
			
		||||
LoadBalancer::chooseForwardingSource()
 | 
			
		||||
{
 | 
			
		||||
    hasForwardingSource_ = false;
 | 
			
		||||
    LOG(log_.info()) << "Choosing a new source to forward subscriptions";
 | 
			
		||||
    auto hasForwardingSourceLock = hasForwardingSource_.lock();
 | 
			
		||||
    hasForwardingSourceLock.get() = false;
 | 
			
		||||
    for (auto& source : sources_) {
 | 
			
		||||
        if (not hasForwardingSource_ and source->isConnected()) {
 | 
			
		||||
        if (not hasForwardingSourceLock.get() and source->isConnected()) {
 | 
			
		||||
            source->setForwarding(true);
 | 
			
		||||
            hasForwardingSource_ = true;
 | 
			
		||||
            hasForwardingSourceLock.get() = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            source->setForwarding(false);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,10 @@
 | 
			
		||||
#include "etl/ETLState.hpp"
 | 
			
		||||
#include "etl/NetworkValidatedLedgersInterface.hpp"
 | 
			
		||||
#include "etl/Source.hpp"
 | 
			
		||||
#include "etl/impl/ForwardingCache.hpp"
 | 
			
		||||
#include "feed/SubscriptionManagerInterface.hpp"
 | 
			
		||||
#include "rpc/Errors.hpp"
 | 
			
		||||
#include "util/Mutex.hpp"
 | 
			
		||||
#include "util/ResponseExpirationCache.hpp"
 | 
			
		||||
#include "util/config/Config.hpp"
 | 
			
		||||
#include "util/log/Logger.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -39,7 +40,6 @@
 | 
			
		||||
#include <org/xrpl/rpc/v1/ledger.pb.h>
 | 
			
		||||
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
 | 
			
		||||
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <expected>
 | 
			
		||||
@@ -69,14 +69,17 @@ private:
 | 
			
		||||
 | 
			
		||||
    util::Logger log_{"ETL"};
 | 
			
		||||
    // Forwarding cache must be destroyed after sources because sources have a callback to invalidate cache
 | 
			
		||||
    std::optional<impl::ForwardingCache> forwardingCache_;
 | 
			
		||||
    std::optional<util::ResponseExpirationCache> forwardingCache_;
 | 
			
		||||
    std::optional<std::string> forwardingXUserValue_;
 | 
			
		||||
 | 
			
		||||
    std::vector<SourcePtr> sources_;
 | 
			
		||||
    std::optional<ETLState> etlState_;
 | 
			
		||||
    std::uint32_t downloadRanges_ =
 | 
			
		||||
        DEFAULT_DOWNLOAD_RANGES; /*< The number of markers to use when downloading initial ledger */
 | 
			
		||||
    std::atomic_bool hasForwardingSource_{false};
 | 
			
		||||
 | 
			
		||||
    // Using mutext instead of atomic_bool because choosing a new source to
 | 
			
		||||
    // forward messages should be done with a mutual exclusion otherwise there will be a race condition
 | 
			
		||||
    util::Mutex<bool> hasForwardingSource_{false};
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										83
									
								
								src/etl/MPTHelpers.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/etl/MPTHelpers.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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/DBHelpers.hpp"
 | 
			
		||||
 | 
			
		||||
#include <ripple/protocol/STBase.h>
 | 
			
		||||
#include <ripple/protocol/STTx.h>
 | 
			
		||||
#include <ripple/protocol/TxMeta.h>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
#include <xrpl/protocol/LedgerFormats.h>
 | 
			
		||||
#include <xrpl/protocol/SField.h>
 | 
			
		||||
#include <xrpl/protocol/STLedgerEntry.h>
 | 
			
		||||
#include <xrpl/protocol/STObject.h>
 | 
			
		||||
#include <xrpl/protocol/TER.h>
 | 
			
		||||
#include <xrpl/protocol/TxFormats.h>
 | 
			
		||||
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace etl {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Get the MPToken created from a transaction
 | 
			
		||||
 *
 | 
			
		||||
 * @param txMeta Transaction metadata
 | 
			
		||||
 * @return MPT and holder account pair
 | 
			
		||||
 */
 | 
			
		||||
static std::optional<MPTHolderData>
 | 
			
		||||
getMPTokenAuthorize(ripple::TxMeta const& txMeta)
 | 
			
		||||
{
 | 
			
		||||
    for (ripple::STObject const& node : txMeta.getNodes()) {
 | 
			
		||||
        if (node.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        if (node.getFName() == ripple::sfCreatedNode) {
 | 
			
		||||
            auto const& newMPT = node.peekAtField(ripple::sfNewFields).downcast<ripple::STObject>();
 | 
			
		||||
            return MPTHolderData{newMPT[ripple::sfMPTokenIssuanceID], newMPT.getAccountID(ripple::sfAccount)};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<MPTHolderData>
 | 
			
		||||
getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
 | 
			
		||||
{
 | 
			
		||||
    if (txMeta.getResultTER() != ripple::tesSUCCESS || sttx.getTxnType() != ripple::TxType::ttMPTOKEN_AUTHORIZE)
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    return getMPTokenAuthorize(txMeta);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<MPTHolderData>
 | 
			
		||||
getMPTHolderFromObj(std::string const& key, std::string const& blob)
 | 
			
		||||
{
 | 
			
		||||
    ripple::STLedgerEntry const sle =
 | 
			
		||||
        ripple::STLedgerEntry(ripple::SerialIter{blob.data(), blob.size()}, ripple::uint256::fromVoid(key.data()));
 | 
			
		||||
 | 
			
		||||
    if (sle.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN)
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    auto const mptIssuanceID = sle[ripple::sfMPTokenIssuanceID];
 | 
			
		||||
    auto const holder = sle.getAccountID(ripple::sfAccount);
 | 
			
		||||
 | 
			
		||||
    return MPTHolderData{mptIssuanceID, holder};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace etl
 | 
			
		||||
							
								
								
									
										50
									
								
								src/etl/MPTHelpers.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/etl/MPTHelpers.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
/** @file */
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "data/DBHelpers.hpp"
 | 
			
		||||
 | 
			
		||||
#include <ripple/protocol/STTx.h>
 | 
			
		||||
#include <ripple/protocol/TxMeta.h>
 | 
			
		||||
 | 
			
		||||
namespace etl {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Pull MPT data from TX via ETLService.
 | 
			
		||||
 *
 | 
			
		||||
 * @param txMeta Transaction metadata
 | 
			
		||||
 * @param sttx The transaction
 | 
			
		||||
 * @return The MPTIssuanceID and holder pair as a optional
 | 
			
		||||
 */
 | 
			
		||||
std::optional<MPTHolderData>
 | 
			
		||||
getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Pull MPT data from ledger object via loadInitialLedger.
 | 
			
		||||
 *
 | 
			
		||||
 * @param key The owner key
 | 
			
		||||
 * @param blob Object data as blob
 | 
			
		||||
 * @return The MPTIssuanceID and holder pair as a optional
 | 
			
		||||
 */
 | 
			
		||||
std::optional<MPTHolderData>
 | 
			
		||||
getMPTHolderFromObj(std::string const& key, std::string const& blob);
 | 
			
		||||
 | 
			
		||||
}  // namespace etl
 | 
			
		||||
@@ -53,7 +53,7 @@ namespace etl {
 | 
			
		||||
class SourceBase {
 | 
			
		||||
public:
 | 
			
		||||
    using OnConnectHook = std::function<void()>;
 | 
			
		||||
    using OnDisconnectHook = std::function<void()>;
 | 
			
		||||
    using OnDisconnectHook = std::function<void(bool)>;
 | 
			
		||||
    using OnLedgerClosedHook = std::function<void()>;
 | 
			
		||||
 | 
			
		||||
    virtual ~SourceBase() = default;
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
#include "data/BackendInterface.hpp"
 | 
			
		||||
#include "data/Types.hpp"
 | 
			
		||||
#include "etl/ETLHelpers.hpp"
 | 
			
		||||
#include "etl/MPTHelpers.hpp"
 | 
			
		||||
#include "etl/NFTHelpers.hpp"
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/log/Logger.hpp"
 | 
			
		||||
@@ -154,6 +155,11 @@ public:
 | 
			
		||||
                    backend.writeSuccessor(std::move(lastKey_), request_.ledger().sequence(), std::string{obj.key()});
 | 
			
		||||
                lastKey_ = obj.key();
 | 
			
		||||
                backend.writeNFTs(getNFTDataFromObj(request_.ledger().sequence(), obj.key(), obj.data()));
 | 
			
		||||
 | 
			
		||||
                auto const maybeMPTHolder = getMPTHolderFromObj(obj.key(), obj.data());
 | 
			
		||||
                if (maybeMPTHolder)
 | 
			
		||||
                    backend.writeMPTHolders({*maybeMPTHolder});
 | 
			
		||||
 | 
			
		||||
                backend.writeLedgerObject(
 | 
			
		||||
                    std::move(*obj.mutable_key()), request_.ledger().sequence(), std::move(*obj.mutable_data())
 | 
			
		||||
                );
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@
 | 
			
		||||
namespace etl::impl {
 | 
			
		||||
 | 
			
		||||
GrpcSource::GrpcSource(std::string const& ip, std::string const& grpcPort, std::shared_ptr<BackendInterface> backend)
 | 
			
		||||
    : log_(fmt::format("ETL_Grpc[{}:{}]", ip, grpcPort)), backend_(std::move(backend))
 | 
			
		||||
    : log_(fmt::format("GrpcSource[{}:{}]", ip, grpcPort)), backend_(std::move(backend))
 | 
			
		||||
{
 | 
			
		||||
    try {
 | 
			
		||||
        boost::asio::io_context ctx;
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
#include "data/BackendInterface.hpp"
 | 
			
		||||
#include "data/DBHelpers.hpp"
 | 
			
		||||
#include "data/Types.hpp"
 | 
			
		||||
#include "etl/MPTHelpers.hpp"
 | 
			
		||||
#include "etl/NFTHelpers.hpp"
 | 
			
		||||
#include "etl/SystemState.hpp"
 | 
			
		||||
#include "etl/impl/LedgerFetcher.hpp"
 | 
			
		||||
@@ -55,6 +56,7 @@ struct FormattedTransactionsData {
 | 
			
		||||
    std::vector<AccountTransactionsData> accountTxData;
 | 
			
		||||
    std::vector<NFTTransactionsData> nfTokenTxData;
 | 
			
		||||
    std::vector<NFTsData> nfTokensData;
 | 
			
		||||
    std::vector<MPTHolderData> mptHoldersData;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace etl::impl {
 | 
			
		||||
@@ -124,6 +126,10 @@ public:
 | 
			
		||||
            if (maybeNFT)
 | 
			
		||||
                result.nfTokensData.push_back(*maybeNFT);
 | 
			
		||||
 | 
			
		||||
            auto const maybeMPTHolder = getMPTHolderFromTx(txMeta, sttx);
 | 
			
		||||
            if (maybeMPTHolder)
 | 
			
		||||
                result.mptHoldersData.push_back(*maybeMPTHolder);
 | 
			
		||||
 | 
			
		||||
            result.accountTxData.emplace_back(txMeta, sttx.getTransactionID());
 | 
			
		||||
            static constexpr std::size_t KEY_SIZE = 32;
 | 
			
		||||
            std::string keyStr{reinterpret_cast<char const*>(sttx.getTransactionID().data()), KEY_SIZE};
 | 
			
		||||
@@ -240,6 +246,7 @@ public:
 | 
			
		||||
                backend_->writeAccountTransactions(std::move(insertTxResult.accountTxData));
 | 
			
		||||
                backend_->writeNFTs(insertTxResult.nfTokensData);
 | 
			
		||||
                backend_->writeNFTTransactions(insertTxResult.nfTokenTxData);
 | 
			
		||||
                backend_->writeMPTHolders(insertTxResult.mptHoldersData);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            backend_->finishWrites(sequence);
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,8 @@
 | 
			
		||||
#include "rpc/JS.hpp"
 | 
			
		||||
#include "util/Retry.hpp"
 | 
			
		||||
#include "util/log/Logger.hpp"
 | 
			
		||||
#include "util/prometheus/Label.hpp"
 | 
			
		||||
#include "util/prometheus/Prometheus.hpp"
 | 
			
		||||
#include "util/requests/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/algorithm/string/classification.hpp>
 | 
			
		||||
@@ -66,22 +68,28 @@ SubscriptionSource::SubscriptionSource(
 | 
			
		||||
    OnConnectHook onConnect,
 | 
			
		||||
    OnDisconnectHook onDisconnect,
 | 
			
		||||
    OnLedgerClosedHook onLedgerClosed,
 | 
			
		||||
    std::chrono::steady_clock::duration const connectionTimeout,
 | 
			
		||||
    std::chrono::steady_clock::duration const wsTimeout,
 | 
			
		||||
    std::chrono::steady_clock::duration const retryDelay
 | 
			
		||||
)
 | 
			
		||||
    : log_(fmt::format("GrpcSource[{}:{}]", ip, wsPort))
 | 
			
		||||
    : log_(fmt::format("SubscriptionSource[{}:{}]", ip, wsPort))
 | 
			
		||||
    , wsConnectionBuilder_(ip, wsPort)
 | 
			
		||||
    , validatedLedgers_(std::move(validatedLedgers))
 | 
			
		||||
    , subscriptions_(std::move(subscriptions))
 | 
			
		||||
    , strand_(boost::asio::make_strand(ioContext))
 | 
			
		||||
    , wsTimeout_(wsTimeout)
 | 
			
		||||
    , retry_(util::makeRetryExponentialBackoff(retryDelay, RETRY_MAX_DELAY, strand_))
 | 
			
		||||
    , onConnect_(std::move(onConnect))
 | 
			
		||||
    , onDisconnect_(std::move(onDisconnect))
 | 
			
		||||
    , onLedgerClosed_(std::move(onLedgerClosed))
 | 
			
		||||
    , lastMessageTimeSecondsSinceEpoch_(PrometheusService::gaugeInt(
 | 
			
		||||
          "subscription_source_last_message_time",
 | 
			
		||||
          util::prometheus::Labels({{"source", fmt::format("{}:{}", ip, wsPort)}}),
 | 
			
		||||
          "Seconds since epoch of the last message received from rippled subscription streams"
 | 
			
		||||
      ))
 | 
			
		||||
{
 | 
			
		||||
    wsConnectionBuilder_.addHeader({boost::beast::http::field::user_agent, "clio-client"})
 | 
			
		||||
        .addHeader({"X-User", "clio-client"})
 | 
			
		||||
        .setConnectionTimeout(connectionTimeout);
 | 
			
		||||
        .setConnectionTimeout(wsTimeout_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SubscriptionSource::~SubscriptionSource()
 | 
			
		||||
@@ -133,6 +141,7 @@ void
 | 
			
		||||
SubscriptionSource::setForwarding(bool isForwarding)
 | 
			
		||||
{
 | 
			
		||||
    isForwarding_ = isForwarding;
 | 
			
		||||
    LOG(log_.info()) << "Forwarding set to " << isForwarding_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::chrono::steady_clock::time_point
 | 
			
		||||
@@ -166,20 +175,22 @@ SubscriptionSource::subscribe()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            wsConnection_ = std::move(connection).value();
 | 
			
		||||
            isConnected_ = true;
 | 
			
		||||
            onConnect_();
 | 
			
		||||
 | 
			
		||||
            auto const& subscribeCommand = getSubscribeCommandJson();
 | 
			
		||||
            auto const writeErrorOpt = wsConnection_->write(subscribeCommand, yield);
 | 
			
		||||
            auto const writeErrorOpt = wsConnection_->write(subscribeCommand, yield, wsTimeout_);
 | 
			
		||||
            if (writeErrorOpt) {
 | 
			
		||||
                handleError(writeErrorOpt.value(), yield);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            isConnected_ = true;
 | 
			
		||||
            LOG(log_.info()) << "Connected";
 | 
			
		||||
            onConnect_();
 | 
			
		||||
 | 
			
		||||
            retry_.reset();
 | 
			
		||||
 | 
			
		||||
            while (!stop_) {
 | 
			
		||||
                auto const message = wsConnection_->read(yield);
 | 
			
		||||
                auto const message = wsConnection_->read(yield, wsTimeout_);
 | 
			
		||||
                if (not message) {
 | 
			
		||||
                    handleError(message.error(), yield);
 | 
			
		||||
                    return;
 | 
			
		||||
@@ -224,10 +235,11 @@ SubscriptionSource::handleMessage(std::string const& message)
 | 
			
		||||
                auto validatedLedgers = boost::json::value_to<std::string>(result.at(JS(validated_ledgers)));
 | 
			
		||||
                setValidatedRange(std::move(validatedLedgers));
 | 
			
		||||
            }
 | 
			
		||||
            LOG(log_.info()) << "Received a message on ledger subscription stream. Message : " << object;
 | 
			
		||||
            LOG(log_.debug()) << "Received a message on ledger subscription stream. Message: " << object;
 | 
			
		||||
 | 
			
		||||
        } else if (object.contains(JS(type)) && object.at(JS(type)) == JS_LedgerClosed) {
 | 
			
		||||
            LOG(log_.info()) << "Received a message on ledger subscription stream. Message : " << object;
 | 
			
		||||
            LOG(log_.debug()) << "Received a message of type 'ledgerClosed' on ledger subscription stream. Message: "
 | 
			
		||||
                              << object;
 | 
			
		||||
            if (object.contains(JS(ledger_index))) {
 | 
			
		||||
                ledgerIndex = object.at(JS(ledger_index)).as_int64();
 | 
			
		||||
            }
 | 
			
		||||
@@ -245,10 +257,13 @@ SubscriptionSource::handleMessage(std::string const& message)
 | 
			
		||||
                // 2 - Validated transaction
 | 
			
		||||
                // Only forward proposed transaction, validated transactions are sent by Clio itself
 | 
			
		||||
                if (object.contains(JS(transaction)) and !object.contains(JS(meta))) {
 | 
			
		||||
                    LOG(log_.debug()) << "Forwarding proposed transaction: " << object;
 | 
			
		||||
                    subscriptions_->forwardProposedTransaction(object);
 | 
			
		||||
                } else if (object.contains(JS(type)) && object.at(JS(type)) == JS_ValidationReceived) {
 | 
			
		||||
                    LOG(log_.debug()) << "Forwarding validation: " << object;
 | 
			
		||||
                    subscriptions_->forwardValidation(object);
 | 
			
		||||
                } else if (object.contains(JS(type)) && object.at(JS(type)) == JS_ManifestReceived) {
 | 
			
		||||
                    LOG(log_.debug()) << "Forwarding manifest: " << object;
 | 
			
		||||
                    subscriptions_->forwardManifest(object);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -261,7 +276,7 @@ SubscriptionSource::handleMessage(std::string const& message)
 | 
			
		||||
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    } catch (std::exception const& e) {
 | 
			
		||||
        LOG(log_.error()) << "Exception in handleMessage : " << e.what();
 | 
			
		||||
        LOG(log_.error()) << "Exception in handleMessage: " << e.what();
 | 
			
		||||
        return util::requests::RequestError{fmt::format("Error handling message: {}", e.what())};
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -270,16 +285,14 @@ void
 | 
			
		||||
SubscriptionSource::handleError(util::requests::RequestError const& error, boost::asio::yield_context yield)
 | 
			
		||||
{
 | 
			
		||||
    isConnected_ = false;
 | 
			
		||||
    isForwarding_ = false;
 | 
			
		||||
    bool const wasForwarding = isForwarding_.exchange(false);
 | 
			
		||||
    if (not stop_) {
 | 
			
		||||
        onDisconnect_();
 | 
			
		||||
        LOG(log_.info()) << "Disconnected";
 | 
			
		||||
        onDisconnect_(wasForwarding);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (wsConnection_ != nullptr) {
 | 
			
		||||
        auto const err = wsConnection_->close(yield);
 | 
			
		||||
        if (err) {
 | 
			
		||||
            LOG(log_.error()) << "Error closing websocket connection: " << err->message();
 | 
			
		||||
        }
 | 
			
		||||
        wsConnection_->close(yield);
 | 
			
		||||
        wsConnection_.reset();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -306,7 +319,11 @@ SubscriptionSource::logError(util::requests::RequestError const& error) const
 | 
			
		||||
void
 | 
			
		||||
SubscriptionSource::setLastMessageTime()
 | 
			
		||||
{
 | 
			
		||||
    lastMessageTime_.lock().get() = std::chrono::steady_clock::now();
 | 
			
		||||
    lastMessageTimeSecondsSinceEpoch_.get().set(
 | 
			
		||||
        std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()
 | 
			
		||||
    );
 | 
			
		||||
    auto lock = lastMessageTime_.lock();
 | 
			
		||||
    lock.get() = std::chrono::steady_clock::now();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@
 | 
			
		||||
#include "util/Mutex.hpp"
 | 
			
		||||
#include "util/Retry.hpp"
 | 
			
		||||
#include "util/log/Logger.hpp"
 | 
			
		||||
#include "util/prometheus/Gauge.hpp"
 | 
			
		||||
#include "util/requests/Types.hpp"
 | 
			
		||||
#include "util/requests/WsConnection.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -37,6 +38,7 @@
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <future>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <optional>
 | 
			
		||||
@@ -71,6 +73,8 @@ private:
 | 
			
		||||
 | 
			
		||||
    boost::asio::strand<boost::asio::io_context::executor_type> strand_;
 | 
			
		||||
 | 
			
		||||
    std::chrono::steady_clock::duration wsTimeout_;
 | 
			
		||||
 | 
			
		||||
    util::Retry retry_;
 | 
			
		||||
 | 
			
		||||
    OnConnectHook onConnect_;
 | 
			
		||||
@@ -83,9 +87,11 @@ private:
 | 
			
		||||
 | 
			
		||||
    util::Mutex<std::chrono::steady_clock::time_point> lastMessageTime_;
 | 
			
		||||
 | 
			
		||||
    std::reference_wrapper<util::prometheus::GaugeInt> lastMessageTimeSecondsSinceEpoch_;
 | 
			
		||||
 | 
			
		||||
    std::future<void> runFuture_;
 | 
			
		||||
 | 
			
		||||
    static constexpr std::chrono::seconds CONNECTION_TIMEOUT{30};
 | 
			
		||||
    static constexpr std::chrono::seconds WS_TIMEOUT{30};
 | 
			
		||||
    static constexpr std::chrono::seconds RETRY_MAX_DELAY{30};
 | 
			
		||||
    static constexpr std::chrono::seconds RETRY_DELAY{1};
 | 
			
		||||
 | 
			
		||||
@@ -103,7 +109,7 @@ public:
 | 
			
		||||
     * @param onDisconnect The onDisconnect hook. Called when the connection is lost
 | 
			
		||||
     * @param onLedgerClosed The onLedgerClosed hook. Called when the ledger is closed but only if the source is
 | 
			
		||||
     * forwarding
 | 
			
		||||
     * @param connectionTimeout The connection timeout. Defaults to 30 seconds
 | 
			
		||||
     * @param wsTimeout A timeout for websocket operations. Defaults to 30 seconds
 | 
			
		||||
     * @param retryDelay The retry delay. Defaults to 1 second
 | 
			
		||||
     */
 | 
			
		||||
    SubscriptionSource(
 | 
			
		||||
@@ -115,7 +121,7 @@ public:
 | 
			
		||||
        OnConnectHook onConnect,
 | 
			
		||||
        OnDisconnectHook onDisconnect,
 | 
			
		||||
        OnLedgerClosedHook onLedgerClosed,
 | 
			
		||||
        std::chrono::steady_clock::duration const connectionTimeout = CONNECTION_TIMEOUT,
 | 
			
		||||
        std::chrono::steady_clock::duration const wsTimeout = WS_TIMEOUT,
 | 
			
		||||
        std::chrono::steady_clock::duration const retryDelay = RETRY_DELAY
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -213,6 +213,7 @@ private:
 | 
			
		||||
        backend_->writeAccountTransactions(std::move(insertTxResultOp->accountTxData));
 | 
			
		||||
        backend_->writeNFTs(insertTxResultOp->nfTokensData);
 | 
			
		||||
        backend_->writeNFTTransactions(insertTxResultOp->nfTokenTxData);
 | 
			
		||||
        backend_->writeMPTHolders(insertTxResultOp->mptHoldersData);
 | 
			
		||||
 | 
			
		||||
        auto [success, duration] =
 | 
			
		||||
            ::util::timed<std::chrono::duration<double>>([&]() { return backend_->finishWrites(lgrInfo.seq); });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								src/etlng/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/etlng/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
add_library(clio_etlng INTERFACE)
 | 
			
		||||
 | 
			
		||||
# target_sources(clio_etlng PRIVATE )
 | 
			
		||||
 | 
			
		||||
target_link_libraries(clio_etlng INTERFACE clio_data)
 | 
			
		||||
							
								
								
									
										129
									
								
								src/etlng/Models.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/etlng/Models.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "util/Concepts.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <xrpl/basics/Blob.h>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
#include <xrpl/proto/org/xrpl/rpc/v1/get_ledger.pb.h>
 | 
			
		||||
#include <xrpl/proto/org/xrpl/rpc/v1/ledger.pb.h>
 | 
			
		||||
#include <xrpl/protocol/LedgerHeader.h>
 | 
			
		||||
#include <xrpl/protocol/STTx.h>
 | 
			
		||||
#include <xrpl/protocol/TxFormats.h>
 | 
			
		||||
#include <xrpl/protocol/TxMeta.h>
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace etlng::model {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A specification for the Registry.
 | 
			
		||||
 *
 | 
			
		||||
 * This specification simply defines the transaction types that are to be filtered out from the incoming transactions by
 | 
			
		||||
 * the Registry for its `onTransaction` and `onInitialTransaction` hooks.
 | 
			
		||||
 * It's a compilation error to list the same transaction type more than once.
 | 
			
		||||
 */
 | 
			
		||||
template <ripple::TxType... Types>
 | 
			
		||||
    requires(util::hasNoDuplicates(Types...))
 | 
			
		||||
struct Spec {
 | 
			
		||||
    static constexpr bool SpecTag = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Checks if the transaction type was requested.
 | 
			
		||||
     *
 | 
			
		||||
     * @param type The transaction type
 | 
			
		||||
     * @return true if the transaction was requested; false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] constexpr static bool
 | 
			
		||||
    wants(ripple::TxType type) noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return ((Types == type) || ...);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents a single transaction on the ledger.
 | 
			
		||||
 */
 | 
			
		||||
struct Transaction {
 | 
			
		||||
    std::string raw;  // raw binary blob
 | 
			
		||||
    std::string metaRaw;
 | 
			
		||||
 | 
			
		||||
    // unpacked blob and meta
 | 
			
		||||
    ripple::STTx sttx;
 | 
			
		||||
    ripple::TxMeta meta;
 | 
			
		||||
 | 
			
		||||
    // commonly used stuff
 | 
			
		||||
    ripple::uint256 id;
 | 
			
		||||
    std::string key;  // key is the above id as a string of 32 characters
 | 
			
		||||
    ripple::TxType type;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents a single object on the ledger.
 | 
			
		||||
 */
 | 
			
		||||
struct Object {
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Modification type for the object.
 | 
			
		||||
     */
 | 
			
		||||
    enum class ModType : int {
 | 
			
		||||
        Unspecified = 0,
 | 
			
		||||
        Created = 1,
 | 
			
		||||
        Modified = 2,
 | 
			
		||||
        Deleted = 3,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ripple::uint256 key;
 | 
			
		||||
    std::string keyRaw;
 | 
			
		||||
    ripple::Blob data;
 | 
			
		||||
    std::string dataRaw;
 | 
			
		||||
    std::string successor;
 | 
			
		||||
    std::string predecessor;
 | 
			
		||||
 | 
			
		||||
    ModType type;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents a book successor.
 | 
			
		||||
 */
 | 
			
		||||
struct BookSuccessor {
 | 
			
		||||
    std::string firstBook;
 | 
			
		||||
    std::string bookBase;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents an entire ledger diff worth of transactions and objects.
 | 
			
		||||
 */
 | 
			
		||||
struct LedgerData {
 | 
			
		||||
    std::vector<Transaction> transactions;
 | 
			
		||||
    std::vector<Object> objects;
 | 
			
		||||
    std::optional<std::vector<BookSuccessor>> successors;
 | 
			
		||||
 | 
			
		||||
    ripple::LedgerHeader header;
 | 
			
		||||
    std::string rawHeader;
 | 
			
		||||
    uint32_t seq;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace etlng::model
 | 
			
		||||
							
								
								
									
										108
									
								
								src/etlng/RegistryInterface.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/etlng/RegistryInterface.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "etlng/Models.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace etlng {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The interface for a registry that can dispatch transactions and objects to extensions.
 | 
			
		||||
 *
 | 
			
		||||
 * This class defines the interface for dispatching data through to extensions.
 | 
			
		||||
 *
 | 
			
		||||
 * @note
 | 
			
		||||
 * The registry itself consists of Extensions.
 | 
			
		||||
 * Each extension must define at least one valid hook:
 | 
			
		||||
 * - for ongoing ETL dispatch:
 | 
			
		||||
 *   - void onLedgerData(etlng::model::LedgerData const&)
 | 
			
		||||
 *   - void onTransaction(uint32_t, etlng::model::Transaction const&)
 | 
			
		||||
 *   - void onObject(uint32_t, etlng::model::Object const&)
 | 
			
		||||
 * - for initial ledger load
 | 
			
		||||
 *   - void onInitialData(etlng::model::LedgerData const&)
 | 
			
		||||
 *   - void onInitialTransaction(uint32_t, etlng::model::Transaction const&)
 | 
			
		||||
 * - for initial objects (called for each downloaded batch)
 | 
			
		||||
 *   - void onInitialObjects(uint32_t, std::vector<etlng::model::Object> const&, std::string)
 | 
			
		||||
 *   - void onInitialObject(uint32_t, etlng::model::Object const&)
 | 
			
		||||
 *
 | 
			
		||||
 * When the registry dispatches (initial)data or objects, each of the above hooks will be called in order on each
 | 
			
		||||
 * registered extension.
 | 
			
		||||
 * This means that the order of execution is from left to right (hooks) and top to bottom (registered extensions).
 | 
			
		||||
 *
 | 
			
		||||
 * If either `onTransaction` or `onInitialTransaction` are defined, the extension will have to additionally define a
 | 
			
		||||
 * Specification. The specification lists transaction types to filter from the incoming data such that `onTransaction`
 | 
			
		||||
 * and `onInitialTransaction` are only called for the transactions that are of interest for the given extension.
 | 
			
		||||
 *
 | 
			
		||||
 * The specification is setup like so:
 | 
			
		||||
 * @code{.cpp}
 | 
			
		||||
 * struct Ext {
 | 
			
		||||
 *   using spec = etlng::model::Spec<
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_BURN,
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_ACCEPT_OFFER,
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_CREATE_OFFER,
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_CANCEL_OFFER,
 | 
			
		||||
 *     ripple::TxType::ttNFTOKEN_MINT>;
 | 
			
		||||
 *
 | 
			
		||||
 *   static void
 | 
			
		||||
 *   onInitialTransaction(uint32_t, etlng::model::Transaction const&);
 | 
			
		||||
 * };
 | 
			
		||||
 * @endcode
 | 
			
		||||
 */
 | 
			
		||||
struct RegistryInterface {
 | 
			
		||||
    virtual ~RegistryInterface() = default;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Dispatch initial objects.
 | 
			
		||||
     *
 | 
			
		||||
     * These objects are received during initial ledger load.
 | 
			
		||||
     *
 | 
			
		||||
     * @param seq The sequence
 | 
			
		||||
     * @param data The objects to dispatch
 | 
			
		||||
     * @param lastKey The predcessor of the first object in data if known; an empty string otherwise
 | 
			
		||||
     */
 | 
			
		||||
    virtual void
 | 
			
		||||
    dispatchInitialObjects(uint32_t seq, std::vector<model::Object> const& data, std::string lastKey) = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Dispatch initial ledger data.
 | 
			
		||||
     *
 | 
			
		||||
     * The transactions, header and edge keys are received during initial ledger load.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data The data to dispatch
 | 
			
		||||
     */
 | 
			
		||||
    virtual void
 | 
			
		||||
    dispatchInitialData(model::LedgerData const& data) = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Dispatch an entire ledger diff.
 | 
			
		||||
     *
 | 
			
		||||
     * This is used to dispatch incoming diffs through the extensions.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data The data to dispatch
 | 
			
		||||
     */
 | 
			
		||||
    virtual void
 | 
			
		||||
    dispatch(model::LedgerData const& data) = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace etlng
 | 
			
		||||
							
								
								
									
										219
									
								
								src/etlng/impl/Registry.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								src/etlng/impl/Registry.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,219 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "etlng/Models.hpp"
 | 
			
		||||
#include "etlng/RegistryInterface.hpp"
 | 
			
		||||
 | 
			
		||||
#include <xrpl/protocol/TxFormats.h>
 | 
			
		||||
 | 
			
		||||
#include <concepts>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <tuple>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace etlng::impl {
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasLedgerDataHook = requires(T p) {
 | 
			
		||||
    { p.onLedgerData(std::declval<etlng::model::LedgerData>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasInitialDataHook = requires(T p) {
 | 
			
		||||
    { p.onInitialData(std::declval<etlng::model::LedgerData>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasTransactionHook = requires(T p) {
 | 
			
		||||
    { p.onTransaction(uint32_t{}, std::declval<etlng::model::Transaction>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasObjectHook = requires(T p) {
 | 
			
		||||
    { p.onObject(uint32_t{}, std::declval<etlng::model::Object>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasInitialTransactionHook = requires(T p) {
 | 
			
		||||
    { p.onInitialTransaction(uint32_t{}, std::declval<etlng::model::Transaction>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasInitialObjectsHook = requires(T p) {
 | 
			
		||||
    {
 | 
			
		||||
        p.onInitialObjects(uint32_t{}, std::declval<std::vector<etlng::model::Object>>(), std::string{})
 | 
			
		||||
    } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept HasInitialObjectHook = requires(T p) {
 | 
			
		||||
    { p.onInitialObject(uint32_t{}, std::declval<etlng::model::Object>()) } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept ContainsSpec = std::decay_t<T>::spec::SpecTag;
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept ContainsValidHook = HasLedgerDataHook<T> or HasInitialDataHook<T> or
 | 
			
		||||
    (HasTransactionHook<T> and ContainsSpec<T>) or (HasInitialTransactionHook<T> and ContainsSpec<T>) or
 | 
			
		||||
    HasObjectHook<T> or HasInitialObjectsHook<T> or HasInitialObjectHook<T>;
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept NoTwoOfKind = not(HasLedgerDataHook<T> and HasTransactionHook<T>) and
 | 
			
		||||
    not(HasInitialDataHook<T> and HasInitialTransactionHook<T>) and not(HasInitialDataHook<T> and HasObjectHook<T>) and
 | 
			
		||||
    not(HasInitialObjectsHook<T> and HasInitialObjectHook<T>);
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeExtension = NoTwoOfKind<T> and ContainsValidHook<T>;
 | 
			
		||||
 | 
			
		||||
template <SomeExtension... Ps>
 | 
			
		||||
class Registry : public RegistryInterface {
 | 
			
		||||
    std::tuple<Ps...> store_;
 | 
			
		||||
 | 
			
		||||
    static_assert(
 | 
			
		||||
        (((not HasTransactionHook<std::decay_t<Ps>>) or ContainsSpec<std::decay_t<Ps>>) and ...),
 | 
			
		||||
        "Spec must be specified when 'onTransaction' function exists."
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    static_assert(
 | 
			
		||||
        (((not HasInitialTransactionHook<std::decay_t<Ps>>) or ContainsSpec<std::decay_t<Ps>>) and ...),
 | 
			
		||||
        "Spec must be specified when 'onInitialTransaction' function exists."
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    explicit constexpr Registry(SomeExtension auto&&... exts)
 | 
			
		||||
        requires(std::is_same_v<std::decay_t<decltype(exts)>, std::decay_t<Ps>> and ...)
 | 
			
		||||
        : store_(std::forward<Ps>(exts)...)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~Registry() override = default;
 | 
			
		||||
    Registry(Registry const&) = delete;
 | 
			
		||||
    Registry(Registry&&) = default;
 | 
			
		||||
    Registry&
 | 
			
		||||
    operator=(Registry const&) = delete;
 | 
			
		||||
    Registry&
 | 
			
		||||
    operator=(Registry&&) = default;
 | 
			
		||||
 | 
			
		||||
    constexpr void
 | 
			
		||||
    dispatch(model::LedgerData const& data) override
 | 
			
		||||
    {
 | 
			
		||||
        // send entire batch of data at once
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&](auto& p) {
 | 
			
		||||
                if constexpr (requires { p.onLedgerData(data); }) {
 | 
			
		||||
                    p.onLedgerData(data);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // send filtered transactions
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&]<typename P>(P& p, model::Transaction const& t) {
 | 
			
		||||
                if constexpr (requires { p.onTransaction(data.seq, t); }) {
 | 
			
		||||
                    if (std::decay_t<P>::spec::wants(t.type))
 | 
			
		||||
                        p.onTransaction(data.seq, t);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for (auto const& t : data.transactions) {
 | 
			
		||||
                std::apply([&expand, &t](auto&&... xs) { (expand(xs, t), ...); }, store_);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // send per object path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&]<typename P>(P&& p, model::Object const& o) {
 | 
			
		||||
                if constexpr (requires { p.onObject(data.seq, o); }) {
 | 
			
		||||
                    p.onObject(data.seq, o);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for (auto const& obj : data.objects) {
 | 
			
		||||
                std::apply([&expand, &obj](auto&&... xs) { (expand(xs, obj), ...); }, store_);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constexpr void
 | 
			
		||||
    dispatchInitialObjects(uint32_t seq, std::vector<model::Object> const& data, std::string lastKey) override
 | 
			
		||||
    {
 | 
			
		||||
        // send entire vector path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&](auto&& p) {
 | 
			
		||||
                if constexpr (requires { p.onInitialObjects(seq, data, lastKey); }) {
 | 
			
		||||
                    p.onInitialObjects(seq, data, lastKey);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // send per object path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&]<typename P>(P&& p, model::Object const& o) {
 | 
			
		||||
                if constexpr (requires { p.onInitialObject(seq, o); }) {
 | 
			
		||||
                    p.onInitialObject(seq, o);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for (auto const& obj : data) {
 | 
			
		||||
                std::apply([&expand, &obj](auto&&... xs) { (expand(xs, obj), ...); }, store_);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constexpr void
 | 
			
		||||
    dispatchInitialData(model::LedgerData const& data) override
 | 
			
		||||
    {
 | 
			
		||||
        // send entire batch path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&](auto&& p) {
 | 
			
		||||
                if constexpr (requires { p.onInitialData(data); }) {
 | 
			
		||||
                    p.onInitialData(data);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // send per tx path
 | 
			
		||||
        {
 | 
			
		||||
            auto const expand = [&]<typename P>(P&& p, model::Transaction const& tx) {
 | 
			
		||||
                if constexpr (requires { p.onInitialTransaction(data.seq, tx); }) {
 | 
			
		||||
                    if (std::decay_t<P>::spec::wants(tx.type))
 | 
			
		||||
                        p.onInitialTransaction(data.seq, tx);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for (auto const& tx : data.transactions) {
 | 
			
		||||
                std::apply([&expand, &tx](auto&&... xs) { (expand(xs, tx), ...); }, store_);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace etlng::impl
 | 
			
		||||
@@ -30,6 +30,7 @@
 | 
			
		||||
#include "feed/impl/TransactionFeed.hpp"
 | 
			
		||||
#include "util/async/AnyExecutionContext.hpp"
 | 
			
		||||
#include "util/async/context/BasicExecutionContext.hpp"
 | 
			
		||||
#include "util/config/Config.hpp"
 | 
			
		||||
#include "util/log/Logger.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/executor_work_guard.hpp>
 | 
			
		||||
@@ -44,6 +45,7 @@
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -67,16 +69,36 @@ class SubscriptionManager : public SubscriptionManagerInterface {
 | 
			
		||||
    impl::ProposedTransactionFeed proposedTransactionFeed_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Factory function to create a new SubscriptionManager with a PoolExecutionContext.
 | 
			
		||||
     *
 | 
			
		||||
     * @param config The configuration to use
 | 
			
		||||
     * @param backend The backend to use
 | 
			
		||||
     * @return A shared pointer to a new instance of SubscriptionManager
 | 
			
		||||
     */
 | 
			
		||||
    static std::shared_ptr<SubscriptionManager>
 | 
			
		||||
    make_SubscriptionManager(util::Config const& config, std::shared_ptr<data::BackendInterface const> const& backend)
 | 
			
		||||
    {
 | 
			
		||||
        auto const workersNum = config.valueOr<std::uint64_t>("subscription_workers", 1);
 | 
			
		||||
 | 
			
		||||
        util::Logger const logger{"Subscriptions"};
 | 
			
		||||
        LOG(logger.info()) << "Starting subscription manager with " << workersNum << " workers";
 | 
			
		||||
 | 
			
		||||
        return std::make_shared<feed::SubscriptionManager>(util::async::PoolExecutionContext(workersNum), backend);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Construct a new Subscription Manager object
 | 
			
		||||
     *
 | 
			
		||||
     * @param executor The executor to use to publish the feeds
 | 
			
		||||
     * @param backend The backend to use
 | 
			
		||||
     */
 | 
			
		||||
    template <class ExecutorCtx>
 | 
			
		||||
    SubscriptionManager(ExecutorCtx& executor, std::shared_ptr<data::BackendInterface const> const& backend)
 | 
			
		||||
    SubscriptionManager(
 | 
			
		||||
        util::async::AnyExecutionContext&& executor,
 | 
			
		||||
        std::shared_ptr<data::BackendInterface const> const& backend
 | 
			
		||||
    )
 | 
			
		||||
        : backend_(backend)
 | 
			
		||||
        , ctx_(executor)
 | 
			
		||||
        , ctx_(std::move(executor))
 | 
			
		||||
        , manifestFeed_(ctx_, "manifest")
 | 
			
		||||
        , validationsFeed_(ctx_, "validations")
 | 
			
		||||
        , ledgerFeed_(ctx_)
 | 
			
		||||
@@ -291,41 +313,4 @@ public:
 | 
			
		||||
    report() const final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The help class to run the subscription manager. The container of PoolExecutionContext which is used to publish
 | 
			
		||||
 * the feeds.
 | 
			
		||||
 */
 | 
			
		||||
class SubscriptionManagerRunner {
 | 
			
		||||
    std::uint64_t workersNum_;
 | 
			
		||||
    using ActualExecutionCtx = util::async::PoolExecutionContext;
 | 
			
		||||
    ActualExecutionCtx ctx_;
 | 
			
		||||
    std::shared_ptr<SubscriptionManager> subscriptionManager_;
 | 
			
		||||
    util::Logger logger_{"Subscriptions"};
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Construct a new Subscription Manager Runner object
 | 
			
		||||
     *
 | 
			
		||||
     * @param config The configuration
 | 
			
		||||
     * @param backend The backend to use
 | 
			
		||||
     */
 | 
			
		||||
    SubscriptionManagerRunner(util::Config const& config, std::shared_ptr<data::BackendInterface> const& backend)
 | 
			
		||||
        : workersNum_(config.valueOr<std::uint64_t>("subscription_workers", 1))
 | 
			
		||||
        , ctx_(workersNum_)
 | 
			
		||||
        , subscriptionManager_(std::make_shared<SubscriptionManager>(ctx_, backend))
 | 
			
		||||
    {
 | 
			
		||||
        LOG(logger_.info()) << "Starting subscription manager with " << workersNum_ << " workers";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Get the subscription manager
 | 
			
		||||
     *
 | 
			
		||||
     * @return The subscription manager
 | 
			
		||||
     */
 | 
			
		||||
    std::shared_ptr<SubscriptionManager>
 | 
			
		||||
    getManager()
 | 
			
		||||
    {
 | 
			
		||||
        return subscriptionManager_;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}  // namespace feed
 | 
			
		||||
 
 | 
			
		||||
@@ -203,6 +203,7 @@ TransactionFeed::pub(
 | 
			
		||||
        pubObj[JS(meta)] = rpc::toJson(*meta);
 | 
			
		||||
        rpc::insertDeliveredAmount(pubObj[JS(meta)].as_object(), tx, meta, txMeta.date);
 | 
			
		||||
        rpc::insertDeliverMaxAlias(pubObj[txKey].as_object(), version);
 | 
			
		||||
        rpc::insertMPTIssuanceID(pubObj[JS(meta)].as_object(), tx, meta);
 | 
			
		||||
 | 
			
		||||
        pubObj[JS(type)] = "transaction";
 | 
			
		||||
        pubObj[JS(validated)] = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ target_sources(
 | 
			
		||||
          Factories.cpp
 | 
			
		||||
          AMMHelpers.cpp
 | 
			
		||||
          RPCHelpers.cpp
 | 
			
		||||
          CredentialHelpers.cpp
 | 
			
		||||
          Counters.cpp
 | 
			
		||||
          WorkQueue.cpp
 | 
			
		||||
          common/Specs.cpp
 | 
			
		||||
@@ -33,6 +34,7 @@ target_sources(
 | 
			
		||||
          handlers/LedgerEntry.cpp
 | 
			
		||||
          handlers/LedgerIndex.cpp
 | 
			
		||||
          handlers/LedgerRange.cpp
 | 
			
		||||
          handlers/MPTHolders.cpp
 | 
			
		||||
          handlers/NFTsByIssuer.cpp
 | 
			
		||||
          handlers/NFTBuyOffers.cpp
 | 
			
		||||
          handlers/NFTHistory.cpp
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										161
									
								
								src/rpc/CredentialHelpers.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								src/rpc/CredentialHelpers.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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/BackendInterface.hpp"
 | 
			
		||||
#include "rpc/Errors.hpp"
 | 
			
		||||
#include "rpc/JS.hpp"
 | 
			
		||||
#include "rpc/common/Types.hpp"
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/spawn.hpp>
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <boost/json/value_to.hpp>
 | 
			
		||||
#include <xrpl/basics/Slice.h>
 | 
			
		||||
#include <xrpl/basics/StringUtilities.h>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
#include <xrpl/basics/chrono.h>
 | 
			
		||||
#include <xrpl/protocol/AccountID.h>
 | 
			
		||||
#include <xrpl/protocol/Indexes.h>
 | 
			
		||||
#include <xrpl/protocol/LedgerFormats.h>
 | 
			
		||||
#include <xrpl/protocol/LedgerHeader.h>
 | 
			
		||||
#include <xrpl/protocol/SField.h>
 | 
			
		||||
#include <xrpl/protocol/STArray.h>
 | 
			
		||||
#include <xrpl/protocol/STLedgerEntry.h>
 | 
			
		||||
#include <xrpl/protocol/STObject.h>
 | 
			
		||||
#include <xrpl/protocol/Serializer.h>
 | 
			
		||||
#include <xrpl/protocol/jss.h>
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <expected>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <set>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace rpc::credentials {
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
checkExpired(ripple::SLE const& sleCred, ripple::LedgerHeader const& ledger)
 | 
			
		||||
{
 | 
			
		||||
    if (sleCred.isFieldPresent(ripple::sfExpiration)) {
 | 
			
		||||
        std::uint32_t const exp = sleCred.getFieldU32(ripple::sfExpiration);
 | 
			
		||||
        std::uint32_t const now = ledger.parentCloseTime.time_since_epoch().count();
 | 
			
		||||
        return now > exp;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::set<std::pair<ripple::AccountID, ripple::Slice>>
 | 
			
		||||
createAuthCredentials(ripple::STArray const& in)
 | 
			
		||||
{
 | 
			
		||||
    std::set<std::pair<ripple::AccountID, ripple::Slice>> out;
 | 
			
		||||
    for (auto const& cred : in)
 | 
			
		||||
        out.insert({cred[ripple::sfIssuer], cred[ripple::sfCredentialType]});
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ripple::STArray
 | 
			
		||||
parseAuthorizeCredentials(boost::json::array const& jv)
 | 
			
		||||
{
 | 
			
		||||
    ripple::STArray arr;
 | 
			
		||||
    for (auto const& jo : jv) {
 | 
			
		||||
        ASSERT(
 | 
			
		||||
            jo.at(JS(issuer)).is_string(),
 | 
			
		||||
            "issuer must be string, should already be checked in AuthorizeCredentialValidator"
 | 
			
		||||
        );
 | 
			
		||||
        auto const issuer =
 | 
			
		||||
            ripple::parseBase58<ripple::AccountID>(static_cast<std::string>(jo.at(JS(issuer)).as_string()));
 | 
			
		||||
        ASSERT(
 | 
			
		||||
            issuer.has_value(), "issuer must be present, should already be checked in AuthorizeCredentialValidator."
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        ASSERT(
 | 
			
		||||
            jo.at(JS(credential_type)).is_string(),
 | 
			
		||||
            "credential_type must be string, should already be checked in AuthorizeCredentialValidator"
 | 
			
		||||
        );
 | 
			
		||||
        auto const credentialType = ripple::strUnHex(static_cast<std::string>(jo.at(JS(credential_type)).as_string()));
 | 
			
		||||
        ASSERT(
 | 
			
		||||
            credentialType.has_value(),
 | 
			
		||||
            "credential_type must be present, should already be checked in AuthorizeCredentialValidator."
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        auto credential = ripple::STObject::makeInnerObject(ripple::sfCredential);
 | 
			
		||||
        credential.setAccountID(ripple::sfIssuer, *issuer);
 | 
			
		||||
        credential.setFieldVL(ripple::sfCredentialType, *credentialType);
 | 
			
		||||
        arr.push_back(std::move(credential));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return arr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::expected<ripple::STArray, Status>
 | 
			
		||||
fetchCredentialArray(
 | 
			
		||||
    std::optional<boost::json::array> const& credID,
 | 
			
		||||
    ripple::AccountID const& srcAcc,
 | 
			
		||||
    BackendInterface const& backend,
 | 
			
		||||
    ripple::LedgerHeader const& info,
 | 
			
		||||
    boost::asio::yield_context const& yield
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
    ripple::STArray authCreds;
 | 
			
		||||
    std::unordered_set<std::string_view> elems;
 | 
			
		||||
    for (auto const& elem : credID.value()) {
 | 
			
		||||
        ASSERT(elem.is_string(), "should already be checked in validators.hpp that elem is a string.");
 | 
			
		||||
 | 
			
		||||
        if (elems.contains(elem.as_string()))
 | 
			
		||||
            return Error{Status{RippledError::rpcBAD_CREDENTIALS, "duplicates in credentials."}};
 | 
			
		||||
        elems.insert(elem.as_string());
 | 
			
		||||
 | 
			
		||||
        ripple::uint256 credHash;
 | 
			
		||||
        ASSERT(
 | 
			
		||||
            credHash.parseHex(boost::json::value_to<std::string>(elem)),
 | 
			
		||||
            "should already be checked in validators.hpp that elem is a uint256 hex"
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        auto const credKeylet = ripple::keylet::credential(credHash).key;
 | 
			
		||||
        auto const credLedgerObject = backend.fetchLedgerObject(credKeylet, info.seq, yield);
 | 
			
		||||
        if (!credLedgerObject)
 | 
			
		||||
            return Error{Status{RippledError::rpcBAD_CREDENTIALS, "credentials don't exist."}};
 | 
			
		||||
 | 
			
		||||
        auto credIt = ripple::SerialIter{credLedgerObject->data(), credLedgerObject->size()};
 | 
			
		||||
        auto const sleCred = ripple::SLE{credIt, credKeylet};
 | 
			
		||||
 | 
			
		||||
        if ((sleCred.getType() != ripple::ltCREDENTIAL) ||
 | 
			
		||||
            ((sleCred.getFieldU32(ripple::sfFlags) & ripple::lsfAccepted) == 0u))
 | 
			
		||||
            return Error{Status{RippledError::rpcBAD_CREDENTIALS, "credentials aren't accepted"}};
 | 
			
		||||
 | 
			
		||||
        if (credentials::checkExpired(sleCred, info))
 | 
			
		||||
            return Error{Status{RippledError::rpcBAD_CREDENTIALS, "credentials are expired"}};
 | 
			
		||||
 | 
			
		||||
        if (sleCred.getAccountID(ripple::sfSubject) != srcAcc)
 | 
			
		||||
            return Error{Status{RippledError::rpcBAD_CREDENTIALS, "credentials don't belong to the root account"}};
 | 
			
		||||
 | 
			
		||||
        auto credential = ripple::STObject::makeInnerObject(ripple::sfCredential);
 | 
			
		||||
        credential.setAccountID(ripple::sfIssuer, sleCred.getAccountID(ripple::sfIssuer));
 | 
			
		||||
        credential.setFieldVL(ripple::sfCredentialType, sleCred.getFieldVL(ripple::sfCredentialType));
 | 
			
		||||
        authCreds.push_back(std::move(credential));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return authCreds;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace rpc::credentials
 | 
			
		||||
							
								
								
									
										89
									
								
								src/rpc/CredentialHelpers.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/rpc/CredentialHelpers.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 <boost/asio/spawn.hpp>
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <xrpl/basics/Slice.h>
 | 
			
		||||
#include <xrpl/basics/chrono.h>
 | 
			
		||||
#include <xrpl/protocol/AccountID.h>
 | 
			
		||||
#include <xrpl/protocol/LedgerHeader.h>
 | 
			
		||||
#include <xrpl/protocol/Protocol.h>
 | 
			
		||||
#include <xrpl/protocol/STLedgerEntry.h>
 | 
			
		||||
#include <xrpl/protocol/STObject.h>
 | 
			
		||||
 | 
			
		||||
#include <expected>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <set>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace rpc::credentials {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Check if credential is expired
 | 
			
		||||
 *
 | 
			
		||||
 * @param sleCred The credential to check
 | 
			
		||||
 * @param ledger The ledger to check the closed time of
 | 
			
		||||
 * @return true if credential not expired, false otherwise
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
checkExpired(ripple::SLE const& sleCred, ripple::LedgerHeader const& ledger);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Creates authentication credential field (which is a set of pairs of AccountID and Credential ID)
 | 
			
		||||
 *
 | 
			
		||||
 * @param in The array of Credential objects to check
 | 
			
		||||
 * @return Auth Credential array
 | 
			
		||||
 */
 | 
			
		||||
std::set<std::pair<ripple::AccountID, ripple::Slice>>
 | 
			
		||||
createAuthCredentials(ripple::STArray const& in);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Parses each credential object and makes sure the credential type and values are correct
 | 
			
		||||
 *
 | 
			
		||||
 * @param jv The boost json array of credentials to parse
 | 
			
		||||
 * @return Array of credentials after parsing
 | 
			
		||||
 */
 | 
			
		||||
ripple::STArray
 | 
			
		||||
parseAuthorizeCredentials(boost::json::array const& jv);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Get Array of Credential objects
 | 
			
		||||
 *
 | 
			
		||||
 * @param credID Array of CredentialID's to parse
 | 
			
		||||
 * @param srcAcc The Source Account
 | 
			
		||||
 * @param backend backend interface
 | 
			
		||||
 * @param info The ledger header
 | 
			
		||||
 * @param yield The coroutine context
 | 
			
		||||
 * @return Array of credential objects, error if failed otherwise
 | 
			
		||||
 */
 | 
			
		||||
std::expected<ripple::STArray, Status>
 | 
			
		||||
fetchCredentialArray(
 | 
			
		||||
    std::optional<boost::json::array> const& credID,
 | 
			
		||||
    ripple::AccountID const& srcAcc,
 | 
			
		||||
    BackendInterface const& backend,
 | 
			
		||||
    ripple::LedgerHeader const& info,
 | 
			
		||||
    boost::asio::yield_context const& yield
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
}  // namespace rpc::credentials
 | 
			
		||||
@@ -83,6 +83,9 @@ getErrorInfo(ClioError code)
 | 
			
		||||
        {ClioError::rpcUNKNOWN_OPTION, "unknownOption", "Unknown option."},
 | 
			
		||||
        {ClioError::rpcFIELD_NOT_FOUND_TRANSACTION, "fieldNotFoundTransaction", "Missing field."},
 | 
			
		||||
        {ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID, "malformedDocumentID", "Malformed oracle_document_id."},
 | 
			
		||||
        {ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS,
 | 
			
		||||
         "malformedAuthorizedCredentials",
 | 
			
		||||
         "Malformed authorized credentials."},
 | 
			
		||||
        // special system errors
 | 
			
		||||
        {ClioError::rpcINVALID_API_VERSION, JS(invalid_API_version), "Invalid API version."},
 | 
			
		||||
        {ClioError::rpcCOMMAND_IS_MISSING, JS(missingCommand), "Method is not specified or is not a string."},
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@ enum class ClioError {
 | 
			
		||||
    rpcUNKNOWN_OPTION = 5005,
 | 
			
		||||
    rpcFIELD_NOT_FOUND_TRANSACTION = 5006,
 | 
			
		||||
    rpcMALFORMED_ORACLE_DOCUMENT_ID = 5007,
 | 
			
		||||
    rpcMALFORMED_AUTHORIZED_CREDENTIALS = 5008,
 | 
			
		||||
 | 
			
		||||
    // special system errors start with 6000
 | 
			
		||||
    rpcINVALID_API_VERSION = 6000,
 | 
			
		||||
 
 | 
			
		||||
@@ -20,19 +20,22 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "data/BackendInterface.hpp"
 | 
			
		||||
#include "rpc/Counters.hpp"
 | 
			
		||||
#include "rpc/Errors.hpp"
 | 
			
		||||
#include "rpc/RPCHelpers.hpp"
 | 
			
		||||
#include "rpc/WorkQueue.hpp"
 | 
			
		||||
#include "rpc/common/HandlerProvider.hpp"
 | 
			
		||||
#include "rpc/common/Types.hpp"
 | 
			
		||||
#include "rpc/common/impl/ForwardingProxy.hpp"
 | 
			
		||||
#include "util/ResponseExpirationCache.hpp"
 | 
			
		||||
#include "util/log/Logger.hpp"
 | 
			
		||||
#include "web/Context.hpp"
 | 
			
		||||
#include "web/dosguard/DOSGuardInterface.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/spawn.hpp>
 | 
			
		||||
#include <boost/iterator/transform_iterator.hpp>
 | 
			
		||||
#include <boost/json.hpp>
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <fmt/format.h>
 | 
			
		||||
#include <xrpl/protocol/ErrorCodes.h>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
@@ -41,14 +44,9 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
// forward declarations
 | 
			
		||||
namespace etl {
 | 
			
		||||
class LoadBalancer;
 | 
			
		||||
class ETLService;
 | 
			
		||||
}  // namespace etl
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief This namespace contains all the RPC logic and handlers.
 | 
			
		||||
 */
 | 
			
		||||
@@ -57,6 +55,7 @@ namespace rpc {
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The RPC engine that ties all RPC-related functionality together.
 | 
			
		||||
 */
 | 
			
		||||
template <typename LoadBalancerType, typename CountersType>
 | 
			
		||||
class RPCEngine {
 | 
			
		||||
    util::Logger perfLog_{"Performance"};
 | 
			
		||||
    util::Logger log_{"RPC"};
 | 
			
		||||
@@ -64,16 +63,19 @@ class RPCEngine {
 | 
			
		||||
    std::shared_ptr<BackendInterface> backend_;
 | 
			
		||||
    std::reference_wrapper<web::dosguard::DOSGuardInterface const> dosGuard_;
 | 
			
		||||
    std::reference_wrapper<WorkQueue> workQueue_;
 | 
			
		||||
    std::reference_wrapper<Counters> counters_;
 | 
			
		||||
    std::reference_wrapper<CountersType> counters_;
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<HandlerProvider const> handlerProvider_;
 | 
			
		||||
 | 
			
		||||
    impl::ForwardingProxy<etl::LoadBalancer, Counters, HandlerProvider> forwardingProxy_;
 | 
			
		||||
    impl::ForwardingProxy<LoadBalancerType, CountersType, HandlerProvider> forwardingProxy_;
 | 
			
		||||
 | 
			
		||||
    std::optional<util::ResponseExpirationCache> responseCache_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Construct a new RPCEngine object
 | 
			
		||||
     *
 | 
			
		||||
     * @param config The config to use
 | 
			
		||||
     * @param backend The backend to use
 | 
			
		||||
     * @param balancer The load balancer to use
 | 
			
		||||
     * @param dosGuard The DOS guard to use
 | 
			
		||||
@@ -82,11 +84,12 @@ public:
 | 
			
		||||
     * @param handlerProvider The handler provider to use
 | 
			
		||||
     */
 | 
			
		||||
    RPCEngine(
 | 
			
		||||
        util::Config const& config,
 | 
			
		||||
        std::shared_ptr<BackendInterface> const& backend,
 | 
			
		||||
        std::shared_ptr<etl::LoadBalancer> const& balancer,
 | 
			
		||||
        std::shared_ptr<LoadBalancerType> const& balancer,
 | 
			
		||||
        web::dosguard::DOSGuardInterface const& dosGuard,
 | 
			
		||||
        WorkQueue& workQueue,
 | 
			
		||||
        Counters& counters,
 | 
			
		||||
        CountersType& counters,
 | 
			
		||||
        std::shared_ptr<HandlerProvider const> const& handlerProvider
 | 
			
		||||
    )
 | 
			
		||||
        : backend_{backend}
 | 
			
		||||
@@ -96,11 +99,22 @@ public:
 | 
			
		||||
        , handlerProvider_{handlerProvider}
 | 
			
		||||
        , forwardingProxy_{balancer, counters, handlerProvider}
 | 
			
		||||
    {
 | 
			
		||||
        // Let main thread catch the exception if config type is wrong
 | 
			
		||||
        auto const cacheTimeout = config.valueOr<float>("rpc.cache_timeout", 0.f);
 | 
			
		||||
 | 
			
		||||
        if (cacheTimeout > 0.f) {
 | 
			
		||||
            LOG(log_.info()) << fmt::format("Init RPC Cache, timeout: {} seconds", cacheTimeout);
 | 
			
		||||
 | 
			
		||||
            responseCache_.emplace(
 | 
			
		||||
                util::Config::toMilliseconds(cacheTimeout), std::unordered_set<std::string>{"server_info"}
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Factory function to create a new instance of the RPC engine.
 | 
			
		||||
     *
 | 
			
		||||
     * @param config The config to use
 | 
			
		||||
     * @param backend The backend to use
 | 
			
		||||
     * @param balancer The load balancer to use
 | 
			
		||||
     * @param dosGuard The DOS guard to use
 | 
			
		||||
@@ -111,15 +125,16 @@ public:
 | 
			
		||||
     */
 | 
			
		||||
    static std::shared_ptr<RPCEngine>
 | 
			
		||||
    make_RPCEngine(
 | 
			
		||||
        util::Config const& config,
 | 
			
		||||
        std::shared_ptr<BackendInterface> const& backend,
 | 
			
		||||
        std::shared_ptr<etl::LoadBalancer> const& balancer,
 | 
			
		||||
        std::shared_ptr<LoadBalancerType> const& balancer,
 | 
			
		||||
        web::dosguard::DOSGuardInterface const& dosGuard,
 | 
			
		||||
        WorkQueue& workQueue,
 | 
			
		||||
        Counters& counters,
 | 
			
		||||
        CountersType& counters,
 | 
			
		||||
        std::shared_ptr<HandlerProvider const> const& handlerProvider
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        return std::make_shared<RPCEngine>(backend, balancer, dosGuard, workQueue, counters, handlerProvider);
 | 
			
		||||
        return std::make_shared<RPCEngine>(config, backend, balancer, dosGuard, workQueue, counters, handlerProvider);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -131,8 +146,18 @@ public:
 | 
			
		||||
    Result
 | 
			
		||||
    buildResponse(web::Context const& ctx)
 | 
			
		||||
    {
 | 
			
		||||
        if (forwardingProxy_.shouldForward(ctx))
 | 
			
		||||
        if (forwardingProxy_.shouldForward(ctx)) {
 | 
			
		||||
            // Disallow forwarding of the admin api, only user api is allowed for security reasons.
 | 
			
		||||
            if (isAdminCmd(ctx.method, ctx.params))
 | 
			
		||||
                return Result{Status{RippledError::rpcNO_PERMISSION}};
 | 
			
		||||
 | 
			
		||||
            return forwardingProxy_.forward(ctx);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (not ctx.isAdmin and responseCache_) {
 | 
			
		||||
            if (auto res = responseCache_->get(ctx.method); res.has_value())
 | 
			
		||||
                return Result{std::move(res).value()};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (backend_->isTooBusy()) {
 | 
			
		||||
            LOG(log_.error()) << "Database is too busy. Rejecting request";
 | 
			
		||||
@@ -154,8 +179,11 @@ public:
 | 
			
		||||
 | 
			
		||||
            LOG(perfLog_.debug()) << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
 | 
			
		||||
 | 
			
		||||
            if (not v)
 | 
			
		||||
            if (not v) {
 | 
			
		||||
                notifyErrored(ctx.method);
 | 
			
		||||
            } else if (not ctx.isAdmin and responseCache_) {
 | 
			
		||||
                responseCache_->put(ctx.method, v.result->as_object());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Result{std::move(v)};
 | 
			
		||||
        } catch (data::DatabaseTimeout const& t) {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/parse.hpp>
 | 
			
		||||
#include <boost/json/serialize.hpp>
 | 
			
		||||
#include <boost/json/string.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <boost/json/value_to.hpp>
 | 
			
		||||
@@ -49,6 +50,7 @@
 | 
			
		||||
#include <xrpl/basics/chrono.h>
 | 
			
		||||
#include <xrpl/basics/strHex.h>
 | 
			
		||||
#include <xrpl/beast/utility/Zero.h>
 | 
			
		||||
#include <xrpl/json/json_reader.h>
 | 
			
		||||
#include <xrpl/json/json_value.h>
 | 
			
		||||
#include <xrpl/protocol/AccountID.h>
 | 
			
		||||
#include <xrpl/protocol/Book.h>
 | 
			
		||||
@@ -79,6 +81,7 @@
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cassert>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
@@ -257,6 +260,7 @@ toExpandedJson(
 | 
			
		||||
    auto metaJson = toJson(*meta);
 | 
			
		||||
    insertDeliveredAmount(metaJson, txn, meta, blobs.date);
 | 
			
		||||
    insertDeliverMaxAlias(txnJson, apiVersion);
 | 
			
		||||
    insertMPTIssuanceID(metaJson, txn, meta);
 | 
			
		||||
 | 
			
		||||
    if (nftEnabled == NFTokenjson::ENABLE) {
 | 
			
		||||
        Json::Value nftJson;
 | 
			
		||||
@@ -312,6 +316,67 @@ insertDeliveredAmount(
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Get the delivered amount
 | 
			
		||||
 *
 | 
			
		||||
 * @param meta The metadata
 | 
			
		||||
 * @return The mpt_issuance_id or std::nullopt if not available
 | 
			
		||||
 */
 | 
			
		||||
static std::optional<ripple::uint192>
 | 
			
		||||
getMPTIssuanceID(std::shared_ptr<ripple::TxMeta const> const& meta)
 | 
			
		||||
{
 | 
			
		||||
    ripple::TxMeta const& transactionMeta = *meta;
 | 
			
		||||
 | 
			
		||||
    for (ripple::STObject const& node : transactionMeta.getNodes()) {
 | 
			
		||||
        if (node.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN_ISSUANCE ||
 | 
			
		||||
            node.getFName() != ripple::sfCreatedNode)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        auto const& mptNode = node.peekAtField(ripple::sfNewFields).downcast<ripple::STObject>();
 | 
			
		||||
        return ripple::makeMptID(mptNode[ripple::sfSequence], mptNode[ripple::sfIssuer]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Check if transaction has a new MPToken created
 | 
			
		||||
 *
 | 
			
		||||
 * @param txn The transaction
 | 
			
		||||
 * @param meta The metadata
 | 
			
		||||
 * @return true if the transaction can have a mpt_issuance_id
 | 
			
		||||
 */
 | 
			
		||||
static bool
 | 
			
		||||
canHaveMPTIssuanceID(std::shared_ptr<ripple::STTx const> const& txn, std::shared_ptr<ripple::TxMeta const> const& meta)
 | 
			
		||||
{
 | 
			
		||||
    if (txn->getTxnType() != ripple::ttMPTOKEN_ISSUANCE_CREATE)
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    if (meta->getResultTER() != ripple::tesSUCCESS)
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
insertMPTIssuanceID(
 | 
			
		||||
    boost::json::object& metaJson,
 | 
			
		||||
    std::shared_ptr<ripple::STTx const> const& txn,
 | 
			
		||||
    std::shared_ptr<ripple::TxMeta const> const& meta
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
    if (!canHaveMPTIssuanceID(txn, meta))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    if (auto const id = getMPTIssuanceID(meta)) {
 | 
			
		||||
        metaJson[JS(mpt_issuance_id)] = ripple::to_string(*id);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assert(false);
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
insertDeliverMaxAlias(boost::json::object& txJson, std::uint32_t const apiVersion)
 | 
			
		||||
{
 | 
			
		||||
@@ -428,8 +493,9 @@ ledgerHeaderFromRequest(std::shared_ptr<data::BackendInterface const> const& bac
 | 
			
		||||
            } else {
 | 
			
		||||
                ledgerSequence = parseStringAsUInt(stringIndex);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (indexValue.is_int64())
 | 
			
		||||
        } else if (indexValue.is_int64()) {
 | 
			
		||||
            ledgerSequence = indexValue.as_int64();
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        ledgerSequence = ctx.range.maxSequence;
 | 
			
		||||
    }
 | 
			
		||||
@@ -947,7 +1013,8 @@ accountHolds(
 | 
			
		||||
    auto const blob = backend.fetchLedgerObject(key, sequence, yield);
 | 
			
		||||
 | 
			
		||||
    if (!blob) {
 | 
			
		||||
        amount.clear({currency, issuer});
 | 
			
		||||
        amount.setIssue(ripple::Issue(currency, issuer));
 | 
			
		||||
        amount.clear();
 | 
			
		||||
        return amount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -955,7 +1022,8 @@ accountHolds(
 | 
			
		||||
    ripple::SLE const sle{it, key};
 | 
			
		||||
 | 
			
		||||
    if (zeroIfFrozen && isFrozen(backend, sequence, account, currency, issuer, yield)) {
 | 
			
		||||
        amount.clear(ripple::Issue(currency, issuer));
 | 
			
		||||
        amount.setIssue(ripple::Issue(currency, issuer));
 | 
			
		||||
        amount.clear();
 | 
			
		||||
    } else {
 | 
			
		||||
        amount = sle.getFieldAmount(ripple::sfBalance);
 | 
			
		||||
        if (account > issuer) {
 | 
			
		||||
@@ -1273,6 +1341,31 @@ specifiesCurrentOrClosedLedger(boost::json::object const& request)
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
isAdminCmd(std::string const& method, boost::json::object const& request)
 | 
			
		||||
{
 | 
			
		||||
    if (method == JS(ledger)) {
 | 
			
		||||
        auto const requestStr = boost::json::serialize(request);
 | 
			
		||||
        Json::Value jv;
 | 
			
		||||
        Json::Reader{}.parse(requestStr, jv);
 | 
			
		||||
        // rippled considers string/non-zero int/non-empty array/ non-empty json as true.
 | 
			
		||||
        // Use rippled's API asBool to get the same result.
 | 
			
		||||
        // https://github.com/XRPLF/rippled/issues/5119
 | 
			
		||||
        auto const isFieldSet = [&jv](auto const field) { return jv.isMember(field) and jv[field].asBool(); };
 | 
			
		||||
 | 
			
		||||
        // According to doc
 | 
			
		||||
        // https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger,
 | 
			
		||||
        // full/accounts/type are admin only, but type only works when full/accounts are set, so we don't need to check
 | 
			
		||||
        // type.
 | 
			
		||||
        if (isFieldSet(JS(full)) or isFieldSet(JS(accounts)))
 | 
			
		||||
            return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (method == JS(feature) and request.contains(JS(vetoed)))
 | 
			
		||||
        return true;
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::variant<ripple::uint256, Status>
 | 
			
		||||
getNFTID(boost::json::object const& request)
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -191,6 +191,21 @@ insertDeliveredAmount(
 | 
			
		||||
    uint32_t date
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Add "mpt_issuance_id" into MPTokenIssuanceCreate transaction json.
 | 
			
		||||
 *
 | 
			
		||||
 * @param metaJson The metadata json object to add "MPTokenIssuanceID"
 | 
			
		||||
 * @param txn The transaction object
 | 
			
		||||
 * @param meta The metadata object
 | 
			
		||||
 * @return true if the "mpt_issuance_id" is added to the metadata json object
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
insertMPTIssuanceID(
 | 
			
		||||
    boost::json::object& metaJson,
 | 
			
		||||
    std::shared_ptr<ripple::STTx const> const& txn,
 | 
			
		||||
    std::shared_ptr<ripple::TxMeta const> const& meta
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Convert STBase object to JSON
 | 
			
		||||
 *
 | 
			
		||||
@@ -557,6 +572,16 @@ parseIssue(boost::json::object const& issue);
 | 
			
		||||
bool
 | 
			
		||||
specifiesCurrentOrClosedLedger(boost::json::object const& request);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Check whether a request requires administrative privileges on rippled side.
 | 
			
		||||
 *
 | 
			
		||||
 * @param method The method name to check
 | 
			
		||||
 * @param request The request to check
 | 
			
		||||
 * @return true if the request requires ADMIN role
 | 
			
		||||
 */
 | 
			
		||||
bool
 | 
			
		||||
isAdminCmd(std::string const& method, boost::json::object const& request);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Get the NFTID from the request
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,10 @@
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <boost/json/value_to.hpp>
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <xrpl/basics/StringUtilities.h>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
#include <xrpl/protocol/AccountID.h>
 | 
			
		||||
#include <xrpl/protocol/Protocol.h>
 | 
			
		||||
#include <xrpl/protocol/UintTypes.h>
 | 
			
		||||
 | 
			
		||||
#include <charconv>
 | 
			
		||||
@@ -89,16 +91,19 @@ checkIsU32Numeric(std::string_view sv)
 | 
			
		||||
    return ec == std::errc();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CustomValidator CustomValidators::Uint160HexStringValidator =
 | 
			
		||||
    CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
 | 
			
		||||
        return makeHexStringValidator<ripple::uint160>(value, key);
 | 
			
		||||
    }};
 | 
			
		||||
 | 
			
		||||
CustomValidator CustomValidators::Uint192HexStringValidator =
 | 
			
		||||
    CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
 | 
			
		||||
        return makeHexStringValidator<ripple::uint192>(value, key);
 | 
			
		||||
    }};
 | 
			
		||||
 | 
			
		||||
CustomValidator CustomValidators::Uint256HexStringValidator =
 | 
			
		||||
    CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
 | 
			
		||||
        if (!value.is_string())
 | 
			
		||||
            return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
 | 
			
		||||
 | 
			
		||||
        ripple::uint256 ledgerHash;
 | 
			
		||||
        if (!ledgerHash.parseHex(boost::json::value_to<std::string>(value)))
 | 
			
		||||
            return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}};
 | 
			
		||||
 | 
			
		||||
        return MaybeError{};
 | 
			
		||||
        return makeHexStringValidator<ripple::uint256>(value, key);
 | 
			
		||||
    }};
 | 
			
		||||
 | 
			
		||||
CustomValidator CustomValidators::LedgerIndexValidator =
 | 
			
		||||
@@ -200,15 +205,13 @@ CustomValidator CustomValidators::SubscribeStreamValidator =
 | 
			
		||||
            "ledger", "transactions", "transactions_proposed", "book_changes", "manifests", "validations"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        static std::unordered_set<std::string> const reportingNotSupportStreams = {
 | 
			
		||||
            "peer_status", "consensus", "server"
 | 
			
		||||
        };
 | 
			
		||||
        static std::unordered_set<std::string> const notSupportStreams = {"peer_status", "consensus", "server"};
 | 
			
		||||
        for (auto const& v : value.as_array()) {
 | 
			
		||||
            if (!v.is_string())
 | 
			
		||||
                return Error{Status{RippledError::rpcINVALID_PARAMS, "streamNotString"}};
 | 
			
		||||
 | 
			
		||||
            if (reportingNotSupportStreams.contains(boost::json::value_to<std::string>(v)))
 | 
			
		||||
                return Error{Status{RippledError::rpcREPORTING_UNSUPPORTED}};
 | 
			
		||||
            if (notSupportStreams.contains(boost::json::value_to<std::string>(v)))
 | 
			
		||||
                return Error{Status{RippledError::rpcNOT_SUPPORTED}};
 | 
			
		||||
 | 
			
		||||
            if (not validStreams.contains(boost::json::value_to<std::string>(v)))
 | 
			
		||||
                return Error{Status{RippledError::rpcSTREAM_MALFORMED}};
 | 
			
		||||
@@ -252,4 +255,79 @@ CustomValidator CustomValidators::CurrencyIssueValidator =
 | 
			
		||||
        return MaybeError{};
 | 
			
		||||
    }};
 | 
			
		||||
 | 
			
		||||
CustomValidator CustomValidators::CredentialTypeValidator =
 | 
			
		||||
    CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
 | 
			
		||||
        if (not value.is_string())
 | 
			
		||||
            return Error{Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, std::string(key) + " NotString"}};
 | 
			
		||||
 | 
			
		||||
        auto const& credTypeHex = ripple::strViewUnHex(value.as_string());
 | 
			
		||||
        if (!credTypeHex.has_value())
 | 
			
		||||
            return Error{Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, std::string(key) + " NotHexString"}};
 | 
			
		||||
 | 
			
		||||
        if (credTypeHex->empty())
 | 
			
		||||
            return Error{Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, std::string(key) + " is empty"}};
 | 
			
		||||
 | 
			
		||||
        if (credTypeHex->size() > ripple::maxCredentialTypeLength) {
 | 
			
		||||
            return Error{
 | 
			
		||||
                Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, std::string(key) + " greater than max length"}
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MaybeError{};
 | 
			
		||||
    }};
 | 
			
		||||
 | 
			
		||||
CustomValidator CustomValidators::AuthorizeCredentialValidator =
 | 
			
		||||
    CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
 | 
			
		||||
        if (not value.is_array())
 | 
			
		||||
            return Error{Status{ClioError::rpcMALFORMED_REQUEST, std::string(key) + " not array"}};
 | 
			
		||||
 | 
			
		||||
        auto const& authCred = value.as_array();
 | 
			
		||||
        if (authCred.empty()) {
 | 
			
		||||
            return Error{Status{
 | 
			
		||||
                ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS,
 | 
			
		||||
                fmt::format("Requires at least one element in authorized_credentials array.")
 | 
			
		||||
            }};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (authCred.size() > ripple::maxCredentialsArraySize) {
 | 
			
		||||
            return Error{Status{
 | 
			
		||||
                ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS,
 | 
			
		||||
                fmt::format(
 | 
			
		||||
                    "Max {} number of credentials in authorized_credentials array", ripple::maxCredentialsArraySize
 | 
			
		||||
                )
 | 
			
		||||
            }};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (auto const& credObj : value.as_array()) {
 | 
			
		||||
            if (!credObj.is_object()) {
 | 
			
		||||
                return Error{Status{
 | 
			
		||||
                    ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS,
 | 
			
		||||
                    "authorized_credentials elements in array are not objects."
 | 
			
		||||
                }};
 | 
			
		||||
            }
 | 
			
		||||
            auto const& obj = credObj.as_object();
 | 
			
		||||
 | 
			
		||||
            if (!obj.contains("issuer")) {
 | 
			
		||||
                return Error{
 | 
			
		||||
                    Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, "Field 'Issuer' is required but missing."}
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // don't want to change issuer error message to be about credentials
 | 
			
		||||
            if (!IssuerValidator.verify(credObj, "issuer"))
 | 
			
		||||
                return Error{Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, "issuer NotString"}};
 | 
			
		||||
 | 
			
		||||
            if (!obj.contains("credential_type")) {
 | 
			
		||||
                return Error{Status{
 | 
			
		||||
                    ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, "Field 'CredentialType' is required but missing."
 | 
			
		||||
                }};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (auto const err = CredentialTypeValidator.verify(credObj, "credential_type"); !err)
 | 
			
		||||
                return err;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MaybeError{};
 | 
			
		||||
    }};
 | 
			
		||||
 | 
			
		||||
}  // namespace rpc::validation
 | 
			
		||||
 
 | 
			
		||||
@@ -27,15 +27,13 @@
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
#include <xrpl/protocol/ErrorCodes.h>
 | 
			
		||||
 | 
			
		||||
#include <concepts>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <ctime>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <initializer_list>
 | 
			
		||||
#include <iomanip>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <utility>
 | 
			
		||||
@@ -153,7 +151,7 @@ struct Type final {
 | 
			
		||||
    verify(boost::json::value const& value, std::string_view key) const
 | 
			
		||||
    {
 | 
			
		||||
        if (not value.is_object() or not value.as_object().contains(key.data()))
 | 
			
		||||
            return {};  // ignore. field does not exist, let 'required' fail instead
 | 
			
		||||
            return {};  // ignore. If field is supposed to exist, let 'required' fail instead
 | 
			
		||||
 | 
			
		||||
        auto const& res = value.as_object().at(key.data());
 | 
			
		||||
        auto const convertible = (checkType<Types>(res) || ...);
 | 
			
		||||
@@ -458,6 +456,21 @@ public:
 | 
			
		||||
[[nodiscard]] bool
 | 
			
		||||
checkIsU32Numeric(std::string_view sv);
 | 
			
		||||
 | 
			
		||||
template <class HexType>
 | 
			
		||||
    requires(std::is_same_v<HexType, ripple::uint160> || std::is_same_v<HexType, ripple::uint192> || std::is_same_v<HexType, ripple::uint256>)
 | 
			
		||||
MaybeError
 | 
			
		||||
makeHexStringValidator(boost::json::value const& value, std::string_view key)
 | 
			
		||||
{
 | 
			
		||||
    if (!value.is_string())
 | 
			
		||||
        return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
 | 
			
		||||
 | 
			
		||||
    HexType parsedInt;
 | 
			
		||||
    if (!parsedInt.parseHex(value.as_string().c_str()))
 | 
			
		||||
        return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "Malformed"}};
 | 
			
		||||
 | 
			
		||||
    return MaybeError{};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A group of custom validation functions
 | 
			
		||||
 */
 | 
			
		||||
@@ -492,6 +505,22 @@ struct CustomValidators final {
 | 
			
		||||
     */
 | 
			
		||||
    static CustomValidator AccountMarkerValidator;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Provides a commonly used validator for uint160(AccountID) hex string.
 | 
			
		||||
     *
 | 
			
		||||
     * It must be a string and also a decodable hex.
 | 
			
		||||
     * AccountID uses this validator.
 | 
			
		||||
     */
 | 
			
		||||
    static CustomValidator Uint160HexStringValidator;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Provides a commonly used validator for uint192 hex string.
 | 
			
		||||
     *
 | 
			
		||||
     * It must be a string and also a decodable hex.
 | 
			
		||||
     * MPTIssuanceID uses this validator.
 | 
			
		||||
     */
 | 
			
		||||
    static CustomValidator Uint192HexStringValidator;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Provides a commonly used validator for uint256 hex string.
 | 
			
		||||
     *
 | 
			
		||||
@@ -528,6 +557,51 @@ struct CustomValidators final {
 | 
			
		||||
     * Used by amm_info.
 | 
			
		||||
     */
 | 
			
		||||
    static CustomValidator CurrencyIssueValidator;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Provides a validator for validating authorized_credentials json array.
 | 
			
		||||
     *
 | 
			
		||||
     * Used by deposit_preauth.
 | 
			
		||||
     */
 | 
			
		||||
    static CustomValidator AuthorizeCredentialValidator;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Provides a validator for validating credential_type.
 | 
			
		||||
     *
 | 
			
		||||
     * Used by AuthorizeCredentialValidator in deposit_preauth.
 | 
			
		||||
     */
 | 
			
		||||
    static CustomValidator CredentialTypeValidator;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Validates that the elements of the array is of type Hex256 uint
 | 
			
		||||
 */
 | 
			
		||||
struct Hex256ItemType final {
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Validates given the prerequisite that the type of the json value is an array,
 | 
			
		||||
     * verifies all values within the array is of uint256 hash
 | 
			
		||||
     *
 | 
			
		||||
     * @param value the value to verify
 | 
			
		||||
     * @param key The key used to retrieve the tested value from the outer object
 | 
			
		||||
     * @return `RippledError::rpcINVALID_PARAMS` if validation failed; otherwise no error is returned
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] static MaybeError
 | 
			
		||||
    verify(boost::json::value const& value, std::string_view key)
 | 
			
		||||
    {
 | 
			
		||||
        if (not value.is_object() or not value.as_object().contains(key.data()))
 | 
			
		||||
            return {};  // ignore. If field is supposed to exist, let 'required' fail instead
 | 
			
		||||
 | 
			
		||||
        auto const& res = value.as_object().at(key.data());
 | 
			
		||||
 | 
			
		||||
        // loop through each item in the array and make sure it is uint256 hex string
 | 
			
		||||
        for (auto const& elem : res.as_array()) {
 | 
			
		||||
            ripple::uint256 num;
 | 
			
		||||
            if (!elem.is_string() || !num.parseHex(elem.as_string())) {
 | 
			
		||||
                return Error{Status{RippledError::rpcINVALID_PARAMS, "Item is not a valid uint256 type."}};
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace rpc::validation
 | 
			
		||||
 
 | 
			
		||||
@@ -60,10 +60,6 @@ public:
 | 
			
		||||
        if (ctx.method == "subscribe" || ctx.method == "unsubscribe")
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        // Disallow forwarding of the admin api, only user api is allowed for security reasons.
 | 
			
		||||
        if (ctx.method == "feature" and request.contains("vetoed"))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        if (handlerProvider_->isClioOnly(ctx.method))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
@@ -73,6 +69,9 @@ public:
 | 
			
		||||
        if (specifiesCurrentOrClosedLedger(request))
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
        if (isForcedForward(ctx))
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
        auto const checkAccountInfoForward = [&]() {
 | 
			
		||||
            return ctx.method == "account_info" and request.contains("queue") and request.at("queue").is_bool() and
 | 
			
		||||
                request.at("queue").as_bool();
 | 
			
		||||
@@ -142,6 +141,14 @@ private:
 | 
			
		||||
    {
 | 
			
		||||
        return handlerProvider_->contains(method) || isProxied(method);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool
 | 
			
		||||
    isForcedForward(web::Context const& ctx) const
 | 
			
		||||
    {
 | 
			
		||||
        static constexpr auto FORCE_FORWARD = "force_forward";
 | 
			
		||||
        return ctx.isAdmin and ctx.params.contains(FORCE_FORWARD) and ctx.params.at(FORCE_FORWARD).is_bool() and
 | 
			
		||||
            ctx.params.at(FORCE_FORWARD).as_bool();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace rpc::impl
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@
 | 
			
		||||
#include "rpc/handlers/LedgerEntry.hpp"
 | 
			
		||||
#include "rpc/handlers/LedgerIndex.hpp"
 | 
			
		||||
#include "rpc/handlers/LedgerRange.hpp"
 | 
			
		||||
#include "rpc/handlers/MPTHolders.hpp"
 | 
			
		||||
#include "rpc/handlers/NFTBuyOffers.hpp"
 | 
			
		||||
#include "rpc/handlers/NFTHistory.hpp"
 | 
			
		||||
#include "rpc/handlers/NFTInfo.hpp"
 | 
			
		||||
@@ -97,6 +98,7 @@ ProductionHandlerProvider::ProductionHandlerProvider(
 | 
			
		||||
          {"ledger_entry", {LedgerEntryHandler{backend}}},
 | 
			
		||||
          {"ledger_index", {LedgerIndexHandler{backend}, true}},  // clio only
 | 
			
		||||
          {"ledger_range", {LedgerRangeHandler{backend}}},
 | 
			
		||||
          {"mpt_holders", {MPTHoldersHandler{backend}, true}},       // clio only
 | 
			
		||||
          {"nfts_by_issuer", {NFTsByIssuerHandler{backend}, true}},  // clio only
 | 
			
		||||
          {"nft_history", {NFTHistoryHandler{backend}, true}},       // clio only
 | 
			
		||||
          {"nft_buy_offers", {NFTBuyOffersHandler{backend}}},
 | 
			
		||||
 
 | 
			
		||||
@@ -78,10 +78,17 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx)
 | 
			
		||||
        input.marker ? ripple::uint256{input.marker->c_str()} : ripple::keylet::nftpage_max(*accountID).key;
 | 
			
		||||
    auto const blob = sharedPtrBackend_->fetchLedgerObject(pageKey, lgrInfo.seq, ctx.yield);
 | 
			
		||||
 | 
			
		||||
    if (!blob)
 | 
			
		||||
    if (!blob) {
 | 
			
		||||
        if (input.marker.has_value())
 | 
			
		||||
            return Error{Status{RippledError::rpcINVALID_PARAMS, "Marker field does not match any valid Page ID"}};
 | 
			
		||||
        return response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::optional<ripple::SLE const> page{ripple::SLE{ripple::SerialIter{blob->data(), blob->size()}, pageKey}};
 | 
			
		||||
 | 
			
		||||
    if (page->getType() != ripple::ltNFTOKEN_PAGE)
 | 
			
		||||
        return Error{Status{RippledError::rpcINVALID_PARAMS, "Marker matches Page ID from another Account"}};
 | 
			
		||||
 | 
			
		||||
    auto numPages = 0u;
 | 
			
		||||
 | 
			
		||||
    while (page) {
 | 
			
		||||
 
 | 
			
		||||
@@ -55,10 +55,6 @@ class AccountObjectsHandler {
 | 
			
		||||
    // dependencies
 | 
			
		||||
    std::shared_ptr<BackendInterface> sharedPtrBackend_;
 | 
			
		||||
 | 
			
		||||
    // constants
 | 
			
		||||
    static std::unordered_map<std::string, ripple::LedgerEntryType> const TYPES_MAP;
 | 
			
		||||
    static std::unordered_set<std::string> const TYPES_KEYS;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    static auto constexpr LIMIT_MIN = 10;
 | 
			
		||||
    static auto constexpr LIMIT_MAX = 400;
 | 
			
		||||
 
 | 
			
		||||
@@ -19,25 +19,32 @@
 | 
			
		||||
 | 
			
		||||
#include "rpc/handlers/DepositAuthorized.hpp"
 | 
			
		||||
 | 
			
		||||
#include "rpc/CredentialHelpers.hpp"
 | 
			
		||||
#include "rpc/Errors.hpp"
 | 
			
		||||
#include "rpc/JS.hpp"
 | 
			
		||||
#include "rpc/RPCHelpers.hpp"
 | 
			
		||||
#include "rpc/common/Types.hpp"
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <boost/json/conversion.hpp>
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <boost/json/value_to.hpp>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
#include <xrpl/basics/strHex.h>
 | 
			
		||||
#include <xrpl/protocol/Indexes.h>
 | 
			
		||||
#include <xrpl/protocol/LedgerFormats.h>
 | 
			
		||||
#include <xrpl/protocol/LedgerHeader.h>
 | 
			
		||||
#include <xrpl/protocol/SField.h>
 | 
			
		||||
#include <xrpl/protocol/Protocol.h>
 | 
			
		||||
#include <xrpl/protocol/STLedgerEntry.h>
 | 
			
		||||
#include <xrpl/protocol/STObject.h>
 | 
			
		||||
#include <xrpl/protocol/Serializer.h>
 | 
			
		||||
#include <xrpl/protocol/jss.h>
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
namespace rpc {
 | 
			
		||||
@@ -71,26 +78,55 @@ DepositAuthorizedHandler::process(DepositAuthorizedHandler::Input input, Context
 | 
			
		||||
 | 
			
		||||
    Output response;
 | 
			
		||||
 | 
			
		||||
    auto it = ripple::SerialIter{dstAccountLedgerObject->data(), dstAccountLedgerObject->size()};
 | 
			
		||||
    auto const sleDest = ripple::SLE{it, dstKeylet};
 | 
			
		||||
    bool const reqAuth = sleDest.isFlag(ripple::lsfDepositAuth) && (sourceAccountID != destinationAccountID);
 | 
			
		||||
    auto const& creds = input.credentials;
 | 
			
		||||
    bool const credentialsPresent = creds.has_value();
 | 
			
		||||
 | 
			
		||||
    ripple::STArray authCreds;
 | 
			
		||||
    if (credentialsPresent) {
 | 
			
		||||
        if (creds.value().empty()) {
 | 
			
		||||
            return Error{Status{RippledError::rpcINVALID_PARAMS, "credential array has no elements."}};
 | 
			
		||||
        }
 | 
			
		||||
        if (creds.value().size() > ripple::maxCredentialsArraySize) {
 | 
			
		||||
            return Error{Status{RippledError::rpcINVALID_PARAMS, "credential array too long."}};
 | 
			
		||||
        }
 | 
			
		||||
        auto const credArray = credentials::fetchCredentialArray(
 | 
			
		||||
            input.credentials, *sourceAccountID, *sharedPtrBackend_, lgrInfo, ctx.yield
 | 
			
		||||
        );
 | 
			
		||||
        if (!credArray.has_value())
 | 
			
		||||
            return Error{std::move(credArray).error()};
 | 
			
		||||
        authCreds = std::move(credArray).value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the two accounts are the same OR if that flag is
 | 
			
		||||
    // not set, then the deposit should be fine.
 | 
			
		||||
    bool depositAuthorized = true;
 | 
			
		||||
 | 
			
		||||
    if (reqAuth) {
 | 
			
		||||
        ripple::uint256 hashKey;
 | 
			
		||||
        if (credentialsPresent) {
 | 
			
		||||
            auto const sortedAuthCreds = credentials::createAuthCredentials(authCreds);
 | 
			
		||||
            ASSERT(
 | 
			
		||||
                sortedAuthCreds.size() == authCreds.size(), "should already be checked above that there is no duplicate"
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            hashKey = ripple::keylet::depositPreauth(*destinationAccountID, sortedAuthCreds).key;
 | 
			
		||||
        } else {
 | 
			
		||||
            hashKey = ripple::keylet::depositPreauth(*destinationAccountID, *sourceAccountID).key;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        depositAuthorized = sharedPtrBackend_->fetchLedgerObject(hashKey, lgrInfo.seq, ctx.yield).has_value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    response.sourceAccount = input.sourceAccount;
 | 
			
		||||
    response.destinationAccount = input.destinationAccount;
 | 
			
		||||
    response.ledgerHash = ripple::strHex(lgrInfo.hash);
 | 
			
		||||
    response.ledgerIndex = lgrInfo.seq;
 | 
			
		||||
 | 
			
		||||
    // If the two accounts are the same, then the deposit should be fine.
 | 
			
		||||
    if (sourceAccountID != destinationAccountID) {
 | 
			
		||||
        auto it = ripple::SerialIter{dstAccountLedgerObject->data(), dstAccountLedgerObject->size()};
 | 
			
		||||
        auto sle = ripple::SLE{it, dstKeylet};
 | 
			
		||||
 | 
			
		||||
        // Check destination for the DepositAuth flag.
 | 
			
		||||
        // If that flag is not set then a deposit should be just fine.
 | 
			
		||||
        if ((sle.getFieldU32(ripple::sfFlags) & ripple::lsfDepositAuth) != 0u) {
 | 
			
		||||
            // See if a preauthorization entry is in the ledger.
 | 
			
		||||
            auto const depositPreauthKeylet = ripple::keylet::depositPreauth(*destinationAccountID, *sourceAccountID);
 | 
			
		||||
            auto const sleDepositAuth =
 | 
			
		||||
                sharedPtrBackend_->fetchLedgerObject(depositPreauthKeylet.key, lgrInfo.seq, ctx.yield);
 | 
			
		||||
            response.depositAuthorized = static_cast<bool>(sleDepositAuth);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    response.depositAuthorized = depositAuthorized;
 | 
			
		||||
    if (credentialsPresent)
 | 
			
		||||
        response.credentials = input.credentials.value();
 | 
			
		||||
 | 
			
		||||
    return response;
 | 
			
		||||
}
 | 
			
		||||
@@ -115,6 +151,10 @@ tag_invoke(boost::json::value_to_tag<DepositAuthorizedHandler::Input>, boost::js
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (jsonObject.contains(JS(credentials))) {
 | 
			
		||||
        input.credentials = boost::json::value_to<boost::json::array>(jv.at(JS(credentials)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return input;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -127,8 +167,10 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, DepositAuthorize
 | 
			
		||||
        {JS(destination_account), output.destinationAccount},
 | 
			
		||||
        {JS(ledger_hash), output.ledgerHash},
 | 
			
		||||
        {JS(ledger_index), output.ledgerIndex},
 | 
			
		||||
        {JS(validated), output.validated},
 | 
			
		||||
        {JS(validated), output.validated}
 | 
			
		||||
    };
 | 
			
		||||
    if (output.credentials)
 | 
			
		||||
        jv.as_object()[JS(credentials)] = *output.credentials;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace rpc
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,10 @@
 | 
			
		||||
#include "rpc/common/Types.hpp"
 | 
			
		||||
#include "rpc/common/Validators.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <boost/json/conversion.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <xrpl/protocol/STArray.h>
 | 
			
		||||
#include <xrpl/protocol/jss.h>
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
@@ -59,6 +61,8 @@ public:
 | 
			
		||||
        std::string destinationAccount;
 | 
			
		||||
        std::string ledgerHash;
 | 
			
		||||
        uint32_t ledgerIndex{};
 | 
			
		||||
        std::optional<boost::json::array> credentials;
 | 
			
		||||
 | 
			
		||||
        // validated should be sent via framework
 | 
			
		||||
        bool validated = true;
 | 
			
		||||
    };
 | 
			
		||||
@@ -71,6 +75,7 @@ public:
 | 
			
		||||
        std::string destinationAccount;
 | 
			
		||||
        std::optional<std::string> ledgerHash;
 | 
			
		||||
        std::optional<uint32_t> ledgerIndex;
 | 
			
		||||
        std::optional<boost::json::array> credentials;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    using Result = HandlerReturnType<Output>;
 | 
			
		||||
@@ -99,6 +104,7 @@ public:
 | 
			
		||||
            {JS(destination_account), validation::Required{}, validation::CustomValidators::AccountValidator},
 | 
			
		||||
            {JS(ledger_hash), validation::CustomValidators::Uint256HexStringValidator},
 | 
			
		||||
            {JS(ledger_index), validation::CustomValidators::LedgerIndexValidator},
 | 
			
		||||
            {JS(credentials), validation::Type<boost::json::array>{}, validation::Hex256ItemType()}
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return rpcSpec;
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,6 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
namespace rpc {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
 | 
			
		||||
#include "rpc/handlers/LedgerEntry.hpp"
 | 
			
		||||
 | 
			
		||||
#include "rpc/CredentialHelpers.hpp"
 | 
			
		||||
#include "rpc/Errors.hpp"
 | 
			
		||||
#include "rpc/JS.hpp"
 | 
			
		||||
#include "rpc/RPCHelpers.hpp"
 | 
			
		||||
@@ -30,6 +31,8 @@
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <boost/json/value_to.hpp>
 | 
			
		||||
#include <xrpl/basics/Slice.h>
 | 
			
		||||
#include <xrpl/basics/StringUtilities.h>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
#include <xrpl/basics/strHex.h>
 | 
			
		||||
#include <xrpl/json/json_value.h>
 | 
			
		||||
@@ -97,11 +100,30 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx)
 | 
			
		||||
        auto const owner = util::parseBase58Wrapper<ripple::AccountID>(
 | 
			
		||||
            boost::json::value_to<std::string>(input.depositPreauth->at(JS(owner)))
 | 
			
		||||
        );
 | 
			
		||||
        auto const authorized = util::parseBase58Wrapper<ripple::AccountID>(
 | 
			
		||||
            boost::json::value_to<std::string>(input.depositPreauth->at(JS(authorized)))
 | 
			
		||||
        );
 | 
			
		||||
        // Only one of authorize or authorized_credentials MUST exist;
 | 
			
		||||
        if (input.depositPreauth->contains(JS(authorized)) ==
 | 
			
		||||
            input.depositPreauth->contains(JS(authorized_credentials))) {
 | 
			
		||||
            return Error{
 | 
			
		||||
                Status{ClioError::rpcMALFORMED_REQUEST, "Must have one of authorized or authorized_credentials."}
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        key = ripple::keylet::depositPreauth(*owner, *authorized).key;
 | 
			
		||||
        if (input.depositPreauth->contains(JS(authorized))) {
 | 
			
		||||
            auto const authorized = util::parseBase58Wrapper<ripple::AccountID>(
 | 
			
		||||
                boost::json::value_to<std::string>(input.depositPreauth->at(JS(authorized)))
 | 
			
		||||
            );
 | 
			
		||||
            key = ripple::keylet::depositPreauth(*owner, *authorized).key;
 | 
			
		||||
        } else {
 | 
			
		||||
            auto const authorizedCredentials = rpc::credentials::parseAuthorizeCredentials(
 | 
			
		||||
                input.depositPreauth->at(JS(authorized_credentials)).as_array()
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            auto const authCreds = credentials::createAuthCredentials(authorizedCredentials);
 | 
			
		||||
            if (authCreds.size() != authorizedCredentials.size())
 | 
			
		||||
                return Error{Status{ClioError::rpcMALFORMED_AUTHORIZED_CREDENTIALS, "duplicates in credentials."}};
 | 
			
		||||
 | 
			
		||||
            key = ripple::keylet::depositPreauth(owner.value(), authCreds).key;
 | 
			
		||||
        }
 | 
			
		||||
    } else if (input.ticket) {
 | 
			
		||||
        auto const id =
 | 
			
		||||
            util::parseBase58Wrapper<ripple::AccountID>(boost::json::value_to<std::string>(input.ticket->at(JS(account))
 | 
			
		||||
@@ -145,6 +167,18 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx)
 | 
			
		||||
        }
 | 
			
		||||
    } else if (input.oracleNode) {
 | 
			
		||||
        key = input.oracleNode.value();
 | 
			
		||||
    } else if (input.credential) {
 | 
			
		||||
        key = input.credential.value();
 | 
			
		||||
    } else if (input.mptIssuance) {
 | 
			
		||||
        auto const mptIssuanceID = ripple::uint192{std::string_view(*(input.mptIssuance))};
 | 
			
		||||
        key = ripple::keylet::mptIssuance(mptIssuanceID).key;
 | 
			
		||||
    } else if (input.mptoken) {
 | 
			
		||||
        auto const holder =
 | 
			
		||||
            ripple::parseBase58<ripple::AccountID>(boost::json::value_to<std::string>(input.mptoken->at(JS(account))));
 | 
			
		||||
        auto const mptIssuanceID =
 | 
			
		||||
            ripple::uint192{std::string_view(boost::json::value_to<std::string>(input.mptoken->at(JS(mpt_issuance_id))))
 | 
			
		||||
            };
 | 
			
		||||
        key = ripple::keylet::mptoken(mptIssuanceID, *holder).key;
 | 
			
		||||
    } else {
 | 
			
		||||
        // Must specify 1 of the following fields to indicate what type
 | 
			
		||||
        if (ctx.apiVersion == 1)
 | 
			
		||||
@@ -277,6 +311,8 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
 | 
			
		||||
        {JS(xchain_owned_create_account_claim_id), ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID},
 | 
			
		||||
        {JS(xchain_owned_claim_id), ripple::ltXCHAIN_OWNED_CLAIM_ID},
 | 
			
		||||
        {JS(oracle), ripple::ltORACLE},
 | 
			
		||||
        {JS(credential), ripple::ltCREDENTIAL},
 | 
			
		||||
        {JS(mptoken), ripple::ltMPTOKEN},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto const parseBridgeFromJson = [](boost::json::value const& bridgeJson) {
 | 
			
		||||
@@ -302,6 +338,16 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
 | 
			
		||||
        return ripple::keylet::oracle(*account, documentId).key;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto const parseCredentialFromJson = [](boost::json::value const& json) {
 | 
			
		||||
        auto const subject =
 | 
			
		||||
            util::parseBase58Wrapper<ripple::AccountID>(boost::json::value_to<std::string>(json.at(JS(subject))));
 | 
			
		||||
        auto const issuer =
 | 
			
		||||
            util::parseBase58Wrapper<ripple::AccountID>(boost::json::value_to<std::string>(json.at(JS(issuer))));
 | 
			
		||||
        auto const credType = ripple::strUnHex(boost::json::value_to<std::string>(json.at(JS(credential_type))));
 | 
			
		||||
 | 
			
		||||
        return ripple::keylet::credential(*subject, *issuer, ripple::Slice(credType->data(), credType->size())).key;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    auto const indexFieldType =
 | 
			
		||||
        std::find_if(indexFieldTypeMap.begin(), indexFieldTypeMap.end(), [&jsonObject](auto const& pair) {
 | 
			
		||||
            auto const& [field, _] = pair;
 | 
			
		||||
@@ -317,6 +363,8 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
 | 
			
		||||
        input.accountRoot = boost::json::value_to<std::string>(jv.at(JS(account_root)));
 | 
			
		||||
    } else if (jsonObject.contains(JS(did))) {
 | 
			
		||||
        input.did = boost::json::value_to<std::string>(jv.at(JS(did)));
 | 
			
		||||
    } else if (jsonObject.contains(JS(mpt_issuance))) {
 | 
			
		||||
        input.mptIssuance = boost::json::value_to<std::string>(jv.at(JS(mpt_issuance)));
 | 
			
		||||
    }
 | 
			
		||||
    // no need to check if_object again, validator only allows string or object
 | 
			
		||||
    else if (jsonObject.contains(JS(directory))) {
 | 
			
		||||
@@ -348,6 +396,10 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
 | 
			
		||||
        );
 | 
			
		||||
    } else if (jsonObject.contains(JS(oracle))) {
 | 
			
		||||
        input.oracleNode = parseOracleFromJson(jv.at(JS(oracle)));
 | 
			
		||||
    } else if (jsonObject.contains(JS(credential))) {
 | 
			
		||||
        input.credential = parseCredentialFromJson(jv.at(JS(credential)));
 | 
			
		||||
    } else if (jsonObject.contains(JS(mptoken))) {
 | 
			
		||||
        input.mptoken = jv.at(JS(mptoken)).as_object();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (jsonObject.contains("include_deleted"))
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@
 | 
			
		||||
#include "rpc/common/Validators.hpp"
 | 
			
		||||
#include "util/AccountUtils.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <boost/json/conversion.hpp>
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
@@ -91,6 +92,8 @@ public:
 | 
			
		||||
        std::optional<std::string> accountRoot;
 | 
			
		||||
        // account id to address did object
 | 
			
		||||
        std::optional<std::string> did;
 | 
			
		||||
        // mpt issuance id to address mptIssuance object
 | 
			
		||||
        std::optional<std::string> mptIssuance;
 | 
			
		||||
        // TODO: extract into custom objects, remove json from Input
 | 
			
		||||
        std::optional<boost::json::object> directory;
 | 
			
		||||
        std::optional<boost::json::object> offer;
 | 
			
		||||
@@ -99,11 +102,13 @@ public:
 | 
			
		||||
        std::optional<boost::json::object> depositPreauth;
 | 
			
		||||
        std::optional<boost::json::object> ticket;
 | 
			
		||||
        std::optional<boost::json::object> amm;
 | 
			
		||||
        std::optional<boost::json::object> mptoken;
 | 
			
		||||
        std::optional<ripple::STXChainBridge> bridge;
 | 
			
		||||
        std::optional<std::string> bridgeAccount;
 | 
			
		||||
        std::optional<uint32_t> chainClaimId;
 | 
			
		||||
        std::optional<uint32_t> createAccountClaimId;
 | 
			
		||||
        std::optional<ripple::uint256> oracleNode;
 | 
			
		||||
        std::optional<ripple::uint256> credential;
 | 
			
		||||
        bool includeDeleted = false;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -194,7 +199,8 @@ public:
 | 
			
		||||
                      meta::WithCustomError{
 | 
			
		||||
                          validation::CustomValidators::AccountBase58Validator, Status(ClioError::rpcMALFORMED_OWNER)
 | 
			
		||||
                      }},
 | 
			
		||||
                     {JS(authorized), validation::Required{}, validation::CustomValidators::AccountBase58Validator},
 | 
			
		||||
                     {JS(authorized), validation::CustomValidators::AccountBase58Validator},
 | 
			
		||||
                     {JS(authorized_credentials), validation::CustomValidators::AuthorizeCredentialValidator}
 | 
			
		||||
                 },
 | 
			
		||||
             }},
 | 
			
		||||
            {JS(directory),
 | 
			
		||||
@@ -315,6 +321,59 @@ public:
 | 
			
		||||
                  },
 | 
			
		||||
                  meta::WithCustomError{modifiers::ToNumber{}, Status(ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID)}},
 | 
			
		||||
             }}},
 | 
			
		||||
            {JS(credential),
 | 
			
		||||
             meta::WithCustomError{
 | 
			
		||||
                 validation::Type<std::string, boost::json::object>{}, Status(ClioError::rpcMALFORMED_REQUEST)
 | 
			
		||||
             },
 | 
			
		||||
             meta::IfType<std::string>{
 | 
			
		||||
                 meta::WithCustomError{malformedRequestHexStringValidator, Status(ClioError::rpcMALFORMED_ADDRESS)}
 | 
			
		||||
             },
 | 
			
		||||
             meta::IfType<boost::json::object>{meta::Section{
 | 
			
		||||
                 {JS(subject),
 | 
			
		||||
                  meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
 | 
			
		||||
                  meta::WithCustomError{
 | 
			
		||||
                      validation::CustomValidators::AccountBase58Validator, Status(ClioError::rpcMALFORMED_ADDRESS)
 | 
			
		||||
                  }},
 | 
			
		||||
                 {JS(issuer),
 | 
			
		||||
                  meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
 | 
			
		||||
                  meta::WithCustomError{
 | 
			
		||||
                      validation::CustomValidators::AccountBase58Validator, Status(ClioError::rpcMALFORMED_ADDRESS)
 | 
			
		||||
                  }},
 | 
			
		||||
                 {
 | 
			
		||||
                     JS(credential_type),
 | 
			
		||||
                     meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
 | 
			
		||||
                     meta::WithCustomError{validation::Type<std::string>{}, Status(ClioError::rpcMALFORMED_REQUEST)},
 | 
			
		||||
                 },
 | 
			
		||||
             }}},
 | 
			
		||||
            {JS(mpt_issuance),
 | 
			
		||||
             meta::WithCustomError{
 | 
			
		||||
                 validation::CustomValidators::Uint192HexStringValidator, Status(ClioError::rpcMALFORMED_REQUEST)
 | 
			
		||||
             }},
 | 
			
		||||
            {JS(mptoken),
 | 
			
		||||
             meta::WithCustomError{
 | 
			
		||||
                 validation::Type<std::string, boost::json::object>{}, Status(ClioError::rpcMALFORMED_REQUEST)
 | 
			
		||||
             },
 | 
			
		||||
             meta::IfType<std::string>{malformedRequestHexStringValidator},
 | 
			
		||||
             meta::IfType<boost::json::object>{
 | 
			
		||||
                 meta::Section{
 | 
			
		||||
                     {
 | 
			
		||||
                         JS(account),
 | 
			
		||||
                         meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
 | 
			
		||||
                         meta::WithCustomError{
 | 
			
		||||
                             validation::CustomValidators::AccountBase58Validator,
 | 
			
		||||
                             Status(ClioError::rpcMALFORMED_ADDRESS)
 | 
			
		||||
                         },
 | 
			
		||||
                     },
 | 
			
		||||
                     {
 | 
			
		||||
                         JS(mpt_issuance_id),
 | 
			
		||||
                         meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
 | 
			
		||||
                         meta::WithCustomError{
 | 
			
		||||
                             validation::CustomValidators::Uint192HexStringValidator,
 | 
			
		||||
                             Status(ClioError::rpcMALFORMED_REQUEST)
 | 
			
		||||
                         },
 | 
			
		||||
                     },
 | 
			
		||||
                 },
 | 
			
		||||
             }},
 | 
			
		||||
            {JS(ledger), check::Deprecated{}},
 | 
			
		||||
            {"include_deleted", validation::Type<bool>{}},
 | 
			
		||||
        };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										139
									
								
								src/rpc/handlers/MPTHolders.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/rpc/handlers/MPTHolders.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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/MPTHolders.hpp"
 | 
			
		||||
 | 
			
		||||
#include "rpc/Errors.hpp"
 | 
			
		||||
#include "rpc/JS.hpp"
 | 
			
		||||
#include "rpc/RPCHelpers.hpp"
 | 
			
		||||
#include "rpc/common/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <boost/json/conversion.hpp>
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <ripple/basics/base_uint.h>
 | 
			
		||||
#include <ripple/basics/strHex.h>
 | 
			
		||||
#include <ripple/protocol/AccountID.h>
 | 
			
		||||
#include <ripple/protocol/Indexes.h>
 | 
			
		||||
#include <ripple/protocol/LedgerHeader.h>
 | 
			
		||||
#include <ripple/protocol/jss.h>
 | 
			
		||||
#include <xrpl/protocol/SField.h>
 | 
			
		||||
#include <xrpl/protocol/STLedgerEntry.h>
 | 
			
		||||
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
using namespace ripple;
 | 
			
		||||
 | 
			
		||||
namespace rpc {
 | 
			
		||||
 | 
			
		||||
MPTHoldersHandler::Result
 | 
			
		||||
MPTHoldersHandler::process(MPTHoldersHandler::Input input, Context const& ctx) const
 | 
			
		||||
{
 | 
			
		||||
    auto const range = sharedPtrBackend_->fetchLedgerRange();
 | 
			
		||||
    auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
 | 
			
		||||
        *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
 | 
			
		||||
    );
 | 
			
		||||
    if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
 | 
			
		||||
        return Error{*status};
 | 
			
		||||
 | 
			
		||||
    auto const lgrInfo = std::get<LedgerInfo>(lgrInfoOrStatus);
 | 
			
		||||
    auto const limit = input.limit.value_or(MPTHoldersHandler::LIMIT_DEFAULT);
 | 
			
		||||
    auto const mptID = ripple::uint192{input.mptID.c_str()};
 | 
			
		||||
 | 
			
		||||
    auto const issuanceLedgerObject =
 | 
			
		||||
        sharedPtrBackend_->fetchLedgerObject(ripple::keylet::mptIssuance(mptID).key, lgrInfo.seq, ctx.yield);
 | 
			
		||||
    if (!issuanceLedgerObject)
 | 
			
		||||
        return Error{Status{RippledError::rpcOBJECT_NOT_FOUND, "objectNotFound"}};
 | 
			
		||||
 | 
			
		||||
    std::optional<ripple::AccountID> cursor;
 | 
			
		||||
    if (input.marker)
 | 
			
		||||
        cursor = ripple::AccountID{input.marker->c_str()};
 | 
			
		||||
 | 
			
		||||
    auto const dbResponse = sharedPtrBackend_->fetchMPTHolders(mptID, limit, cursor, lgrInfo.seq, ctx.yield);
 | 
			
		||||
    auto output = MPTHoldersHandler::Output{};
 | 
			
		||||
    output.mptID = to_string(mptID);
 | 
			
		||||
    output.limit = limit;
 | 
			
		||||
    output.ledgerIndex = lgrInfo.seq;
 | 
			
		||||
 | 
			
		||||
    boost::json::array const mpts;
 | 
			
		||||
    for (auto const& mpt : dbResponse.mptokens) {
 | 
			
		||||
        ripple::STLedgerEntry const sle{ripple::SerialIter{mpt.data(), mpt.size()}, keylet::mptIssuance(mptID).key};
 | 
			
		||||
        boost::json::object mptJson;
 | 
			
		||||
 | 
			
		||||
        mptJson[JS(account)] = toBase58(sle[ripple::sfAccount]);
 | 
			
		||||
        mptJson[JS(flags)] = sle.getFlags();
 | 
			
		||||
        mptJson["mpt_amount"] =
 | 
			
		||||
            toBoostJson(ripple::STUInt64{ripple::sfMPTAmount, sle[ripple::sfMPTAmount]}.getJson(JsonOptions::none));
 | 
			
		||||
        mptJson["mptoken_index"] = ripple::to_string(ripple::keylet::mptoken(mptID, sle[ripple::sfAccount]).key);
 | 
			
		||||
 | 
			
		||||
        output.mpts.push_back(mptJson);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dbResponse.cursor.has_value())
 | 
			
		||||
        output.marker = strHex(*dbResponse.cursor);
 | 
			
		||||
 | 
			
		||||
    return output;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, MPTHoldersHandler::Output const& output)
 | 
			
		||||
{
 | 
			
		||||
    jv = {
 | 
			
		||||
        {JS(mpt_issuance_id), output.mptID},
 | 
			
		||||
        {JS(limit), output.limit},
 | 
			
		||||
        {JS(ledger_index), output.ledgerIndex},
 | 
			
		||||
        {"mptokens", output.mpts},
 | 
			
		||||
        {JS(validated), output.validated},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (output.marker.has_value())
 | 
			
		||||
        jv.as_object()[JS(marker)] = *(output.marker);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MPTHoldersHandler::Input
 | 
			
		||||
tag_invoke(boost::json::value_to_tag<MPTHoldersHandler::Input>, boost::json::value const& jv)
 | 
			
		||||
{
 | 
			
		||||
    auto const& jsonObject = jv.as_object();
 | 
			
		||||
    MPTHoldersHandler::Input input;
 | 
			
		||||
 | 
			
		||||
    input.mptID = jsonObject.at(JS(mpt_issuance_id)).as_string().c_str();
 | 
			
		||||
 | 
			
		||||
    if (jsonObject.contains(JS(ledger_hash)))
 | 
			
		||||
        input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str();
 | 
			
		||||
 | 
			
		||||
    if (jsonObject.contains(JS(ledger_index))) {
 | 
			
		||||
        if (!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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (jsonObject.contains(JS(limit)))
 | 
			
		||||
        input.limit = jsonObject.at(JS(limit)).as_int64();
 | 
			
		||||
 | 
			
		||||
    if (jsonObject.contains(JS(marker)))
 | 
			
		||||
        input.marker = jsonObject.at(JS(marker)).as_string().c_str();
 | 
			
		||||
 | 
			
		||||
    return input;
 | 
			
		||||
}
 | 
			
		||||
}  // namespace rpc
 | 
			
		||||
							
								
								
									
										128
									
								
								src/rpc/handlers/MPTHolders.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/rpc/handlers/MPTHolders.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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/JS.hpp"
 | 
			
		||||
#include "rpc/common/Modifiers.hpp"
 | 
			
		||||
#include "rpc/common/Specs.hpp"
 | 
			
		||||
#include "rpc/common/Types.hpp"
 | 
			
		||||
#include "rpc/common/Validators.hpp"
 | 
			
		||||
 | 
			
		||||
namespace rpc {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The mpt_holders command asks the Clio server for all holders of a particular MPTokenIssuance.
 | 
			
		||||
 */
 | 
			
		||||
class MPTHoldersHandler {
 | 
			
		||||
    std::shared_ptr<BackendInterface> sharedPtrBackend_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    static auto constexpr LIMIT_MIN = 1;
 | 
			
		||||
    static auto constexpr LIMIT_MAX = 100;
 | 
			
		||||
    static auto constexpr LIMIT_DEFAULT = 50;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief A struct to hold the output data of the command
 | 
			
		||||
     */
 | 
			
		||||
    struct Output {
 | 
			
		||||
        boost::json::array mpts;
 | 
			
		||||
        uint32_t ledgerIndex;
 | 
			
		||||
        std::string mptID;
 | 
			
		||||
        bool validated = true;
 | 
			
		||||
        uint32_t limit;
 | 
			
		||||
        std::optional<std::string> marker;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief A struct to hold the input data for the command
 | 
			
		||||
     */
 | 
			
		||||
    struct Input {
 | 
			
		||||
        std::string mptID;
 | 
			
		||||
        std::optional<std::string> ledgerHash;
 | 
			
		||||
        std::optional<uint32_t> ledgerIndex;
 | 
			
		||||
        std::optional<std::string> marker;
 | 
			
		||||
        std::optional<uint32_t> limit;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    using Result = HandlerReturnType<Output>;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Construct a new MPTHoldersHandler object
 | 
			
		||||
     *
 | 
			
		||||
     * @param sharedPtrBackend The backend to use
 | 
			
		||||
     */
 | 
			
		||||
    MPTHoldersHandler(std::shared_ptr<BackendInterface> const& sharedPtrBackend) : sharedPtrBackend_(sharedPtrBackend)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @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 rpcSpec = RpcSpec{
 | 
			
		||||
            {JS(mpt_issuance_id), validation::Required{}, validation::CustomValidators::Uint192HexStringValidator},
 | 
			
		||||
            {JS(ledger_hash), validation::CustomValidators::Uint256HexStringValidator},
 | 
			
		||||
            {JS(ledger_index), validation::CustomValidators::LedgerIndexValidator},
 | 
			
		||||
            {JS(limit),
 | 
			
		||||
             validation::Type<uint32_t>{},
 | 
			
		||||
             validation::Min(1u),
 | 
			
		||||
             modifiers::Clamp<int32_t>{LIMIT_MIN, LIMIT_MAX}},
 | 
			
		||||
            {JS(marker), validation::CustomValidators::Uint160HexStringValidator},
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return rpcSpec;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Process the MPTHolders 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 [out] 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<Input>, boost::json::value const& jv);
 | 
			
		||||
};
 | 
			
		||||
}  // namespace rpc
 | 
			
		||||
@@ -19,6 +19,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <xrpl/basics/StringUtilities.h>
 | 
			
		||||
#include <xrpl/protocol/AccountID.h>
 | 
			
		||||
#include <xrpl/protocol/tokens.h>
 | 
			
		||||
 | 
			
		||||
#include <cctype>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ target_sources(
 | 
			
		||||
  clio_util
 | 
			
		||||
  PRIVATE build/Build.cpp
 | 
			
		||||
          config/Config.cpp
 | 
			
		||||
          CoroutineGroup.cpp
 | 
			
		||||
          log/Logger.cpp
 | 
			
		||||
          prometheus/Http.cpp
 | 
			
		||||
          prometheus/Label.cpp
 | 
			
		||||
@@ -19,15 +20,19 @@ target_sources(
 | 
			
		||||
          requests/Types.cpp
 | 
			
		||||
          requests/WsConnection.cpp
 | 
			
		||||
          requests/impl/SslContext.cpp
 | 
			
		||||
          ResponseExpirationCache.cpp
 | 
			
		||||
          SignalsHandler.cpp
 | 
			
		||||
          Taggable.cpp
 | 
			
		||||
          TerminationHandler.cpp
 | 
			
		||||
          TimeUtils.cpp
 | 
			
		||||
          TxUtils.cpp
 | 
			
		||||
          LedgerUtils.cpp
 | 
			
		||||
          newconfig/ConfigDefinition.cpp
 | 
			
		||||
          newconfig/ObjectView.cpp
 | 
			
		||||
          newconfig/Array.cpp
 | 
			
		||||
          newconfig/ArrayView.cpp
 | 
			
		||||
          newconfig/ConfigConstraints.cpp
 | 
			
		||||
          newconfig/ConfigDefinition.cpp
 | 
			
		||||
          newconfig/ConfigFileJson.cpp
 | 
			
		||||
          newconfig/ObjectView.cpp
 | 
			
		||||
          newconfig/ValueView.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
namespace util {
 | 
			
		||||
@@ -29,4 +31,19 @@ namespace util {
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeNumberType = std::is_arithmetic_v<T> && !std::is_same_v<T, bool> && !std::is_const_v<T>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Checks that the list of given values contains no duplicates
 | 
			
		||||
 *
 | 
			
		||||
 * @param values The list of values to check
 | 
			
		||||
 * @returns true if no duplicates exist; false otherwise
 | 
			
		||||
 */
 | 
			
		||||
static consteval auto
 | 
			
		||||
hasNoDuplicates(auto&&... values)
 | 
			
		||||
{
 | 
			
		||||
    auto store = std::array{values...};
 | 
			
		||||
    auto end = store.end();
 | 
			
		||||
    std::ranges::sort(store);
 | 
			
		||||
    return (std::unique(std::begin(store), end) == end);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										82
									
								
								src/util/CoroutineGroup.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/util/CoroutineGroup.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "util/CoroutineGroup.hpp"
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/spawn.hpp>
 | 
			
		||||
#include <boost/asio/steady_timer.hpp>
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace util {
 | 
			
		||||
 | 
			
		||||
CoroutineGroup::CoroutineGroup(boost::asio::yield_context yield, std::optional<int> maxChildren)
 | 
			
		||||
    : timer_{yield.get_executor(), boost::asio::steady_timer::duration::max()}, maxChildren_{maxChildren}
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CoroutineGroup::~CoroutineGroup()
 | 
			
		||||
{
 | 
			
		||||
    ASSERT(childrenCounter_ == 0, "CoroutineGroup is destroyed without waiting for child coroutines to finish");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
CoroutineGroup::canSpawn() const
 | 
			
		||||
{
 | 
			
		||||
    return not maxChildren_.has_value() or childrenCounter_ < *maxChildren_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
CoroutineGroup::spawn(boost::asio::yield_context yield, std::function<void(boost::asio::yield_context)> fn)
 | 
			
		||||
{
 | 
			
		||||
    if (not canSpawn())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    ++childrenCounter_;
 | 
			
		||||
    boost::asio::spawn(yield, [this, fn = std::move(fn)](boost::asio::yield_context yield) {
 | 
			
		||||
        fn(yield);
 | 
			
		||||
        --childrenCounter_;
 | 
			
		||||
        if (childrenCounter_ == 0)
 | 
			
		||||
            timer_.cancel();
 | 
			
		||||
    });
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
CoroutineGroup::asyncWait(boost::asio::yield_context yield)
 | 
			
		||||
{
 | 
			
		||||
    if (childrenCounter_ == 0)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    boost::system::error_code error;
 | 
			
		||||
    timer_.async_wait(yield[error]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t
 | 
			
		||||
CoroutineGroup::size() const
 | 
			
		||||
{
 | 
			
		||||
    return childrenCounter_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util
 | 
			
		||||
							
								
								
									
										96
									
								
								src/util/CoroutineGroup.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/util/CoroutineGroup.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 <boost/asio/spawn.hpp>
 | 
			
		||||
#include <boost/asio/steady_timer.hpp>
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <optional>
 | 
			
		||||
 | 
			
		||||
namespace util {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief CoroutineGroup is a helper class to manage a group of coroutines. It allows to spawn multiple coroutines and
 | 
			
		||||
 * wait for all of them to finish.
 | 
			
		||||
 */
 | 
			
		||||
class CoroutineGroup {
 | 
			
		||||
    boost::asio::steady_timer timer_;
 | 
			
		||||
    std::optional<int> maxChildren_;
 | 
			
		||||
    int childrenCounter_{0};
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Construct a new Coroutine Group object
 | 
			
		||||
     *
 | 
			
		||||
     * @param yield The yield context to use for the internal timer
 | 
			
		||||
     * @param maxChildren The maximum number of coroutines that can be spawned at the same time. If not provided, there
 | 
			
		||||
     * is no limit
 | 
			
		||||
     */
 | 
			
		||||
    CoroutineGroup(boost::asio::yield_context yield, std::optional<int> maxChildren = std::nullopt);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Destroy the Coroutine Group object
 | 
			
		||||
     *
 | 
			
		||||
     * @note asyncWait() must be called before the object is destroyed
 | 
			
		||||
     */
 | 
			
		||||
    ~CoroutineGroup();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if a new coroutine can be spawned (i.e. there is space for a new coroutine in the group)
 | 
			
		||||
     *
 | 
			
		||||
     * @return true If a new coroutine can be spawned. false if the maximum number of coroutines has been reached
 | 
			
		||||
     */
 | 
			
		||||
    bool
 | 
			
		||||
    canSpawn() const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Spawn a new coroutine in the group
 | 
			
		||||
     *
 | 
			
		||||
     * @param yield The yield context to use for the coroutine (it should be the same as the one used in the
 | 
			
		||||
     * constructor)
 | 
			
		||||
     * @param fn The function to execute
 | 
			
		||||
     * @return true If the coroutine was spawned successfully. false if the maximum number of coroutines has been
 | 
			
		||||
     * reached
 | 
			
		||||
     */
 | 
			
		||||
    bool
 | 
			
		||||
    spawn(boost::asio::yield_context yield, std::function<void(boost::asio::yield_context)> fn);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Wait for all the coroutines in the group to finish
 | 
			
		||||
     *
 | 
			
		||||
     * @note This method must be called before the object is destroyed
 | 
			
		||||
     *
 | 
			
		||||
     * @param yield The yield context to use for the internal timer
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    asyncWait(boost::asio::yield_context yield);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Get the number of coroutines in the group
 | 
			
		||||
     *
 | 
			
		||||
     * @return size_t The number of coroutines in the group
 | 
			
		||||
     */
 | 
			
		||||
    size_t
 | 
			
		||||
    size() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util
 | 
			
		||||
@@ -112,7 +112,10 @@ class LedgerTypes {
 | 
			
		||||
        ),
 | 
			
		||||
        LedgerTypeAttribute::AccountOwnedLedgerType(JS(did), ripple::ltDID),
 | 
			
		||||
        LedgerTypeAttribute::AccountOwnedLedgerType(JS(oracle), ripple::ltORACLE),
 | 
			
		||||
        LedgerTypeAttribute::AccountOwnedLedgerType(JS(credential), ripple::ltCREDENTIAL),
 | 
			
		||||
        LedgerTypeAttribute::ChainLedgerType(JS(nunl), ripple::ltNEGATIVE_UNL),
 | 
			
		||||
        LedgerTypeAttribute::DeletionBlockerLedgerType(JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE),
 | 
			
		||||
        LedgerTypeAttribute::DeletionBlockerLedgerType(JS(mptoken), ripple::ltMPTOKEN),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 
 | 
			
		||||
@@ -17,88 +17,56 @@
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "etl/impl/ForwardingCache.hpp"
 | 
			
		||||
#include "util/ResponseExpirationCache.hpp"
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/value_to.hpp>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <shared_mutex>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace etl::impl {
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
std::optional<std::string>
 | 
			
		||||
getCommand(boost::json::object const& request)
 | 
			
		||||
{
 | 
			
		||||
    if (not request.contains("command")) {
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
    return boost::json::value_to<std::string>(request.at("command"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace
 | 
			
		||||
namespace util {
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
CacheEntry::put(boost::json::object response)
 | 
			
		||||
ResponseExpirationCache::Entry::put(boost::json::object response)
 | 
			
		||||
{
 | 
			
		||||
    response_ = std::move(response);
 | 
			
		||||
    lastUpdated_ = std::chrono::steady_clock::now();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<boost::json::object>
 | 
			
		||||
CacheEntry::get() const
 | 
			
		||||
ResponseExpirationCache::Entry::get() const
 | 
			
		||||
{
 | 
			
		||||
    return response_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::chrono::steady_clock::time_point
 | 
			
		||||
CacheEntry::lastUpdated() const
 | 
			
		||||
ResponseExpirationCache::Entry::lastUpdated() const
 | 
			
		||||
{
 | 
			
		||||
    return lastUpdated_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
CacheEntry::invalidate()
 | 
			
		||||
ResponseExpirationCache::Entry::invalidate()
 | 
			
		||||
{
 | 
			
		||||
    response_.reset();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::unordered_set<std::string> const
 | 
			
		||||
    ForwardingCache::CACHEABLE_COMMANDS{"server_info", "server_state", "server_definitions", "fee", "ledger_closed"};
 | 
			
		||||
 | 
			
		||||
ForwardingCache::ForwardingCache(std::chrono::steady_clock::duration const cacheTimeout) : cacheTimeout_{cacheTimeout}
 | 
			
		||||
{
 | 
			
		||||
    for (auto const& command : CACHEABLE_COMMANDS) {
 | 
			
		||||
        cache_.emplace(command, CacheEntry{});
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
ForwardingCache::shouldCache(boost::json::object const& request)
 | 
			
		||||
ResponseExpirationCache::shouldCache(std::string const& cmd)
 | 
			
		||||
{
 | 
			
		||||
    auto const command = getCommand(request);
 | 
			
		||||
    return command.has_value() and CACHEABLE_COMMANDS.contains(*command);
 | 
			
		||||
    return cache_.contains(cmd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<boost::json::object>
 | 
			
		||||
ForwardingCache::get(boost::json::object const& request) const
 | 
			
		||||
ResponseExpirationCache::get(std::string const& cmd) const
 | 
			
		||||
{
 | 
			
		||||
    auto const command = getCommand(request);
 | 
			
		||||
 | 
			
		||||
    if (not command.has_value()) {
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto it = cache_.find(*command);
 | 
			
		||||
    auto it = cache_.find(cmd);
 | 
			
		||||
    if (it == cache_.end())
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
 | 
			
		||||
@@ -110,20 +78,19 @@ ForwardingCache::get(boost::json::object const& request) const
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
ForwardingCache::put(boost::json::object const& request, boost::json::object const& response)
 | 
			
		||||
ResponseExpirationCache::put(std::string const& cmd, boost::json::object const& response)
 | 
			
		||||
{
 | 
			
		||||
    auto const command = getCommand(request);
 | 
			
		||||
    if (not command.has_value() or not shouldCache(request))
 | 
			
		||||
    if (not shouldCache(cmd))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    ASSERT(cache_.contains(*command), "Command is not in the cache: {}", *command);
 | 
			
		||||
    ASSERT(cache_.contains(cmd), "Command is not in the cache: {}", cmd);
 | 
			
		||||
 | 
			
		||||
    auto entry = cache_[*command].lock<std::unique_lock>();
 | 
			
		||||
    auto entry = cache_[cmd].lock<std::unique_lock>();
 | 
			
		||||
    entry->put(response);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
ForwardingCache::invalidate()
 | 
			
		||||
ResponseExpirationCache::invalidate()
 | 
			
		||||
{
 | 
			
		||||
    for (auto& [_, entry] : cache_) {
 | 
			
		||||
        auto entryLock = entry.lock<std::unique_lock>();
 | 
			
		||||
@@ -131,4 +98,4 @@ ForwardingCache::invalidate()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace etl::impl
 | 
			
		||||
}  // namespace util
 | 
			
		||||
@@ -30,90 +30,92 @@
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
namespace etl::impl {
 | 
			
		||||
namespace util {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A class to store a cache entry.
 | 
			
		||||
 * @brief Cache of requests' responses with TTL support and configurable cachable commands
 | 
			
		||||
 */
 | 
			
		||||
class CacheEntry {
 | 
			
		||||
    std::chrono::steady_clock::time_point lastUpdated_;
 | 
			
		||||
    std::optional<boost::json::object> response_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
class ResponseExpirationCache {
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Put a response into the cache
 | 
			
		||||
     *
 | 
			
		||||
     * @param response The response to store
 | 
			
		||||
     * @brief A class to store a cache entry.
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    put(boost::json::object response);
 | 
			
		||||
    class Entry {
 | 
			
		||||
        std::chrono::steady_clock::time_point lastUpdated_;
 | 
			
		||||
        std::optional<boost::json::object> response_;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Get the response from the cache
 | 
			
		||||
     *
 | 
			
		||||
     * @return The response
 | 
			
		||||
     */
 | 
			
		||||
    std::optional<boost::json::object>
 | 
			
		||||
    get() const;
 | 
			
		||||
    public:
 | 
			
		||||
        /**
 | 
			
		||||
         * @brief Put a response into the cache
 | 
			
		||||
         *
 | 
			
		||||
         * @param response The response to store
 | 
			
		||||
         */
 | 
			
		||||
        void
 | 
			
		||||
        put(boost::json::object response);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Get the last time the cache was updated
 | 
			
		||||
     *
 | 
			
		||||
     * @return The last time the cache was updated
 | 
			
		||||
     */
 | 
			
		||||
    std::chrono::steady_clock::time_point
 | 
			
		||||
    lastUpdated() const;
 | 
			
		||||
        /**
 | 
			
		||||
         * @brief Get the response from the cache
 | 
			
		||||
         *
 | 
			
		||||
         * @return The response
 | 
			
		||||
         */
 | 
			
		||||
        std::optional<boost::json::object>
 | 
			
		||||
        get() const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Invalidate the cache entry
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    invalidate();
 | 
			
		||||
};
 | 
			
		||||
        /**
 | 
			
		||||
         * @brief Get the last time the cache was updated
 | 
			
		||||
         *
 | 
			
		||||
         * @return The last time the cache was updated
 | 
			
		||||
         */
 | 
			
		||||
        std::chrono::steady_clock::time_point
 | 
			
		||||
        lastUpdated() const;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @brief Invalidate the cache entry
 | 
			
		||||
         */
 | 
			
		||||
        void
 | 
			
		||||
        invalidate();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A class to store a cache of forwarding responses
 | 
			
		||||
 */
 | 
			
		||||
class ForwardingCache {
 | 
			
		||||
    std::chrono::steady_clock::duration cacheTimeout_;
 | 
			
		||||
    std::unordered_map<std::string, util::Mutex<CacheEntry, std::shared_mutex>> cache_;
 | 
			
		||||
    std::unordered_map<std::string, util::Mutex<Entry, std::shared_mutex>> cache_;
 | 
			
		||||
 | 
			
		||||
    bool
 | 
			
		||||
    shouldCache(std::string const& cmd);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    static std::unordered_set<std::string> const CACHEABLE_COMMANDS;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Construct a new Forwarding Cache object
 | 
			
		||||
     * @brief Construct a new Cache object
 | 
			
		||||
     *
 | 
			
		||||
     * @param cacheTimeout The time for cache entries to expire
 | 
			
		||||
     * @param cmds The commands that should be cached
 | 
			
		||||
     */
 | 
			
		||||
    ForwardingCache(std::chrono::steady_clock::duration cacheTimeout);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if a request should be cached
 | 
			
		||||
     *
 | 
			
		||||
     * @param request The request to check
 | 
			
		||||
     * @return true if the request should be cached and false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] static bool
 | 
			
		||||
    shouldCache(boost::json::object const& request);
 | 
			
		||||
    ResponseExpirationCache(
 | 
			
		||||
        std::chrono::steady_clock::duration cacheTimeout,
 | 
			
		||||
        std::unordered_set<std::string> const& cmds
 | 
			
		||||
    )
 | 
			
		||||
        : cacheTimeout_(cacheTimeout)
 | 
			
		||||
    {
 | 
			
		||||
        for (auto const& command : cmds) {
 | 
			
		||||
            cache_.emplace(command, Entry{});
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Get a response from the cache
 | 
			
		||||
     *
 | 
			
		||||
     * @param request The request to get the response for
 | 
			
		||||
     * @param cmd The command to get the response for
 | 
			
		||||
     * @return The response if it exists or std::nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<boost::json::object>
 | 
			
		||||
    get(boost::json::object const& request) const;
 | 
			
		||||
    get(std::string const& cmd) const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Put a response into the cache if the request should be cached
 | 
			
		||||
     *
 | 
			
		||||
     * @param request The request to store the response for
 | 
			
		||||
     * @param cmd The command to store the response for
 | 
			
		||||
     * @param response The response to store
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    put(boost::json::object const& request, boost::json::object const& response);
 | 
			
		||||
    put(std::string const& cmd, boost::json::object const& response);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Invalidate all entries in the cache
 | 
			
		||||
@@ -121,5 +123,4 @@ public:
 | 
			
		||||
    void
 | 
			
		||||
    invalidate();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace etl::impl
 | 
			
		||||
}  // namespace util
 | 
			
		||||
@@ -57,10 +57,16 @@ Retry::Retry(RetryStrategyPtr strategy, boost::asio::strand<boost::asio::io_cont
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Retry::~Retry()
 | 
			
		||||
{
 | 
			
		||||
    *canceled_ = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Retry::cancel()
 | 
			
		||||
{
 | 
			
		||||
    timer_.cancel();
 | 
			
		||||
    *canceled_ = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@
 | 
			
		||||
#include <boost/asio/steady_timer.hpp>
 | 
			
		||||
#include <boost/asio/strand.hpp>
 | 
			
		||||
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <memory>
 | 
			
		||||
@@ -80,6 +81,7 @@ class Retry {
 | 
			
		||||
    RetryStrategyPtr strategy_;
 | 
			
		||||
    boost::asio::steady_timer timer_;
 | 
			
		||||
    size_t attemptNumber_ = 0;
 | 
			
		||||
    std::shared_ptr<std::atomic_bool> canceled_{std::make_shared<std::atomic_bool>(false)};
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
@@ -90,6 +92,11 @@ public:
 | 
			
		||||
     */
 | 
			
		||||
    Retry(RetryStrategyPtr strategy, boost::asio::strand<boost::asio::io_context::executor_type> strand);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Destroy the Retry object
 | 
			
		||||
     */
 | 
			
		||||
    ~Retry();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Schedule a retry
 | 
			
		||||
     *
 | 
			
		||||
@@ -100,15 +107,18 @@ public:
 | 
			
		||||
    void
 | 
			
		||||
    retry(Fn&& func)
 | 
			
		||||
    {
 | 
			
		||||
        *canceled_ = false;
 | 
			
		||||
        timer_.expires_after(strategy_->getDelay());
 | 
			
		||||
        strategy_->increaseDelay();
 | 
			
		||||
        timer_.async_wait([this, func = std::forward<Fn>(func)](boost::system::error_code const& ec) {
 | 
			
		||||
            if (ec == boost::asio::error::operation_aborted) {
 | 
			
		||||
                return;
 | 
			
		||||
        timer_.async_wait(
 | 
			
		||||
            [this, canceled = canceled_, func = std::forward<Fn>(func)](boost::system::error_code const& ec) {
 | 
			
		||||
                if (ec == boost::asio::error::operation_aborted or *canceled) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                ++attemptNumber_;
 | 
			
		||||
                func();
 | 
			
		||||
            }
 | 
			
		||||
            ++attemptNumber_;
 | 
			
		||||
            func();
 | 
			
		||||
        });
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										71
									
								
								src/util/WithTimeout.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/util/WithTimeout.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 <boost/asio/associated_executor.hpp>
 | 
			
		||||
#include <boost/asio/bind_cancellation_slot.hpp>
 | 
			
		||||
#include <boost/asio/cancellation_signal.hpp>
 | 
			
		||||
#include <boost/asio/cancellation_type.hpp>
 | 
			
		||||
#include <boost/asio/spawn.hpp>
 | 
			
		||||
#include <boost/asio/steady_timer.hpp>
 | 
			
		||||
#include <boost/system/detail/error_code.hpp>
 | 
			
		||||
#include <boost/system/errc.hpp>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <ctime>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace util {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Perform a coroutine operation with a timeout.
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam Operation The operation type to perform. Must be a callable accepting yield context with bound cancellation
 | 
			
		||||
 * token.
 | 
			
		||||
 * @param operation The operation to perform.
 | 
			
		||||
 * @param yield The yield context.
 | 
			
		||||
 * @param timeout The timeout duration.
 | 
			
		||||
 * @return The error code of the operation.
 | 
			
		||||
 */
 | 
			
		||||
template <typename Operation>
 | 
			
		||||
boost::system::error_code
 | 
			
		||||
withTimeout(Operation&& operation, boost::asio::yield_context yield, std::chrono::steady_clock::duration timeout)
 | 
			
		||||
{
 | 
			
		||||
    boost::system::error_code error;
 | 
			
		||||
    auto operationCompleted = std::make_shared<bool>(false);
 | 
			
		||||
    boost::asio::cancellation_signal cancellationSignal;
 | 
			
		||||
    auto cyield = boost::asio::bind_cancellation_slot(cancellationSignal.slot(), yield[error]);
 | 
			
		||||
 | 
			
		||||
    boost::asio::steady_timer timer{boost::asio::get_associated_executor(cyield), timeout};
 | 
			
		||||
    timer.async_wait([&cancellationSignal, operationCompleted](boost::system::error_code errorCode) {
 | 
			
		||||
        if (!errorCode and !*operationCompleted)
 | 
			
		||||
            cancellationSignal.emit(boost::asio::cancellation_type::terminal);
 | 
			
		||||
    });
 | 
			
		||||
    operation(cyield);
 | 
			
		||||
    *operationCompleted = true;
 | 
			
		||||
 | 
			
		||||
    // Map error code to timeout
 | 
			
		||||
    if (error == boost::system::errc::operation_canceled) {
 | 
			
		||||
        return boost::system::errc::make_error_code(boost::system::errc::timed_out);
 | 
			
		||||
    }
 | 
			
		||||
    return error;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util
 | 
			
		||||
@@ -369,7 +369,7 @@ public:
 | 
			
		||||
     * @brief Block until all operations are completed
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    join() noexcept
 | 
			
		||||
    join() const noexcept
 | 
			
		||||
    {
 | 
			
		||||
        context_.join();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										84
									
								
								src/util/newconfig/Array.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/util/newconfig/Array.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "util/newconfig/Array.hpp"
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigValue.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
Array::Array(ConfigValue arg) : itemPattern_{std::move(arg)}
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<Error>
 | 
			
		||||
Array::addValue(Value value, std::optional<std::string_view> key)
 | 
			
		||||
{
 | 
			
		||||
    auto const& configValPattern = itemPattern_;
 | 
			
		||||
    auto const constraint = configValPattern.getConstraint();
 | 
			
		||||
 | 
			
		||||
    auto newElem = constraint.has_value() ? ConfigValue{configValPattern.type()}.withConstraint(constraint->get())
 | 
			
		||||
                                          : ConfigValue{configValPattern.type()};
 | 
			
		||||
    if (auto const maybeError = newElem.setValue(value, key); maybeError.has_value())
 | 
			
		||||
        return maybeError;
 | 
			
		||||
    elements_.emplace_back(std::move(newElem));
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t
 | 
			
		||||
Array::size() const
 | 
			
		||||
{
 | 
			
		||||
    return elements_.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ConfigValue const&
 | 
			
		||||
Array::at(std::size_t idx) const
 | 
			
		||||
{
 | 
			
		||||
    ASSERT(idx < elements_.size(), "Index is out of scope");
 | 
			
		||||
    return elements_[idx];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ConfigValue const&
 | 
			
		||||
Array::getArrayPattern() const
 | 
			
		||||
{
 | 
			
		||||
    return itemPattern_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ConfigValue>::const_iterator
 | 
			
		||||
Array::begin() const
 | 
			
		||||
{
 | 
			
		||||
    return elements_.begin();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ConfigValue>::const_iterator
 | 
			
		||||
Array::end() const
 | 
			
		||||
{
 | 
			
		||||
    return elements_.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
@@ -19,47 +19,42 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigValue.hpp"
 | 
			
		||||
#include "util/newconfig/ObjectView.hpp"
 | 
			
		||||
#include "util/newconfig/ValueView.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Array definition for Json/Yaml config
 | 
			
		||||
 * @brief Array definition to store multiple values provided by the user from Json/Yaml
 | 
			
		||||
 *
 | 
			
		||||
 * Used in ClioConfigDefinition to represent multiple potential values (like whitelist)
 | 
			
		||||
 * Is constructed with only 1 element which states which type/constraint must every element
 | 
			
		||||
 * In the array satisfy
 | 
			
		||||
 */
 | 
			
		||||
class Array {
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Constructs an Array with the provided arguments
 | 
			
		||||
     * @brief Constructs an Array with provided Arg
 | 
			
		||||
     *
 | 
			
		||||
     * @tparam Args Types of the arguments
 | 
			
		||||
     * @param args Arguments to initialize the elements of the Array
 | 
			
		||||
     * @param arg Argument to set the type and constraint of ConfigValues in Array
 | 
			
		||||
     */
 | 
			
		||||
    template <typename... Args>
 | 
			
		||||
    constexpr Array(Args&&... args) : elements_{std::forward<Args>(args)...}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
    Array(ConfigValue arg);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Add ConfigValues to Array class
 | 
			
		||||
     *
 | 
			
		||||
     * @param value The ConfigValue to add
 | 
			
		||||
     * @param key optional string key to include that will show in error message
 | 
			
		||||
     * @return optional error if adding config value to array fails. nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    emplaceBack(ConfigValue value)
 | 
			
		||||
    {
 | 
			
		||||
        elements_.push_back(std::move(value));
 | 
			
		||||
    }
 | 
			
		||||
    std::optional<Error>
 | 
			
		||||
    addValue(Value value, std::optional<std::string_view> key = std::nullopt);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Returns the number of values stored in the Array
 | 
			
		||||
@@ -67,10 +62,7 @@ public:
 | 
			
		||||
     * @return Number of values stored in the Array
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] size_t
 | 
			
		||||
    size() const
 | 
			
		||||
    {
 | 
			
		||||
        return elements_.size();
 | 
			
		||||
    }
 | 
			
		||||
    size() const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Returns the ConfigValue at the specified index
 | 
			
		||||
@@ -79,13 +71,35 @@ public:
 | 
			
		||||
     * @return ConfigValue at the specified index
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] ConfigValue const&
 | 
			
		||||
    at(std::size_t idx) const
 | 
			
		||||
    {
 | 
			
		||||
        ASSERT(idx < elements_.size(), "index is out of scope");
 | 
			
		||||
        return elements_[idx];
 | 
			
		||||
    }
 | 
			
		||||
    at(std::size_t idx) const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Returns the ConfigValue that defines the type/constraint every
 | 
			
		||||
     * ConfigValue must follow in Array
 | 
			
		||||
     *
 | 
			
		||||
     * @return The item_pattern
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] ConfigValue const&
 | 
			
		||||
    getArrayPattern() const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Returns an iterator to the beginning of the ConfigValue vector.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A constant iterator to the beginning of the vector.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::vector<ConfigValue>::const_iterator
 | 
			
		||||
    begin() const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Returns an iterator to the end of the ConfigValue vector.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A constant iterator to the end of the vector.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::vector<ConfigValue>::const_iterator
 | 
			
		||||
    end() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    ConfigValue itemPattern_;
 | 
			
		||||
    std::vector<ConfigValue> elements_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										103
									
								
								src/util/newconfig/ConfigConstraints.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/util/newconfig/ConfigConstraints.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "util/newconfig/ConfigConstraints.hpp"
 | 
			
		||||
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <regex>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
std::optional<Error>
 | 
			
		||||
PortConstraint::checkTypeImpl(Value const& port) const
 | 
			
		||||
{
 | 
			
		||||
    if (!(std::holds_alternative<int64_t>(port) || std::holds_alternative<std::string>(port)))
 | 
			
		||||
        return Error{"Port must be a string or integer"};
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<Error>
 | 
			
		||||
PortConstraint::checkValueImpl(Value const& port) const
 | 
			
		||||
{
 | 
			
		||||
    uint32_t p = 0;
 | 
			
		||||
    if (std::holds_alternative<std::string>(port)) {
 | 
			
		||||
        try {
 | 
			
		||||
            p = static_cast<uint32_t>(std::stoi(std::get<std::string>(port)));
 | 
			
		||||
        } catch (std::invalid_argument const& e) {
 | 
			
		||||
            return Error{"Port string must be an integer."};
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        p = static_cast<uint32_t>(std::get<int64_t>(port));
 | 
			
		||||
    }
 | 
			
		||||
    if (p >= portMin && p <= portMax)
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    return Error{"Port does not satisfy the constraint bounds"};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<Error>
 | 
			
		||||
ValidIPConstraint::checkTypeImpl(Value const& ip) const
 | 
			
		||||
{
 | 
			
		||||
    if (!std::holds_alternative<std::string>(ip))
 | 
			
		||||
        return Error{"Ip value must be a string"};
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<Error>
 | 
			
		||||
ValidIPConstraint::checkValueImpl(Value const& ip) const
 | 
			
		||||
{
 | 
			
		||||
    if (std::get<std::string>(ip) == "localhost")
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
 | 
			
		||||
    static std::regex const ipv4(
 | 
			
		||||
        R"(^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$)"
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    static std::regex const ip_url(
 | 
			
		||||
        R"(^((http|https):\/\/)?((([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6})|(((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])))(:\d{1,5})?(\/[^\s]*)?$)"
 | 
			
		||||
    );
 | 
			
		||||
    if (std::regex_match(std::get<std::string>(ip), ipv4) || std::regex_match(std::get<std::string>(ip), ip_url))
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
 | 
			
		||||
    return Error{"Ip is not a valid ip address"};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<Error>
 | 
			
		||||
PositiveDouble::checkTypeImpl(Value const& num) const
 | 
			
		||||
{
 | 
			
		||||
    if (!(std::holds_alternative<double>(num) || std::holds_alternative<int64_t>(num)))
 | 
			
		||||
        return Error{"Double number must be of type int or double"};
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<Error>
 | 
			
		||||
PositiveDouble::checkValueImpl(Value const& num) const
 | 
			
		||||
{
 | 
			
		||||
    if (std::get<double>(num) >= 0)
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    return Error{"Double number must be greater than 0"};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
							
								
								
									
										362
									
								
								src/util/newconfig/ConfigConstraints.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										362
									
								
								src/util/newconfig/ConfigConstraints.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,362 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
   This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
   Copyright (c) 2024, 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 "rpc/common/APIVersion.hpp"
 | 
			
		||||
#include "util/log/Logger.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <fmt/format.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
class ValueView;
 | 
			
		||||
class ConfigValue;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief specific values that are accepted for logger levels in config.
 | 
			
		||||
 */
 | 
			
		||||
static constexpr std::array<char const*, 7> LOG_LEVELS = {
 | 
			
		||||
    "trace",
 | 
			
		||||
    "debug",
 | 
			
		||||
    "info",
 | 
			
		||||
    "warning",
 | 
			
		||||
    "error",
 | 
			
		||||
    "fatal",
 | 
			
		||||
    "count",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief specific values that are accepted for logger tag style in config.
 | 
			
		||||
 */
 | 
			
		||||
static constexpr std::array<char const*, 5> LOG_TAGS = {
 | 
			
		||||
    "int",
 | 
			
		||||
    "uint",
 | 
			
		||||
    "null",
 | 
			
		||||
    "none",
 | 
			
		||||
    "uuid",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief specific values that are accepted for cache loading in config.
 | 
			
		||||
 */
 | 
			
		||||
static constexpr std::array<char const*, 3> LOAD_CACHE_MODE = {
 | 
			
		||||
    "sync",
 | 
			
		||||
    "async",
 | 
			
		||||
    "none",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief specific values that are accepted for database type in config.
 | 
			
		||||
 */
 | 
			
		||||
static constexpr std::array<char const*, 1> DATABASE_TYPE = {"cassandra"};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief An interface to enforce constraints on certain values within ClioConfigDefinition.
 | 
			
		||||
 */
 | 
			
		||||
class Constraint {
 | 
			
		||||
public:
 | 
			
		||||
    constexpr virtual ~Constraint() noexcept = default;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the value meets the specific constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param val The value to be checked
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]]
 | 
			
		||||
    std::optional<Error>
 | 
			
		||||
    checkConstraint(Value const& val) const
 | 
			
		||||
    {
 | 
			
		||||
        if (auto const maybeError = checkTypeImpl(val); maybeError.has_value())
 | 
			
		||||
            return maybeError;
 | 
			
		||||
        return checkValueImpl(val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Creates an error message for all constraints that must satisfy certain hard-coded values.
 | 
			
		||||
     *
 | 
			
		||||
     * @tparam arrSize, the size of the array of hardcoded values
 | 
			
		||||
     * @param key The key to the value
 | 
			
		||||
     * @param value The value the user provided
 | 
			
		||||
     * @param arr The array with hard-coded values to add to error message
 | 
			
		||||
     * @return The error message specifying what the value of key must be
 | 
			
		||||
     */
 | 
			
		||||
    template <std::size_t arrSize>
 | 
			
		||||
    constexpr std::string
 | 
			
		||||
    makeErrorMsg(std::string_view key, Value const& value, std::array<char const*, arrSize> arr) const
 | 
			
		||||
    {
 | 
			
		||||
        // Extract the value from the variant
 | 
			
		||||
        auto const valueStr = std::visit([](auto const& v) { return fmt::format("{}", v); }, value);
 | 
			
		||||
 | 
			
		||||
        // Create the error message
 | 
			
		||||
        return fmt::format(
 | 
			
		||||
            R"(You provided value "{}". Key "{}"'s value must be one of the following: {})",
 | 
			
		||||
            valueStr,
 | 
			
		||||
            key,
 | 
			
		||||
            fmt::join(arr, ", ")
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the value is of a correct type for the constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param val The value type to be checked
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    virtual std::optional<Error>
 | 
			
		||||
    checkTypeImpl(Value const& val) const = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the value is within the constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param val The value type to be checked
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    virtual std::optional<Error>
 | 
			
		||||
    checkValueImpl(Value const& val) const = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A constraint to ensure the port number is within a valid range.
 | 
			
		||||
 */
 | 
			
		||||
class PortConstraint final : public Constraint {
 | 
			
		||||
public:
 | 
			
		||||
    constexpr ~PortConstraint() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the type of the value is correct for this specific constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param port The type to be checked
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkTypeImpl(Value const& port) const override;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the value is within the constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param port The value to be checked
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkValueImpl(Value const& port) const override;
 | 
			
		||||
 | 
			
		||||
    static constexpr uint32_t portMin = 1;
 | 
			
		||||
    static constexpr uint32_t portMax = 65535;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A constraint to ensure the IP address is valid.
 | 
			
		||||
 */
 | 
			
		||||
class ValidIPConstraint final : public Constraint {
 | 
			
		||||
public:
 | 
			
		||||
    constexpr ~ValidIPConstraint() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the type of the value is correct for this specific constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ip The type to be checked.
 | 
			
		||||
     * @return An optional Error if the constraint is not met, std::nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkTypeImpl(Value const& ip) const override;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the value is within the constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ip The value to be checked
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkValueImpl(Value const& ip) const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A constraint class to ensure the provided value is one of the specified values in an array.
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam arrSize The size of the array containing the valid values for the constraint
 | 
			
		||||
 */
 | 
			
		||||
template <std::size_t arrSize>
 | 
			
		||||
class OneOf final : public Constraint {
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Constructs a constraint where the value must be one of the values in the provided array.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key The key of the ConfigValue that has this constraint
 | 
			
		||||
     * @param arr The value that has this constraint must be of the values in arr
 | 
			
		||||
     */
 | 
			
		||||
    constexpr OneOf(std::string_view key, std::array<char const*, arrSize> arr) : key_{key}, arr_{arr}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constexpr ~OneOf() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the type of the value is correct for this specific constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param val The type to be checked
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkTypeImpl(Value const& val) const override
 | 
			
		||||
    {
 | 
			
		||||
        if (!std::holds_alternative<std::string>(val))
 | 
			
		||||
            return Error{fmt::format(R"(Key "{}"'s value must be a string)", key_)};
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the value matches one of the value in the provided array
 | 
			
		||||
     *
 | 
			
		||||
     * @param val The value to check
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkValueImpl(Value const& val) const override
 | 
			
		||||
    {
 | 
			
		||||
        namespace rg = std::ranges;
 | 
			
		||||
        auto const check = [&val](std::string_view name) { return std::get<std::string>(val) == name; };
 | 
			
		||||
        if (rg::any_of(arr_, check))
 | 
			
		||||
            return std::nullopt;
 | 
			
		||||
 | 
			
		||||
        return Error{makeErrorMsg(key_, val, arr_)};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string_view key_;
 | 
			
		||||
    std::array<char const*, arrSize> arr_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A constraint class to ensure an integer value is between two numbers (inclusive)
 | 
			
		||||
 */
 | 
			
		||||
template <typename numType>
 | 
			
		||||
class NumberValueConstraint final : public Constraint {
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Constructs a constraint where the number must be between min_ and max_.
 | 
			
		||||
     *
 | 
			
		||||
     * @param min the minimum number it can be to satisfy this constraint
 | 
			
		||||
     * @param max the maximum number it can be to satisfy this constraint
 | 
			
		||||
     */
 | 
			
		||||
    constexpr NumberValueConstraint(numType min, numType max) : min_{min}, max_{max}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constexpr ~NumberValueConstraint() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the type of the value is correct for this specific constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param num The type to be checked
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkTypeImpl(Value const& num) const override
 | 
			
		||||
    {
 | 
			
		||||
        if (!std::holds_alternative<int64_t>(num))
 | 
			
		||||
            return Error{"Number must be of type integer"};
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the number is positive.
 | 
			
		||||
     *
 | 
			
		||||
     * @param num The number to check
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkValueImpl(Value const& num) const override
 | 
			
		||||
    {
 | 
			
		||||
        auto const numValue = std::get<int64_t>(num);
 | 
			
		||||
        if (numValue >= static_cast<int64_t>(min_) && numValue <= static_cast<int64_t>(max_))
 | 
			
		||||
            return std::nullopt;
 | 
			
		||||
        return Error{fmt::format("Number must be between {} and {}", min_, max_)};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    numType min_;
 | 
			
		||||
    numType max_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A constraint to ensure a double number is positive
 | 
			
		||||
 */
 | 
			
		||||
class PositiveDouble final : public Constraint {
 | 
			
		||||
public:
 | 
			
		||||
    constexpr ~PositiveDouble() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the type of the value is correct for this specific constraint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param num The type to be checked
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkTypeImpl(Value const& num) const override;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Check if the number is positive.
 | 
			
		||||
     *
 | 
			
		||||
     * @param num The number to check
 | 
			
		||||
     * @return An Error object if the constraint is not met, nullopt otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    checkValueImpl(Value const& num) const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static constinit PortConstraint validatePort{};
 | 
			
		||||
static constinit ValidIPConstraint validateIP{};
 | 
			
		||||
 | 
			
		||||
static constinit OneOf validateChannelName{"channel", Logger::CHANNELS};
 | 
			
		||||
static constinit OneOf validateLogLevelName{"log_level", LOG_LEVELS};
 | 
			
		||||
static constinit OneOf validateCassandraName{"database.type", DATABASE_TYPE};
 | 
			
		||||
static constinit OneOf validateLoadMode{"cache.load", LOAD_CACHE_MODE};
 | 
			
		||||
static constinit OneOf validateLogTag{"log_tag_style", LOG_TAGS};
 | 
			
		||||
 | 
			
		||||
static constinit PositiveDouble validatePositiveDouble{};
 | 
			
		||||
 | 
			
		||||
static constinit NumberValueConstraint<uint16_t> validateUint16{
 | 
			
		||||
    std::numeric_limits<uint16_t>::min(),
 | 
			
		||||
    std::numeric_limits<uint16_t>::max()
 | 
			
		||||
};
 | 
			
		||||
static constinit NumberValueConstraint<uint32_t> validateUint32{
 | 
			
		||||
    std::numeric_limits<uint32_t>::min(),
 | 
			
		||||
    std::numeric_limits<uint32_t>::max()
 | 
			
		||||
};
 | 
			
		||||
static constinit NumberValueConstraint<uint32_t> validateApiVersion{rpc::API_VERSION_MIN, rpc::API_VERSION_MAX};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
@@ -20,10 +20,15 @@
 | 
			
		||||
#include "util/newconfig/ConfigDefinition.hpp"
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/OverloadSet.hpp"
 | 
			
		||||
#include "util/newconfig/Array.hpp"
 | 
			
		||||
#include "util/newconfig/ArrayView.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigConstraints.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigFileInterface.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigValue.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
#include "util/newconfig/ObjectView.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
#include "util/newconfig/ValueView.hpp"
 | 
			
		||||
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
@@ -38,6 +43,7 @@
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <variant>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
/**
 | 
			
		||||
@@ -47,62 +53,76 @@ namespace util::config {
 | 
			
		||||
 * without default values must be present in the user's config file.
 | 
			
		||||
 */
 | 
			
		||||
static ClioConfigDefinition ClioConfig = ClioConfigDefinition{
 | 
			
		||||
    {{"database.type", ConfigValue{ConfigType::String}.defaultValue("cassandra")},
 | 
			
		||||
    {{"database.type", ConfigValue{ConfigType::String}.defaultValue("cassandra").withConstraint(validateCassandraName)},
 | 
			
		||||
     {"database.cassandra.contact_points", ConfigValue{ConfigType::String}.defaultValue("localhost")},
 | 
			
		||||
     {"database.cassandra.port", ConfigValue{ConfigType::Integer}},
 | 
			
		||||
     {"database.cassandra.port", ConfigValue{ConfigType::Integer}.withConstraint(validatePort)},
 | 
			
		||||
     {"database.cassandra.keyspace", ConfigValue{ConfigType::String}.defaultValue("clio")},
 | 
			
		||||
     {"database.cassandra.replication_factor", ConfigValue{ConfigType::Integer}.defaultValue(3u)},
 | 
			
		||||
     {"database.cassandra.table_prefix", ConfigValue{ConfigType::String}.defaultValue("table_prefix")},
 | 
			
		||||
     {"database.cassandra.max_write_requests_outstanding", ConfigValue{ConfigType::Integer}.defaultValue(10'000)},
 | 
			
		||||
     {"database.cassandra.max_read_requests_outstanding", ConfigValue{ConfigType::Integer}.defaultValue(100'000)},
 | 
			
		||||
     {"database.cassandra.max_write_requests_outstanding",
 | 
			
		||||
      ConfigValue{ConfigType::Integer}.defaultValue(10'000).withConstraint(validateUint32)},
 | 
			
		||||
     {"database.cassandra.max_read_requests_outstanding",
 | 
			
		||||
      ConfigValue{ConfigType::Integer}.defaultValue(100'000).withConstraint(validateUint32)},
 | 
			
		||||
     {"database.cassandra.threads",
 | 
			
		||||
      ConfigValue{ConfigType::Integer}.defaultValue(static_cast<uint32_t>(std::thread::hardware_concurrency()))},
 | 
			
		||||
     {"database.cassandra.core_connections_per_host", ConfigValue{ConfigType::Integer}.defaultValue(1)},
 | 
			
		||||
     {"database.cassandra.queue_size_io", ConfigValue{ConfigType::Integer}.optional()},
 | 
			
		||||
     {"database.cassandra.write_batch_size", ConfigValue{ConfigType::Integer}.defaultValue(20)},
 | 
			
		||||
     {"etl_source.[].ip", Array{ConfigValue{ConfigType::String}.optional()}},
 | 
			
		||||
     {"etl_source.[].ws_port", Array{ConfigValue{ConfigType::String}.optional().min(1).max(65535)}},
 | 
			
		||||
     {"etl_source.[].grpc_port", Array{ConfigValue{ConfigType::String}.optional().min(1).max(65535)}},
 | 
			
		||||
     {"forwarding.cache_timeout", ConfigValue{ConfigType::Double}.defaultValue(0.0)},
 | 
			
		||||
     {"forwarding.request_timeout", ConfigValue{ConfigType::Double}.defaultValue(10.0)},
 | 
			
		||||
      ConfigValue{ConfigType::Integer}
 | 
			
		||||
          .defaultValue(static_cast<uint32_t>(std::thread::hardware_concurrency()))
 | 
			
		||||
          .withConstraint(validateUint32)},
 | 
			
		||||
     {"database.cassandra.core_connections_per_host",
 | 
			
		||||
      ConfigValue{ConfigType::Integer}.defaultValue(1).withConstraint(validateUint16)},
 | 
			
		||||
     {"database.cassandra.queue_size_io", ConfigValue{ConfigType::Integer}.optional().withConstraint(validateUint16)},
 | 
			
		||||
     {"database.cassandra.write_batch_size",
 | 
			
		||||
      ConfigValue{ConfigType::Integer}.defaultValue(20).withConstraint(validateUint16)},
 | 
			
		||||
     {"etl_source.[].ip", Array{ConfigValue{ConfigType::String}.withConstraint(validateIP)}},
 | 
			
		||||
     {"etl_source.[].ws_port", Array{ConfigValue{ConfigType::String}.withConstraint(validatePort)}},
 | 
			
		||||
     {"etl_source.[].grpc_port", Array{ConfigValue{ConfigType::String}.withConstraint(validatePort)}},
 | 
			
		||||
     {"forwarding.cache_timeout",
 | 
			
		||||
      ConfigValue{ConfigType::Double}.defaultValue(0.0).withConstraint(validatePositiveDouble)},
 | 
			
		||||
     {"forwarding.request_timeout",
 | 
			
		||||
      ConfigValue{ConfigType::Double}.defaultValue(10.0).withConstraint(validatePositiveDouble)},
 | 
			
		||||
     {"dos_guard.whitelist.[]", Array{ConfigValue{ConfigType::String}}},
 | 
			
		||||
     {"dos_guard.max_fetches", ConfigValue{ConfigType::Integer}.defaultValue(1000'000)},
 | 
			
		||||
     {"dos_guard.max_connections", ConfigValue{ConfigType::Integer}.defaultValue(20)},
 | 
			
		||||
     {"dos_guard.max_requests", ConfigValue{ConfigType::Integer}.defaultValue(20)},
 | 
			
		||||
     {"dos_guard.sweep_interval", ConfigValue{ConfigType::Double}.defaultValue(1.0)},
 | 
			
		||||
     {"cache.peers.[].ip", Array{ConfigValue{ConfigType::String}}},
 | 
			
		||||
     {"cache.peers.[].port", Array{ConfigValue{ConfigType::String}}},
 | 
			
		||||
     {"server.ip", ConfigValue{ConfigType::String}},
 | 
			
		||||
     {"server.port", ConfigValue{ConfigType::Integer}},
 | 
			
		||||
     {"server.max_queue_size", ConfigValue{ConfigType::Integer}.defaultValue(0)},
 | 
			
		||||
     {"dos_guard.max_fetches", ConfigValue{ConfigType::Integer}.defaultValue(1000'000).withConstraint(validateUint32)},
 | 
			
		||||
     {"dos_guard.max_connections", ConfigValue{ConfigType::Integer}.defaultValue(20).withConstraint(validateUint32)},
 | 
			
		||||
     {"dos_guard.max_requests", ConfigValue{ConfigType::Integer}.defaultValue(20).withConstraint(validateUint32)},
 | 
			
		||||
     {"dos_guard.sweep_interval",
 | 
			
		||||
      ConfigValue{ConfigType::Double}.defaultValue(1.0).withConstraint(validatePositiveDouble)},
 | 
			
		||||
     {"cache.peers.[].ip", Array{ConfigValue{ConfigType::String}.withConstraint(validateIP)}},
 | 
			
		||||
     {"cache.peers.[].port", Array{ConfigValue{ConfigType::String}.withConstraint(validatePort)}},
 | 
			
		||||
     {"server.ip", ConfigValue{ConfigType::String}.withConstraint(validateIP)},
 | 
			
		||||
     {"server.port", ConfigValue{ConfigType::Integer}.withConstraint(validatePort)},
 | 
			
		||||
     {"server.workers", ConfigValue{ConfigType::Integer}.withConstraint(validateUint32)},
 | 
			
		||||
     {"server.max_queue_size", ConfigValue{ConfigType::Integer}.defaultValue(0).withConstraint(validateUint32)},
 | 
			
		||||
     {"server.local_admin", ConfigValue{ConfigType::Boolean}.optional()},
 | 
			
		||||
     {"server.admin_password", ConfigValue{ConfigType::String}.optional()},
 | 
			
		||||
     {"prometheus.enabled", ConfigValue{ConfigType::Boolean}.defaultValue(true)},
 | 
			
		||||
     {"prometheus.compress_reply", ConfigValue{ConfigType::Boolean}.defaultValue(true)},
 | 
			
		||||
     {"io_threads", ConfigValue{ConfigType::Integer}.defaultValue(2)},
 | 
			
		||||
     {"cache.num_diffs", ConfigValue{ConfigType::Integer}.defaultValue(32)},
 | 
			
		||||
     {"cache.num_markers", ConfigValue{ConfigType::Integer}.defaultValue(48)},
 | 
			
		||||
     {"cache.num_cursors_from_diff", ConfigValue{ConfigType::Integer}.defaultValue(0)},
 | 
			
		||||
     {"cache.num_cursors_from_account", ConfigValue{ConfigType::Integer}.defaultValue(0)},
 | 
			
		||||
     {"cache.page_fetch_size", ConfigValue{ConfigType::Integer}.defaultValue(512)},
 | 
			
		||||
     {"cache.load", ConfigValue{ConfigType::String}.defaultValue("async")},
 | 
			
		||||
     {"log_channels.[].channel", Array{ConfigValue{ConfigType::String}.optional()}},
 | 
			
		||||
     {"log_channels.[].log_level", Array{ConfigValue{ConfigType::String}.optional()}},
 | 
			
		||||
     {"log_level", ConfigValue{ConfigType::String}.defaultValue("info")},
 | 
			
		||||
     {"io_threads", ConfigValue{ConfigType::Integer}.defaultValue(2).withConstraint(validateUint16)},
 | 
			
		||||
     {"cache.num_diffs", ConfigValue{ConfigType::Integer}.defaultValue(32).withConstraint(validateUint16)},
 | 
			
		||||
     {"cache.num_markers", ConfigValue{ConfigType::Integer}.defaultValue(48).withConstraint(validateUint16)},
 | 
			
		||||
     {"cache.num_cursors_from_diff", ConfigValue{ConfigType::Integer}.defaultValue(0).withConstraint(validateUint16)},
 | 
			
		||||
     {"cache.num_cursors_from_account", ConfigValue{ConfigType::Integer}.defaultValue(0).withConstraint(validateUint16)
 | 
			
		||||
     },
 | 
			
		||||
     {"cache.page_fetch_size", ConfigValue{ConfigType::Integer}.defaultValue(512).withConstraint(validateUint16)},
 | 
			
		||||
     {"cache.load", ConfigValue{ConfigType::String}.defaultValue("async").withConstraint(validateLoadMode)},
 | 
			
		||||
     {"log_channels.[].channel", Array{ConfigValue{ConfigType::String}.optional().withConstraint(validateChannelName)}},
 | 
			
		||||
     {"log_channels.[].log_level",
 | 
			
		||||
      Array{ConfigValue{ConfigType::String}.optional().withConstraint(validateLogLevelName)}},
 | 
			
		||||
     {"log_level", ConfigValue{ConfigType::String}.defaultValue("info").withConstraint(validateLogLevelName)},
 | 
			
		||||
     {"log_format",
 | 
			
		||||
      ConfigValue{ConfigType::String}.defaultValue(
 | 
			
		||||
          R"(%TimeStamp% (%SourceLocation%) [%ThreadID%] %Channel%:%Severity% %Message%)"
 | 
			
		||||
      )},
 | 
			
		||||
     {"log_to_console", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
 | 
			
		||||
     {"log_directory", ConfigValue{ConfigType::String}.optional()},
 | 
			
		||||
     {"log_rotation_size", ConfigValue{ConfigType::Integer}.defaultValue(2048)},
 | 
			
		||||
     {"log_directory_max_size", ConfigValue{ConfigType::Integer}.defaultValue(50 * 1024)},
 | 
			
		||||
     {"log_rotation_hour_interval", ConfigValue{ConfigType::Integer}.defaultValue(12)},
 | 
			
		||||
     {"log_tag_style", ConfigValue{ConfigType::String}.defaultValue("uint")},
 | 
			
		||||
     {"extractor_threads", ConfigValue{ConfigType::Integer}.defaultValue(2u)},
 | 
			
		||||
     {"log_rotation_size", ConfigValue{ConfigType::Integer}.defaultValue(2048u).withConstraint(validateUint32)},
 | 
			
		||||
     {"log_directory_max_size",
 | 
			
		||||
      ConfigValue{ConfigType::Integer}.defaultValue(50u * 1024u).withConstraint(validateUint32)},
 | 
			
		||||
     {"log_rotation_hour_interval", ConfigValue{ConfigType::Integer}.defaultValue(12).withConstraint(validateUint32)},
 | 
			
		||||
     {"log_tag_style", ConfigValue{ConfigType::String}.defaultValue("uint").withConstraint(validateLogTag)},
 | 
			
		||||
     {"extractor_threads", ConfigValue{ConfigType::Integer}.defaultValue(2u).withConstraint(validateUint32)},
 | 
			
		||||
     {"read_only", ConfigValue{ConfigType::Boolean}.defaultValue(false)},
 | 
			
		||||
     {"txn_threshold", ConfigValue{ConfigType::Integer}.defaultValue(0)},
 | 
			
		||||
     {"start_sequence", ConfigValue{ConfigType::String}.optional()},
 | 
			
		||||
     {"finish_sequence", ConfigValue{ConfigType::String}.optional()},
 | 
			
		||||
     {"txn_threshold", ConfigValue{ConfigType::Integer}.defaultValue(0).withConstraint(validateUint16)},
 | 
			
		||||
     {"start_sequence", ConfigValue{ConfigType::Integer}.optional().withConstraint(validateUint32)},
 | 
			
		||||
     {"finish_sequence", ConfigValue{ConfigType::Integer}.optional().withConstraint(validateUint32)},
 | 
			
		||||
     {"ssl_cert_file", ConfigValue{ConfigType::String}.optional()},
 | 
			
		||||
     {"ssl_key_file", ConfigValue{ConfigType::String}.optional()},
 | 
			
		||||
     {"api_version.min", ConfigValue{ConfigType::Integer}},
 | 
			
		||||
@@ -113,7 +133,7 @@ ClioConfigDefinition::ClioConfigDefinition(std::initializer_list<KeyValuePair> p
 | 
			
		||||
{
 | 
			
		||||
    for (auto const& [key, value] : pair) {
 | 
			
		||||
        if (key.contains("[]"))
 | 
			
		||||
            ASSERT(std::holds_alternative<Array>(value), "Value must be array if key has \"[]\"");
 | 
			
		||||
            ASSERT(std::holds_alternative<Array>(value), R"(Value must be array if key has "[]")");
 | 
			
		||||
        map_.insert({key, value});
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -206,4 +226,51 @@ ClioConfigDefinition::arraySize(std::string_view prefix) const
 | 
			
		||||
    std::unreachable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<std::vector<Error>>
 | 
			
		||||
ClioConfigDefinition::parse(ConfigFileInterface const& config)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<Error> listOfErrors;
 | 
			
		||||
    for (auto& [key, value] : map_) {
 | 
			
		||||
        // if key doesn't exist in user config, makes sure it is marked as ".optional()" or has ".defaultValue()"" in
 | 
			
		||||
        // ClioConfigDefitinion above
 | 
			
		||||
        if (!config.containsKey(key)) {
 | 
			
		||||
            if (std::holds_alternative<ConfigValue>(value)) {
 | 
			
		||||
                if (!(std::get<ConfigValue>(value).isOptional() || std::get<ConfigValue>(value).hasValue()))
 | 
			
		||||
                    listOfErrors.emplace_back(key, "key is required in user Config");
 | 
			
		||||
            } else if (std::holds_alternative<Array>(value)) {
 | 
			
		||||
                if (!(std::get<Array>(value).getArrayPattern().isOptional()))
 | 
			
		||||
                    listOfErrors.emplace_back(key, "key is required in user Config");
 | 
			
		||||
            }
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        ASSERT(
 | 
			
		||||
            std::holds_alternative<ConfigValue>(value) || std::holds_alternative<Array>(value),
 | 
			
		||||
            "Value must be of type ConfigValue or Array"
 | 
			
		||||
        );
 | 
			
		||||
        std::visit(
 | 
			
		||||
            util::OverloadSet{// handle the case where the config value is a single element.
 | 
			
		||||
                              // attempt to set the value from the configuration for the specified key.
 | 
			
		||||
                              [&key, &config, &listOfErrors](ConfigValue& val) {
 | 
			
		||||
                                  if (auto const maybeError = val.setValue(config.getValue(key), key);
 | 
			
		||||
                                      maybeError.has_value())
 | 
			
		||||
                                      listOfErrors.emplace_back(maybeError.value());
 | 
			
		||||
                              },
 | 
			
		||||
                              // handle the case where the config value is an array.
 | 
			
		||||
                              // iterate over each provided value in the array and attempt to set it for the key.
 | 
			
		||||
                              [&key, &config, &listOfErrors](Array& arr) {
 | 
			
		||||
                                  for (auto const& val : config.getArray(key)) {
 | 
			
		||||
                                      if (auto const maybeError = arr.addValue(val, key); maybeError.has_value())
 | 
			
		||||
                                          listOfErrors.emplace_back(maybeError.value());
 | 
			
		||||
                                  }
 | 
			
		||||
                              }
 | 
			
		||||
            },
 | 
			
		||||
            value
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    if (!listOfErrors.empty())
 | 
			
		||||
        return listOfErrors;
 | 
			
		||||
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@
 | 
			
		||||
#include "util/newconfig/ConfigDescription.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigFileInterface.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigValue.hpp"
 | 
			
		||||
#include "util/newconfig/Errors.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
#include "util/newconfig/ObjectView.hpp"
 | 
			
		||||
#include "util/newconfig/ValueView.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +41,7 @@
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <variant>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
@@ -66,12 +67,13 @@ public:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Parses the configuration file
 | 
			
		||||
     *
 | 
			
		||||
     * Should also check that no extra configuration key/value pairs are present
 | 
			
		||||
     * Also checks that no extra configuration key/value pairs are present. Adds to list of Errors
 | 
			
		||||
     * if it does
 | 
			
		||||
     *
 | 
			
		||||
     * @param config The configuration file interface
 | 
			
		||||
     * @return An optional Error object if parsing fails
 | 
			
		||||
     * @return An optional vector of Error objects stating all the failures if parsing fails
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    [[nodiscard]] std::optional<std::vector<Error>>
 | 
			
		||||
    parse(ConfigFileInterface const& config);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -80,9 +82,9 @@ public:
 | 
			
		||||
     * Should only check for valid values, without populating
 | 
			
		||||
     *
 | 
			
		||||
     * @param config The configuration file interface
 | 
			
		||||
     * @return An optional Error object if validation fails
 | 
			
		||||
     * @return An optional vector of Error objects stating all the failures if validation fails
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    [[nodiscard]] std::optional<std::vector<Error>>
 | 
			
		||||
    validate(ConfigFileInterface const& config) const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,9 @@ private:
 | 
			
		||||
        KV{"server.ip", "IP address of the Clio HTTP server."},
 | 
			
		||||
        KV{"server.port", "Port number of the Clio HTTP server."},
 | 
			
		||||
        KV{"server.max_queue_size", "Maximum size of the server's request queue."},
 | 
			
		||||
        KV{"server.workers", "Maximum number of threads for server to run with."},
 | 
			
		||||
        KV{"server.local_admin", "Indicates if the server should run with admin privileges."},
 | 
			
		||||
        KV{"server.admin_password", "Password for Clio admin-only APIs."},
 | 
			
		||||
        KV{"prometheus.enabled", "Enable or disable Prometheus metrics."},
 | 
			
		||||
        KV{"prometheus.compress_reply", "Enable or disable compression of Prometheus responses."},
 | 
			
		||||
        KV{"io_threads", "Number of I/O threads."},
 | 
			
		||||
 
 | 
			
		||||
@@ -19,9 +19,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/newconfig/ConfigValue.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
@@ -36,31 +35,33 @@ namespace util::config {
 | 
			
		||||
class ConfigFileInterface {
 | 
			
		||||
public:
 | 
			
		||||
    virtual ~ConfigFileInterface() = default;
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Parses the provided path of user clio configuration data
 | 
			
		||||
     *
 | 
			
		||||
     * @param filePath The path to the Clio Config data
 | 
			
		||||
     */
 | 
			
		||||
    virtual void
 | 
			
		||||
    parse(std::string_view filePath) = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Retrieves a configuration value.
 | 
			
		||||
     * @brief Retrieves the value of configValue.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key The key of the configuration value.
 | 
			
		||||
     * @return An optional containing the configuration value if found, otherwise std::nullopt.
 | 
			
		||||
     * @param key The key of configuration.
 | 
			
		||||
     * @return the value assosiated with key.
 | 
			
		||||
     */
 | 
			
		||||
    virtual std::optional<ConfigValue>
 | 
			
		||||
    virtual Value
 | 
			
		||||
    getValue(std::string_view key) const = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Retrieves an array of configuration values.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key The key of the configuration array.
 | 
			
		||||
     * @return An optional containing a vector of configuration values if found, otherwise std::nullopt.
 | 
			
		||||
     * @return A vector of configuration values if found, otherwise std::nullopt.
 | 
			
		||||
     */
 | 
			
		||||
    virtual std::optional<std::vector<ConfigValue>>
 | 
			
		||||
    virtual std::vector<Value>
 | 
			
		||||
    getArray(std::string_view key) const = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Checks if key exist in configuration file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key The key to search for.
 | 
			
		||||
     * @return true if key exists in configuration file, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    virtual bool
 | 
			
		||||
    containsKey(std::string_view key) const = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										166
									
								
								src/util/newconfig/ConfigFileJson.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								src/util/newconfig/ConfigFileJson.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "util/newconfig/ConfigFileJson.hpp"
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/filesystem/path.hpp>
 | 
			
		||||
#include <boost/json/array.hpp>
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
#include <boost/json/parse.hpp>
 | 
			
		||||
#include <boost/json/parse_options.hpp>
 | 
			
		||||
#include <boost/json/value.hpp>
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <exception>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <ios>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <ostream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Extracts the value from a JSON object and converts it into the corresponding type.
 | 
			
		||||
 *
 | 
			
		||||
 * @param jsonValue The JSON value to extract.
 | 
			
		||||
 * @return A variant containing the same type corresponding to the extracted value.
 | 
			
		||||
 */
 | 
			
		||||
[[nodiscard]] Value
 | 
			
		||||
extractJsonValue(boost::json::value const& jsonValue)
 | 
			
		||||
{
 | 
			
		||||
    if (jsonValue.is_int64()) {
 | 
			
		||||
        return jsonValue.as_int64();
 | 
			
		||||
    }
 | 
			
		||||
    if (jsonValue.is_string()) {
 | 
			
		||||
        return jsonValue.as_string().c_str();
 | 
			
		||||
    }
 | 
			
		||||
    if (jsonValue.is_bool()) {
 | 
			
		||||
        return jsonValue.as_bool();
 | 
			
		||||
    }
 | 
			
		||||
    if (jsonValue.is_double()) {
 | 
			
		||||
        return jsonValue.as_double();
 | 
			
		||||
    }
 | 
			
		||||
    ASSERT(false, "Json is not of type int, string, bool or double");
 | 
			
		||||
    std::unreachable();
 | 
			
		||||
}
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
ConfigFileJson::ConfigFileJson(boost::json::object jsonObj)
 | 
			
		||||
{
 | 
			
		||||
    flattenJson(jsonObj, "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::expected<ConfigFileJson, Error>
 | 
			
		||||
ConfigFileJson::make_ConfigFileJson(boost::filesystem::path configFilePath)
 | 
			
		||||
{
 | 
			
		||||
    try {
 | 
			
		||||
        if (auto const in = std::ifstream(configFilePath.string(), std::ios::in | std::ios::binary); in) {
 | 
			
		||||
            std::stringstream contents;
 | 
			
		||||
            contents << in.rdbuf();
 | 
			
		||||
            auto opts = boost::json::parse_options{};
 | 
			
		||||
            opts.allow_comments = true;
 | 
			
		||||
            auto const tempObj = boost::json::parse(contents.str(), {}, opts).as_object();
 | 
			
		||||
            return ConfigFileJson{tempObj};
 | 
			
		||||
        }
 | 
			
		||||
        return std::unexpected<Error>(
 | 
			
		||||
            Error{fmt::format("Could not open configuration file '{}'", configFilePath.string())}
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    } catch (std::exception const& e) {
 | 
			
		||||
        return std::unexpected<Error>(Error{fmt::format(
 | 
			
		||||
            "An error occurred while processing configuration file '{}': {}", configFilePath.string(), e.what()
 | 
			
		||||
        )});
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Value
 | 
			
		||||
ConfigFileJson::getValue(std::string_view key) const
 | 
			
		||||
{
 | 
			
		||||
    auto const jsonValue = jsonObject_.at(key);
 | 
			
		||||
    auto const value = extractJsonValue(jsonValue);
 | 
			
		||||
    return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<Value>
 | 
			
		||||
ConfigFileJson::getArray(std::string_view key) const
 | 
			
		||||
{
 | 
			
		||||
    ASSERT(jsonObject_.at(key).is_array(), "Key {} has value that is not an array", key);
 | 
			
		||||
 | 
			
		||||
    std::vector<Value> configValues;
 | 
			
		||||
    auto const arr = jsonObject_.at(key).as_array();
 | 
			
		||||
 | 
			
		||||
    for (auto const& item : arr) {
 | 
			
		||||
        auto const value = extractJsonValue(item);
 | 
			
		||||
        configValues.emplace_back(value);
 | 
			
		||||
    }
 | 
			
		||||
    return configValues;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool
 | 
			
		||||
ConfigFileJson::containsKey(std::string_view key) const
 | 
			
		||||
{
 | 
			
		||||
    return jsonObject_.contains(key);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
ConfigFileJson::flattenJson(boost::json::object const& obj, std::string const& prefix)
 | 
			
		||||
{
 | 
			
		||||
    for (auto const& [key, value] : obj) {
 | 
			
		||||
        std::string const fullKey = prefix.empty() ? std::string(key) : fmt::format("{}.{}", prefix, std::string(key));
 | 
			
		||||
 | 
			
		||||
        // In ClioConfigDefinition, value must be a primitive or array
 | 
			
		||||
        if (value.is_object()) {
 | 
			
		||||
            flattenJson(value.as_object(), fullKey);
 | 
			
		||||
        } else if (value.is_array()) {
 | 
			
		||||
            auto const& arr = value.as_array();
 | 
			
		||||
            for (std::size_t i = 0; i < arr.size(); ++i) {
 | 
			
		||||
                std::string const arrayPrefix = fullKey + ".[]";
 | 
			
		||||
                if (arr[i].is_object()) {
 | 
			
		||||
                    flattenJson(arr[i].as_object(), arrayPrefix);
 | 
			
		||||
                } else {
 | 
			
		||||
                    jsonObject_[arrayPrefix] = arr;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // if "[]" is present in key, then value must be an array instead of primitive
 | 
			
		||||
            if (fullKey.contains(".[]") && !jsonObject_.contains(fullKey)) {
 | 
			
		||||
                boost::json::array newArray;
 | 
			
		||||
                newArray.emplace_back(value);
 | 
			
		||||
                jsonObject_[fullKey] = newArray;
 | 
			
		||||
            } else if (fullKey.contains(".[]") && jsonObject_.contains(fullKey)) {
 | 
			
		||||
                jsonObject_[fullKey].as_array().emplace_back(value);
 | 
			
		||||
            } else {
 | 
			
		||||
                jsonObject_[fullKey] = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
							
								
								
									
										100
									
								
								src/util/newconfig/ConfigFileJson.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/util/newconfig/ConfigFileJson.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "util/newconfig/ConfigFileInterface.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/filesystem/path.hpp>
 | 
			
		||||
#include <boost/json/object.hpp>
 | 
			
		||||
 | 
			
		||||
#include <expected>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
/** @brief Json representation of config */
 | 
			
		||||
class ConfigFileJson final : public ConfigFileInterface {
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Construct a new ConfigJson object and stores the values from
 | 
			
		||||
     * user's config into a json object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param jsonObj the Json object to parse; represents user's config
 | 
			
		||||
     */
 | 
			
		||||
    ConfigFileJson(boost::json::object jsonObj);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Retrieves a configuration value by its key.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key The key of the configuration value to retrieve.
 | 
			
		||||
     * @return A variant containing the same type corresponding to the extracted value.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] Value
 | 
			
		||||
    getValue(std::string_view key) const override;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Retrieves an array of configuration values by its key.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key The key of the configuration array to retrieve.
 | 
			
		||||
     * @return A vector of variants holding the config values specified by user.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::vector<Value>
 | 
			
		||||
    getArray(std::string_view key) const override;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Checks if the configuration contains a specific key.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key The key to check for.
 | 
			
		||||
     * @return True if the key exists, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] bool
 | 
			
		||||
    containsKey(std::string_view key) const override;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Creates a new ConfigFileJson by parsing the provided JSON file and
 | 
			
		||||
     * stores the values in the object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param configFilePath The path to the JSON file to be parsed.
 | 
			
		||||
     * @return A ConfigFileJson object if parsing user file is successful. Error otherwise
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] static std::expected<ConfigFileJson, Error>
 | 
			
		||||
    make_ConfigFileJson(boost::filesystem::path configFilePath);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Recursive function to flatten a JSON object into the same structure as the Clio Config.
 | 
			
		||||
     *
 | 
			
		||||
     * The keys will end up having the same naming convensions in Clio Config.
 | 
			
		||||
     * Other than the keys specified in user Config file, no new keys are created.
 | 
			
		||||
     *
 | 
			
		||||
     * @param obj The JSON object to flatten.
 | 
			
		||||
     * @param prefix The prefix to use for the keys in the flattened object.
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    flattenJson(boost::json::object const& obj, std::string const& prefix);
 | 
			
		||||
 | 
			
		||||
    boost::json::object jsonObject_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
							
								
								
									
										49
									
								
								src/util/newconfig/ConfigFileYaml.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/util/newconfig/ConfigFileYaml.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "util/newconfig/ConfigFileInterface.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/filesystem/path.hpp>
 | 
			
		||||
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
// TODO: implement when we support yaml
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
/** @brief Yaml representation of config */
 | 
			
		||||
class ConfigFileYaml final : public ConfigFileInterface {
 | 
			
		||||
public:
 | 
			
		||||
    ConfigFileYaml() = default;
 | 
			
		||||
 | 
			
		||||
    Value
 | 
			
		||||
    getValue(std::string_view key) const override;
 | 
			
		||||
 | 
			
		||||
    std::vector<Value>
 | 
			
		||||
    getArray(std::string_view key) const override;
 | 
			
		||||
 | 
			
		||||
    bool
 | 
			
		||||
    containsKey(std::string_view key) const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
@@ -20,43 +20,23 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/UnsupportedType.hpp"
 | 
			
		||||
#include "util/OverloadSet.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigConstraints.hpp"
 | 
			
		||||
#include "util/newconfig/Error.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
/** @brief Custom clio config types */
 | 
			
		||||
enum class ConfigType { Integer, String, Double, Boolean };
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Get the corresponding clio config type
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam Type The type to get the corresponding ConfigType for
 | 
			
		||||
 * @return The corresponding ConfigType
 | 
			
		||||
 */
 | 
			
		||||
template <typename Type>
 | 
			
		||||
constexpr ConfigType
 | 
			
		||||
getType()
 | 
			
		||||
{
 | 
			
		||||
    if constexpr (std::is_same_v<Type, int64_t>) {
 | 
			
		||||
        return ConfigType::Integer;
 | 
			
		||||
    } else if constexpr (std::is_same_v<Type, std::string>) {
 | 
			
		||||
        return ConfigType::String;
 | 
			
		||||
    } else if constexpr (std::is_same_v<Type, double>) {
 | 
			
		||||
        return ConfigType::Double;
 | 
			
		||||
    } else if constexpr (std::is_same_v<Type, bool>) {
 | 
			
		||||
        return ConfigType::Boolean;
 | 
			
		||||
    } else {
 | 
			
		||||
        static_assert(util::Unsupported<Type>, "Wrong config type");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Represents the config values for Json/Yaml config
 | 
			
		||||
 *
 | 
			
		||||
@@ -65,8 +45,6 @@ getType()
 | 
			
		||||
 */
 | 
			
		||||
class ConfigValue {
 | 
			
		||||
public:
 | 
			
		||||
    using Type = std::variant<int64_t, std::string, bool, double>;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Constructor initializing with the config type
 | 
			
		||||
     *
 | 
			
		||||
@@ -83,12 +61,92 @@ public:
 | 
			
		||||
     * @return Reference to this ConfigValue
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] ConfigValue&
 | 
			
		||||
    defaultValue(Type value)
 | 
			
		||||
    defaultValue(Value value)
 | 
			
		||||
    {
 | 
			
		||||
        setValue(value);
 | 
			
		||||
        auto const err = checkTypeConsistency(type_, value);
 | 
			
		||||
        ASSERT(!err.has_value(), "{}", err->error);
 | 
			
		||||
        value_ = value;
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Sets the value current ConfigValue given by the User's defined value
 | 
			
		||||
     *
 | 
			
		||||
     * @param value The value to set
 | 
			
		||||
     * @param key The Config key associated with the value. Optional to include; Used for debugging message to user.
 | 
			
		||||
     * @return optional Error if user tries to set a value of wrong type or not within a constraint
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<Error>
 | 
			
		||||
    setValue(Value value, std::optional<std::string_view> key = std::nullopt)
 | 
			
		||||
    {
 | 
			
		||||
        auto err = checkTypeConsistency(type_, value);
 | 
			
		||||
        if (err.has_value()) {
 | 
			
		||||
            if (key.has_value())
 | 
			
		||||
                err->error = fmt::format("{} {}", key.value(), err->error);
 | 
			
		||||
            return err;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (cons_.has_value()) {
 | 
			
		||||
            auto constraintCheck = cons_->get().checkConstraint(value);
 | 
			
		||||
            if (constraintCheck.has_value()) {
 | 
			
		||||
                if (key.has_value())
 | 
			
		||||
                    constraintCheck->error = fmt::format("{} {}", key.value(), constraintCheck->error);
 | 
			
		||||
                return constraintCheck;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        value_ = value;
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Assigns a constraint to the ConfigValue.
 | 
			
		||||
     *
 | 
			
		||||
     * This method associates a specific constraint with the ConfigValue.
 | 
			
		||||
     * If the ConfigValue already holds a value, the method will check whether
 | 
			
		||||
     * the value satisfies the given constraint. If the constraint is not satisfied,
 | 
			
		||||
     * an assertion failure will occur with a detailed error message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param cons The constraint to be applied to the ConfigValue.
 | 
			
		||||
     * @return A reference to the modified ConfigValue object.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] constexpr ConfigValue&
 | 
			
		||||
    withConstraint(Constraint const& cons)
 | 
			
		||||
    {
 | 
			
		||||
        cons_ = std::reference_wrapper<Constraint const>(cons);
 | 
			
		||||
        ASSERT(cons_.has_value(), "Constraint must be defined");
 | 
			
		||||
 | 
			
		||||
        if (value_.has_value()) {
 | 
			
		||||
            auto const& temp = cons_.value().get();
 | 
			
		||||
            auto const& result = temp.checkConstraint(value_.value());
 | 
			
		||||
            if (result.has_value()) {
 | 
			
		||||
                // useful for specifying clear Error message
 | 
			
		||||
                std::string type;
 | 
			
		||||
                std::visit(
 | 
			
		||||
                    util::OverloadSet{
 | 
			
		||||
                        [&type](bool tmp) { type = fmt::format("bool {}", tmp); },
 | 
			
		||||
                        [&type](std::string const& tmp) { type = fmt::format("string {}", tmp); },
 | 
			
		||||
                        [&type](double tmp) { type = fmt::format("double {}", tmp); },
 | 
			
		||||
                        [&type](int64_t tmp) { type = fmt::format("int {}", tmp); }
 | 
			
		||||
                    },
 | 
			
		||||
                    value_.value()
 | 
			
		||||
                );
 | 
			
		||||
                ASSERT(false, "Value {} ConfigValue does not satisfy the set Constraint", type);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Retrieves the constraint associated with this ConfigValue, if any.
 | 
			
		||||
     *
 | 
			
		||||
     * @return An optional reference to the associated Constraint.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] std::optional<std::reference_wrapper<Constraint const>>
 | 
			
		||||
    getConstraint() const
 | 
			
		||||
    {
 | 
			
		||||
        return cons_;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Gets the config type
 | 
			
		||||
     *
 | 
			
		||||
@@ -100,32 +158,6 @@ public:
 | 
			
		||||
        return type_;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Sets the minimum value for the config
 | 
			
		||||
     *
 | 
			
		||||
     * @param min The minimum value
 | 
			
		||||
     * @return Reference to this ConfigValue
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] constexpr ConfigValue&
 | 
			
		||||
    min(std::uint32_t min)
 | 
			
		||||
    {
 | 
			
		||||
        min_ = min;
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Sets the maximum value for the config
 | 
			
		||||
     *
 | 
			
		||||
     * @param max The maximum value
 | 
			
		||||
     * @return Reference to this ConfigValue
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] constexpr ConfigValue&
 | 
			
		||||
    max(std::uint32_t max)
 | 
			
		||||
    {
 | 
			
		||||
        max_ = max;
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Sets the config value as optional, meaning the user doesn't have to provide the value in their config
 | 
			
		||||
     *
 | 
			
		||||
@@ -165,7 +197,7 @@ public:
 | 
			
		||||
     *
 | 
			
		||||
     * @return Config Value
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] Type const&
 | 
			
		||||
    [[nodiscard]] Value const&
 | 
			
		||||
    getValue() const
 | 
			
		||||
    {
 | 
			
		||||
        return value_.value();
 | 
			
		||||
@@ -178,39 +210,28 @@ private:
 | 
			
		||||
     * @param type The config type
 | 
			
		||||
     * @param value The config value
 | 
			
		||||
     */
 | 
			
		||||
    static void
 | 
			
		||||
    checkTypeConsistency(ConfigType type, Type value)
 | 
			
		||||
    static std::optional<Error>
 | 
			
		||||
    checkTypeConsistency(ConfigType type, Value value)
 | 
			
		||||
    {
 | 
			
		||||
        if (std::holds_alternative<std::string>(value)) {
 | 
			
		||||
            ASSERT(type == ConfigType::String, "Value does not match type string");
 | 
			
		||||
        } else if (std::holds_alternative<bool>(value)) {
 | 
			
		||||
            ASSERT(type == ConfigType::Boolean, "Value does not match type boolean");
 | 
			
		||||
        } else if (std::holds_alternative<double>(value)) {
 | 
			
		||||
            ASSERT(type == ConfigType::Double, "Value does not match type double");
 | 
			
		||||
        } else if (std::holds_alternative<int64_t>(value)) {
 | 
			
		||||
            ASSERT(type == ConfigType::Integer, "Value does not match type integer");
 | 
			
		||||
        if (type == ConfigType::String && !std::holds_alternative<std::string>(value)) {
 | 
			
		||||
            return Error{"value does not match type string"};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Sets the value for the config
 | 
			
		||||
     *
 | 
			
		||||
     * @param value The value to set
 | 
			
		||||
     * @return The value that was set
 | 
			
		||||
     */
 | 
			
		||||
    Type
 | 
			
		||||
    setValue(Type value)
 | 
			
		||||
    {
 | 
			
		||||
        checkTypeConsistency(type_, value);
 | 
			
		||||
        value_ = value;
 | 
			
		||||
        return value;
 | 
			
		||||
        if (type == ConfigType::Boolean && !std::holds_alternative<bool>(value)) {
 | 
			
		||||
            return Error{"value does not match type boolean"};
 | 
			
		||||
        }
 | 
			
		||||
        if (type == ConfigType::Double && !std::holds_alternative<double>(value)) {
 | 
			
		||||
            return Error{"value does not match type double"};
 | 
			
		||||
        }
 | 
			
		||||
        if (type == ConfigType::Integer && !std::holds_alternative<int64_t>(value)) {
 | 
			
		||||
            return Error{"value does not match type integer"};
 | 
			
		||||
        }
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ConfigType type_{};
 | 
			
		||||
    bool optional_{false};
 | 
			
		||||
    std::optional<Type> value_;
 | 
			
		||||
    std::optional<std::uint32_t> min_;
 | 
			
		||||
    std::optional<std::uint32_t> max_;
 | 
			
		||||
    std::optional<Value> value_;
 | 
			
		||||
    std::optional<std::reference_wrapper<Constraint const>> cons_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										57
									
								
								src/util/newconfig/Error.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/util/newconfig/Error.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 <fmt/core.h>
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
/** @brief Displays the different errors when parsing user config */
 | 
			
		||||
struct Error {
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Constructs an Error with a custom error message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param err the error message to display to users.
 | 
			
		||||
     */
 | 
			
		||||
    Error(std::string err) : error{std::move(err)}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Constructs an Error with a custom error message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key the key associated with the error.
 | 
			
		||||
     * @param err the error message to display to users.
 | 
			
		||||
     */
 | 
			
		||||
    Error(std::string_view key, std::string_view err)
 | 
			
		||||
        : error{
 | 
			
		||||
              fmt::format("{} {}", key, err),
 | 
			
		||||
          }
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string error;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
							
								
								
									
										60
									
								
								src/util/newconfig/Types.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/util/newconfig/Types.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
   This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
   Copyright (c) 2024, 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 "util/UnsupportedType.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
namespace util::config {
 | 
			
		||||
 | 
			
		||||
/** @brief Custom clio config types */
 | 
			
		||||
enum class ConfigType { Integer, String, Double, Boolean };
 | 
			
		||||
 | 
			
		||||
/** @brief Represents the supported Config Values */
 | 
			
		||||
using Value = std::variant<int64_t, std::string, bool, double>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Get the corresponding clio config type
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam Type The type to get the corresponding ConfigType for
 | 
			
		||||
 * @return The corresponding ConfigType
 | 
			
		||||
 */
 | 
			
		||||
template <typename Type>
 | 
			
		||||
constexpr ConfigType
 | 
			
		||||
getType()
 | 
			
		||||
{
 | 
			
		||||
    if constexpr (std::is_same_v<Type, int64_t>) {
 | 
			
		||||
        return ConfigType::Integer;
 | 
			
		||||
    } else if constexpr (std::is_same_v<Type, std::string>) {
 | 
			
		||||
        return ConfigType::String;
 | 
			
		||||
    } else if constexpr (std::is_same_v<Type, double>) {
 | 
			
		||||
        return ConfigType::Double;
 | 
			
		||||
    } else if constexpr (std::is_same_v<Type, bool>) {
 | 
			
		||||
        return ConfigType::Boolean;
 | 
			
		||||
    } else {
 | 
			
		||||
        static_assert(util::Unsupported<Type>, "Wrong config type");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace util::config
 | 
			
		||||
@@ -21,6 +21,7 @@
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigValue.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -55,9 +56,9 @@ double
 | 
			
		||||
ValueView::asDouble() const
 | 
			
		||||
{
 | 
			
		||||
    if (configVal_.get().hasValue()) {
 | 
			
		||||
        if (type() == ConfigType::Double) {
 | 
			
		||||
        if (type() == ConfigType::Double)
 | 
			
		||||
            return std::get<double>(configVal_.get().getValue());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (type() == ConfigType::Integer)
 | 
			
		||||
            return static_cast<double>(std::get<int64_t>(configVal_.get().getValue()));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@
 | 
			
		||||
 | 
			
		||||
#include "util/Assert.hpp"
 | 
			
		||||
#include "util/newconfig/ConfigValue.hpp"
 | 
			
		||||
#include "util/newconfig/Types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
 | 
			
		||||
@@ -84,7 +85,7 @@ public:
 | 
			
		||||
                return static_cast<T>(val);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ASSERT(false, "Value view is not of any Int type");
 | 
			
		||||
        ASSERT(false, "Value view is not of Int type");
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/WithTimeout.hpp"
 | 
			
		||||
#include "util/requests/Types.hpp"
 | 
			
		||||
#include "util/requests/WsConnection.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -39,8 +40,10 @@
 | 
			
		||||
#include <boost/beast/websocket/stream_base.hpp>
 | 
			
		||||
#include <boost/system/errc.hpp>
 | 
			
		||||
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <expected>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
@@ -65,15 +68,13 @@ public:
 | 
			
		||||
 | 
			
		||||
        auto operation = [&](auto&& token) { ws_.async_read(buffer, token); };
 | 
			
		||||
        if (timeout) {
 | 
			
		||||
            withTimeout(operation, yield[errorCode], *timeout);
 | 
			
		||||
            errorCode = util::withTimeout(operation, yield[errorCode], *timeout);
 | 
			
		||||
        } else {
 | 
			
		||||
            operation(yield[errorCode]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (errorCode) {
 | 
			
		||||
            errorCode = mapError(errorCode);
 | 
			
		||||
        if (errorCode)
 | 
			
		||||
            return std::unexpected{RequestError{"Read error", errorCode}};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return boost::beast::buffers_to_string(std::move(buffer).data());
 | 
			
		||||
    }
 | 
			
		||||
@@ -88,15 +89,13 @@ public:
 | 
			
		||||
        boost::beast::error_code errorCode;
 | 
			
		||||
        auto operation = [&](auto&& token) { ws_.async_write(boost::asio::buffer(message), token); };
 | 
			
		||||
        if (timeout) {
 | 
			
		||||
            withTimeout(operation, yield[errorCode], *timeout);
 | 
			
		||||
            errorCode = util::withTimeout(operation, yield, *timeout);
 | 
			
		||||
        } else {
 | 
			
		||||
            operation(yield[errorCode]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (errorCode) {
 | 
			
		||||
            errorCode = mapError(errorCode);
 | 
			
		||||
        if (errorCode)
 | 
			
		||||
            return RequestError{"Write error", errorCode};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
@@ -117,31 +116,6 @@ public:
 | 
			
		||||
            return RequestError{"Close error", errorCode};
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    template <typename Operation>
 | 
			
		||||
    static void
 | 
			
		||||
    withTimeout(Operation&& operation, boost::asio::yield_context yield, std::chrono::steady_clock::duration timeout)
 | 
			
		||||
    {
 | 
			
		||||
        boost::asio::cancellation_signal cancellationSignal;
 | 
			
		||||
        auto cyield = boost::asio::bind_cancellation_slot(cancellationSignal.slot(), yield);
 | 
			
		||||
 | 
			
		||||
        boost::asio::steady_timer timer{boost::asio::get_associated_executor(cyield), timeout};
 | 
			
		||||
        timer.async_wait([&cancellationSignal](boost::system::error_code errorCode) {
 | 
			
		||||
            if (!errorCode)
 | 
			
		||||
                cancellationSignal.emit(boost::asio::cancellation_type::terminal);
 | 
			
		||||
        });
 | 
			
		||||
        operation(cyield);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static boost::system::error_code
 | 
			
		||||
    mapError(boost::system::error_code const ec)
 | 
			
		||||
    {
 | 
			
		||||
        if (ec == boost::system::errc::operation_canceled) {
 | 
			
		||||
            return boost::system::errc::make_error_code(boost::system::errc::timed_out);
 | 
			
		||||
        }
 | 
			
		||||
        return ec;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using PlainWsConnection = WsConnectionImpl<boost::beast::websocket::stream<boost::beast::tcp_stream>>;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,17 @@ add_library(clio_web)
 | 
			
		||||
target_sources(
 | 
			
		||||
  clio_web
 | 
			
		||||
  PRIVATE Resolver.cpp
 | 
			
		||||
          Server.cpp
 | 
			
		||||
          dosguard/DOSGuard.cpp
 | 
			
		||||
          dosguard/IntervalSweepHandler.cpp
 | 
			
		||||
          dosguard/WhitelistHandler.cpp
 | 
			
		||||
          impl/AdminVerificationStrategy.cpp
 | 
			
		||||
          impl/ServerSslContext.cpp
 | 
			
		||||
          ng/Connection.cpp
 | 
			
		||||
          ng/impl/ConnectionHandler.cpp
 | 
			
		||||
          ng/impl/ServerSslContext.cpp
 | 
			
		||||
          ng/impl/WsConnection.cpp
 | 
			
		||||
          ng/Server.cpp
 | 
			
		||||
          ng/Request.cpp
 | 
			
		||||
          ng/Response.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(clio_web PUBLIC clio_util)
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@
 | 
			
		||||
#include <boost/beast/core/flat_buffer.hpp>
 | 
			
		||||
#include <boost/beast/core/tcp_stream.hpp>
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -52,6 +53,7 @@ class HttpSession : public impl::HttpBase<HttpSession, HandlerType>,
 | 
			
		||||
                    public std::enable_shared_from_this<HttpSession<HandlerType>> {
 | 
			
		||||
    boost::beast::tcp_stream stream_;
 | 
			
		||||
    std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
 | 
			
		||||
    std::uint32_t maxWsSendingQueueSize_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
@@ -64,6 +66,7 @@ public:
 | 
			
		||||
     * @param dosGuard The denial of service guard to use
 | 
			
		||||
     * @param handler The server handler to use
 | 
			
		||||
     * @param buffer Buffer with initial data received from the peer
 | 
			
		||||
     * @param maxWsSendingQueueSize The maximum size of the sending queue for websocket
 | 
			
		||||
     */
 | 
			
		||||
    explicit HttpSession(
 | 
			
		||||
        tcp::socket&& socket,
 | 
			
		||||
@@ -72,7 +75,8 @@ public:
 | 
			
		||||
        std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
 | 
			
		||||
        std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
 | 
			
		||||
        std::shared_ptr<HandlerType> const& handler,
 | 
			
		||||
        boost::beast::flat_buffer buffer
 | 
			
		||||
        boost::beast::flat_buffer buffer,
 | 
			
		||||
        std::uint32_t maxWsSendingQueueSize
 | 
			
		||||
    )
 | 
			
		||||
        : impl::HttpBase<HttpSession, HandlerType>(
 | 
			
		||||
              ip,
 | 
			
		||||
@@ -84,6 +88,7 @@ public:
 | 
			
		||||
          )
 | 
			
		||||
        , stream_(std::move(socket))
 | 
			
		||||
        , tagFactory_(tagFactory)
 | 
			
		||||
        , maxWsSendingQueueSize_(maxWsSendingQueueSize)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -128,7 +133,8 @@ public:
 | 
			
		||||
            this->handler_,
 | 
			
		||||
            std::move(this->buffer_),
 | 
			
		||||
            std::move(this->req_),
 | 
			
		||||
            ConnectionBase::isAdmin()
 | 
			
		||||
            ConnectionBase::isAdmin(),
 | 
			
		||||
            maxWsSendingQueueSize_
 | 
			
		||||
        )
 | 
			
		||||
            ->run();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,8 @@ public:
 | 
			
		||||
     * @param dosGuard The denial of service guard to use
 | 
			
		||||
     * @param handler The server handler to use
 | 
			
		||||
     * @param buffer Buffer with initial data received from the peer
 | 
			
		||||
     * @param isAdmin Whether the connection has admin privileges
 | 
			
		||||
     * @param isAdmin Whether the connection has admin privileges,
 | 
			
		||||
     * @param maxSendingQueueSize The maximum size of the sending queue for websocket
 | 
			
		||||
     */
 | 
			
		||||
    explicit PlainWsSession(
 | 
			
		||||
        boost::asio::ip::tcp::socket&& socket,
 | 
			
		||||
@@ -71,9 +72,17 @@ public:
 | 
			
		||||
        std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
 | 
			
		||||
        std::shared_ptr<HandlerType> const& handler,
 | 
			
		||||
        boost::beast::flat_buffer&& buffer,
 | 
			
		||||
        bool isAdmin
 | 
			
		||||
        bool isAdmin,
 | 
			
		||||
        std::uint32_t maxSendingQueueSize
 | 
			
		||||
    )
 | 
			
		||||
        : impl::WsBase<PlainWsSession, HandlerType>(ip, tagFactory, dosGuard, handler, std::move(buffer))
 | 
			
		||||
        : impl::WsBase<PlainWsSession, HandlerType>(
 | 
			
		||||
              ip,
 | 
			
		||||
              tagFactory,
 | 
			
		||||
              dosGuard,
 | 
			
		||||
              handler,
 | 
			
		||||
              std::move(buffer),
 | 
			
		||||
              maxSendingQueueSize
 | 
			
		||||
          )
 | 
			
		||||
        , ws_(std::move(socket))
 | 
			
		||||
    {
 | 
			
		||||
        ConnectionBase::isAdmin_ = isAdmin;  // NOLINT(cppcoreguidelines-prefer-member-initializer)
 | 
			
		||||
@@ -107,6 +116,7 @@ class WsUpgrader : public std::enable_shared_from_this<WsUpgrader<HandlerType>>
 | 
			
		||||
    std::string ip_;
 | 
			
		||||
    std::shared_ptr<HandlerType> const handler_;
 | 
			
		||||
    bool isAdmin_;
 | 
			
		||||
    std::uint32_t maxWsSendingQueueSize_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
@@ -120,6 +130,7 @@ public:
 | 
			
		||||
     * @param buffer Buffer with initial data received from the peer. Ownership is transferred
 | 
			
		||||
     * @param request The request. Ownership is transferred
 | 
			
		||||
     * @param isAdmin Whether the connection has admin privileges
 | 
			
		||||
     * @param maxWsSendingQueueSize The maximum size of the sending queue for websocket
 | 
			
		||||
     */
 | 
			
		||||
    WsUpgrader(
 | 
			
		||||
        boost::beast::tcp_stream&& stream,
 | 
			
		||||
@@ -129,7 +140,8 @@ public:
 | 
			
		||||
        std::shared_ptr<HandlerType> const& handler,
 | 
			
		||||
        boost::beast::flat_buffer&& buffer,
 | 
			
		||||
        http::request<http::string_body> request,
 | 
			
		||||
        bool isAdmin
 | 
			
		||||
        bool isAdmin,
 | 
			
		||||
        std::uint32_t maxWsSendingQueueSize
 | 
			
		||||
    )
 | 
			
		||||
        : http_(std::move(stream))
 | 
			
		||||
        , buffer_(std::move(buffer))
 | 
			
		||||
@@ -139,6 +151,7 @@ public:
 | 
			
		||||
        , ip_(std::move(ip))
 | 
			
		||||
        , handler_(handler)
 | 
			
		||||
        , isAdmin_(isAdmin)
 | 
			
		||||
        , maxWsSendingQueueSize_(maxWsSendingQueueSize)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -175,7 +188,14 @@ private:
 | 
			
		||||
        boost::beast::get_lowest_layer(http_).expires_never();
 | 
			
		||||
 | 
			
		||||
        std::make_shared<PlainWsSession<HandlerType>>(
 | 
			
		||||
            http_.release_socket(), ip_, tagFactory_, dosGuard_, handler_, std::move(buffer_), isAdmin_
 | 
			
		||||
            http_.release_socket(),
 | 
			
		||||
            ip_,
 | 
			
		||||
            tagFactory_,
 | 
			
		||||
            dosGuard_,
 | 
			
		||||
            handler_,
 | 
			
		||||
            std::move(buffer_),
 | 
			
		||||
            isAdmin_,
 | 
			
		||||
            maxWsSendingQueueSize_
 | 
			
		||||
        )
 | 
			
		||||
            ->run(std::move(req_));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, 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 "web/Server.hpp"
 | 
			
		||||
 | 
			
		||||
#include "util/config/Config.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/ssl/context.hpp>
 | 
			
		||||
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace web {
 | 
			
		||||
 | 
			
		||||
std::expected<std::optional<boost::asio::ssl::context>, std::string>
 | 
			
		||||
makeServerSslContext(util::Config const& config)
 | 
			
		||||
{
 | 
			
		||||
    bool const configHasCertFile = config.contains("ssl_cert_file");
 | 
			
		||||
    bool const configHasKeyFile = config.contains("ssl_key_file");
 | 
			
		||||
 | 
			
		||||
    if (configHasCertFile != configHasKeyFile)
 | 
			
		||||
        return std::unexpected{"Config entries 'ssl_cert_file' and 'ssl_key_file' must be set or unset together."};
 | 
			
		||||
 | 
			
		||||
    if (not configHasCertFile)
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
 | 
			
		||||
    auto const certFilename = config.value<std::string>("ssl_cert_file");
 | 
			
		||||
    auto const keyFilename = config.value<std::string>("ssl_key_file");
 | 
			
		||||
 | 
			
		||||
    return impl::makeServerSslContext(certFilename, keyFilename);
 | 
			
		||||
}
 | 
			
		||||
}  // namespace web
 | 
			
		||||
@@ -24,8 +24,8 @@
 | 
			
		||||
#include "web/HttpSession.hpp"
 | 
			
		||||
#include "web/SslHttpSession.hpp"
 | 
			
		||||
#include "web/dosguard/DOSGuardInterface.hpp"
 | 
			
		||||
#include "web/impl/ServerSslContext.hpp"
 | 
			
		||||
#include "web/interface/Concepts.hpp"
 | 
			
		||||
#include "web/ng/impl/ServerSslContext.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/io_context.hpp>
 | 
			
		||||
#include <boost/asio/ip/address.hpp>
 | 
			
		||||
@@ -41,6 +41,7 @@
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <exception>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <memory>
 | 
			
		||||
@@ -59,15 +60,6 @@
 | 
			
		||||
 */
 | 
			
		||||
namespace web {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A helper function to create a server SSL context.
 | 
			
		||||
 *
 | 
			
		||||
 * @param config The config to create the context
 | 
			
		||||
 * @return Optional SSL context or error message if any
 | 
			
		||||
 */
 | 
			
		||||
std::expected<std::optional<boost::asio::ssl::context>, std::string>
 | 
			
		||||
makeServerSslContext(util::Config const& config);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The Detector class to detect if the connection is a ssl or not.
 | 
			
		||||
 *
 | 
			
		||||
@@ -79,10 +71,8 @@ makeServerSslContext(util::Config const& config);
 | 
			
		||||
 * @tparam HandlerType The executor to handle the requests
 | 
			
		||||
 */
 | 
			
		||||
template <
 | 
			
		||||
    template <typename>
 | 
			
		||||
    class PlainSessionType,
 | 
			
		||||
    template <typename>
 | 
			
		||||
    class SslSessionType,
 | 
			
		||||
    template <typename> class PlainSessionType,
 | 
			
		||||
    template <typename> class SslSessionType,
 | 
			
		||||
    SomeServerHandler HandlerType>
 | 
			
		||||
class Detector : public std::enable_shared_from_this<Detector<PlainSessionType, SslSessionType, HandlerType>> {
 | 
			
		||||
    using std::enable_shared_from_this<Detector<PlainSessionType, SslSessionType, HandlerType>>::shared_from_this;
 | 
			
		||||
@@ -95,6 +85,7 @@ class Detector : public std::enable_shared_from_this<Detector<PlainSessionType,
 | 
			
		||||
    std::shared_ptr<HandlerType> const handler_;
 | 
			
		||||
    boost::beast::flat_buffer buffer_;
 | 
			
		||||
    std::shared_ptr<impl::AdminVerificationStrategy> const adminVerification_;
 | 
			
		||||
    std::uint32_t maxWsSendingQueueSize_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
@@ -106,6 +97,7 @@ public:
 | 
			
		||||
     * @param dosGuard The denial of service guard to use
 | 
			
		||||
     * @param handler The server handler to use
 | 
			
		||||
     * @param adminVerification The admin verification strategy to use
 | 
			
		||||
     * @param maxWsSendingQueueSize The maximum size of the sending queue for websocket
 | 
			
		||||
     */
 | 
			
		||||
    Detector(
 | 
			
		||||
        tcp::socket&& socket,
 | 
			
		||||
@@ -113,7 +105,8 @@ public:
 | 
			
		||||
        std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
 | 
			
		||||
        std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
 | 
			
		||||
        std::shared_ptr<HandlerType> handler,
 | 
			
		||||
        std::shared_ptr<impl::AdminVerificationStrategy> adminVerification
 | 
			
		||||
        std::shared_ptr<impl::AdminVerificationStrategy> adminVerification,
 | 
			
		||||
        std::uint32_t maxWsSendingQueueSize
 | 
			
		||||
    )
 | 
			
		||||
        : stream_(std::move(socket))
 | 
			
		||||
        , ctx_(ctx)
 | 
			
		||||
@@ -121,6 +114,7 @@ public:
 | 
			
		||||
        , dosGuard_(dosGuard)
 | 
			
		||||
        , handler_(std::move(handler))
 | 
			
		||||
        , adminVerification_(std::move(adminVerification))
 | 
			
		||||
        , maxWsSendingQueueSize_(maxWsSendingQueueSize)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -178,14 +172,22 @@ public:
 | 
			
		||||
                tagFactory_,
 | 
			
		||||
                dosGuard_,
 | 
			
		||||
                handler_,
 | 
			
		||||
                std::move(buffer_)
 | 
			
		||||
                std::move(buffer_),
 | 
			
		||||
                maxWsSendingQueueSize_
 | 
			
		||||
            )
 | 
			
		||||
                ->run();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::make_shared<PlainSessionType<HandlerType>>(
 | 
			
		||||
            stream_.release_socket(), ip, adminVerification_, tagFactory_, dosGuard_, handler_, std::move(buffer_)
 | 
			
		||||
            stream_.release_socket(),
 | 
			
		||||
            ip,
 | 
			
		||||
            adminVerification_,
 | 
			
		||||
            tagFactory_,
 | 
			
		||||
            dosGuard_,
 | 
			
		||||
            handler_,
 | 
			
		||||
            std::move(buffer_),
 | 
			
		||||
            maxWsSendingQueueSize_
 | 
			
		||||
        )
 | 
			
		||||
            ->run();
 | 
			
		||||
    }
 | 
			
		||||
@@ -201,10 +203,8 @@ public:
 | 
			
		||||
 * @tparam HandlerType The handler to process the request and return response.
 | 
			
		||||
 */
 | 
			
		||||
template <
 | 
			
		||||
    template <typename>
 | 
			
		||||
    class PlainSessionType,
 | 
			
		||||
    template <typename>
 | 
			
		||||
    class SslSessionType,
 | 
			
		||||
    template <typename> class PlainSessionType,
 | 
			
		||||
    template <typename> class SslSessionType,
 | 
			
		||||
    SomeServerHandler HandlerType>
 | 
			
		||||
class Server : public std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>> {
 | 
			
		||||
    using std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>>::shared_from_this;
 | 
			
		||||
@@ -217,6 +217,7 @@ class Server : public std::enable_shared_from_this<Server<PlainSessionType, SslS
 | 
			
		||||
    std::shared_ptr<HandlerType> handler_;
 | 
			
		||||
    tcp::acceptor acceptor_;
 | 
			
		||||
    std::shared_ptr<impl::AdminVerificationStrategy> adminVerification_;
 | 
			
		||||
    std::uint32_t maxWsSendingQueueSize_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
@@ -229,6 +230,7 @@ public:
 | 
			
		||||
     * @param dosGuard The denial of service guard to use
 | 
			
		||||
     * @param handler The server handler to use
 | 
			
		||||
     * @param adminPassword The optional password to verify admin role in requests
 | 
			
		||||
     * @param maxWsSendingQueueSize The maximum size of the sending queue for websocket
 | 
			
		||||
     */
 | 
			
		||||
    Server(
 | 
			
		||||
        boost::asio::io_context& ioc,
 | 
			
		||||
@@ -237,7 +239,8 @@ public:
 | 
			
		||||
        util::TagDecoratorFactory tagFactory,
 | 
			
		||||
        dosguard::DOSGuardInterface& dosGuard,
 | 
			
		||||
        std::shared_ptr<HandlerType> handler,
 | 
			
		||||
        std::optional<std::string> adminPassword
 | 
			
		||||
        std::optional<std::string> adminPassword,
 | 
			
		||||
        std::uint32_t maxWsSendingQueueSize
 | 
			
		||||
    )
 | 
			
		||||
        : ioc_(std::ref(ioc))
 | 
			
		||||
        , ctx_(std::move(ctx))
 | 
			
		||||
@@ -246,6 +249,7 @@ public:
 | 
			
		||||
        , handler_(std::move(handler))
 | 
			
		||||
        , acceptor_(boost::asio::make_strand(ioc))
 | 
			
		||||
        , adminVerification_(impl::make_AdminVerificationStrategy(std::move(adminPassword)))
 | 
			
		||||
        , maxWsSendingQueueSize_(maxWsSendingQueueSize)
 | 
			
		||||
    {
 | 
			
		||||
        boost::beast::error_code ec;
 | 
			
		||||
 | 
			
		||||
@@ -299,7 +303,13 @@ private:
 | 
			
		||||
                ctx_ ? std::optional<std::reference_wrapper<boost::asio::ssl::context>>{ctx_.value()} : std::nullopt;
 | 
			
		||||
 | 
			
		||||
            std::make_shared<Detector<PlainSessionType, SslSessionType, HandlerType>>(
 | 
			
		||||
                std::move(socket), ctxRef, std::cref(tagFactory_), dosGuard_, handler_, adminVerification_
 | 
			
		||||
                std::move(socket),
 | 
			
		||||
                ctxRef,
 | 
			
		||||
                std::cref(tagFactory_),
 | 
			
		||||
                dosGuard_,
 | 
			
		||||
                handler_,
 | 
			
		||||
                adminVerification_,
 | 
			
		||||
                maxWsSendingQueueSize_
 | 
			
		||||
            )
 | 
			
		||||
                ->run();
 | 
			
		||||
        }
 | 
			
		||||
@@ -333,7 +343,7 @@ make_HttpServer(
 | 
			
		||||
{
 | 
			
		||||
    static util::Logger const log{"WebServer"};
 | 
			
		||||
 | 
			
		||||
    auto expectedSslContext = makeServerSslContext(config);
 | 
			
		||||
    auto expectedSslContext = ng::impl::makeServerSslContext(config);
 | 
			
		||||
    if (not expectedSslContext) {
 | 
			
		||||
        LOG(log.error()) << "Failed to create SSL context: " << expectedSslContext.error();
 | 
			
		||||
        return nullptr;
 | 
			
		||||
@@ -361,6 +371,10 @@ make_HttpServer(
 | 
			
		||||
        throw std::logic_error("Admin config error, one method must be specified to authorize admin.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the transactions number is 200 per ledger, A client which subscribes everything will send 400+ feeds for
 | 
			
		||||
    // each ledger. we allow user delay 3 ledgers by default
 | 
			
		||||
    auto const maxWsSendingQueueSize = serverConfig.valueOr("ws_max_sending_queue_size", 1500);
 | 
			
		||||
 | 
			
		||||
    auto server = std::make_shared<HttpServer<HandlerType>>(
 | 
			
		||||
        ioc,
 | 
			
		||||
        std::move(expectedSslContext).value(),
 | 
			
		||||
@@ -368,7 +382,8 @@ make_HttpServer(
 | 
			
		||||
        util::TagDecoratorFactory(config),
 | 
			
		||||
        dosGuard,
 | 
			
		||||
        handler,
 | 
			
		||||
        std::move(adminPassword)
 | 
			
		||||
        std::move(adminPassword),
 | 
			
		||||
        maxWsSendingQueueSize
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    server->run();
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -59,6 +60,7 @@ class SslHttpSession : public impl::HttpBase<SslHttpSession, HandlerType>,
 | 
			
		||||
                       public std::enable_shared_from_this<SslHttpSession<HandlerType>> {
 | 
			
		||||
    boost::beast::ssl_stream<boost::beast::tcp_stream> stream_;
 | 
			
		||||
    std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
 | 
			
		||||
    std::uint32_t maxWsSendingQueueSize_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
@@ -72,6 +74,7 @@ public:
 | 
			
		||||
     * @param dosGuard The denial of service guard to use
 | 
			
		||||
     * @param handler The server handler to use
 | 
			
		||||
     * @param buffer Buffer with initial data received from the peer
 | 
			
		||||
     * @param maxWsSendingQueueSize The maximum size of the sending queue for websocket
 | 
			
		||||
     */
 | 
			
		||||
    explicit SslHttpSession(
 | 
			
		||||
        tcp::socket&& socket,
 | 
			
		||||
@@ -81,7 +84,8 @@ public:
 | 
			
		||||
        std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
 | 
			
		||||
        std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
 | 
			
		||||
        std::shared_ptr<HandlerType> const& handler,
 | 
			
		||||
        boost::beast::flat_buffer buffer
 | 
			
		||||
        boost::beast::flat_buffer buffer,
 | 
			
		||||
        std::uint32_t maxWsSendingQueueSize
 | 
			
		||||
    )
 | 
			
		||||
        : impl::HttpBase<SslHttpSession, HandlerType>(
 | 
			
		||||
              ip,
 | 
			
		||||
@@ -93,6 +97,7 @@ public:
 | 
			
		||||
          )
 | 
			
		||||
        , stream_(std::move(socket), ctx)
 | 
			
		||||
        , tagFactory_(tagFactory)
 | 
			
		||||
        , maxWsSendingQueueSize_(maxWsSendingQueueSize)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -173,7 +178,8 @@ public:
 | 
			
		||||
            this->handler_,
 | 
			
		||||
            std::move(this->buffer_),
 | 
			
		||||
            std::move(this->req_),
 | 
			
		||||
            ConnectionBase::isAdmin()
 | 
			
		||||
            ConnectionBase::isAdmin(),
 | 
			
		||||
            maxWsSendingQueueSize_
 | 
			
		||||
        )
 | 
			
		||||
            ->run();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@
 | 
			
		||||
#include <boost/optional/optional.hpp>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -64,6 +65,7 @@ public:
 | 
			
		||||
     * @param handler The server handler to use
 | 
			
		||||
     * @param buffer Buffer with initial data received from the peer
 | 
			
		||||
     * @param isAdmin Whether the connection has admin privileges
 | 
			
		||||
     * @param maxWsSendingQueueSize The maximum size of the sending queue for websocket
 | 
			
		||||
     */
 | 
			
		||||
    explicit SslWsSession(
 | 
			
		||||
        boost::beast::ssl_stream<boost::beast::tcp_stream>&& stream,
 | 
			
		||||
@@ -72,9 +74,17 @@ public:
 | 
			
		||||
        std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
 | 
			
		||||
        std::shared_ptr<HandlerType> const& handler,
 | 
			
		||||
        boost::beast::flat_buffer&& buffer,
 | 
			
		||||
        bool isAdmin
 | 
			
		||||
        bool isAdmin,
 | 
			
		||||
        std::uint32_t maxWsSendingQueueSize
 | 
			
		||||
    )
 | 
			
		||||
        : impl::WsBase<SslWsSession, HandlerType>(ip, tagFactory, dosGuard, handler, std::move(buffer))
 | 
			
		||||
        : impl::WsBase<SslWsSession, HandlerType>(
 | 
			
		||||
              ip,
 | 
			
		||||
              tagFactory,
 | 
			
		||||
              dosGuard,
 | 
			
		||||
              handler,
 | 
			
		||||
              std::move(buffer),
 | 
			
		||||
              maxWsSendingQueueSize
 | 
			
		||||
          )
 | 
			
		||||
        , ws_(std::move(stream))
 | 
			
		||||
    {
 | 
			
		||||
        ConnectionBase::isAdmin_ = isAdmin;  // NOLINT(cppcoreguidelines-prefer-member-initializer)
 | 
			
		||||
@@ -106,6 +116,7 @@ class SslWsUpgrader : public std::enable_shared_from_this<SslWsUpgrader<HandlerT
 | 
			
		||||
    std::shared_ptr<HandlerType> const handler_;
 | 
			
		||||
    http::request<http::string_body> req_;
 | 
			
		||||
    bool isAdmin_;
 | 
			
		||||
    std::uint32_t maxWsSendingQueueSize_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /**
 | 
			
		||||
@@ -119,6 +130,7 @@ public:
 | 
			
		||||
     * @param buffer Buffer with initial data received from the peer. Ownership is transferred
 | 
			
		||||
     * @param request The request. Ownership is transferred
 | 
			
		||||
     * @param isAdmin Whether the connection has admin privileges
 | 
			
		||||
     * @param maxWsSendingQueueSize The maximum size of the sending queue for websocket
 | 
			
		||||
     */
 | 
			
		||||
    SslWsUpgrader(
 | 
			
		||||
        boost::beast::ssl_stream<boost::beast::tcp_stream> stream,
 | 
			
		||||
@@ -128,7 +140,8 @@ public:
 | 
			
		||||
        std::shared_ptr<HandlerType> handler,
 | 
			
		||||
        boost::beast::flat_buffer&& buffer,
 | 
			
		||||
        http::request<http::string_body> request,
 | 
			
		||||
        bool isAdmin
 | 
			
		||||
        bool isAdmin,
 | 
			
		||||
        std::uint32_t maxWsSendingQueueSize
 | 
			
		||||
    )
 | 
			
		||||
        : https_(std::move(stream))
 | 
			
		||||
        , buffer_(std::move(buffer))
 | 
			
		||||
@@ -138,6 +151,7 @@ public:
 | 
			
		||||
        , handler_(std::move(handler))
 | 
			
		||||
        , req_(std::move(request))
 | 
			
		||||
        , isAdmin_(isAdmin)
 | 
			
		||||
        , maxWsSendingQueueSize_(maxWsSendingQueueSize)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -179,7 +193,14 @@ private:
 | 
			
		||||
        boost::beast::get_lowest_layer(https_).expires_never();
 | 
			
		||||
 | 
			
		||||
        std::make_shared<SslWsSession<HandlerType>>(
 | 
			
		||||
            std::move(https_), ip_, tagFactory_, dosGuard_, handler_, std::move(buffer_), isAdmin_
 | 
			
		||||
            std::move(https_),
 | 
			
		||||
            ip_,
 | 
			
		||||
            tagFactory_,
 | 
			
		||||
            dosGuard_,
 | 
			
		||||
            handler_,
 | 
			
		||||
            std::move(buffer_),
 | 
			
		||||
            isAdmin_,
 | 
			
		||||
            maxWsSendingQueueSize_
 | 
			
		||||
        )
 | 
			
		||||
            ->run(std::move(req_));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
#include "web/impl/AdminVerificationStrategy.hpp"
 | 
			
		||||
 | 
			
		||||
#include "util/JsonUtils.hpp"
 | 
			
		||||
#include "util/config/Config.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/beast/http/field.hpp>
 | 
			
		||||
#include <xrpl/basics/base_uint.h>
 | 
			
		||||
@@ -79,4 +80,20 @@ make_AdminVerificationStrategy(std::optional<std::string> password)
 | 
			
		||||
    return std::make_shared<IPAdminVerificationStrategy>();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::expected<std::shared_ptr<AdminVerificationStrategy>, std::string>
 | 
			
		||||
make_AdminVerificationStrategy(util::Config const& serverConfig)
 | 
			
		||||
{
 | 
			
		||||
    auto adminPassword = serverConfig.maybeValue<std::string>("admin_password");
 | 
			
		||||
    auto const localAdmin = serverConfig.maybeValue<bool>("local_admin");
 | 
			
		||||
    bool const localAdminEnabled = localAdmin && localAdmin.value();
 | 
			
		||||
 | 
			
		||||
    if (localAdminEnabled == adminPassword.has_value()) {
 | 
			
		||||
        if (adminPassword.has_value())
 | 
			
		||||
            return std::unexpected{"Admin config error, local_admin and admin_password can not be set together."};
 | 
			
		||||
        return std::unexpected{"Admin config error, either local_admin and admin_password must be specified."};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return make_AdminVerificationStrategy(std::move(adminPassword));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace web::impl
 | 
			
		||||
 
 | 
			
		||||
@@ -19,10 +19,13 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/config/Config.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/beast/http.hpp>
 | 
			
		||||
#include <boost/beast/http/message.hpp>
 | 
			
		||||
#include <boost/beast/http/string_body.hpp>
 | 
			
		||||
 | 
			
		||||
#include <expected>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -82,4 +85,7 @@ public:
 | 
			
		||||
std::shared_ptr<AdminVerificationStrategy>
 | 
			
		||||
make_AdminVerificationStrategy(std::optional<std::string> password);
 | 
			
		||||
 | 
			
		||||
std::expected<std::shared_ptr<AdminVerificationStrategy>, std::string>
 | 
			
		||||
make_AdminVerificationStrategy(util::Config const& serverConfig);
 | 
			
		||||
 | 
			
		||||
}  // namespace web::impl
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user